# HG changeset patch # User Da Risk # Date 1358105771 -3600 # Node ID c337d8c387f5b331dc08f34dd821c90dabbec831 # Parent 63669480c9414940f9e7403fa3ccae45276109de Implement an ScramSaslMechanism to use it with Smack. Fix #476 diff -r 63669480c941 -r c337d8c387f5 src/com/beem/project/beem/BeemService.java --- a/src/com/beem/project/beem/BeemService.java Sun Jan 13 20:11:53 2013 +0100 +++ b/src/com/beem/project/beem/BeemService.java Sun Jan 13 20:36:11 2013 +0100 @@ -77,6 +77,7 @@ import com.beem.project.beem.smack.avatar.AvatarProvider; import com.beem.project.beem.smack.ping.PingExtension; import com.beem.project.beem.smack.sasl.SASLGoogleOAuth2Mechanism; +import com.beem.project.beem.smack.sasl.ScramSaslMechanism; import com.beem.project.beem.utils.BeemBroadcastReceiver; import com.beem.project.beem.utils.BeemConnectivity; import com.beem.project.beem.utils.Status; @@ -580,7 +581,10 @@ /* register additionnals sasl mechanisms */ SASLAuthentication.registerSASLMechanism(SASLGoogleOAuth2Mechanism.MECHANISM_NAME, SASLGoogleOAuth2Mechanism.class); + SASLAuthentication.registerSASLMechanism(ScramSaslMechanism.MECHANISM_NAME, + ScramSaslMechanism.class); + SASLAuthentication.supportSASLMechanism(ScramSaslMechanism.MECHANISM_NAME); // Configure entity caps manager. This must be done only once File f = new File(getCacheDir(), "entityCaps"); f.mkdirs(); diff -r 63669480c941 -r c337d8c387f5 src/com/beem/project/beem/smack/sasl/ScramSaslClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/beem/project/beem/smack/sasl/ScramSaslClient.java Sun Jan 13 20:36:11 2013 +0100 @@ -0,0 +1,339 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.smack.sasl; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import com.isode.stroke.base.ByteArray; +import com.isode.stroke.sasl.SCRAMSHA1ClientAuthenticator; + +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.NameCallback; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import org.apache.harmony.javax.security.sasl.SaslClient; +import org.apache.harmony.javax.security.sasl.SaslException; + +/** + * A SaslClient which uses the SCRAM-SHA-! mechanism. + * This implementation is based on Stroke (http://swift.im/git/stroke) + */ +public class ScramSaslClient implements SaslClient { + + private static final int NONCE_BYTE_COUNT = 32; + private static final int NONCE_HEX_COUNT = 2 * NONCE_BYTE_COUNT; + private SCRAMSHA1ClientAuthenticator clientAuthenticator; + private CallbackHandler cbh; + private String authzid; + + /** + * Create a ScramSaslClient. + * @param authorizationId the authorizationId uses by the client + * @param cbh a CallbackHandler to get more informations + */ + public ScramSaslClient(final String authorizationId, final CallbackHandler cbh) { + this.cbh = cbh; + this.authzid = authorizationId; + } + + /** + * Disposes of any system resources or security-sensitive information the + * SaslClient might be using. Invoking this method invalidates the + * SaslClient instance. This method is idempotent. + * + * @exception SaslException if a problem was encountered while disposing + * of the resources + */ + @Override + public void dispose() throws SaslException { + } + + /** + * Evaluates the challenge data and generates a response. If a challenge + * is received from the server during the authentication process, this + * method is called to prepare an appropriate next response to submit to + * the server. + * + * @param challenge The non-null challenge sent from the server. The + * challenge array may have zero length. + * + * @return The possibly null reponse to send to the server. It is null + * if the challenge accompanied a "SUCCESS" status and the + * challenge only contains data for the client to update its + * state and no response needs to be sent to the server. + * The response is a zero-length byte array if the client is to + * send a response with no data. + * + * @exception SaslException If an error occurred while processing the + * challenge or generating a response. + */ + @Override + public byte[] evaluateChallenge(byte[] challenge) throws SaslException { + if (clientAuthenticator == null) { + Object[] userInfo = getUserInfo(); + String authcid = (String) userInfo[0]; + byte[] passwdBytes = (byte[]) userInfo[1]; + String passwd = new String(passwdBytes); + String nonce = getClientNonce(); + clientAuthenticator = new SCRAMSHA1ClientAuthenticator(nonce); + clientAuthenticator.setCredentials(authcid, passwd, authzid); + return clientAuthenticator.getResponse().getData(); + } + clientAuthenticator.setChallenge(new ByteArray(challenge)); + return clientAuthenticator.getResponse().getData(); + } + + /** + * Returns the IANA-registered mechanism name of this SASL client. + * (e.g. "CRAM-MD5", "GSSAPI") + * + * @return "SCRAM-SHA-!" the IANA-registered mechanism name of this SASL + * client. + */ + @Override + public String getMechanismName() { + return "SCRAM-SHA-1"; + } + + /** + * Retrieves the negotiated property. This method can be called only after + * the authentication exchange has completed (i.e., when isComplete() + * returns true); otherwise, an IllegalStateException is thrown. + * + * + * @param propName The non-null property name + * + * @return The value of the negotiated property. If null, the property was + * not negotiated or is not applicable to this mechanism. This + * implementation allways returns null. + * + * @exception IllegalStateException if this authentication exchange has + * not completed + */ + @Override + public Object getNegotiatedProperty(String propName) { + return null; + } + + /** + * Determines if this mechanism has an optional initial response. If true, + * caller should call evaluateChallenge() with an empty array to get the + * initial response. + * + * @return true if this mechanism has an initial response. This + * implementation always return true. + */ + @Override + public boolean hasInitialResponse() { + return true; + } + + /** + * Determines if the authentication exchange has completed. This method + * may be called at any time, but typically, it will not be called until + * the caller has received indication from the server (in a protocol- + * specific manner) that the exchange has completed. + * + * @return true if the authentication exchange has completed; + * false otherwise. + */ + @Override + public boolean isComplete() { + if (clientAuthenticator == null) + return false; + return clientAuthenticator.getResponse() == null; + } + + /** + * Unwraps a byte array received from the server. This method can be called + * only after the authentication exchange has completed (i.e., when + * isComplete() returns true) and only if the authentication exchange has + * negotiated integrity and/or privacy as the quality of protection; + * otherwise, an IllegalStateException is thrown. + * + * This implementation always throw an IllegalStateException. + * + * incoming is the contents of the SASL buffer as defined in RFC 2222 + * without the leading four octet field that represents the length. + * offset and len specify the portion of incoming to use. + * + * @param incoming A non-null byte array containing the encoded bytes + * from the server + * @param offset The starting position at incoming of the bytes to use + * + * @param len The number of bytes from incoming to use + * + * @return A non-null byte array containing the decoded bytes + * @throws SaslException if an error occurs + * + */ + @Override + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { + throw new IllegalStateException("SCRAM-SHA-1: this mechanism supports " + + "neither integrity nor privacy"); + } + + /** + * Wraps a byte array to be sent to the server. This method can be called + * only after the authentication exchange has completed (i.e., when + * isComplete() returns true) and only if the authentication exchange has + * negotiated integrity and/or privacy as the quality of protection; + * otherwise, an IllegalStateException is thrown. + * + * This implementation always throw an IllegalStateException. + * + * The result of this method will make up the contents of the SASL buffer as + * defined in RFC 2222 without the leading four octet field that represents + * the length. offset and len specify the portion of outgoing to use. + * + * @param outgoing A non-null byte array containing the bytes to encode + * @param offset The starting position at outgoing of the bytes to use + * @param len The number of bytes from outgoing to use + * + * @return A non-null byte array containing the encoded bytes + * + * @exception SaslException if incoming cannot be successfully unwrapped. + * + * @exception IllegalStateException if the authentication exchange has + * not completed, or if the negotiated quality of + * protection has neither integrity nor privacy. + */ + @Override + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { + throw new IllegalStateException("SCRAM-SHA-1: this mechanism supports " + + "neither integrity nor privacy"); + } + + + /** + * Calculates the Nonce value of the Client. + * + * @return Nonce value of the client + * + * @exception SaslException If an error Occurs + */ + private String getClientNonce() throws SaslException { + byte[] nonceBytes = new byte[NONCE_BYTE_COUNT]; + SecureRandom prng; + byte nonceByte; + char[] hexNonce = new char[NONCE_HEX_COUNT]; + + try { + prng = SecureRandom.getInstance("SHA1PRNG"); + prng.nextBytes(nonceBytes); + for (int i = 0; i < NONCE_BYTE_COUNT; i++) { + //low nibble + hexNonce[i * 2] = getHexChar((byte) (nonceBytes[i] & 0x0f)); + //high nibble + hexNonce[(i * 2) + 1] = getHexChar((byte) ((nonceBytes[i] & 0xf0) >> 4)); + } + return new String(hexNonce); + } catch (NoSuchAlgorithmException e) { + throw new SaslException("No random number generator available", e); + } + } + + /** + * Get informations supplied by the user of the SaslClient. + * These informations are retrived by using the CallbackHandler. + * + * @return an array of object + * @throws SaslException if the informations cannot be retrieved + */ + private Object[] getUserInfo() throws SaslException { + try { + final String userPrompt = "Authentication id: "; + final String pwPrompt = "Password: "; + NameCallback nameCb = new NameCallback(userPrompt); + PasswordCallback passwordCb = new PasswordCallback(pwPrompt, false); + cbh.handle(new Callback[] {nameCb, passwordCb }); + String userid = nameCb.getName(); + char[] pwchars = passwordCb.getPassword(); + byte[] pwbytes; + if (pwchars != null) { + pwbytes = (new String(pwchars)).getBytes("UTF8"); + passwordCb.clearPassword(); + } else { + pwbytes = null; + } + return new Object[] {userid, pwbytes }; + } catch (IOException e) { + throw new SaslException("Cannot get password", e); + } catch (UnsupportedCallbackException e) { + throw new SaslException("Cannot get userid/password", e); + } + } + + /** + * This function returns hex character representing the value of the input. + * + * @param value Input value in byte + * + * @return Hex value of the Input byte value + */ + private static char getHexChar(byte value) { + switch (value) { + case 0: + return '0'; + case 1: + return '1'; + case 2: + return '2'; + case 3: + return '3'; + case 4: + return '4'; + case 5: + return '5'; + case 6: + return '6'; + case 7: + return '7'; + case 8: + return '8'; + case 9: + return '9'; + case 10: + return 'a'; + case 11: + return 'b'; + case 12: + return 'c'; + case 13: + return 'd'; + case 14: + return 'e'; + case 15: + return 'f'; + default: + return 'Z'; + } + } +} diff -r 63669480c941 -r c337d8c387f5 src/com/beem/project/beem/smack/sasl/ScramSaslMechanism.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/beem/project/beem/smack/sasl/ScramSaslMechanism.java Sun Jan 13 20:36:11 2013 +0100 @@ -0,0 +1,81 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2012 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + +*/ +package com.beem.project.beem.smack.sasl; + +import java.io.IOException; + + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.jivesoftware.smack.SASLAuthentication; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.sasl.SASLMechanism; + +/** + * An implementation of the SCRAM-SHA-1 SASL mechanism. + * This implementation is based on Stroke http://swift.im/git/stroke. + */ +public class ScramSaslMechanism extends SASLMechanism { + + /** + * The name of the SASL mechanism. + */ + public static final String MECHANISM_NAME = "SCRAM-SHA-1"; + + /** + * Create a ScramSaslMechanism. + * + * @param saslAuthentication the smack SASLAuthentication. + * + */ + public ScramSaslMechanism(final SASLAuthentication saslAuthentication) { + super(saslAuthentication); + } + + @Override + public void authenticate(String username, String host, String password) throws IOException, XMPPException { + this.authenticationId = username; + this.password = password; + this.hostname = host; + + sc = new ScramSaslClient(username, this); + authenticate(); + } + + @Override + public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { + this.authenticationId = username; + this.hostname = host; + sc = new ScramSaslClient(username, cbh); + authenticate(); + } + + @Override + protected String getName() { + return MECHANISM_NAME; + } + +}