Skip to content

Commit

Permalink
WIP: Add support for GS2 SASL (RFC 5801)
Browse files Browse the repository at this point in the history
  • Loading branch information
adiaholic committed May 14, 2020
1 parent 3ab4405 commit 2938239
Show file tree
Hide file tree
Showing 11 changed files with 688 additions and 2 deletions.
1 change: 1 addition & 0 deletions smack-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
compile "org.jxmpp:jxmpp-core:$jxmppVersion"
compile "org.jxmpp:jxmpp-jid:$jxmppVersion"
compile "org.minidns:minidns-core:$miniDnsVersion"
compile "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion"

testFixturesImplementation project(':smack-xmlparser-stax')
testFixturesImplementation project(':smack-xmlparser-xpp3')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.jivesoftware.smack.sasl.gssApi;

public class GssApiContext extends GssContext {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.jivesoftware.smack.sasl.gssApi;

import org.ietf.jgss.*;
import org.jivesoftware.smack.XMPPConnection;

import java.security.Provider;

public class GssApiManager {

GSSManager gssManager;
GSSName userName = null;
GSSName serverName = null;
GSSCredential userCredential = null;
GSSContext clientContext = null;

Oid objectIdentifiers;
byte[] clientToken;

private static GssApiManager gssApiManager = null;

public static synchronized GssApiManager getInstanceFor(XMPPConnection connection) {
if (gssApiManager == null) {
return new GssApiManager(connection);
}
return gssApiManager;
}

private GssApiManager(XMPPConnection connection){
gssManager = GSSManager.getInstance();

try {
userName = gssManager.createName(connection.getUser().asEntityBareJid().asEntityBareJidString(),GSSName.NT_USER_NAME);
serverName = gssManager.createName(connection.getHost(),objectIdentifiers);
userCredential = gssManager.createCredential(userName,GSSCredential.DEFAULT_LIFETIME,objectIdentifiers,GSSCredential.INITIATE_ONLY);
clientContext = gssManager.createContext(serverName,objectIdentifiers,userCredential,GSSContext.DEFAULT_LIFETIME);
clientContext.requestMutualAuth(true);
clientContext.requestConf(true);
clientContext.requestInteg(true);
} catch (GSSException e) {
e.printStackTrace();
}
}

public byte[] authenticate() throws GSSException {
clientToken = clientContext.initSecContext(new byte[0], 0, 0);

// Send client token over the network
return clientToken;
}

public byte[] acceptServerToken(byte[] serverToken) {
try {
return clientContext.initSecContext(serverToken,0,serverToken.length);
} catch (GSSException e) {
e.printStackTrace();
return null;
}
}

public boolean isContextEstablished() {
return clientContext.isEstablished();
}

public byte[] sendData(byte[] messageBytes) throws GSSException {
MessageProp clientProp = new MessageProp(0,true);
clientToken = clientContext.wrap(messageBytes,0,messageBytes.length,clientProp);

// Send client token over the network
return clientToken;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.sasl.gssApi;

import java.io.IOException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;

import javax.security.auth.callback.CallbackHandler;

import org.ietf.jgss.*;
import org.jivesoftware.smack.SmackException.SmackSaslException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.sasl.SASLMechanism;

import org.jivesoftware.smack.util.SHA1;
import org.jivesoftware.smack.util.stringencoder.Base32;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;

/**
* Use of GSS-API mechanism in SASL by defining a new SASL mechanism family called GS2.
* This mechanism offers a number of improvements over the previous "SASL/GSSAPI" mechanism.
* In general, it uses fewer messages for authentication phase in some cases, and supports negotiable use of channel binding.
* Only GSS-API mechanisms that support channel binding and mutual authentication are supported.
* <br>
* The absence of `PLUS` suffix in the name `GSS-API` suggests that the server doesn't support channel binding.
* <br>
* SASL implementations can use the GSS_Inquire_SASLname_for_mech call
* to query for the SASL mechanism name of a GSS-API mechanism.
* <br>
* If the GSS_Inquire_SASLname_for_mech interface is not used, the GS2
* implementation needs some other mechanism to map mechanism Object
* Identifiers (OIDs) to SASL names internally.
* <br>
* In this case, the implementation can only support the mechanisms for which it knows the
* SASL name.
*
* @author adiaholic
*/
public class GssApiMechanism extends SASLMechanism{

public static final String NAME = "GSS-API";

public static final String GS2_PREFIX = "GS2-";

private Oid objectIdentifiers = null;

private GssApiManager gssApiManager = null;

GssApiMechanism(Oid oid, XMPPConnection connection) {
super();
this.connection = connection;
gssApiManager = GssApiManager.getInstanceFor(connection);
setObjectIdentifier(oid);
}

@Override
protected byte[] getAuthenticationText() throws SmackSaslException {
try {
return gssApiManager.authenticate();
} catch (GSSException e) {
e.printStackTrace();
return null;
}
}

@Override
protected byte[] evaluateChallenge(byte[] challenge) throws SmackSaslException {
return gssApiManager.acceptServerToken(challenge);
}

/*
* The SASL Mechanism name is concatenation of the string "GS2-" and the
* Base32 encoding of the first 55 bits of the binary SHA-1 hash string
* computed over the ASN.1 DER encoding
* <br>
* Note : Some older GSS-API mechanisms were not specified with a SASL GS2 mechanism name.
*/
public String generateSASLMechanismNameFromGSSApiOIDs (String objectIdentifier) throws NoSuchAlgorithmException, IOException {

// To generate a SHA-1 hash string over ASN1.DER.
byte[] ASN1_DER = getASN1DERencoding(objectIdentifier);
String sha1_hash = SHA1.hex(ASN1_DER);

// Obtain first 55 bits of the SHA-1 hash.
String binary55Bits = getbinary55Bits(sha1_hash);

// Make groups of 5 bits each
String[] binaryGroups = new String[11];
for (int i = 0 ; i < binary55Bits.length() / 5 ; i++) {
String binaryGroup = "";
for (int j = 0 ; j < 5 ; j++) {
binaryGroup += binary55Bits.charAt(5 * i + j);
}
// int decimalForGroup = Integer.parseInt(binaryGroup,2);

binaryGroups[i] = binaryGroup;
}

// Base32 encoding for the above binaryGroups
String base32Encoding = "";
for (int i = 0 ; i < 11 ; i++) {
int decimalValue = Integer.parseInt(binaryGroups[i], 2);
base32Encoding += Base32.encodeIntValue(decimalValue);
}
return GS2_PREFIX + base32Encoding;
}

private static String getbinary55Bits(String sha1_hash) {
// Get first 7 octets.
String first7Octets = sha1_hash.substring(0, 14);

// Convert first 7 octets of the sha1 hash into binary.
String binaryOctets = new BigInteger(first7Octets, 16).toString(2);

// Return first 55 bits of the binary hash.
return binaryOctets.substring(0, 55);
}

public byte[] getASN1DERencoding(String objectIdentifier) throws IOException {
ASN1ObjectIdentifier asn1ObjectIdentifier = new ASN1ObjectIdentifier(objectIdentifier).intern();
byte[] encoded = asn1ObjectIdentifier.getEncoded();
return encoded;
}

@Override
protected void authenticateInternal(CallbackHandler cbh) throws SmackSaslException {
}

@Override
public String getName() {
return NAME;
}

@Override
public int getPriority() {
return 0;
}

@Override
protected void checkIfSuccessfulOrThrow() throws SmackSaslException {
}

@Override
protected GssApiMechanism newInstance() {
return new GssApiMechanism(null,null);
}

public void setObjectIdentifier(Oid oid) {
this.objectIdentifiers = oid;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
*
* Copyright 2020 Aditya Borikar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.sasl.gssApi;

import javax.security.auth.callback.CallbackHandler;

import org.jivesoftware.smack.SmackException.SmackSaslException;
import org.jivesoftware.smack.sasl.SASLMechanism;

/**
* The plus inside the GSS-API-plus mechanism name suggests that the server supports channel binding.
*
* @author adiaholic
*/
public class GssApiPlusMechanism extends SASLMechanism{

@Override
protected void authenticateInternal(CallbackHandler cbh) throws SmackSaslException {
}

@Override
protected byte[] getAuthenticationText() throws SmackSaslException {
return null;
}

@Override
public String getName() {
return null;
}

@Override
public int getPriority() {
return 0;
}

@Override
protected void checkIfSuccessfulOrThrow() throws SmackSaslException {
}

@Override
protected SASLMechanism newInstance() {
return null;
}
}
Loading

0 comments on commit 2938239

Please sign in to comment.