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.io.Serializable;
025: import java.math.BigInteger;
026: import java.security.MessageDigest;
027: import java.security.NoSuchAlgorithmException;
028: import java.util.Arrays;
029:
030: import org.jboss.logging.Logger;
031: import org.jboss.security.Util;
032:
033: /** The server side logic to the SRP protocol. The class is the server side
034: equivalent of the SRPClientSession object. An implementation of
035: SRPServerInterface creates an SRPServerSession on the start of a login
036: session.
037:
038: The client side algorithm using these classes consists of:
039:
040: 1. Get server, SRPServerInterface server = (SRPServerInterface) Naming.lookup(...);
041: 2. Get SRP parameters, SRPParameters params = server.getSRPParameters(username);
042: 3. Create a client session, SRPClientSession client = new SRPClientSession(username, password, params);
043: 4. Exchange public keys, byte[] A = client.exponential();
044: byte[] B = server.init(username, A);
045: 5. Exchange challenges, byte[] M1 = client.response(B);
046: byte[] M2 = server.verify(username, M1);
047: 6. Verify the server response, if( client.verify(M2) == false )
048: throw new SecurityException("Failed to validate server reply");
049: 7. Validation complete
050:
051: Note that these steps are stateful. They must be performed in order and a
052: step cannot be repeated to update the session state.
053:
054: This product uses the 'Secure Remote Password' cryptographic
055: authentication system developed by Tom Wu (tjw@CS.Stanford.EDU).
056:
057: @author Scott.Stark@jboss.org
058: @version $Revision: 57210 $
059: */
060: public class SRPServerSession implements Serializable {
061: /** The serial version ID
062: @since 1.6
063: */
064: static final long serialVersionUID = -2448005747721323704L;
065: private static int B_LEN = 64; // 64 bits for 'b'
066: private static Logger log = Logger
067: .getLogger(SRPServerSession.class);
068:
069: private SRPParameters params;
070: private BigInteger N;
071: private BigInteger g;
072: private BigInteger v;
073: private BigInteger b;
074: private BigInteger B;
075: private byte[] K;
076: /** The M1 = H(H(N) xor H(g) | H(U) | s | A | B | K) hash */
077: private transient MessageDigest clientHash;
078: private byte[] M1;
079: /** The M2 = H(A | M | K) hash */
080: private transient MessageDigest serverHash;
081: private byte[] M2;
082:
083: /** Creates a new SRP server session object from the username, password
084: verifier, and session parameters.
085: @param username, the user ID
086: @param vb, the password verifier byte sequence
087: @param params, the SRP parameters for the session
088: */
089: public SRPServerSession(String username, byte[] vb,
090: SRPParameters params) {
091: this .params = params;
092: this .v = new BigInteger(1, vb);
093: this .g = new BigInteger(1, params.g);
094: this .N = new BigInteger(1, params.N);
095: if (log.isTraceEnabled())
096: log.trace("g: " + Util.tob64(params.g));
097: if (log.isTraceEnabled())
098: log.trace("v: " + Util.tob64(vb));
099: serverHash = Util.newDigest();
100: clientHash = Util.newDigest();
101: // H(N)
102: byte[] hn = Util.newDigest().digest(params.N);
103: if (log.isTraceEnabled())
104: log.trace("H(N): " + Util.tob64(hn));
105: // H(g)
106: byte[] hg = Util.newDigest().digest(params.g);
107: if (log.isTraceEnabled())
108: log.trace("H(g): " + Util.tob64(hg));
109: // clientHash = H(N) xor H(g)
110: byte[] hxg = Util.xor(hn, hg, 20);
111: if (log.isTraceEnabled())
112: log.trace("H(N) xor H(g): " + Util.tob64(hxg));
113: clientHash.update(hxg);
114: if (log.isTraceEnabled()) {
115: MessageDigest tmp = Util.copy(clientHash);
116: log.trace("H[H(N) xor H(g)]: " + Util.tob64(tmp.digest()));
117: }
118: // clientHash = H(N) xor H(g) | H(U)
119: clientHash.update(Util.newDigest().digest(username.getBytes()));
120: if (log.isTraceEnabled()) {
121: MessageDigest tmp = Util.copy(clientHash);
122: log.trace("H[H(N) xor H(g) | H(U)]: "
123: + Util.tob64(tmp.digest()));
124: }
125: // clientHash = H(N) xor H(g) | H(U) | s
126: clientHash.update(params.s);
127: if (log.isTraceEnabled()) {
128: MessageDigest tmp = Util.copy(clientHash);
129: log.trace("H[H(N) xor H(g) | H(U) | s]: "
130: + Util.tob64(tmp.digest()));
131: }
132: K = null;
133: }
134:
135: /**
136: * @returns The user's password salt
137: */
138: public SRPParameters getParameters() {
139: return params;
140: }
141:
142: /**
143: * @returns The exponential residue (parameter B) to be sent to the
144: * client.
145: */
146: public byte[] exponential() {
147: if (B == null) {
148: BigInteger one = BigInteger.valueOf(1);
149: do {
150: b = new BigInteger(B_LEN, Util.getPRNG());
151: } while (b.compareTo(one) <= 0);
152: B = v.add(g.modPow(b, N));
153: if (B.compareTo(N) >= 0)
154: B = B.subtract(N);
155: }
156: return Util.trim(B.toByteArray());
157: }
158:
159: /**
160: @param ab The client's exponential (parameter A).
161: @returns The secret shared session K between client and server
162: @exception NoSuchAlgorithmException thrown if the session key
163: MessageDigest algorithm cannot be found.
164: */
165: public void buildSessionKey(byte[] ab)
166: throws NoSuchAlgorithmException {
167: if (log.isTraceEnabled())
168: log.trace("A: " + Util.tob64(ab));
169: byte[] nb = Util.trim(B.toByteArray());
170: // clientHash = H(N) xor H(g) | H(U) | s | A
171: clientHash.update(ab);
172: if (log.isTraceEnabled()) {
173: MessageDigest tmp = Util.copy(clientHash);
174: log.trace("H[H(N) xor H(g) | H(U) | s | A]: "
175: + Util.tob64(tmp.digest()));
176: }
177: // clientHash = H(N) xor H(g) | H(U) | A | B
178: clientHash.update(nb);
179: if (log.isTraceEnabled()) {
180: MessageDigest tmp = Util.copy(clientHash);
181: log.trace("H[H(N) xor H(g) | H(U) | s | A | B]: "
182: + Util.tob64(tmp.digest()));
183: }
184: // serverHash = A
185: serverHash.update(ab);
186: // Calculate u as the first 32 bits of H(B)
187: byte[] hB = Util.newDigest().digest(nb);
188: byte[] ub = { hB[0], hB[1], hB[2], hB[3] };
189: // Calculate S = (A * v^u) ^ b % N
190: BigInteger A = new BigInteger(1, ab);
191: if (log.isTraceEnabled())
192: log.trace("A: " + Util.tob64(A.toByteArray()));
193: if (log.isTraceEnabled())
194: log.trace("B: " + Util.tob64(B.toByteArray()));
195: if (log.isTraceEnabled())
196: log.trace("v: " + Util.tob64(v.toByteArray()));
197: BigInteger u = new BigInteger(1, ub);
198: if (log.isTraceEnabled())
199: log.trace("u: " + Util.tob64(u.toByteArray()));
200: BigInteger A_v2u = A.multiply(v.modPow(u, N)).mod(N);
201: if (log.isTraceEnabled())
202: log.trace("A * v^u: " + Util.tob64(A_v2u.toByteArray()));
203: BigInteger S = A_v2u.modPow(b, N);
204: if (log.isTraceEnabled())
205: log.trace("S: " + Util.tob64(S.toByteArray()));
206: // K = SessionHash(S)
207: MessageDigest sessionDigest = MessageDigest
208: .getInstance(params.hashAlgorithm);
209: K = sessionDigest.digest(S.toByteArray());
210: if (log.isTraceEnabled())
211: log.trace("K: " + Util.tob64(K));
212: // clientHash = H(N) xor H(g) | H(U) | A | B | K
213: clientHash.update(K);
214: if (log.isTraceEnabled()) {
215: MessageDigest tmp = Util.copy(clientHash);
216: log.trace("H[H(N) xor H(g) | H(U) | s | A | B | K]: "
217: + Util.tob64(tmp.digest()));
218: }
219: }
220:
221: /** Returns the negotiated session K, K = SessionHash(S)
222: @return the private session K byte[]
223: @throws SecurityException - if the current thread does not have an
224: getSessionKey SRPPermission.
225: */
226: public byte[] getSessionKey() throws SecurityException {
227: SecurityManager sm = System.getSecurityManager();
228: if (sm != null) {
229: SRPPermission p = new SRPPermission("getSessionKey");
230: sm.checkPermission(p);
231: }
232: return K;
233: }
234:
235: /**
236: @returns M2 = H(A | M | K)
237: */
238: public byte[] getServerResponse() {
239: if (M2 == null)
240: M2 = serverHash.digest();
241: return M2;
242: }
243:
244: public byte[] getClientResponse() {
245: return M1;
246: }
247:
248: /**
249: * @param resp The client's response to the server's challenge
250: * @returns True if and only if the client's response was correct.
251: */
252: public boolean verify(byte[] clientM1) {
253: boolean valid = false;
254: // M1 = H(H(N) xor H(g) | H(U) | A | B | K)
255: M1 = clientHash.digest();
256: if (log.isTraceEnabled()) {
257: log.trace("verify M1: " + Util.tob64(M1));
258: log.trace("verify clientM1: " + Util.tob64(clientM1));
259: }
260: if (Arrays.equals(clientM1, M1)) {
261: // serverHash = A | M
262: serverHash.update(M1);
263: // serverHash = A | M | K
264: serverHash.update(K);
265: if (log.isTraceEnabled()) {
266: MessageDigest tmp = Util.copy(serverHash);
267: log.trace("H(A | M1 | K)" + Util.tob64(tmp.digest()));
268: }
269: valid = true;
270: }
271: return valid;
272: }
273: }
|