| /* SRPClient.java -- |
| Copyright (C) 2003, 2006, 2010 Free Software Foundation, Inc. |
| |
| This file is a part of GNU Classpath. |
| |
| GNU Classpath 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 2 of the License, or (at |
| your option) any later version. |
| |
| GNU Classpath 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 GNU Classpath; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 |
| USA |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.javax.crypto.sasl.srp; |
| |
| import gnu.java.lang.CPStringBuilder; |
| |
| import gnu.java.security.Configuration; |
| import gnu.java.security.Registry; |
| import gnu.java.security.hash.MD5; |
| import gnu.java.security.util.PRNG; |
| import gnu.java.security.util.Util; |
| import gnu.javax.crypto.assembly.Direction; |
| import gnu.javax.crypto.cipher.CipherFactory; |
| import gnu.javax.crypto.cipher.IBlockCipher; |
| import gnu.javax.crypto.key.IKeyAgreementParty; |
| import gnu.javax.crypto.key.IncomingMessage; |
| import gnu.javax.crypto.key.KeyAgreementException; |
| import gnu.javax.crypto.key.KeyAgreementFactory; |
| import gnu.javax.crypto.key.OutgoingMessage; |
| import gnu.javax.crypto.key.srp6.SRP6KeyAgreement; |
| import gnu.javax.crypto.sasl.ClientMechanism; |
| import gnu.javax.crypto.sasl.IllegalMechanismStateException; |
| import gnu.javax.crypto.sasl.InputBuffer; |
| import gnu.javax.crypto.sasl.IntegrityException; |
| import gnu.javax.crypto.sasl.OutputBuffer; |
| import gnu.javax.security.auth.Password; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.math.BigInteger; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.StringTokenizer; |
| import java.util.logging.Logger; |
| |
| import javax.security.auth.DestroyFailedException; |
| import javax.security.auth.callback.Callback; |
| import javax.security.auth.callback.NameCallback; |
| import javax.security.auth.callback.PasswordCallback; |
| import javax.security.auth.callback.UnsupportedCallbackException; |
| import javax.security.sasl.AuthenticationException; |
| import javax.security.sasl.SaslClient; |
| import javax.security.sasl.SaslException; |
| |
| /** |
| * The SASL-SRP client-side mechanism. |
| */ |
| public class SRPClient |
| extends ClientMechanism |
| implements SaslClient |
| { |
| private static final Logger log = Configuration.DEBUG ? |
| Logger.getLogger(SRPClient.class.getName()) : null; |
| private String uid; // the unique key for this type of client |
| private String U; // the authentication identity |
| BigInteger N, g, A, B; |
| private Password password; // the authentication credentials |
| private byte[] s; // the user's salt |
| private byte[] cIV, sIV; // client+server IVs, when confidentiality is on |
| private byte[] M1, M2; // client+server evidences |
| private byte[] cn, sn; // client's and server's nonce |
| private SRP srp; // SRP algorithm instance used by this client |
| private byte[] sid; // session ID when re-used |
| private int ttl; // session time-to-live in seconds |
| private byte[] sCB; // the peer's channel binding data |
| private String L; // available options |
| private String o; |
| private String chosenIntegrityAlgorithm; |
| private String chosenConfidentialityAlgorithm; |
| private int rawSendSize = Registry.SASL_BUFFER_MAX_LIMIT; |
| private byte[] K; // shared session key |
| private boolean replayDetection = true; // whether Replay Detection is on |
| private int inCounter = 0; // messages sequence numbers |
| private int outCounter = 0; |
| private IALG inMac, outMac; // if !null, use for integrity |
| private CALG inCipher, outCipher; // if !null, use for confidentiality |
| private IKeyAgreementParty clientHandler = |
| KeyAgreementFactory.getPartyAInstance(Registry.SRP_SASL_KA); |
| /** Our default source of randomness. */ |
| private PRNG prng = null; |
| |
| public SRPClient() |
| { |
| super(Registry.SASL_SRP_MECHANISM); |
| } |
| |
| protected void initMechanism() throws SaslException |
| { |
| // we shall keep track of the sid (and the security context of this SRP |
| // client) based on the initialisation parameters of an SRP session. |
| // we shall compute a unique key for those parameters and key the sid |
| // (and the security context) accordingly. |
| // 1. compute the mapping key. use MD5 (the fastest) for this purpose |
| final MD5 md = new MD5(); |
| byte[] b; |
| b = authorizationID.getBytes(); |
| md.update(b, 0, b.length); |
| b = serverName.getBytes(); |
| md.update(b, 0, b.length); |
| b = protocol.getBytes(); |
| md.update(b, 0, b.length); |
| if (channelBinding.length > 0) |
| md.update(channelBinding, 0, channelBinding.length); |
| |
| uid = Util.toBase64(md.digest()); |
| if (ClientStore.instance().isAlive(uid)) |
| { |
| final SecurityContext ctx = ClientStore.instance().restoreSession(uid); |
| srp = SRP.instance(ctx.getMdName()); |
| sid = ctx.getSID(); |
| K = ctx.getK(); |
| cIV = ctx.getClientIV(); |
| sIV = ctx.getServerIV(); |
| replayDetection = ctx.hasReplayDetection(); |
| inCounter = ctx.getInCounter(); |
| outCounter = ctx.getOutCounter(); |
| inMac = ctx.getInMac(); |
| outMac = ctx.getOutMac(); |
| inCipher = ctx.getInCipher(); |
| outCipher = ctx.getOutCipher(); |
| } |
| else |
| { |
| sid = new byte[0]; |
| ttl = 0; |
| K = null; |
| cIV = null; |
| sIV = null; |
| cn = null; |
| sn = null; |
| } |
| } |
| |
| protected void resetMechanism() throws SaslException |
| { |
| try |
| { |
| password.destroy(); |
| } |
| catch (DestroyFailedException dfe) |
| { |
| SaslException se = new SaslException("resetMechanism()"); |
| se.initCause(dfe); |
| throw se; |
| } |
| password = null; |
| M1 = null; |
| K = null; |
| cIV = null; |
| sIV = null; |
| inMac = outMac = null; |
| inCipher = outCipher = null; |
| sid = null; |
| ttl = 0; |
| cn = null; |
| sn = null; |
| } |
| |
| public boolean hasInitialResponse() |
| { |
| return true; |
| } |
| |
| public byte[] evaluateChallenge(final byte[] challenge) throws SaslException |
| { |
| switch (state) |
| { |
| case 0: |
| state++; |
| return sendIdentities(); |
| case 1: |
| state++; |
| final byte[] result = sendPublicKey(challenge); |
| try |
| { |
| password.destroy(); //don't need further this session |
| } |
| catch (DestroyFailedException x) |
| { |
| SaslException se = new SaslException("sendPublicKey()"); |
| se.initCause(se); |
| throw se; |
| } |
| return result; |
| case 2: // should only occur if session re-use was rejected |
| if (! complete) |
| { |
| state++; |
| return receiveEvidence(challenge); |
| } |
| // else fall through |
| default: |
| throw new IllegalMechanismStateException("evaluateChallenge()"); |
| } |
| } |
| |
| protected byte[] engineUnwrap(final byte[] incoming, final int offset, |
| final int len) throws SaslException |
| { |
| if (Configuration.DEBUG) |
| log.entering(this.getClass().getName(), "engineUnwrap"); |
| if (inMac == null && inCipher == null) |
| throw new IllegalStateException("connection is not protected"); |
| // at this point one, or both, of confidentiality and integrity protection |
| // services are active. |
| final byte[] result; |
| try |
| { |
| if (inMac != null) |
| { // integrity bytes are at the end of the stream |
| final int macBytesCount = inMac.length(); |
| final int payloadLength = len - macBytesCount; |
| final byte[] received_mac = new byte[macBytesCount]; |
| System.arraycopy(incoming, offset + payloadLength, received_mac, 0, |
| macBytesCount); |
| if (Configuration.DEBUG) |
| log.fine("Got C (received MAC): " + Util.dumpString(received_mac)); |
| inMac.update(incoming, offset, payloadLength); |
| if (replayDetection) |
| { |
| inCounter++; |
| if (Configuration.DEBUG) |
| log.fine("inCounter=" + inCounter); |
| inMac.update(new byte[] { |
| (byte)(inCounter >>> 24), |
| (byte)(inCounter >>> 16), |
| (byte)(inCounter >>> 8), |
| (byte) inCounter }); |
| } |
| final byte[] computed_mac = inMac.doFinal(); |
| if (Configuration.DEBUG) |
| log.fine("Computed MAC: " + Util.dumpString(computed_mac)); |
| if (! Arrays.equals(received_mac, computed_mac)) |
| throw new IntegrityException("engineUnwrap()"); |
| // deal with the payload, which can be either plain or encrypted |
| if (inCipher != null) |
| result = inCipher.doFinal(incoming, offset, payloadLength); |
| else |
| { |
| result = new byte[len - macBytesCount]; |
| System.arraycopy(incoming, offset, result, 0, result.length); |
| } |
| } |
| else // no integrity protection; just confidentiality |
| result = inCipher.doFinal(incoming, offset, len); |
| } |
| catch (IOException x) |
| { |
| if (x instanceof SaslException) |
| throw (SaslException) x; |
| throw new SaslException("engineUnwrap()", x); |
| } |
| if (Configuration.DEBUG) |
| log.exiting(this.getClass().getName(), "engineUnwrap"); |
| return result; |
| } |
| |
| protected byte[] engineWrap(final byte[] outgoing, final int offset, |
| final int len) throws SaslException |
| { |
| if (Configuration.DEBUG) |
| log.entering(this.getClass().getName(), "engineWrap"); |
| if (outMac == null && outCipher == null) |
| throw new IllegalStateException("connection is not protected"); |
| // at this point one, or both, of confidentiality and integrity protection |
| // services are active. |
| byte[] result; |
| try |
| { |
| final ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| // Process the data |
| if (outCipher != null) |
| { |
| result = outCipher.doFinal(outgoing, offset, len); |
| if (Configuration.DEBUG) |
| log.fine("Encoding c (encrypted plaintext): " |
| + Util.dumpString(result)); |
| out.write(result); |
| if (outMac != null) |
| { |
| outMac.update(result); |
| if (replayDetection) |
| { |
| outCounter++; |
| if (Configuration.DEBUG) |
| log.fine("outCounter=" + outCounter); |
| outMac.update(new byte[] { |
| (byte)(outCounter >>> 24), |
| (byte)(outCounter >>> 16), |
| (byte)(outCounter >>> 8), |
| (byte) outCounter }); |
| } |
| final byte[] C = outMac.doFinal(); |
| out.write(C); |
| if (Configuration.DEBUG) |
| log.fine("Encoding C (integrity checksum): " + Util.dumpString(C)); |
| } |
| // else confidentiality only; do nothing |
| } |
| else // no confidentiality; just integrity [+ replay detection] |
| { |
| if (Configuration.DEBUG) |
| log.fine("Encoding p (plaintext): " |
| + Util.dumpString(outgoing, offset, len)); |
| out.write(outgoing, offset, len); |
| outMac.update(outgoing, offset, len); |
| if (replayDetection) |
| { |
| outCounter++; |
| if (Configuration.DEBUG) |
| log.fine("outCounter=" + outCounter); |
| outMac.update(new byte[] { |
| (byte)(outCounter >>> 24), |
| (byte)(outCounter >>> 16), |
| (byte)(outCounter >>> 8), |
| (byte) outCounter }); |
| } |
| final byte[] C = outMac.doFinal(); |
| out.write(C); |
| if (Configuration.DEBUG) |
| log.fine("Encoding C (integrity checksum): " + Util.dumpString(C)); |
| } |
| result = out.toByteArray(); |
| } |
| catch (IOException x) |
| { |
| if (x instanceof SaslException) |
| throw (SaslException) x; |
| throw new SaslException("engineWrap()", x); |
| } |
| if (Configuration.DEBUG) |
| log.exiting(this.getClass().getName(), "engineWrap"); |
| return result; |
| } |
| |
| protected String getNegotiatedQOP() |
| { |
| if (inMac != null) |
| { |
| if (inCipher != null) |
| return Registry.QOP_AUTH_CONF; |
| return Registry.QOP_AUTH_INT; |
| } |
| return Registry.QOP_AUTH; |
| } |
| |
| protected String getNegotiatedStrength() |
| { |
| if (inMac != null) |
| { |
| if (inCipher != null) |
| return Registry.STRENGTH_HIGH; |
| return Registry.STRENGTH_MEDIUM; |
| } |
| return Registry.STRENGTH_LOW; |
| } |
| |
| protected String getNegotiatedRawSendSize() |
| { |
| return String.valueOf(rawSendSize); |
| } |
| |
| protected String getReuse() |
| { |
| return Registry.REUSE_TRUE; |
| } |
| |
| private byte[] sendIdentities() throws SaslException |
| { |
| if (Configuration.DEBUG) |
| log.entering(this.getClass().getName(), "sendIdentities"); |
| // If necessary, prompt the client for the username and password |
| getUsernameAndPassword(); |
| if (Configuration.DEBUG) |
| { |
| log.fine("Password: \"" + new String(password.getPassword()) + "\""); |
| log.fine("Encoding U (username): \"" + U + "\""); |
| log.fine("Encoding I (userid): \"" + authorizationID + "\""); |
| } |
| // if session re-use generate new 16-byte nonce |
| if (sid.length != 0) |
| { |
| cn = new byte[16]; |
| getDefaultPRNG().nextBytes(cn); |
| } |
| else |
| cn = new byte[0]; |
| final OutputBuffer frameOut = new OutputBuffer(); |
| try |
| { |
| frameOut.setText(U); |
| frameOut.setText(authorizationID); |
| frameOut.setEOS(sid); // session ID to re-use |
| frameOut.setOS(cn); // client nonce |
| frameOut.setEOS(channelBinding); |
| } |
| catch (IOException x) |
| { |
| if (x instanceof SaslException) |
| throw (SaslException) x; |
| throw new AuthenticationException("sendIdentities()", x); |
| } |
| final byte[] result = frameOut.encode(); |
| if (Configuration.DEBUG) |
| { |
| log.fine("C: " + Util.dumpString(result)); |
| log.fine(" U = " + U); |
| log.fine(" I = " + authorizationID); |
| log.fine("sid = " + new String(sid)); |
| log.fine(" cn = " + Util.dumpString(cn)); |
| log.fine("cCB = " + Util.dumpString(channelBinding)); |
| log.exiting(this.getClass().getName(), "sendIdentities"); |
| } |
| return result; |
| } |
| |
| private byte[] sendPublicKey(final byte[] input) throws SaslException |
| { |
| if (Configuration.DEBUG) |
| { |
| log.entering(this.getClass().getName(), "sendPublicKey"); |
| log.fine("S: " + Util.dumpString(input)); |
| } |
| // Server sends [00], N, g, s, B, L |
| // or [FF], sn, sCB |
| final InputBuffer frameIn = new InputBuffer(input); |
| final int ack; |
| try |
| { |
| ack = (int) frameIn.getScalar(1); |
| if (ack == 0x00) // new session |
| { |
| N = frameIn.getMPI(); |
| if (Configuration.DEBUG) |
| log.fine("Got N (modulus): " + Util.dump(N)); |
| g = frameIn.getMPI(); |
| if (Configuration.DEBUG) |
| log.fine("Got g (generator): " + Util.dump(g)); |
| s = frameIn.getOS(); |
| if (Configuration.DEBUG) |
| log.fine("Got s (salt): " + Util.dumpString(s)); |
| B = frameIn.getMPI(); |
| if (Configuration.DEBUG) |
| log.fine("Got B (server ephermeral public key): " + Util.dump(B)); |
| L = frameIn.getText(); |
| if (Configuration.DEBUG) |
| log.fine("Got L (available options): \"" + L + "\""); |
| } |
| else if (ack == 0xFF) // session re-use |
| { |
| sn = frameIn.getOS(); |
| if (Configuration.DEBUG) |
| log.fine("Got sn (server nonce): " + Util.dumpString(sn)); |
| sCB = frameIn.getEOS(); |
| if (Configuration.DEBUG) |
| log.fine("Got sCB (server channel binding): " + Util.dumpString(sCB)); |
| } |
| else // unexpected scalar |
| throw new SaslException("sendPublicKey(): Invalid scalar (" + ack |
| + ") in server's request"); |
| } |
| catch (IOException x) |
| { |
| if (x instanceof SaslException) |
| throw (SaslException) x; |
| throw new SaslException("sendPublicKey()", x); |
| } |
| if (ack == 0x00) |
| { // new session --------------------------------------- |
| o = createO(L.toLowerCase()); // do this first to initialise the SRP hash |
| final byte[] pBytes; // use ASCII encoding to inter-operate w/ non-java |
| pBytes = password.getBytes(); |
| // ---------------------------------------------------------------------- |
| final HashMap mapA = new HashMap(); |
| mapA.put(SRP6KeyAgreement.HASH_FUNCTION, srp.getAlgorithm()); |
| mapA.put(SRP6KeyAgreement.USER_IDENTITY, U); |
| mapA.put(SRP6KeyAgreement.USER_PASSWORD, pBytes); |
| try |
| { |
| clientHandler.init(mapA); |
| clientHandler.processMessage(null); |
| } |
| catch (KeyAgreementException x) |
| { |
| throw new SaslException("sendPublicKey()", x); |
| } |
| // ------------------------------------------------------------------- |
| try |
| { |
| OutgoingMessage out = new OutgoingMessage(); |
| out.writeMPI(N); |
| out.writeMPI(g); |
| out.writeMPI(new BigInteger(1, s)); |
| out.writeMPI(B); |
| IncomingMessage in = new IncomingMessage(out.toByteArray()); |
| out = clientHandler.processMessage(in); |
| in = new IncomingMessage(out.toByteArray()); |
| A = in.readMPI(); |
| K = clientHandler.getSharedSecret(); |
| } |
| catch (KeyAgreementException x) |
| { |
| throw new SaslException("sendPublicKey()", x); |
| } |
| // ------------------------------------------------------------------- |
| if (Configuration.DEBUG) |
| { |
| log.fine("K: " + Util.dumpString(K)); |
| log.fine("Encoding A (client ephemeral public key): " + Util.dump(A)); |
| } |
| try |
| { |
| M1 = srp.generateM1(N, g, U, s, A, B, K, authorizationID, L, cn, |
| channelBinding); |
| } |
| catch (UnsupportedEncodingException x) |
| { |
| throw new AuthenticationException("sendPublicKey()", x); |
| } |
| if (Configuration.DEBUG) |
| { |
| log.fine("Encoding o (client chosen options): \"" + o + "\""); |
| log.fine("Encoding cIV (client IV): \"" + Util.dumpString(cIV) + "\""); |
| } |
| final OutputBuffer frameOut = new OutputBuffer(); |
| try |
| { |
| frameOut.setMPI(A); |
| frameOut.setOS(M1); |
| frameOut.setText(o); |
| frameOut.setOS(cIV); |
| } |
| catch (IOException x) |
| { |
| if (x instanceof SaslException) |
| throw (SaslException) x; |
| throw new AuthenticationException("sendPublicKey()", x); |
| } |
| final byte[] result = frameOut.encode(); |
| if (Configuration.DEBUG) |
| { |
| log.fine("New session, or session re-use rejected..."); |
| log.fine("C: " + Util.dumpString(result)); |
| log.fine(" A = 0x" + A.toString(16)); |
| log.fine(" M1 = " + Util.dumpString(M1)); |
| log.fine(" o = " + o); |
| log.fine("cIV = " + Util.dumpString(cIV)); |
| log.exiting(this.getClass().getName(), "sendPublicKey"); |
| } |
| return result; |
| } |
| else // session re-use accepted ------------------------------------------- |
| { |
| setupSecurityServices(true); |
| if (Configuration.DEBUG) |
| { |
| log.fine("Session re-use accepted..."); |
| log.exiting(this.getClass().getName(), "sendPublicKey"); |
| } |
| return null; |
| } |
| } |
| |
| private byte[] receiveEvidence(byte[] input) throws SaslException |
| { |
| if (Configuration.DEBUG) |
| { |
| log.entering(this.getClass().getName(), "receiveEvidence"); |
| log.fine("S: " + Util.dumpString(input)); |
| } |
| // Server send M2, sIV, sCB, sid, ttl |
| final InputBuffer frameIn = new InputBuffer(input); |
| try |
| { |
| M2 = frameIn.getOS(); |
| if (Configuration.DEBUG) |
| log.fine("Got M2 (server evidence): " + Util.dumpString(M2)); |
| sIV = frameIn.getOS(); |
| if (Configuration.DEBUG) |
| log.fine("Got sIV (server IV): " + Util.dumpString(sIV)); |
| sid = frameIn.getEOS(); |
| if (Configuration.DEBUG) |
| log.fine("Got sid (session ID): " + new String(sid)); |
| ttl = (int) frameIn.getScalar(4); |
| if (Configuration.DEBUG) |
| log.fine("Got ttl (session time-to-live): " + ttl + "sec."); |
| sCB = frameIn.getEOS(); |
| if (Configuration.DEBUG) |
| log.fine("Got sCB (server channel binding): " + Util.dumpString(sCB)); |
| } |
| catch (IOException x) |
| { |
| if (x instanceof SaslException) |
| throw (SaslException) x; |
| throw new AuthenticationException("receiveEvidence()", x); |
| } |
| |
| final byte[] expected; |
| try |
| { |
| expected = srp.generateM2(A, M1, K, U, authorizationID, o, sid, ttl, |
| cIV, sIV, sCB); |
| } |
| catch (UnsupportedEncodingException x) |
| { |
| throw new AuthenticationException("receiveEvidence()", x); |
| } |
| if (Configuration.DEBUG) |
| log.fine("Expected: " + Util.dumpString(expected)); |
| if (! Arrays.equals(M2, expected)) |
| throw new AuthenticationException("M2 mismatch"); |
| setupSecurityServices(false); |
| if (Configuration.DEBUG) |
| log.exiting(this.getClass().getName(), "receiveEvidence"); |
| return null; |
| } |
| |
| private void getUsernameAndPassword() throws AuthenticationException |
| { |
| try |
| { |
| if ((! properties.containsKey(Registry.SASL_USERNAME)) |
| && (! properties.containsKey(Registry.SASL_PASSWORD))) |
| { |
| final NameCallback nameCB; |
| final String defaultName = System.getProperty("user.name"); |
| if (defaultName == null) |
| nameCB = new NameCallback("username: "); |
| else |
| nameCB = new NameCallback("username: ", defaultName); |
| final PasswordCallback pwdCB = new PasswordCallback("password: ", |
| false); |
| handler.handle(new Callback[] { nameCB, pwdCB }); |
| U = nameCB.getName(); |
| password = new Password(pwdCB.getPassword()); |
| } |
| else |
| { |
| if (properties.containsKey(Registry.SASL_USERNAME)) |
| this.U = (String) properties.get(Registry.SASL_USERNAME); |
| else |
| { |
| final NameCallback nameCB; |
| final String defaultName = System.getProperty("user.name"); |
| if (defaultName == null) |
| nameCB = new NameCallback("username: "); |
| else |
| nameCB = new NameCallback("username: ", defaultName); |
| this.handler.handle(new Callback[] { nameCB }); |
| this.U = nameCB.getName(); |
| } |
| |
| if (properties.containsKey(Registry.SASL_PASSWORD)) |
| { |
| Object pw = properties.get(Registry.SASL_PASSWORD); |
| if (pw instanceof char[]) |
| password = new Password((char[]) pw); |
| else if (pw instanceof Password) |
| password = (Password) pw; |
| else if (pw instanceof String) |
| password = new Password(((String) pw).toCharArray()); |
| else |
| throw new IllegalArgumentException(pw.getClass().getName() |
| + "is not a valid password class"); |
| } |
| else |
| { |
| final PasswordCallback pwdCB = new PasswordCallback("password: ", |
| false); |
| this.handler.handle(new Callback[] { pwdCB }); |
| password = new Password(pwdCB.getPassword()); |
| } |
| } |
| |
| if (U == null) |
| throw new AuthenticationException("null username supplied"); |
| if (password == null) |
| throw new AuthenticationException("null password supplied"); |
| } |
| catch (UnsupportedCallbackException x) |
| { |
| throw new AuthenticationException("getUsernameAndPassword()", x); |
| } |
| catch (IOException x) |
| { |
| throw new AuthenticationException("getUsernameAndPassword()", x); |
| } |
| } |
| |
| // We go through the list of available services and for each available one |
| // we decide whether or not we want it enabled, based on properties passed |
| // to us by the client. |
| private String createO(final String aol) throws AuthenticationException |
| { |
| if (Configuration.DEBUG) |
| log.entering(this.getClass().getName(), "createO", aol); |
| boolean replaydetectionAvailable = false; |
| boolean integrityAvailable = false; |
| boolean confidentialityAvailable = false; |
| String option, mandatory = SRPRegistry.DEFAULT_MANDATORY; |
| int i; |
| |
| String mdName = SRPRegistry.SRP_DEFAULT_DIGEST_NAME; |
| final StringTokenizer st = new StringTokenizer(aol, ","); |
| while (st.hasMoreTokens()) |
| { |
| option = st.nextToken(); |
| if (option.startsWith(SRPRegistry.OPTION_SRP_DIGEST + "=")) |
| { |
| option = option.substring(option.indexOf('=') + 1); |
| if (Configuration.DEBUG) |
| log.fine("mda: <" + option + ">"); |
| for (i = 0; i < SRPRegistry.INTEGRITY_ALGORITHMS.length; i++) |
| if (SRPRegistry.SRP_ALGORITHMS[i].equals(option)) |
| { |
| mdName = option; |
| break; |
| } |
| } |
| else if (option.equals(SRPRegistry.OPTION_REPLAY_DETECTION)) |
| replaydetectionAvailable = true; |
| else if (option.startsWith(SRPRegistry.OPTION_INTEGRITY + "=")) |
| { |
| option = option.substring(option.indexOf('=') + 1); |
| if (Configuration.DEBUG) |
| log.fine("ialg: <" + option + ">"); |
| for (i = 0; i < SRPRegistry.INTEGRITY_ALGORITHMS.length; i++) |
| if (SRPRegistry.INTEGRITY_ALGORITHMS[i].equals(option)) |
| { |
| chosenIntegrityAlgorithm = option; |
| integrityAvailable = true; |
| break; |
| } |
| } |
| else if (option.startsWith(SRPRegistry.OPTION_CONFIDENTIALITY + "=")) |
| { |
| option = option.substring(option.indexOf('=') + 1); |
| if (Configuration.DEBUG) |
| log.fine("calg: <" + option + ">"); |
| for (i = 0; i < SRPRegistry.CONFIDENTIALITY_ALGORITHMS.length; i++) |
| if (SRPRegistry.CONFIDENTIALITY_ALGORITHMS[i].equals(option)) |
| { |
| chosenConfidentialityAlgorithm = option; |
| confidentialityAvailable = true; |
| break; |
| } |
| } |
| else if (option.startsWith(SRPRegistry.OPTION_MANDATORY + "=")) |
| mandatory = option.substring(option.indexOf('=') + 1); |
| else if (option.startsWith(SRPRegistry.OPTION_MAX_BUFFER_SIZE + "=")) |
| { |
| final String maxBufferSize = option.substring(option.indexOf('=') + 1); |
| try |
| { |
| rawSendSize = Integer.parseInt(maxBufferSize); |
| if (rawSendSize > Registry.SASL_BUFFER_MAX_LIMIT |
| || rawSendSize < 1) |
| throw new AuthenticationException( |
| "Illegal value for 'maxbuffersize' option"); |
| } |
| catch (NumberFormatException x) |
| { |
| throw new AuthenticationException( |
| SRPRegistry.OPTION_MAX_BUFFER_SIZE + "=" + maxBufferSize, x); |
| } |
| } |
| } |
| String s; |
| Boolean flag; |
| s = (String) properties.get(SRPRegistry.SRP_REPLAY_DETECTION); |
| flag = Boolean.valueOf(s); |
| replayDetection = replaydetectionAvailable && flag.booleanValue(); |
| s = (String) properties.get(SRPRegistry.SRP_INTEGRITY_PROTECTION); |
| flag = Boolean.valueOf(s); |
| boolean integrity = integrityAvailable && flag.booleanValue(); |
| s = (String) properties.get(SRPRegistry.SRP_CONFIDENTIALITY); |
| flag = Boolean.valueOf(s); |
| boolean confidentiality = confidentialityAvailable && flag.booleanValue(); |
| // make sure we do the right thing |
| if (SRPRegistry.OPTION_REPLAY_DETECTION.equals(mandatory)) |
| { |
| replayDetection = true; |
| integrity = true; |
| } |
| else if (SRPRegistry.OPTION_INTEGRITY.equals(mandatory)) |
| integrity = true; |
| else if (SRPRegistry.OPTION_CONFIDENTIALITY.equals(mandatory)) |
| confidentiality = true; |
| |
| if (replayDetection) |
| { |
| if (chosenIntegrityAlgorithm == null) |
| throw new AuthenticationException( |
| "Replay detection is required but no integrity protection " |
| + "algorithm was chosen"); |
| } |
| if (integrity) |
| { |
| if (chosenIntegrityAlgorithm == null) |
| throw new AuthenticationException( |
| "Integrity protection is required but no algorithm was chosen"); |
| } |
| if (confidentiality) |
| { |
| if (chosenConfidentialityAlgorithm == null) |
| throw new AuthenticationException( |
| "Confidentiality protection is required but no algorithm was chosen"); |
| } |
| // 1. check if we'll be using confidentiality; if not set IV to 0-byte |
| if (chosenConfidentialityAlgorithm == null) |
| cIV = new byte[0]; |
| else |
| { |
| // 2. get the block size of the cipher |
| final IBlockCipher cipher = CipherFactory.getInstance(chosenConfidentialityAlgorithm); |
| if (cipher == null) |
| throw new AuthenticationException("createO()", |
| new NoSuchAlgorithmException()); |
| final int blockSize = cipher.defaultBlockSize(); |
| // 3. generate random iv |
| cIV = new byte[blockSize]; |
| getDefaultPRNG().nextBytes(cIV); |
| } |
| srp = SRP.instance(mdName); |
| // Now create the options list specifying which of the available options |
| // we have chosen. |
| |
| // For now we just select the defaults. Later we need to add support for |
| // properties (perhaps in a file) where a user can specify the list of |
| // algorithms they would prefer to use. |
| final CPStringBuilder sb = new CPStringBuilder(); |
| sb.append(SRPRegistry.OPTION_SRP_DIGEST) |
| .append("=").append(mdName).append(","); |
| if (replayDetection) |
| sb.append(SRPRegistry.OPTION_REPLAY_DETECTION).append(","); |
| if (integrity) |
| sb.append(SRPRegistry.OPTION_INTEGRITY) |
| .append("=").append(chosenIntegrityAlgorithm).append(","); |
| if (confidentiality) |
| sb.append(SRPRegistry.OPTION_CONFIDENTIALITY) |
| .append("=").append(chosenConfidentialityAlgorithm).append(","); |
| |
| final String result = sb.append(SRPRegistry.OPTION_MAX_BUFFER_SIZE) |
| .append("=").append(Registry.SASL_BUFFER_MAX_LIMIT) |
| .toString(); |
| if (Configuration.DEBUG) |
| log.exiting(this.getClass().getName(), "createO", result); |
| return result; |
| } |
| |
| private void setupSecurityServices(final boolean sessionReUse) |
| throws SaslException |
| { |
| complete = true; // signal end of authentication phase |
| if (! sessionReUse) |
| { |
| outCounter = inCounter = 0; |
| // instantiate cipher if confidentiality protection filter is active |
| if (chosenConfidentialityAlgorithm != null) |
| { |
| if (Configuration.DEBUG) |
| log.fine("Activating confidentiality protection filter"); |
| inCipher = CALG.getInstance(chosenConfidentialityAlgorithm); |
| outCipher = CALG.getInstance(chosenConfidentialityAlgorithm); |
| } |
| // instantiate hmacs if integrity protection filter is active |
| if (chosenIntegrityAlgorithm != null) |
| { |
| if (Configuration.DEBUG) |
| log.fine("Activating integrity protection filter"); |
| inMac = IALG.getInstance(chosenIntegrityAlgorithm); |
| outMac = IALG.getInstance(chosenIntegrityAlgorithm); |
| } |
| } |
| else // same session new Keys |
| K = srp.generateKn(K, cn, sn); |
| |
| final KDF kdf = KDF.getInstance(K); |
| // initialise in/out ciphers if confidentiality protection is used |
| if (inCipher != null) |
| { |
| inCipher.init(kdf, sIV, Direction.REVERSED); |
| outCipher.init(kdf, cIV, Direction.FORWARD); |
| } |
| // initialise in/out macs if integrity protection is used |
| if (inMac != null) |
| { |
| inMac.init(kdf); |
| outMac.init(kdf); |
| } |
| if (sid != null && sid.length != 0) |
| { // update the security context and save in map |
| if (Configuration.DEBUG) |
| log.fine("Updating security context for UID = " + uid); |
| ClientStore.instance().cacheSession(uid, |
| ttl, |
| new SecurityContext(srp.getAlgorithm(), |
| sid, |
| K, |
| cIV, |
| sIV, |
| replayDetection, |
| inCounter, |
| outCounter, |
| inMac, outMac, |
| inCipher, |
| outCipher)); |
| } |
| } |
| |
| private PRNG getDefaultPRNG() |
| { |
| if (prng == null) |
| prng = PRNG.getInstance(); |
| return prng; |
| } |
| } |