001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.security.srp;
023:
024: import java.math.BigInteger;
025: import java.security.MessageDigest;
026: import java.security.NoSuchAlgorithmException;
027: import java.util.Arrays;
028:
029: import org.jboss.logging.Logger;
030: import org.jboss.security.Util;
031:
032: /** The client side logic to the SRP protocol. The class is intended to be used
033: * with a SRPServerSession object via the SRPServerInterface. The SRP algorithm
034: * using these classes consists of:
035: *
036: * 1. Get server, SRPServerInterface server = (SRPServerInterface) Naming.lookup(...);
037: * 2. Get SRP parameters, SRPParameters params = server.getSRPParameters(username);
038: * 3. Create a client session, SRPClientSession client = new SRPClientSession(username,
039: * password, params);
040: * 4. Exchange public keys, byte[] A = client.exponential();
041: * byte[] B = server.init(username, A);
042: * 5. Exchange challenges, byte[] M1 = client.response(B);
043: * byte[] M2 = server.verify(username, M1);
044: * 6. Verify the server response, if( client.verify(M2) == false )
045: * throw new SecurityException("Failed to validate server reply");
046: * 7. Validation complete
047: *
048: * Note that these steps are stateful. They must be performed in order and a
049: * step cannot be repeated to update the session state.
050: *
051: * This product uses the 'Secure Remote Password' cryptographic
052: * authentication system developed by Tom Wu (tjw@CS.Stanford.EDU).
053: *
054: * @author Scott.Stark@jboss.org
055: * @version $Revision: 57210 $
056: */
057: public class SRPClientSession {
058: private static Logger log = Logger
059: .getLogger(SRPClientSession.class);
060: private SRPParameters params;
061: private BigInteger N;
062: private BigInteger g;
063: private BigInteger x;
064: private BigInteger v;
065: private byte[] s;
066: private BigInteger a;
067: private BigInteger A;
068: private byte[] K;
069: /** The M1 = H(H(N) xor H(g) | H(U) | s | A | B | K) hash */
070: private MessageDigest clientHash;
071: /** The M2 = H(A | M | K) hash */
072: private MessageDigest serverHash;
073:
074: private static int A_LEN = 64;
075:
076: /** Creates a new SRP server session object from the username, password
077: verifier,
078: @param username, the user ID
079: @param password, the user clear text password
080: @param params, the SRP parameters for the session
081: */
082: public SRPClientSession(String username, char[] password,
083: SRPParameters params) {
084: this (username, password, params, null);
085: }
086:
087: /** Creates a new SRP server session object from the username, password
088: verifier,
089: @param username, the user ID
090: @param password, the user clear text password
091: @param params, the SRP parameters for the session
092: @param abytes, the random exponent used in the A public key. This must be
093: 8 bytes in length.
094: */
095: public SRPClientSession(String username, char[] password,
096: SRPParameters params, byte[] abytes) {
097: try {
098: // Initialize the secure random number and message digests
099: Util.init();
100: } catch (NoSuchAlgorithmException e) {
101: }
102: this .params = params;
103: this .g = new BigInteger(1, params.g);
104: this .N = new BigInteger(1, params.N);
105: if (abytes != null) {
106: if (8 * abytes.length != A_LEN)
107: throw new IllegalArgumentException(
108: "The abytes param must be " + (A_LEN / 8)
109: + " in length, abytes.length="
110: + abytes.length);
111: this .a = new BigInteger(abytes);
112: }
113:
114: if (log.isTraceEnabled())
115: log.trace("g: " + Util.tob64(params.g));
116: // Calculate x = H(s | H(U | ':' | password))
117: byte[] xb = Util.calculatePasswordHash(username, password,
118: params.s);
119: if (log.isTraceEnabled())
120: log.trace("x: " + Util.tob64(xb));
121: this .x = new BigInteger(1, xb);
122: this .v = g.modPow(x, N); // g^x % N
123: if (log.isTraceEnabled())
124: log.trace("v: " + Util.tob64(v.toByteArray()));
125:
126: serverHash = Util.newDigest();
127: clientHash = Util.newDigest();
128: // H(N)
129: byte[] hn = Util.newDigest().digest(params.N);
130: if (log.isTraceEnabled())
131: log.trace("H(N): " + Util.tob64(hn));
132: // H(g)
133: byte[] hg = Util.newDigest().digest(params.g);
134: if (log.isTraceEnabled())
135: log.trace("H(g): " + Util.tob64(hg));
136: // clientHash = H(N) xor H(g)
137: byte[] hxg = Util.xor(hn, hg, 20);
138: if (log.isTraceEnabled())
139: log.trace("H(N) xor H(g): " + Util.tob64(hxg));
140: clientHash.update(hxg);
141: if (log.isTraceEnabled()) {
142: MessageDigest tmp = Util.copy(clientHash);
143: log.trace("H[H(N) xor H(g)]: " + Util.tob64(tmp.digest()));
144: }
145: // clientHash = H(N) xor H(g) | H(U)
146: clientHash.update(Util.newDigest().digest(username.getBytes()));
147: if (log.isTraceEnabled()) {
148: MessageDigest tmp = Util.copy(clientHash);
149: log.trace("H[H(N) xor H(g) | H(U)]: "
150: + Util.tob64(tmp.digest()));
151: }
152: // clientHash = H(N) xor H(g) | H(U) | s
153: clientHash.update(params.s);
154: if (log.isTraceEnabled()) {
155: MessageDigest tmp = Util.copy(clientHash);
156: log.trace("H[H(N) xor H(g) | H(U) | s]: "
157: + Util.tob64(tmp.digest()));
158: }
159: K = null;
160: }
161:
162: /**
163: * @returns The exponential residue (parameter A) to be sent to the server.
164: */
165: public byte[] exponential() {
166: byte[] Abytes = null;
167: if (A == null) {
168: /* If the random component of A has not been specified use a random
169: number */
170: if (a == null) {
171: BigInteger one = BigInteger.ONE;
172: do {
173: a = new BigInteger(A_LEN, Util.getPRNG());
174: } while (a.compareTo(one) <= 0);
175: }
176: A = g.modPow(a, N);
177: Abytes = Util.trim(A.toByteArray());
178: // clientHash = H(N) xor H(g) | H(U) | A
179: clientHash.update(Abytes);
180: if (log.isTraceEnabled()) {
181: MessageDigest tmp = Util.copy(clientHash);
182: log.trace("H[H(N) xor H(g) | H(U) | s | A]: "
183: + Util.tob64(tmp.digest()));
184: }
185: // serverHash = A
186: serverHash.update(Abytes);
187: }
188: return Abytes;
189: }
190:
191: /**
192: @returns M1 = H(H(N) xor H(g) | H(U) | s | A | B | K)
193: @exception NoSuchAlgorithmException thrown if the session key
194: MessageDigest algorithm cannot be found.
195: */
196: public byte[] response(byte[] Bbytes)
197: throws NoSuchAlgorithmException {
198: // clientHash = H(N) xor H(g) | H(U) | s | A | B
199: clientHash.update(Bbytes);
200: if (log.isTraceEnabled()) {
201: MessageDigest tmp = Util.copy(clientHash);
202: log.trace("H[H(N) xor H(g) | H(U) | s | A | B]: "
203: + Util.tob64(tmp.digest()));
204: }
205: // Calculate u as the first 32 bits of H(B)
206: byte[] hB = Util.newDigest().digest(Bbytes);
207: byte[] ub = { hB[0], hB[1], hB[2], hB[3] };
208: // Calculate S = (B - g^x) ^ (a + u * x) % N
209: BigInteger B = new BigInteger(1, Bbytes);
210: if (log.isTraceEnabled())
211: log.trace("B: " + Util.tob64(B.toByteArray()));
212: if (B.compareTo(v) < 0)
213: B = B.add(N);
214: if (log.isTraceEnabled())
215: log.trace("B': " + Util.tob64(B.toByteArray()));
216: if (log.isTraceEnabled())
217: log.trace("v: " + Util.tob64(v.toByteArray()));
218: BigInteger u = new BigInteger(1, ub);
219: if (log.isTraceEnabled())
220: log.trace("u: " + Util.tob64(u.toByteArray()));
221: BigInteger B_v = B.subtract(v);
222: if (log.isTraceEnabled())
223: log.trace("B - v: " + Util.tob64(B_v.toByteArray()));
224: BigInteger a_ux = a.add(u.multiply(x));
225: if (log.isTraceEnabled())
226: log.trace("a + u * x: " + Util.tob64(a_ux.toByteArray()));
227: BigInteger S = B_v.modPow(a_ux, N);
228: if (log.isTraceEnabled())
229: log.trace("S: " + Util.tob64(S.toByteArray()));
230: // K = SessionHash(S)
231: MessageDigest sessionDigest = MessageDigest
232: .getInstance(params.hashAlgorithm);
233: K = sessionDigest.digest(S.toByteArray());
234: if (log.isTraceEnabled())
235: log.trace("K: " + Util.tob64(K));
236: // clientHash = H(N) xor H(g) | H(U) | A | B | K
237: clientHash.update(K);
238: byte[] M1 = clientHash.digest();
239: if (log.isTraceEnabled())
240: log.trace("M1: H[H(N) xor H(g) | H(U) | s | A | B | K]: "
241: + Util.tob64(M1));
242: serverHash.update(M1);
243: serverHash.update(K);
244: if (log.isTraceEnabled()) {
245: MessageDigest tmp = Util.copy(serverHash);
246: log.trace("H[A | M1 | K]: " + Util.tob64(tmp.digest()));
247: }
248: return M1;
249: }
250:
251: /**
252: * @param M2 The server's response to the client's challenge
253: * @returns True if and only if the server's response was correct.
254: */
255: public boolean verify(byte[] M2) {
256: // M2 = H(A | M1 | K)
257: byte[] myM2 = serverHash.digest();
258: boolean valid = Arrays.equals(M2, myM2);
259: if (log.isTraceEnabled()) {
260: log.trace("verify serverM2: " + Util.tob64(M2));
261: log.trace("verify M2: " + Util.tob64(myM2));
262: }
263: return valid;
264: }
265:
266: /** Returns the negotiated session K, K = SHA_Interleave(S)
267: @return the private session K byte[]
268: @throws SecurityException - if the current thread does not have an
269: getSessionKey SRPPermission.
270: */
271: public byte[] getSessionKey() throws SecurityException {
272: SecurityManager sm = System.getSecurityManager();
273: if (sm != null) {
274: SRPPermission p = new SRPPermission("getSessionKey");
275: sm.checkPermission(p);
276: }
277: return K;
278: }
279: }
|