diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libjava/classpath/gnu/javax/crypto/sasl/srp | |
download | cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.bz2 cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libjava/classpath/gnu/javax/crypto/sasl/srp')
13 files changed, 4056 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/CALG.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/CALG.java new file mode 100644 index 000000000..22f9c9751 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/CALG.java @@ -0,0 +1,221 @@ +/* CALG.java -- + Copyright (C) 2003, 2006 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.security.Registry; +import gnu.javax.crypto.assembly.Assembly; +import gnu.javax.crypto.assembly.Cascade; +import gnu.javax.crypto.assembly.Direction; +import gnu.javax.crypto.assembly.Stage; +import gnu.javax.crypto.assembly.Transformer; +import gnu.javax.crypto.assembly.TransformerException; +import gnu.javax.crypto.cipher.CipherFactory; +import gnu.javax.crypto.cipher.IBlockCipher; +import gnu.javax.crypto.mode.IMode; +import gnu.javax.crypto.mode.ModeFactory; +import gnu.javax.crypto.pad.IPad; +import gnu.javax.crypto.pad.PadFactory; +import gnu.javax.crypto.sasl.ConfidentialityException; + +import java.util.HashMap; + +import javax.security.sasl.SaslException; + +/** + * A Factory class that returns CALG (Confidentiality Algorithm) instances that + * operate as described in the draft-burdis-cat-sasl-srp-08. + * <p> + * The designated CALG block cipher should be used in OFB (Output Feedback + * Block) mode in the ISO variant, as described in <i>The Handbook of Applied + * Cryptography</i>, algorithm 7.20. + * <p> + * Let <code>k</code> be the block size of the chosen symmetric key block + * cipher algorithm; e.g. for AES this is <code>128</code> bits or + * <code>16</code> octets. The OFB mode used shall be of length/size + * <code>k</code>. + * <p> + * It is recommended that block ciphers operating in OFB mode be used with an + * Initial Vector (the mode's IV). In such a mode of operation - OFB with key + * re-use - the IV need not be secret. For the mechanism in question the IVs + * shall be a random octet sequence of <code>k</code> bytes. + * <p> + * The input data to the confidentiality protection algorithm shall be a + * multiple of the symmetric cipher block size <code>k</code>. When the input + * length is not a multiple of <code>k</code> octets, the data shall be padded + * according to the following scheme: + * <p> + * Assuming the length of the input is <code>l</code> octets, + * <code>(k - (l mod k))</code> octets, all having the value + * <code>(k - (l mod k))</code>, shall be appended to the original data. In + * other words, the input is padded at the trailing end with one of the + * following sequences: + * <pre> + * + * 01 -- if l mod k = k-1 + * 02 02 -- if l mod k = k-2 + * ... + * ... + * ... + * k k ... k k -- if l mod k = 0 + * </pre> + * <p> + * The padding can be removed unambiguously since all input is padded and no + * padding sequence is a suffix of another. This padding method is well-defined + * if and only if <code>k < 256</code> octets, which is the case with + * symmetric key block ciphers today, and in the forseeable future. + */ +public final class CALG +{ + private Assembly assembly; + private Object modeNdx; // initialisation key of the cascade's attributes + private int blockSize; // the underlying cipher's blocksize == IV length + private int keySize; // the underlying cipher's key size (in bytes). + + /** Private constructor to enforce instantiation through Factory method. */ + private CALG(final int blockSize, final int keySize, final Object modeNdx, + final Assembly assembly) + { + super(); + + this.blockSize = blockSize; + this.keySize = keySize; + this.modeNdx = modeNdx; + this.assembly = assembly; + } + + /** + * Returns an instance of a SASL-SRP CALG implementation. + * + * @param algorithm the name of the symmetric cipher algorithm. + * @return an instance of this object. + */ + static synchronized CALG getInstance(final String algorithm) + { + final IBlockCipher cipher = CipherFactory.getInstance(algorithm); + final int blockSize = cipher.defaultBlockSize(); + final int keySize = cipher.defaultKeySize(); + final Cascade ofbCipher = new Cascade(); + IMode ofbMode = ModeFactory.getInstance(Registry.OFB_MODE, + cipher, + blockSize); + Stage modeStage = Stage.getInstance(ofbMode, Direction.FORWARD); + final Object modeNdx = ofbCipher.append(modeStage); + final IPad pkcs7 = PadFactory.getInstance(Registry.PKCS7_PAD); + final Assembly asm = new Assembly(); + asm.addPreTransformer(Transformer.getCascadeTransformer(ofbCipher)); + asm.addPreTransformer(Transformer.getPaddingTransformer(pkcs7)); + return new CALG(blockSize, keySize, modeNdx, asm); + } + + /** + * Initialises a SASL-SRP CALG implementation. + * + * @param kdf the key derivation function. + * @param iv the initial vector value to use. + * @param dir whether this CALG is used for encryption or decryption. + */ + public void init(final KDF kdf, final byte[] iv, final Direction dir) + throws SaslException + { + final byte[] realIV; + if (iv.length == blockSize) + realIV = iv; + else + { + realIV = new byte[blockSize]; + if (iv.length > blockSize) + System.arraycopy(iv, 0, realIV, 0, blockSize); + else // shouldnt happen + System.arraycopy(iv, 0, realIV, 0, iv.length); + } + final HashMap modeAttributes = new HashMap(); + final byte[] sk = kdf.derive(keySize); + modeAttributes.put(IBlockCipher.KEY_MATERIAL, sk); + modeAttributes.put(IMode.IV, realIV); + final HashMap attributes = new HashMap(); + attributes.put(Assembly.DIRECTION, dir); + attributes.put(modeNdx, modeAttributes); + try + { + assembly.init(attributes); + } + catch (TransformerException x) + { + throw new SaslException("getInstance()", x); + } + } + + /** + * Encrypts or decrypts, depending on the mode already set, a designated array + * of bytes and returns the result. + * + * @param data the data to encrypt/decrypt. + * @return the decrypted/encrypted result. + * @throws ConfidentialityException if an exception occurs duirng the process. + */ + public byte[] doFinal(final byte[] data) throws ConfidentialityException + { + return doFinal(data, 0, data.length); + } + + /** + * Encrypts or decrypts, depending on the mode already set, a designated array + * of bytes and returns the result. + * + * @param data the data to encrypt/decrypt. + * @param offset where to start in <code>data</code>. + * @param length how many bytes to consider in <code>data</code>. + * @return the decrypted/encrypted result. + * @throws ConfidentialityException if an exception occurs duirng the process. + */ + public byte[] doFinal(final byte[] data, final int offset, final int length) + throws ConfidentialityException + { + final byte[] result; + try + { + result = assembly.lastUpdate(data, offset, length); + } + catch (TransformerException x) + { + throw new ConfidentialityException("doFinal()", x); + } + return result; + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/ClientStore.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/ClientStore.java new file mode 100644 index 000000000..1d27137d1 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/ClientStore.java @@ -0,0 +1,155 @@ +/* ClientStore.java -- + Copyright (C) 2003, 2006 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 java.util.HashMap; + +/** + * The client-side implementation of the SRP security context store. + */ +public class ClientStore +{ + /** The underlying singleton. */ + private static ClientStore singleton = null; + /** The map of uid --> SASL Security Context record. */ + private static final HashMap uid2ssc = new HashMap(); + /** The map of sid --> Session timing record. */ + private static final HashMap uid2ttl = new HashMap(); + /** A synchronisation lock. */ + private static final Object lock = new Object(); + + /** Private constructor to enforce Singleton pattern. */ + private ClientStore() + { + super(); + + // TODO: add a cleaning timer thread + } + + /** + * Returns the classloader Singleton. + * + * @return the classloader Singleton instance. + */ + static synchronized final ClientStore instance() + { + if (singleton == null) + singleton = new ClientStore(); + return singleton; + } + + /** + * Returns a boolean flag indicating if the designated client's session is + * still alive or not. + * + * @param uid the identifier of the client whose session to check. + * @return <code>true</code> if the designated client's session is still + * alive. <code>false</code> otherwise. + */ + boolean isAlive(final String uid) + { + final boolean result; + synchronized (lock) + { + final Object obj = uid2ssc.get(uid); + result = (obj != null); + if (result) // is it still alive? + { + final StoreEntry sto = (StoreEntry) uid2ttl.get(uid); + if (! sto.isAlive()) // invalidate it + { + uid2ssc.remove(uid); + uid2ttl.remove(uid); + } + } + } + return result; + } + + /** + * Records a mapping between a client's unique identifier and its security + * context. + * + * @param uid the unique identifier of the SRP client for which the session is + * to be cached. + * @param ttl the session's Time-To-Live indicator (in seconds). + * @param ctx the client's security context. + */ + void cacheSession(final String uid, final int ttl, final SecurityContext ctx) + { + synchronized (lock) + { + uid2ssc.put(uid, ctx); + uid2ttl.put(uid, new StoreEntry(ttl)); + } + } + + /** + * Removes the mapping between the designated SRP client unique identifier and + * the its session security context (and other timing information). + * + * @param uid the identifier of the client whose session is to invalidate. + */ + void invalidateSession(final String uid) + { + synchronized (lock) + { + uid2ssc.remove(uid); + uid2ttl.remove(uid); + } + } + + /** + * Returns an SRP client's security context record mapped by that client's + * unique identifier. + * + * @param uid the identifier of the client whose session is to restore. + * @return the SRP client's security context. + */ + SecurityContext restoreSession(final String uid) + { + final SecurityContext result; + synchronized (lock) + { + result = (SecurityContext) uid2ssc.remove(uid); + uid2ttl.remove(uid); + } + return result; + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/IALG.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/IALG.java new file mode 100644 index 000000000..d0c92ea68 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/IALG.java @@ -0,0 +1,128 @@ +/* IALG.java -- + Copyright (C) 2003, 2006 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.javax.crypto.mac.IMac; +import gnu.javax.crypto.mac.MacFactory; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; + +import javax.security.sasl.SaslException; + +/** + * A Factory class that returns IALG (Integrity Algorithm) instances that + * operate as described in the draft-burdis-cat-sasl-srp-04 and later. + */ +public final class IALG + implements Cloneable +{ + private IMac hmac; + + /** Private constructor to enforce instantiation through Factory method. */ + private IALG(final IMac hmac) + { + super(); + + this.hmac = hmac; + } + + /** + * Returns an instance of a SASL-SRP IALG implementation. + * + * @param algorithm the name of the HMAC algorithm. + * @return an instance of this object. + */ + static synchronized IALG getInstance(final String algorithm) + throws SaslException + { + final IMac hmac; + hmac = MacFactory.getInstance(algorithm); + if (hmac == null) + throw new SaslException("getInstance()", + new NoSuchAlgorithmException(algorithm)); + return new IALG(hmac); + } + + public Object clone() throws CloneNotSupportedException + { + return new IALG((IMac) hmac.clone()); + } + + public void init(final KDF kdf) throws SaslException + { + try + { + final byte[] sk = kdf.derive(hmac.macSize()); + final HashMap map = new HashMap(); + map.put(IMac.MAC_KEY_MATERIAL, sk); + hmac.init(map); + } + catch (InvalidKeyException x) + { + throw new SaslException("getInstance()", x); + } + } + + public void update(final byte[] data) + { + hmac.update(data, 0, data.length); + } + + public void update(final byte[] data, final int offset, final int length) + { + hmac.update(data, offset, length); + } + + public byte[] doFinal() + { + return hmac.digest(); + } + + /** + * Returns the length (in bytes) of this SASL SRP Integrity Algorithm. + * + * @return the length, in bytes, of this integrity protection algorithm. + */ + public int length() + { + return hmac.macSize(); + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/KDF.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/KDF.java new file mode 100644 index 000000000..513aafb94 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/KDF.java @@ -0,0 +1,140 @@ +/* KDF.java -- + Copyright (C) 2003, 2006 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.security.Registry; +import gnu.java.security.prng.LimitReachedException; +import gnu.java.security.util.PRNG; +import gnu.javax.crypto.cipher.IBlockCipher; +import gnu.javax.crypto.prng.UMacGenerator; + +import java.util.HashMap; + +/** + * The SASL-SRP KDF implementation, which is also used, depending on how it was + * instantiated, as a secure Pseudo Random Number Generator. + */ +public class KDF +{ + private static final int AES_BLOCK_SIZE = 16; // default block size for AES + private static final int AES_KEY_SIZE = 16; // default key size for the AES + private static final byte[] buffer = new byte[1]; + /** Our default source of randomness. */ + private static final PRNG prng = PRNG.getInstance(); + /** The underlying UMAC Generator instance. */ + private UMacGenerator umac = null; + + /** + * Constructs an instance of the <code>KDF</code> initialised with the + * designated shared secret bytes. + * + * @param keyMaterial the SASL SRP shared secret (K) bytes. + */ + private KDF(final byte[] keyMaterial, final int ndx) + { + super(); + + final HashMap map = new HashMap(); + map.put(UMacGenerator.CIPHER, Registry.AES_CIPHER); + map.put(UMacGenerator.INDEX, Integer.valueOf(ndx)); + map.put(IBlockCipher.CIPHER_BLOCK_SIZE, Integer.valueOf(AES_BLOCK_SIZE)); + final byte[] key = new byte[AES_KEY_SIZE]; + System.arraycopy(keyMaterial, 0, key, 0, AES_KEY_SIZE); + map.put(IBlockCipher.KEY_MATERIAL, key); + umac = new UMacGenerator(); + umac.init(map); + } + + /** + * A Factory mehod that returns an instance of a <code>KDF</code> based on + * supplied seed data. + * + * @param K the SASL SRP shared secret for a <code>KDF</code> to be used for + * <i>CALG</i> and <i>IALG</i> setup. <code>null</code> otherwise. + * @return an instance of a <code>KDF</code>. + */ + static final KDF getInstance(final byte[] K) + { + int ndx = -1; + final byte[] keyMaterial; + if (K != null) + { + keyMaterial = K; + ndx = 0; + } + else + { + keyMaterial = new byte[AES_BLOCK_SIZE]; + while (ndx < 1 || ndx > 255) + ndx = (byte) nextByte(); + } + return new KDF(keyMaterial, ndx); + } + + private static synchronized final int nextByte() + { + prng.nextBytes(buffer); + return (buffer[0] & 0xFF); + } + + /** + * Returns a designated number of bytes suitable for use in the SASL SRP + * mechanism. + * + * @param length the number of bytes needed. + * @return a byte array containing the generated/selected bytes. + */ + public synchronized byte[] derive(final int length) + { + final byte[] result = new byte[length]; + try + { + umac.nextBytes(result, 0, length); + } + catch (IllegalStateException x) // should not happen + { + x.printStackTrace(System.err); + } + catch (LimitReachedException x) // idem + { + x.printStackTrace(System.err); + } + return result; + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/PasswordFile.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/PasswordFile.java new file mode 100644 index 000000000..c13c2fa71 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/PasswordFile.java @@ -0,0 +1,627 @@ +/* PasswordFile.java -- + Copyright (C) 2003, 2006 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.Registry; +import gnu.java.security.util.Util; +import gnu.javax.crypto.key.srp6.SRPAlgorithm; +import gnu.javax.crypto.sasl.NoSuchUserException; +import gnu.javax.crypto.sasl.UserAlreadyExistsException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +/** + * The implementation of SRP password files. + * <p> + * For SRP, there are three (3) files: + * <ol> + * <li>The password configuration file: tpasswd.conf. It contains the pairs + * <N,g> indexed by a number for each pair used for a user. By default, this + * file's pathname is constructed from the base password file pathname by + * prepending it with the ".conf" suffix.</li> + * <li>The base password file: tpasswd. It contains the related password + * entries for all the users with values computed using SRP's default message + * digest algorithm: SHA-1 (with 160-bit output block size).</li> + * <li>The extended password file: tpasswd2. Its name, by default, is + * constructed by adding the suffix "2" to the fully qualified pathname of the + * base password file. It contains, in addition to the same fields as the base + * password file, albeit with a different verifier value, an extra field + * identifying the message digest algorithm used to compute this (verifier) + * value.</li> + * </ol> + * <p> + * This implementation assumes the following message digest algorithm codes: + * <ul> + * <li>0: the default hash algorithm, which is SHA-1 (or its alias SHA-160).</li> + * <li>1: MD5.</li> + * <li>2: RIPEMD-128.</li> + * <li>3: RIPEMD-160.</li> + * <li>4: SHA-256.</li> + * <li>5: SHA-384.</li> + * <li>6: SHA-512.</li> + * </ul> + * <p> + * <b>IMPORTANT:</b> This method computes the verifiers as described in + * RFC-2945, which differs from the description given on the web page for SRP-6. + * <p> + * Reference: + * <ol> + * <li><a href="http://srp.stanford.edu/design.html">SRP Protocol Design</a><br> + * Thomas J. Wu.</li> + * </ol> + */ +public class PasswordFile +{ + // names of property keys used in this class + private static final String USER_FIELD = "user"; + private static final String VERIFIERS_FIELD = "verifier"; + private static final String SALT_FIELD = "salt"; + private static final String CONFIG_FIELD = "config"; + private static String DEFAULT_FILE; + static + { + DEFAULT_FILE = System.getProperty(SRPRegistry.PASSWORD_FILE, + SRPRegistry.DEFAULT_PASSWORD_FILE); + } + /** The SRP algorithm instances used by this object. */ + private static final HashMap srps; + static + { + final HashMap map = new HashMap(SRPRegistry.SRP_ALGORITHMS.length); + // The first entry MUST exist. The others are optional. + map.put("0", SRP.instance(SRPRegistry.SRP_ALGORITHMS[0])); + for (int i = 1; i < SRPRegistry.SRP_ALGORITHMS.length; i++) + { + try + { + map.put(String.valueOf(i), + SRP.instance(SRPRegistry.SRP_ALGORITHMS[i])); + } + catch (Exception x) + { + System.err.println("Ignored: " + x); + x.printStackTrace(System.err); + } + } + srps = map; + } + + private String confName, pwName, pw2Name; + private File configFile, passwdFile, passwd2File; + private long lastmodPasswdFile, lastmodPasswd2File; + private HashMap entries = new HashMap(); + private HashMap configurations = new HashMap(); + // default N values to use when creating a new password.conf file + private static final BigInteger[] Nsrp = new BigInteger[] { + SRPAlgorithm.N_2048, + SRPAlgorithm.N_1536, + SRPAlgorithm.N_1280, + SRPAlgorithm.N_1024, + SRPAlgorithm.N_768, + SRPAlgorithm.N_640, + SRPAlgorithm.N_512 }; + + public PasswordFile() throws IOException + { + this(DEFAULT_FILE); + } + + public PasswordFile(final File pwFile) throws IOException + { + this(pwFile.getAbsolutePath()); + } + + public PasswordFile(final String pwName) throws IOException + { + this(pwName, pwName + "2", pwName + ".conf"); + } + + public PasswordFile(final String pwName, final String confName) + throws IOException + { + this(pwName, pwName + "2", confName); + } + + public PasswordFile(final String pwName, final String pw2Name, + final String confName) throws IOException + { + super(); + + this.pwName = pwName; + this.pw2Name = pw2Name; + this.confName = confName; + + readOrCreateConf(); + update(); + } + + /** + * Returns a string representing the decimal value of an integer identifying + * the message digest algorithm to use for the SRP computations. + * + * @param mdName the canonical name of a message digest algorithm. + * @return a string representing the decimal value of an ID for that + * algorithm. + */ + private static final String nameToID(final String mdName) + { + if (Registry.SHA_HASH.equalsIgnoreCase(mdName) + || Registry.SHA1_HASH.equalsIgnoreCase(mdName) + || Registry.SHA160_HASH.equalsIgnoreCase(mdName)) + return "0"; + else if (Registry.MD5_HASH.equalsIgnoreCase(mdName)) + return "1"; + else if (Registry.RIPEMD128_HASH.equalsIgnoreCase(mdName)) + return "2"; + else if (Registry.RIPEMD160_HASH.equalsIgnoreCase(mdName)) + return "3"; + else if (Registry.SHA256_HASH.equalsIgnoreCase(mdName)) + return "4"; + else if (Registry.SHA384_HASH.equalsIgnoreCase(mdName)) + return "5"; + else if (Registry.SHA512_HASH.equalsIgnoreCase(mdName)) + return "6"; + return "0"; + } + + /** + * Checks if the current configuration file contains the <N, g> pair for + * the designated <code>index</code>. + * + * @param index a string representing 1-digit identification of an <N, g> + * pair used. + * @return <code>true</code> if the designated <code>index</code> is that + * of a known <N, g> pair, and <code>false</code> otherwise. + * @throws IOException if an exception occurs during the process. + * @see SRPRegistry#N_2048_BITS + * @see SRPRegistry#N_1536_BITS + * @see SRPRegistry#N_1280_BITS + * @see SRPRegistry#N_1024_BITS + * @see SRPRegistry#N_768_BITS + * @see SRPRegistry#N_640_BITS + * @see SRPRegistry#N_512_BITS + */ + public synchronized boolean containsConfig(final String index) + throws IOException + { + checkCurrent(); + return configurations.containsKey(index); + } + + /** + * Returns a pair of strings representing the pair of <code>N</code> and + * <code>g</code> MPIs for the designated <code>index</code>. + * + * @param index a string representing 1-digit identification of an <N, g> + * pair to look up. + * @return a pair of strings, arranged in an array, where the first (at index + * position #0) is the repesentation of the MPI <code>N</code>, and + * the second (at index position #1) is the representation of the MPI + * <code>g</code>. If the <code>index</code> refers to an unknown + * pair, then an empty string array is returned. + * @throws IOException if an exception occurs during the process. + */ + public synchronized String[] lookupConfig(final String index) + throws IOException + { + checkCurrent(); + String[] result = null; + if (configurations.containsKey(index)) + result = (String[]) configurations.get(index); + return result; + } + + public synchronized boolean contains(final String user) throws IOException + { + checkCurrent(); + return entries.containsKey(user); + } + + public synchronized void add(final String user, final String passwd, + final byte[] salt, final String index) + throws IOException + { + checkCurrent(); + if (entries.containsKey(user)) + throw new UserAlreadyExistsException(user); + final HashMap fields = new HashMap(4); + fields.put(USER_FIELD, user); // 0 + fields.put(VERIFIERS_FIELD, newVerifiers(user, salt, passwd, index)); // 1 + fields.put(SALT_FIELD, Util.toBase64(salt)); // 2 + fields.put(CONFIG_FIELD, index); // 3 + entries.put(user, fields); + savePasswd(); + } + + public synchronized void changePasswd(final String user, final String passwd) + throws IOException + { + checkCurrent(); + if (! entries.containsKey(user)) + throw new NoSuchUserException(user); + final HashMap fields = (HashMap) entries.get(user); + final byte[] salt; + try + { + salt = Util.fromBase64((String) fields.get(SALT_FIELD)); + } + catch (NumberFormatException x) + { + throw new IOException("Password file corrupt"); + } + final String index = (String) fields.get(CONFIG_FIELD); + fields.put(VERIFIERS_FIELD, newVerifiers(user, salt, passwd, index)); + entries.put(user, fields); + savePasswd(); + } + + public synchronized void savePasswd() throws IOException + { + final FileOutputStream f1 = new FileOutputStream(passwdFile); + final FileOutputStream f2 = new FileOutputStream(passwd2File); + PrintWriter pw1 = null; + PrintWriter pw2 = null; + try + { + pw1 = new PrintWriter(f1, true); + pw2 = new PrintWriter(f2, true); + this.writePasswd(pw1, pw2); + } + finally + { + if (pw1 != null) + try + { + pw1.flush(); + } + finally + { + pw1.close(); + } + if (pw2 != null) + try + { + pw2.flush(); + } + finally + { + pw2.close(); + } + try + { + f1.close(); + } + catch (IOException ignored) + { + } + try + { + f2.close(); + } + catch (IOException ignored) + { + } + } + lastmodPasswdFile = passwdFile.lastModified(); + lastmodPasswd2File = passwd2File.lastModified(); + } + + /** + * Returns the triplet: verifier, salt and configuration file index, of a + * designated user, and a designated message digest algorithm name, as an + * array of strings. + * + * @param user the username. + * @param mdName the canonical name of the SRP's message digest algorithm. + * @return a string array containing, in this order, the BASE-64 encodings of + * the verifier, the salt and the index in the password configuration + * file of the MPIs N and g of the designated user. + */ + public synchronized String[] lookup(final String user, final String mdName) + throws IOException + { + checkCurrent(); + if (! entries.containsKey(user)) + throw new NoSuchUserException(user); + final HashMap fields = (HashMap) entries.get(user); + final HashMap verifiers = (HashMap) fields.get(VERIFIERS_FIELD); + final String salt = (String) fields.get(SALT_FIELD); + final String index = (String) fields.get(CONFIG_FIELD); + final String verifier = (String) verifiers.get(nameToID(mdName)); + return new String[] { verifier, salt, index }; + } + + private synchronized void readOrCreateConf() throws IOException + { + configurations.clear(); + final FileInputStream fis; + configFile = new File(confName); + try + { + fis = new FileInputStream(configFile); + readConf(fis); + } + catch (FileNotFoundException x) + { // create a default one + final String g = Util.toBase64(Util.trim(new BigInteger("2"))); + String index, N; + for (int i = 0; i < Nsrp.length; i++) + { + index = String.valueOf(i + 1); + N = Util.toBase64(Util.trim(Nsrp[i])); + configurations.put(index, new String[] { N, g }); + } + FileOutputStream f0 = null; + PrintWriter pw0 = null; + try + { + f0 = new FileOutputStream(configFile); + pw0 = new PrintWriter(f0, true); + this.writeConf(pw0); + } + finally + { + if (pw0 != null) + pw0.close(); + else if (f0 != null) + f0.close(); + } + } + } + + private void readConf(final InputStream in) throws IOException + { + final BufferedReader din = new BufferedReader(new InputStreamReader(in)); + String line, index, N, g; + StringTokenizer st; + while ((line = din.readLine()) != null) + { + st = new StringTokenizer(line, ":"); + try + { + index = st.nextToken(); + N = st.nextToken(); + g = st.nextToken(); + } + catch (NoSuchElementException x) + { + throw new IOException("SRP password configuration file corrupt"); + } + configurations.put(index, new String[] { N, g }); + } + } + + private void writeConf(final PrintWriter pw) + { + String ndx; + String[] mpi; + CPStringBuilder sb; + for (Iterator it = configurations.keySet().iterator(); it.hasNext();) + { + ndx = (String) it.next(); + mpi = (String[]) configurations.get(ndx); + sb = new CPStringBuilder(ndx) + .append(":").append(mpi[0]) + .append(":").append(mpi[1]); + pw.println(sb.toString()); + } + } + + /** + * Compute the new verifiers for the designated username and password. + * <p> + * <b>IMPORTANT:</b> This method computes the verifiers as described in + * RFC-2945, which differs from the description given on the web page for + * SRP-6. + * + * @param user the user's name. + * @param s the user's salt. + * @param password the user's password + * @param index the index of the <N, g> pair to use for this user. + * @return a {@link java.util.Map} of user verifiers. + * @throws UnsupportedEncodingException if the US-ASCII decoder is not + * available on this platform. + */ + private HashMap newVerifiers(final String user, final byte[] s, + final String password, final String index) + throws UnsupportedEncodingException + { + // to ensure inter-operability with non-java tools + final String[] mpi = (String[]) configurations.get(index); + final BigInteger N = new BigInteger(1, Util.fromBase64(mpi[0])); + final BigInteger g = new BigInteger(1, Util.fromBase64(mpi[1])); + final HashMap result = new HashMap(srps.size()); + BigInteger x, v; + SRP srp; + for (int i = 0; i < srps.size(); i++) + { + final String digestID = String.valueOf(i); + srp = (SRP) srps.get(digestID); + x = new BigInteger(1, srp.computeX(s, user, password)); + v = g.modPow(x, N); + final String verifier = Util.toBase64(v.toByteArray()); + result.put(digestID, verifier); + } + return result; + } + + private synchronized void update() throws IOException + { + entries.clear(); + FileInputStream fis; + passwdFile = new File(pwName); + lastmodPasswdFile = passwdFile.lastModified(); + try + { + fis = new FileInputStream(passwdFile); + readPasswd(fis); + } + catch (FileNotFoundException ignored) + { + } + passwd2File = new File(pw2Name); + lastmodPasswd2File = passwd2File.lastModified(); + try + { + fis = new FileInputStream(passwd2File); + readPasswd2(fis); + } + catch (FileNotFoundException ignored) + { + } + } + + private void checkCurrent() throws IOException + { + if (passwdFile.lastModified() > lastmodPasswdFile + || passwd2File.lastModified() > lastmodPasswd2File) + update(); + } + + private void readPasswd(final InputStream in) throws IOException + { + final BufferedReader din = new BufferedReader(new InputStreamReader(in)); + String line, user, verifier, salt, index; + StringTokenizer st; + while ((line = din.readLine()) != null) + { + st = new StringTokenizer(line, ":"); + try + { + user = st.nextToken(); + verifier = st.nextToken(); + salt = st.nextToken(); + index = st.nextToken(); + } + catch (NoSuchElementException x) + { + throw new IOException("SRP base password file corrupt"); + } + final HashMap verifiers = new HashMap(6); + verifiers.put("0", verifier); + final HashMap fields = new HashMap(4); + fields.put(USER_FIELD, user); + fields.put(VERIFIERS_FIELD, verifiers); + fields.put(SALT_FIELD, salt); + fields.put(CONFIG_FIELD, index); + entries.put(user, fields); + } + } + + private void readPasswd2(final InputStream in) throws IOException + { + final BufferedReader din = new BufferedReader(new InputStreamReader(in)); + String line, digestID, user, verifier; + StringTokenizer st; + HashMap fields, verifiers; + while ((line = din.readLine()) != null) + { + st = new StringTokenizer(line, ":"); + try + { + digestID = st.nextToken(); + user = st.nextToken(); + verifier = st.nextToken(); + } + catch (NoSuchElementException x) + { + throw new IOException("SRP extended password file corrupt"); + } + fields = (HashMap) entries.get(user); + if (fields != null) + { + verifiers = (HashMap) fields.get(VERIFIERS_FIELD); + verifiers.put(digestID, verifier); + } + } + } + + private void writePasswd(final PrintWriter pw1, final PrintWriter pw2) + throws IOException + { + String user, digestID; + HashMap fields, verifiers; + CPStringBuilder sb1, sb2; + Iterator j; + final Iterator i = entries.keySet().iterator(); + while (i.hasNext()) + { + user = (String) i.next(); + fields = (HashMap) entries.get(user); + if (! user.equals(fields.get(USER_FIELD))) + throw new IOException("Inconsistent SRP password data"); + verifiers = (HashMap) fields.get(VERIFIERS_FIELD); + sb1 = new CPStringBuilder(user) + .append(":").append((String) verifiers.get("0")) + .append(":").append((String) fields.get(SALT_FIELD)) + .append(":").append((String) fields.get(CONFIG_FIELD)); + pw1.println(sb1.toString()); + // write extended information + j = verifiers.keySet().iterator(); + while (j.hasNext()) + { + digestID = (String) j.next(); + if (! "0".equals(digestID)) + { + // #0 is the default digest, already present in tpasswd! + sb2 = new CPStringBuilder(digestID) + .append(":").append(user) + .append(":").append((String) verifiers.get(digestID)); + pw2.println(sb2.toString()); + } + } + } + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/SRP.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRP.java new file mode 100644 index 000000000..569855dd7 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRP.java @@ -0,0 +1,255 @@ +/* SRP.java -- + Copyright (C) 2003, 2006 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.security.hash.HashFactory; +import gnu.java.security.hash.IMessageDigest; +import gnu.java.security.util.Util; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.util.HashMap; + +/** + * A Factory class that returns SRP Singletons that know all SRP-related + * mathematical computations and protocol-related operations for both the + * client- and server-sides. + */ +public final class SRP +{ + /** The map of already instantiated SRP algorithm instances. */ + private static final HashMap algorithms = new HashMap(); + private static final byte COLON = (byte) 0x3A; + /** The underlying message digest algorithm used for all SRP calculations. */ + private IMessageDigest mda; + + /** Trivial private constructor to enforce Singleton pattern. */ + private SRP(final IMessageDigest mda) + { + super(); + + this.mda = mda; + } + + /** + * Returns an instance of this object that uses the designated message digest + * algorithm as its digest function. + * + * @return an instance of this object for the designated digest name. + */ + public static synchronized SRP instance(String mdName) + { + if (mdName != null) + mdName = mdName.trim().toLowerCase(); + if (mdName == null || mdName.equals("")) + mdName = SRPRegistry.SRP_DEFAULT_DIGEST_NAME; + SRP result = (SRP) algorithms.get(mdName); + if (result == null) + { + final IMessageDigest mda = HashFactory.getInstance(mdName); + result = new SRP(mda); + algorithms.put(mdName, result); + } + return result; + } + + private static final byte[] xor(final byte[] b1, final byte[] b2, + final int length) + { + final byte[] result = new byte[length]; + for (int i = 0; i < length; ++i) + result[i] = (byte)(b1[i] ^ b2[i]); + return result; + } + + /** @return the message digest algorithm name used by this instance. */ + public String getAlgorithm() + { + return mda.name(); + } + + /** + * Returns a new instance of the SRP message digest algorithm --which is + * SHA-160 by default, but could be anything else provided the proper + * conditions as specified in the SRP specifications. + * + * @return a new instance of the underlying SRP message digest algorithm. + * @throws RuntimeException if the implementation of the message digest + * algorithm does not support cloning. + */ + public IMessageDigest newDigest() + { + return (IMessageDigest) mda.clone(); + } + + /** + * Convenience method to return the result of digesting the designated input + * with a new instance of the SRP message digest algorithm. + * + * @param src some bytes to digest. + * @return the bytes constituting the result of digesting the designated input + * with a new instance of the SRP message digest algorithm. + */ + public byte[] digest(final byte[] src) + { + final IMessageDigest hash = (IMessageDigest) mda.clone(); + hash.update(src, 0, src.length); + return hash.digest(); + } + + /** + * Convenience method to return the result of digesting the designated input + * with a new instance of the SRP message digest algorithm. + * + * @param src a String whose bytes (using US-ASCII encoding) are to be + * digested. + * @return the bytes constituting the result of digesting the designated input + * with a new instance of the SRP message digest algorithm. + * @throws UnsupportedEncodingException if US-ASCII charset is not found. + */ + public byte[] digest(final String src) throws UnsupportedEncodingException + { + return digest(src.getBytes("US-ASCII")); + } + + /** + * Convenience method to XOR N bytes from two arrays; N being the output size + * of the SRP message digest algorithm. + * + * @param a the first byte array. + * @param b the second one. + * @return N bytes which are the result of the XOR operations on the first N + * bytes from the designated arrays. N is the size of the SRP message + * digest algorithm; eg. 20 for SHA-160. + */ + public byte[] xor(final byte[] a, final byte[] b) + { + return xor(a, b, mda.hashSize()); + } + + public byte[] generateM1(final BigInteger N, final BigInteger g, + final String U, final byte[] s, final BigInteger A, + final BigInteger B, final byte[] K, final String I, + final String L, final byte[] cn, final byte[] cCB) + throws UnsupportedEncodingException + { + final IMessageDigest hash = (IMessageDigest) mda.clone(); + byte[] b; + b = xor(digest(Util.trim(N)), digest(Util.trim(g))); + hash.update(b, 0, b.length); + b = digest(U); + hash.update(b, 0, b.length); + hash.update(s, 0, s.length); + b = Util.trim(A); + hash.update(b, 0, b.length); + b = Util.trim(B); + hash.update(b, 0, b.length); + hash.update(K, 0, K.length); + b = digest(I); + hash.update(b, 0, b.length); + b = digest(L); + hash.update(b, 0, b.length); + hash.update(cn, 0, cn.length); + hash.update(cCB, 0, cCB.length); + return hash.digest(); + } + + public byte[] generateM2(final BigInteger A, final byte[] M1, final byte[] K, + final String U, final String I, final String o, + final byte[] sid, final int ttl, final byte[] cIV, + final byte[] sIV, final byte[] sCB) + throws UnsupportedEncodingException + { + final IMessageDigest hash = (IMessageDigest) mda.clone(); + byte[] b; + b = Util.trim(A); + hash.update(b, 0, b.length); + hash.update(M1, 0, M1.length); + hash.update(K, 0, K.length); + b = digest(U); + hash.update(b, 0, b.length); + b = digest(I); + hash.update(b, 0, b.length); + b = digest(o); + hash.update(b, 0, b.length); + hash.update(sid, 0, sid.length); + hash.update((byte)(ttl >>> 24)); + hash.update((byte)(ttl >>> 16)); + hash.update((byte)(ttl >>> 8)); + hash.update((byte) ttl); + hash.update(cIV, 0, cIV.length); + hash.update(sIV, 0, sIV.length); + hash.update(sCB, 0, sCB.length); + return hash.digest(); + } + + public byte[] generateKn(final byte[] K, final byte[] cn, final byte[] sn) + { + final IMessageDigest hash = (IMessageDigest) mda.clone(); + hash.update(K, 0, K.length); + hash.update(cn, 0, cn.length); + hash.update(sn, 0, sn.length); + return hash.digest(); + } + + public byte[] computeX(final byte[] s, final String user, + final String password) + throws UnsupportedEncodingException + { + return computeX(s, user.getBytes("US-ASCII"), password.getBytes("US-ASCII")); + } + + public byte[] computeX(final byte[] s, final String user, final byte[] p) + throws UnsupportedEncodingException + { + return computeX(s, user.getBytes("US-ASCII"), p); + } + + private byte[] computeX(final byte[] s, final byte[] user, final byte[] p) + { + final IMessageDigest hash = (IMessageDigest) mda.clone(); + hash.update(user, 0, user.length); + hash.update(COLON); + hash.update(p, 0, p.length); + final byte[] up = hash.digest(); + hash.update(s, 0, s.length); + hash.update(up, 0, up.length); + return hash.digest(); + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPAuthInfoProvider.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPAuthInfoProvider.java new file mode 100644 index 000000000..e42cfffa9 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPAuthInfoProvider.java @@ -0,0 +1,177 @@ +/* SRPAuthInfoProvider.java -- + Copyright (C) 2003, 2006 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.security.Registry; +import gnu.java.security.util.Util; +import gnu.javax.crypto.sasl.IAuthInfoProvider; +import gnu.javax.crypto.sasl.NoSuchUserException; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.security.sasl.AuthenticationException; + +/** + * The SRP mechanism authentication information provider implementation. + */ +public class SRPAuthInfoProvider + implements IAuthInfoProvider +{ + private PasswordFile passwordFile = null; + + // implicit 0-args constrcutor + + public void activate(Map context) throws AuthenticationException + { + try + { + if (context == null) + passwordFile = new PasswordFile(); + else + { + passwordFile = (PasswordFile) context.get(SRPRegistry.PASSWORD_DB); + if (passwordFile == null) + { + String pfn = (String) context.get(SRPRegistry.PASSWORD_FILE); + if (pfn == null) + passwordFile = new PasswordFile(); + else + passwordFile = new PasswordFile(pfn); + } + } + } + catch (IOException x) + { + throw new AuthenticationException("activate()", x); + } + } + + public void passivate() throws AuthenticationException + { + passwordFile = null; + } + + public boolean contains(String userName) throws AuthenticationException + { + if (passwordFile == null) + throw new AuthenticationException("contains()", + new IllegalStateException()); + boolean result = false; + try + { + result = passwordFile.contains(userName); + } + catch (IOException x) + { + throw new AuthenticationException("contains()", x); + } + return result; + } + + public Map lookup(Map userID) throws AuthenticationException + { + if (passwordFile == null) + throw new AuthenticationException("lookup()", new IllegalStateException()); + Map result = new HashMap(); + try + { + String userName = (String) userID.get(Registry.SASL_USERNAME); + if (userName == null) + throw new NoSuchUserException(""); + String mdName = (String) userID.get(SRPRegistry.MD_NAME_FIELD); + String[] data = passwordFile.lookup(userName, mdName); + result.put(SRPRegistry.USER_VERIFIER_FIELD, data[0]); + result.put(SRPRegistry.SALT_FIELD, data[1]); + result.put(SRPRegistry.CONFIG_NDX_FIELD, data[2]); + } + catch (Exception x) + { + if (x instanceof AuthenticationException) + throw (AuthenticationException) x; + throw new AuthenticationException("lookup()", x); + } + return result; + } + + public void update(Map userCredentials) throws AuthenticationException + { + if (passwordFile == null) + throw new AuthenticationException("update()", new IllegalStateException()); + try + { + String userName = (String) userCredentials.get(Registry.SASL_USERNAME); + String password = (String) userCredentials.get(Registry.SASL_PASSWORD); + String salt = (String) userCredentials.get(SRPRegistry.SALT_FIELD); + String config = (String) userCredentials.get(SRPRegistry.CONFIG_NDX_FIELD); + if (salt == null || config == null) + passwordFile.changePasswd(userName, password); + else + passwordFile.add(userName, password, Util.fromBase64(salt), config); + } + catch (Exception x) + { + if (x instanceof AuthenticationException) + throw (AuthenticationException) x; + throw new AuthenticationException("update()", x); + } + } + + public Map getConfiguration(String mode) throws AuthenticationException + { + if (passwordFile == null) + throw new AuthenticationException("getConfiguration()", + new IllegalStateException()); + Map result = new HashMap(); + try + { + String[] data = passwordFile.lookupConfig(mode); + result.put(SRPRegistry.SHARED_MODULUS, data[0]); + result.put(SRPRegistry.FIELD_GENERATOR, data[1]); + } + catch (Exception x) + { + if (x instanceof AuthenticationException) + throw (AuthenticationException) x; + throw new AuthenticationException("getConfiguration()", x); + } + return result; + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPClient.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPClient.java new file mode 100644 index 000000000..8e44e4ead --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPClient.java @@ -0,0 +1,954 @@ +/* SRPClient.java -- + Copyright (C) 2003, 2006 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 = Logger.getLogger(SRPClient.class.getName()); + 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; + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPRegistry.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPRegistry.java new file mode 100644 index 000000000..b6d24cf14 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPRegistry.java @@ -0,0 +1,165 @@ +/* SRPRegistry.java -- + Copyright (C) 2003, 2006 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.security.Registry; + +/** + * A list of key names designating the values exchanged between the server + * and client in an SRP communication authentication phase. + */ +public interface SRPRegistry +{ + /** Indices of (N, g) parameter values for SRP (.conf) password database. */ + String N_2048_BITS = "1"; + String N_1536_BITS = "2"; + String N_1280_BITS = "3"; + String N_1024_BITS = "4"; + String N_768_BITS = "5"; + String N_640_BITS = "6"; + String N_512_BITS = "7"; + /** Available hash algorithms for all SRP calculations. */ + String[] SRP_ALGORITHMS = { + Registry.SHA160_HASH, // the default one + Registry.MD5_HASH, + Registry.RIPEMD128_HASH, + Registry.RIPEMD160_HASH, + + Registry.SHA256_HASH, + Registry.SHA384_HASH, + Registry.SHA512_HASH }; + /** + * The name of the default message digest algorithm to use when no name is + * explicitely given. In this implementation it is the <b>first</b> among + * those supported; i.e. the algorithm at index position #0: SHA with + * 160-bit output. + */ + String SRP_DEFAULT_DIGEST_NAME = SRP_ALGORITHMS[0]; + /** + * The property name of the message digest algorithm name to use in a given + * SRP incarnation. + */ + String SRP_DIGEST_NAME = "srp.digest.name"; + /** The public shared modulus: n. */ + String SHARED_MODULUS = "srp.N"; + /** The GF generator used: g. */ + String FIELD_GENERATOR = "srp.g"; + /** The list of server's available security options. */ + String AVAILABLE_OPTIONS = "srp.L"; + /** The client's chosen security options. */ + String CHOSEN_OPTIONS = "srp.o"; + /** The client's username. */ + String USER_NAME = "srp.U"; + /** The client's authorization ID. */ + String USER_ROLE = "srp.I"; + /** The user's salt. */ + String USER_SALT = "srp.s"; + /** The user's password verifier. */ + String PASSWORD_VERIFIER = "srp.v"; + /** The client's public ephemeral exponent: A. */ + String CLIENT_PUBLIC_KEY = "srp.A"; + /** The server's public ephemeral exponent: B. */ + String SERVER_PUBLIC_KEY = "srp.B"; + /** The client's evidence: M1. */ + String CLIENT_EVIDENCE = "srp.M1"; + /** The server's evidence: M2. */ + String SERVER_EVIDENCE = "srp.M2"; + /** Name of underlying hash algorithm for use with all SRP calculations. */ + String SRP_HASH = "gnu.crypto.sasl.srp.hash"; + /** Name of SRP mandatory service property. */ + String SRP_MANDATORY = "gnu.crypto.sasl.srp.mandatory"; + /** Name of SRP replay detection property. */ + String SRP_REPLAY_DETECTION = "gnu.crypto.sasl.srp.replay.detection"; + /** Name of SRP integrity protection property. */ + String SRP_INTEGRITY_PROTECTION = "gnu.crypto.sasl.srp.integrity"; + /** Name of SRP confidentiality protection property. */ + String SRP_CONFIDENTIALITY = "gnu.crypto.sasl.srp.confidentiality"; + /** Name of the main SRP password file pathname property. */ + String PASSWORD_FILE = "gnu.crypto.sasl.srp.password.file"; + /** + * Name of the SRP password database property --a reference to + * {@link PasswordFile} object. + */ + String PASSWORD_DB = "gnu.crypto.sasl.srp.password.db"; + /** Default fully qualified pathname of the SRP password file. */ + String DEFAULT_PASSWORD_FILE = "/etc/tpasswd"; + /** Default value for replay detection security service. */ + boolean DEFAULT_REPLAY_DETECTION = true; + /** Default value for integrity protection security service. */ + boolean DEFAULT_INTEGRITY = true; // implied by the previous option + /** Default value for confidentiality protection security service. */ + boolean DEFAULT_CONFIDENTIALITY = false; + // constants defining HMAC names + String HMAC_SHA1 = "hmac-sha1"; + String HMAC_MD5 = "hmac-md5"; + String HMAC_RIPEMD_160 = "hmac-ripemd-160"; + /** Available HMAC algorithms for integrity protection. */ + String[] INTEGRITY_ALGORITHMS = { HMAC_SHA1, HMAC_MD5, HMAC_RIPEMD_160 }; + // constants defining Cipher names + String AES = "aes"; + String BLOWFISH = "blowfish"; + /** Available Cipher algorithms for confidentiality protection. */ + String[] CONFIDENTIALITY_ALGORITHMS = { AES, BLOWFISH }; + /** String for mandatory replay detection. */ + String OPTION_MANDATORY = "mandatory"; + /** String for mda: the SRP digest algorithm name. */ + String OPTION_SRP_DIGEST = "mda"; + /** String for mandatory replay detection. */ + String OPTION_REPLAY_DETECTION = "replay_detection"; + /** String for mandatory integrity protection. */ + String OPTION_INTEGRITY = "integrity"; + /** String for mandatory confidentiality protection. */ + String OPTION_CONFIDENTIALITY = "confidentiality"; + /** String for mandatory replay detection. */ + String OPTION_MAX_BUFFER_SIZE = "maxbuffersize"; + /** String for no mandatory security service. */ + String MANDATORY_NONE = "none"; + /** Default mandatory security service required. */ + String DEFAULT_MANDATORY = OPTION_REPLAY_DETECTION; + /** Name of the UID field in the plain password file. */ + String MD_NAME_FIELD = "srp.md.name"; + /** Name of the GID field in the plain password file. */ + String USER_VERIFIER_FIELD = "srp.user.verifier"; + /** Name of the GECOS field in the plain password file. */ + String SALT_FIELD = "srp.salt"; + /** Name of the SHELL field in the plain password file. */ + String CONFIG_NDX_FIELD = "srp.config.ndx"; + /** Minimum bitlength of the SRP public modulus. */ + int MINIMUM_MODULUS_BITLENGTH = 512; +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPServer.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPServer.java new file mode 100644 index 000000000..fca5c3bf3 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/SRPServer.java @@ -0,0 +1,842 @@ +/* SRPServer.java -- + Copyright (C) 2003, 2006 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.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.IllegalMechanismStateException; +import gnu.javax.crypto.sasl.InputBuffer; +import gnu.javax.crypto.sasl.IntegrityException; +import gnu.javax.crypto.sasl.OutputBuffer; +import gnu.javax.crypto.sasl.ServerMechanism; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashMap; +import java.util.StringTokenizer; +import java.util.logging.Logger; + +import javax.security.sasl.AuthenticationException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +/** + * The SASL-SRP server-side mechanism. + */ +public class SRPServer + extends ServerMechanism + implements SaslServer +{ + private static final Logger log = Logger.getLogger(SRPServer.class.getName()); + private String U = null; // client's username + private BigInteger N, g, A, B; + private byte[] s; // salt + private byte[] cIV, sIV; // client+server IVs, when confidentiality is on + private byte[] cn, sn; // client's and server's nonce + private SRP srp; // SRP algorithm instance used by this server + private byte[] sid; // session ID when re-used + private int ttl = 360; // session time-to-live in seconds + private byte[] cCB; // peer's channel binding' + private String mandatory; // List of available options + private String L = null; + 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 serverHandler = + KeyAgreementFactory.getPartyBInstance(Registry.SRP_SASL_KA); + /** Our default source of randomness. */ + private PRNG prng = null; + + public SRPServer() + { + super(Registry.SASL_SRP_MECHANISM); + } + + protected void initMechanism() throws SaslException + { + // TODO: + // we must have a means to map a given username to a preferred + // SRP hash algorithm; otherwise we end up using _always_ SHA. + // for the time being get it from the mechanism properties map + // and apply it for all users. + final String mda = (String) properties.get(SRPRegistry.SRP_HASH); + srp = SRP.instance(mda == null ? SRPRegistry.SRP_DEFAULT_DIGEST_NAME : mda); + } + + protected void resetMechanism() throws SaslException + { + s = null; + A = B = null; + K = null; + inMac = outMac = null; + inCipher = outCipher = null; + sid = null; + } + + public byte[] evaluateResponse(final byte[] response) throws SaslException + { + switch (state) + { + case 0: + if (response == null) + return null; + state++; + return sendProtocolElements(response); + case 1: + if (! complete) + { + state++; + return sendEvidence(response); + } + // else fall through + default: + throw new IllegalMechanismStateException("evaluateResponse()"); + } + } + + 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"); + if (Configuration.DEBUG) + log.fine("Incoming buffer (before security): " + + Util.dumpString(incoming, offset, len)); + // 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=" + String.valueOf(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[payloadLength]; + 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.fine("Incoming buffer (after security): " + Util.dumpString(result)); + 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"); + if (Configuration.DEBUG) + { + log.fine("Outgoing buffer (before security) (hex): " + + Util.dumpString(outgoing, offset, len)); + log.fine("Outgoing buffer (before security) (str): \"" + + new String(outgoing, offset, len) + "\""); + } + // at this point one, or both, of confidentiality and integrity protection + // services are active. + byte[] result; + try + { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + 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 ciphertext 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[] sendProtocolElements(final byte[] input) throws SaslException + { + if (Configuration.DEBUG) + { + log.entering(this.getClass().getName(), "sendProtocolElements"); + log.fine("C: " + Util.dumpString(input)); + } + // Client send U, I, sid, cn + final InputBuffer frameIn = new InputBuffer(input); + try + { + U = frameIn.getText(); // Extract username + if (Configuration.DEBUG) + log.fine("Got U (username): \"" + U + "\""); + authorizationID = frameIn.getText(); // Extract authorisation ID + if (Configuration.DEBUG) + log.fine("Got I (userid): \"" + authorizationID + "\""); + sid = frameIn.getEOS(); + if (Configuration.DEBUG) + log.fine("Got sid (session ID): " + new String(sid)); + cn = frameIn.getOS(); + if (Configuration.DEBUG) + log.fine("Got cn (client nonce): " + Util.dumpString(cn)); + cCB = frameIn.getEOS(); + if (Configuration.DEBUG) + log.fine("Got cCB (client channel binding): " + Util.dumpString(cCB)); + } + catch (IOException x) + { + if (x instanceof SaslException) + throw (SaslException) x; + throw new AuthenticationException("sendProtocolElements()", x); + } + // do/can we re-use? + if (ServerStore.instance().isAlive(sid)) + { + final SecurityContext ctx = ServerStore.instance().restoreSession(sid); + srp = SRP.instance(ctx.getMdName()); + 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(); + if (sn == null || sn.length != 16) + sn = new byte[16]; + getDefaultPRNG().nextBytes(sn); + setupSecurityServices(false); + final OutputBuffer frameOut = new OutputBuffer(); + try + { + frameOut.setScalar(1, 0xFF); + frameOut.setOS(sn); + frameOut.setEOS(channelBinding); + } + catch (IOException x) + { + if (x instanceof SaslException) + throw (SaslException) x; + throw new AuthenticationException("sendProtocolElements()", x); + } + final byte[] result = frameOut.encode(); + if (Configuration.DEBUG) + { + log.fine("Old session..."); + log.fine("S: " + Util.dumpString(result)); + log.fine(" sn = " + Util.dumpString(sn)); + log.fine(" sCB = " + Util.dumpString(channelBinding)); + log.exiting(this.getClass().getName(), "sendProtocolElements"); + } + return result; + } + else + { // new session + authenticator.activate(properties); + // ------------------------------------------------------------------- + final HashMap mapB = new HashMap(); + mapB.put(SRP6KeyAgreement.HASH_FUNCTION, srp.getAlgorithm()); + mapB.put(SRP6KeyAgreement.HOST_PASSWORD_DB, authenticator); + try + { + serverHandler.init(mapB); + OutgoingMessage out = new OutgoingMessage(); + out.writeString(U); + IncomingMessage in = new IncomingMessage(out.toByteArray()); + out = serverHandler.processMessage(in); + in = new IncomingMessage(out.toByteArray()); + N = in.readMPI(); + g = in.readMPI(); + s = in.readMPI().toByteArray(); + B = in.readMPI(); + } + catch (KeyAgreementException x) + { + throw new SaslException("sendProtocolElements()", x); + } + // ------------------------------------------------------------------- + if (Configuration.DEBUG) + { + log.fine("Encoding N (modulus): " + Util.dump(N)); + log.fine("Encoding g (generator): " + Util.dump(g)); + log.fine("Encoding s (client's salt): " + Util.dumpString(s)); + log.fine("Encoding B (server ephemeral public key): " + Util.dump(B)); + } + // The server creates an options list (L), which consists of a + // comma-separated list of option strings that specify the security + // service options the server supports. + L = createL(); + if (Configuration.DEBUG) + { + log.fine("Encoding L (available options): \"" + L + "\""); + log.fine("Encoding sIV (server IV): " + Util.dumpString(sIV)); + } + final OutputBuffer frameOut = new OutputBuffer(); + try + { + frameOut.setScalar(1, 0x00); + frameOut.setMPI(N); + frameOut.setMPI(g); + frameOut.setOS(s); + frameOut.setMPI(B); + frameOut.setText(L); + } + catch (IOException x) + { + if (x instanceof SaslException) + throw (SaslException) x; + throw new AuthenticationException("sendProtocolElements()", x); + } + final byte[] result = frameOut.encode(); + if (Configuration.DEBUG) + { + log.fine("New session..."); + log.fine("S: " + Util.dumpString(result)); + log.fine(" N = 0x" + N.toString(16)); + log.fine(" g = 0x" + g.toString(16)); + log.fine(" s = " + Util.dumpString(s)); + log.fine(" B = 0x" + B.toString(16)); + log.fine(" L = " + L); + log.exiting(this.getClass().getName(), "sendProtocolElements"); + } + return result; + } + } + + private byte[] sendEvidence(final byte[] input) throws SaslException + { + if (Configuration.DEBUG) + { + log.entering(this.getClass().getName(), "sendEvidence"); + log.fine("C: " + Util.dumpString(input)); + } + // Client send A, M1, o, cIV + final InputBuffer frameIn = new InputBuffer(input); + final byte[] M1; + try + { + A = frameIn.getMPI(); // Extract client's ephemeral public key + if (Configuration.DEBUG) + log.fine("Got A (client ephemeral public key): " + Util.dump(A)); + M1 = frameIn.getOS(); // Extract evidence + if (Configuration.DEBUG) + log.fine("Got M1 (client evidence): " + Util.dumpString(M1)); + o = frameIn.getText(); // Extract client's options list + if (Configuration.DEBUG) + log.fine("Got o (client chosen options): \"" + o + "\""); + cIV = frameIn.getOS(); // Extract client's IV + if (Configuration.DEBUG) + log.fine("Got cIV (client IV): " + Util.dumpString(cIV)); + } + catch (IOException x) + { + if (x instanceof SaslException) + throw (SaslException) x; + throw new AuthenticationException("sendEvidence()", x); + } + // Parse client's options and set security layer variables + parseO(o); + // ---------------------------------------------------------------------- + try + { + final OutgoingMessage out = new OutgoingMessage(); + out.writeMPI(A); + final IncomingMessage in = new IncomingMessage(out.toByteArray()); + serverHandler.processMessage(in); + K = serverHandler.getSharedSecret(); + } + catch (KeyAgreementException x) + { + throw new SaslException("sendEvidence()", x); + } + // ---------------------------------------------------------------------- + if (Configuration.DEBUG) + log.fine("K: " + Util.dumpString(K)); + final byte[] expected; + try + { + expected = srp.generateM1(N, g, U, s, A, B, K, authorizationID, L, cn, + cCB); + } + catch (UnsupportedEncodingException x) + { + throw new AuthenticationException("sendEvidence()", x); + } + // Verify client evidence + if (! Arrays.equals(M1, expected)) + throw new AuthenticationException("M1 mismatch"); + setupSecurityServices(true); + final byte[] M2; + try + { + M2 = srp.generateM2(A, M1, K, U, authorizationID, o, sid, ttl, cIV, + sIV, channelBinding); + } + catch (UnsupportedEncodingException x) + { + throw new AuthenticationException("sendEvidence()", x); + } + final OutputBuffer frameOut = new OutputBuffer(); + try + { + frameOut.setOS(M2); + frameOut.setOS(sIV); + frameOut.setEOS(sid); + frameOut.setScalar(4, ttl); + frameOut.setEOS(channelBinding); + } + catch (IOException x) + { + if (x instanceof SaslException) + throw (SaslException) x; + throw new AuthenticationException("sendEvidence()", x); + } + final byte[] result = frameOut.encode(); + if (Configuration.DEBUG) + { + log.fine("S: " + Util.dumpString(result)); + log.fine(" M2 = " + Util.dumpString(M2)); + log.fine(" sIV = " + Util.dumpString(sIV)); + log.fine(" sid = " + new String(sid)); + log.fine(" ttl = " + ttl); + log.fine(" sCB = " + Util.dumpString(channelBinding)); + log.exiting(this.getClass().getName(), "sendEvidence"); + } + return result; + } + + private String createL() + { + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "createL()"); + String s = (String) properties.get(SRPRegistry.SRP_MANDATORY); + if (s == null) + s = SRPRegistry.DEFAULT_MANDATORY; + + if (! SRPRegistry.MANDATORY_NONE.equals(s) + && ! SRPRegistry.OPTION_REPLAY_DETECTION.equals(s) + && ! SRPRegistry.OPTION_INTEGRITY.equals(s) + && ! SRPRegistry.OPTION_CONFIDENTIALITY.equals(s)) + { + if (Configuration.DEBUG) + log.fine("Unrecognised mandatory option (" + s + "). Using default..."); + s = SRPRegistry.DEFAULT_MANDATORY; + } + mandatory = s; + s = (String) properties.get(SRPRegistry.SRP_CONFIDENTIALITY); + final boolean confidentiality = (s == null ? SRPRegistry.DEFAULT_CONFIDENTIALITY + : Boolean.valueOf(s).booleanValue()); + s = (String) properties.get(SRPRegistry.SRP_INTEGRITY_PROTECTION); + boolean integrity = (s == null ? SRPRegistry.DEFAULT_INTEGRITY + : Boolean.valueOf(s).booleanValue()); + s = (String) properties.get(SRPRegistry.SRP_REPLAY_DETECTION); + final boolean replayDetection = (s == null ? SRPRegistry.DEFAULT_REPLAY_DETECTION + : Boolean.valueOf(s).booleanValue()); + final CPStringBuilder sb = new CPStringBuilder(); + sb.append(SRPRegistry.OPTION_SRP_DIGEST).append("=") + .append(srp.getAlgorithm()).append(","); + + if (! SRPRegistry.MANDATORY_NONE.equals(mandatory)) + sb.append(SRPRegistry.OPTION_MANDATORY) + .append("=").append(mandatory).append(","); + + if (replayDetection) + { + sb.append(SRPRegistry.OPTION_REPLAY_DETECTION).append(","); + // if replay detection is on then force integrity protection + integrity = true; + } + int i; + if (integrity) + { + for (i = 0; i < SRPRegistry.INTEGRITY_ALGORITHMS.length; i++) + sb.append(SRPRegistry.OPTION_INTEGRITY).append("=") + .append(SRPRegistry.INTEGRITY_ALGORITHMS[i]).append(","); + } + if (confidentiality) + { + IBlockCipher cipher; + for (i = 0; i < SRPRegistry.CONFIDENTIALITY_ALGORITHMS.length; i++) + { + cipher = CipherFactory.getInstance(SRPRegistry.CONFIDENTIALITY_ALGORITHMS[i]); + if (cipher != null) + sb.append(SRPRegistry.OPTION_CONFIDENTIALITY).append("=") + .append(SRPRegistry.CONFIDENTIALITY_ALGORITHMS[i]).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(), "createL"); + return result; + } + + // Parse client's options and set security layer variables + private void parseO(final String o) throws AuthenticationException + { + this.replayDetection = false; + boolean integrity = false; + boolean confidentiality = false; + String option; + int i; + + final StringTokenizer st = new StringTokenizer(o.toLowerCase(), ","); + while (st.hasMoreTokens()) + { + option = st.nextToken(); + if (Configuration.DEBUG) + log.fine("option: <" + option + ">"); + if (option.equals(SRPRegistry.OPTION_REPLAY_DETECTION)) + replayDetection = true; + else if (option.startsWith(SRPRegistry.OPTION_INTEGRITY + "=")) + { + if (integrity) + throw new AuthenticationException( + "Only one integrity algorithm may be chosen"); + option = option.substring(option.indexOf('=') + 1); + if (Configuration.DEBUG) + log.fine("algorithm: <" + option + ">"); + for (i = 0; i < SRPRegistry.INTEGRITY_ALGORITHMS.length; i++) + { + if (SRPRegistry.INTEGRITY_ALGORITHMS[i].equals(option)) + { + chosenIntegrityAlgorithm = option; + integrity = true; + break; + } + } + if (! integrity) + throw new AuthenticationException("Unknown integrity algorithm: " + + option); + } + else if (option.startsWith(SRPRegistry.OPTION_CONFIDENTIALITY + "=")) + { + if (confidentiality) + throw new AuthenticationException( + "Only one confidentiality algorithm may be chosen"); + option = option.substring(option.indexOf('=') + 1); + if (Configuration.DEBUG) + log.fine("algorithm: <" + option + ">"); + for (i = 0; i < SRPRegistry.CONFIDENTIALITY_ALGORITHMS.length; i++) + { + if (SRPRegistry.CONFIDENTIALITY_ALGORITHMS[i].equals(option)) + { + chosenConfidentialityAlgorithm = option; + confidentiality = true; + break; + } + } + if (! confidentiality) + throw new AuthenticationException("Unknown confidentiality algorithm: " + + option); + } + 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); + } + } + } + // check if client did the right thing + if (replayDetection) + { + if (! integrity) + throw new AuthenticationException( + "Missing integrity protection algorithm but replay detection is chosen"); + } + if (mandatory.equals(SRPRegistry.OPTION_REPLAY_DETECTION)) + { + if (! replayDetection) + throw new AuthenticationException( + "Replay detection is mandatory but was not chosen"); + } + if (mandatory.equals(SRPRegistry.OPTION_INTEGRITY)) + { + if (! integrity) + throw new AuthenticationException( + "Integrity protection is mandatory but was not chosen"); + } + if (mandatory.equals(SRPRegistry.OPTION_CONFIDENTIALITY)) + { + if (! confidentiality) + throw new AuthenticationException( + "Confidentiality is mandatory but was not chosen"); + } + int blockSize = 0; + if (chosenConfidentialityAlgorithm != null) + { + final IBlockCipher cipher = CipherFactory.getInstance(chosenConfidentialityAlgorithm); + if (cipher != null) + blockSize = cipher.defaultBlockSize(); + else // should not happen + throw new AuthenticationException("Confidentiality algorithm (" + + chosenConfidentialityAlgorithm + + ") not available"); + } + sIV = new byte[blockSize]; + if (blockSize > 0) + getDefaultPRNG().nextBytes(sIV); + } + + private void setupSecurityServices(final boolean newSession) + throws SaslException + { + complete = true; // signal end of authentication phase + if (newSession) + { + 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); + } + // generate a new sid if at least integrity is used + sid = (inMac != null ? ServerStore.getNewSessionID() : new byte[0]); + } + else // same session new keys + K = srp.generateKn(K, cn, sn); + + final KDF kdf = KDF.getInstance(K); + // initialise in/out ciphers if confidentaility protection is used + if (inCipher != null) + { + outCipher.init(kdf, sIV, Direction.FORWARD); + inCipher.init(kdf, cIV, Direction.REVERSED); + } + // initialise in/out macs if integrity protection is used + if (inMac != null) + { + outMac.init(kdf); + inMac.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 sid = " + new String(sid)); + ServerStore.instance().cacheSession(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; + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/SecurityContext.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/SecurityContext.java new file mode 100644 index 000000000..41ec57c81 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/SecurityContext.java @@ -0,0 +1,140 @@ +/* SecurityContext.java -- + Copyright (C) 2003, 2006 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; + +/** + * A package-private placeholder for an SRP security context. + */ +class SecurityContext +{ + private String mdName; + private byte[] sid; + private byte[] K; + private byte[] cIV; + private byte[] sIV; + private boolean replayDetection; + private int inCounter; + private int outCounter; + private IALG inMac; + private IALG outMac; + private CALG inCipher; + private CALG outCipher; + + SecurityContext(final String mdName, final byte[] sid, final byte[] K, + final byte[] cIV, final byte[] sIV, + final boolean replayDetection, final int inCounter, + final int outCounter, final IALG inMac, final IALG outMac, + final CALG inCipher, final CALG outCipher) + { + super(); + + this.mdName = mdName; + this.sid = sid; + this.K = K; + this.cIV = cIV; + this.sIV = sIV; + this.replayDetection = replayDetection; + this.inCounter = inCounter; + this.outCounter = outCounter; + this.inMac = inMac; + this.outMac = outMac; + this.inCipher = inCipher; + this.outCipher = outCipher; + } + + String getMdName() + { + return mdName; + } + + byte[] getSID() + { + return sid; + } + + byte[] getK() + { + return K; + } + + byte[] getClientIV() + { + return cIV; + } + + byte[] getServerIV() + { + return sIV; + } + + boolean hasReplayDetection() + { + return replayDetection; + } + + int getInCounter() + { + return inCounter; + } + + int getOutCounter() + { + return outCounter; + } + + IALG getInMac() + { + return inMac; + } + + IALG getOutMac() + { + return outMac; + } + + CALG getInCipher() + { + return inCipher; + } + + CALG getOutCipher() + { + return outCipher; + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/ServerStore.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/ServerStore.java new file mode 100644 index 000000000..d98747324 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/ServerStore.java @@ -0,0 +1,177 @@ +/* ServerStore.java -- + Copyright (C) 2003, 2006 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 java.util.HashMap; + +/** + * The server-side implementation of the SRP security context store. + */ +public class ServerStore +{ + /** The underlying singleton. */ + private static ServerStore singleton = null; + /** The map of sid --> Security Context record. */ + private static final HashMap sid2ssc = new HashMap(); + /** The map of sid --> Session timing record. */ + private static final HashMap sid2ttl = new HashMap(); + /** A synchronisation lock. */ + private static final Object lock = new Object(); + /** A counter to generate legible SIDs. */ + private static int counter = 0; + + /** Private constructor to enforce Singleton pattern. */ + private ServerStore() + { + super(); + + // TODO: add a cleaning timer thread + } + + /** + * Returns the classloader Singleton. + * + * @return the classloader Singleton instance. + */ + static synchronized final ServerStore instance() + { + if (singleton == null) + singleton = new ServerStore(); + return singleton; + } + + /** + * Returns a legible new session identifier. + * + * @return a new session identifier. + */ + static synchronized final byte[] getNewSessionID() + { + final String sid = String.valueOf(++counter); + return new CPStringBuilder("SID-") + .append("0000000000".substring(0, 10 - sid.length())).append(sid) + .toString().getBytes(); + } + + /** + * Returns a boolean flag indicating if the designated session is still alive + * or not. + * + * @param sid the identifier of the session to check. + * @return <code>true</code> if the designated session is still alive. + * <code>false</code> otherwise. + */ + boolean isAlive(final byte[] sid) + { + boolean result = false; + if (sid != null && sid.length != 0) + { + synchronized (lock) + { + final String key = new String(sid); + final StoreEntry ctx = (StoreEntry) sid2ttl.get(key); + if (ctx != null) + { + result = ctx.isAlive(); + if (! result) // invalidate it en-passant + { + sid2ssc.remove(key); + sid2ttl.remove(key); + } + } + } + } + return result; + } + + /** + * Records a mapping between a session identifier and the Security Context of + * the designated SRP server mechanism instance. + * + * @param ttl the session's Time-To-Live indicator (in seconds). + * @param ctx the server's security context. + */ + void cacheSession(final int ttl, final SecurityContext ctx) + { + synchronized (lock) + { + final String key = new String(ctx.getSID()); + sid2ssc.put(key, ctx); + sid2ttl.put(key, new StoreEntry(ttl)); + } + } + + /** + * Updates the mapping between the designated session identifier and the + * designated server's SASL Security Context. In the process, computes and + * return the underlying mechanism server's evidence that shall be returned to + * the client in a session re-use exchange. + * + * @param sid the identifier of the session to restore. + * @return an SRP server's security context. + */ + SecurityContext restoreSession(final byte[] sid) + { + final String key = new String(sid); + final SecurityContext result; + synchronized (lock) + { + result = (SecurityContext) sid2ssc.remove(key); + sid2ttl.remove(key); + } + return result; + } + + /** + * Removes all information related to the designated session ID. + * + * @param sid the identifier of the seesion to invalidate. + */ + void invalidateSession(final byte[] sid) + { + final String key = new String(sid); + synchronized (lock) + { + sid2ssc.remove(key); + sid2ttl.remove(key); + } + } +} diff --git a/libjava/classpath/gnu/javax/crypto/sasl/srp/StoreEntry.java b/libjava/classpath/gnu/javax/crypto/sasl/srp/StoreEntry.java new file mode 100644 index 000000000..ae64fa774 --- /dev/null +++ b/libjava/classpath/gnu/javax/crypto/sasl/srp/StoreEntry.java @@ -0,0 +1,75 @@ +/* StoreEntry.java -- + Copyright (C) 2003, 2006 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; + +/** + * A simple timing-related object for use by SRP re-use code. + */ +class StoreEntry +{ + private boolean perenial; + private long timeToDie; + + StoreEntry(int ttl) + { + super(); + + if (ttl == 0) + { + perenial = true; + timeToDie = 0L; + } + else + { + perenial = false; + timeToDie = System.currentTimeMillis() + (ttl & 0xFFFFFFFFL) * 1000L; + } + } + + /** + * Returns <code>true</code> if the Time-To_live period has not elapsed. + * + * @return <code>true</code> if the Time-To-Live period (in seconds) has not + * elapsed yet; <code>false</code> otherwise. + */ + boolean isAlive() + { + return (perenial ? true : (System.currentTimeMillis() < timeToDie)); + } +} |