1 |
769 |
jeremybenn |
/* SRPServer.java --
|
2 |
|
|
Copyright (C) 2003, 2006 Free Software Foundation, Inc.
|
3 |
|
|
|
4 |
|
|
This file is a part of GNU Classpath.
|
5 |
|
|
|
6 |
|
|
GNU Classpath is free software; you can redistribute it and/or modify
|
7 |
|
|
it under the terms of the GNU General Public License as published by
|
8 |
|
|
the Free Software Foundation; either version 2 of the License, or (at
|
9 |
|
|
your option) any later version.
|
10 |
|
|
|
11 |
|
|
GNU Classpath is distributed in the hope that it will be useful, but
|
12 |
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 |
|
|
General Public License for more details.
|
15 |
|
|
|
16 |
|
|
You should have received a copy of the GNU General Public License
|
17 |
|
|
along with GNU Classpath; if not, write to the Free Software
|
18 |
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
|
19 |
|
|
USA
|
20 |
|
|
|
21 |
|
|
Linking this library statically or dynamically with other modules is
|
22 |
|
|
making a combined work based on this library. Thus, the terms and
|
23 |
|
|
conditions of the GNU General Public License cover the whole
|
24 |
|
|
combination.
|
25 |
|
|
|
26 |
|
|
As a special exception, the copyright holders of this library give you
|
27 |
|
|
permission to link this library with independent modules to produce an
|
28 |
|
|
executable, regardless of the license terms of these independent
|
29 |
|
|
modules, and to copy and distribute the resulting executable under
|
30 |
|
|
terms of your choice, provided that you also meet, for each linked
|
31 |
|
|
independent module, the terms and conditions of the license of that
|
32 |
|
|
module. An independent module is a module which is not derived from
|
33 |
|
|
or based on this library. If you modify this library, you may extend
|
34 |
|
|
this exception to your version of the library, but you are not
|
35 |
|
|
obligated to do so. If you do not wish to do so, delete this
|
36 |
|
|
exception statement from your version. */
|
37 |
|
|
|
38 |
|
|
|
39 |
|
|
package gnu.javax.crypto.sasl.srp;
|
40 |
|
|
|
41 |
|
|
import gnu.java.lang.CPStringBuilder;
|
42 |
|
|
|
43 |
|
|
import gnu.java.security.Configuration;
|
44 |
|
|
import gnu.java.security.Registry;
|
45 |
|
|
import gnu.java.security.util.PRNG;
|
46 |
|
|
import gnu.java.security.util.Util;
|
47 |
|
|
import gnu.javax.crypto.assembly.Direction;
|
48 |
|
|
import gnu.javax.crypto.cipher.CipherFactory;
|
49 |
|
|
import gnu.javax.crypto.cipher.IBlockCipher;
|
50 |
|
|
import gnu.javax.crypto.key.IKeyAgreementParty;
|
51 |
|
|
import gnu.javax.crypto.key.IncomingMessage;
|
52 |
|
|
import gnu.javax.crypto.key.KeyAgreementException;
|
53 |
|
|
import gnu.javax.crypto.key.KeyAgreementFactory;
|
54 |
|
|
import gnu.javax.crypto.key.OutgoingMessage;
|
55 |
|
|
import gnu.javax.crypto.key.srp6.SRP6KeyAgreement;
|
56 |
|
|
import gnu.javax.crypto.sasl.IllegalMechanismStateException;
|
57 |
|
|
import gnu.javax.crypto.sasl.InputBuffer;
|
58 |
|
|
import gnu.javax.crypto.sasl.IntegrityException;
|
59 |
|
|
import gnu.javax.crypto.sasl.OutputBuffer;
|
60 |
|
|
import gnu.javax.crypto.sasl.ServerMechanism;
|
61 |
|
|
|
62 |
|
|
import java.io.ByteArrayOutputStream;
|
63 |
|
|
import java.io.IOException;
|
64 |
|
|
import java.io.UnsupportedEncodingException;
|
65 |
|
|
import java.math.BigInteger;
|
66 |
|
|
import java.util.Arrays;
|
67 |
|
|
import java.util.HashMap;
|
68 |
|
|
import java.util.StringTokenizer;
|
69 |
|
|
import java.util.logging.Logger;
|
70 |
|
|
|
71 |
|
|
import javax.security.sasl.AuthenticationException;
|
72 |
|
|
import javax.security.sasl.SaslException;
|
73 |
|
|
import javax.security.sasl.SaslServer;
|
74 |
|
|
|
75 |
|
|
/**
|
76 |
|
|
* The SASL-SRP server-side mechanism.
|
77 |
|
|
*/
|
78 |
|
|
public class SRPServer
|
79 |
|
|
extends ServerMechanism
|
80 |
|
|
implements SaslServer
|
81 |
|
|
{
|
82 |
|
|
private static final Logger log = Logger.getLogger(SRPServer.class.getName());
|
83 |
|
|
private String U = null; // client's username
|
84 |
|
|
private BigInteger N, g, A, B;
|
85 |
|
|
private byte[] s; // salt
|
86 |
|
|
private byte[] cIV, sIV; // client+server IVs, when confidentiality is on
|
87 |
|
|
private byte[] cn, sn; // client's and server's nonce
|
88 |
|
|
private SRP srp; // SRP algorithm instance used by this server
|
89 |
|
|
private byte[] sid; // session ID when re-used
|
90 |
|
|
private int ttl = 360; // session time-to-live in seconds
|
91 |
|
|
private byte[] cCB; // peer's channel binding'
|
92 |
|
|
private String mandatory; // List of available options
|
93 |
|
|
private String L = null;
|
94 |
|
|
private String o;
|
95 |
|
|
private String chosenIntegrityAlgorithm;
|
96 |
|
|
private String chosenConfidentialityAlgorithm;
|
97 |
|
|
private int rawSendSize = Registry.SASL_BUFFER_MAX_LIMIT;
|
98 |
|
|
private byte[] K; // shared session key
|
99 |
|
|
private boolean replayDetection = true; // whether Replay Detection is on
|
100 |
|
|
private int inCounter = 0; // messages sequence numbers
|
101 |
|
|
private int outCounter = 0;
|
102 |
|
|
private IALG inMac, outMac; // if !null, use for integrity
|
103 |
|
|
private CALG inCipher, outCipher; // if !null, use for confidentiality
|
104 |
|
|
private IKeyAgreementParty serverHandler =
|
105 |
|
|
KeyAgreementFactory.getPartyBInstance(Registry.SRP_SASL_KA);
|
106 |
|
|
/** Our default source of randomness. */
|
107 |
|
|
private PRNG prng = null;
|
108 |
|
|
|
109 |
|
|
public SRPServer()
|
110 |
|
|
{
|
111 |
|
|
super(Registry.SASL_SRP_MECHANISM);
|
112 |
|
|
}
|
113 |
|
|
|
114 |
|
|
protected void initMechanism() throws SaslException
|
115 |
|
|
{
|
116 |
|
|
// TODO:
|
117 |
|
|
// we must have a means to map a given username to a preferred
|
118 |
|
|
// SRP hash algorithm; otherwise we end up using _always_ SHA.
|
119 |
|
|
// for the time being get it from the mechanism properties map
|
120 |
|
|
// and apply it for all users.
|
121 |
|
|
final String mda = (String) properties.get(SRPRegistry.SRP_HASH);
|
122 |
|
|
srp = SRP.instance(mda == null ? SRPRegistry.SRP_DEFAULT_DIGEST_NAME : mda);
|
123 |
|
|
}
|
124 |
|
|
|
125 |
|
|
protected void resetMechanism() throws SaslException
|
126 |
|
|
{
|
127 |
|
|
s = null;
|
128 |
|
|
A = B = null;
|
129 |
|
|
K = null;
|
130 |
|
|
inMac = outMac = null;
|
131 |
|
|
inCipher = outCipher = null;
|
132 |
|
|
sid = null;
|
133 |
|
|
}
|
134 |
|
|
|
135 |
|
|
public byte[] evaluateResponse(final byte[] response) throws SaslException
|
136 |
|
|
{
|
137 |
|
|
switch (state)
|
138 |
|
|
{
|
139 |
|
|
case 0:
|
140 |
|
|
if (response == null)
|
141 |
|
|
return null;
|
142 |
|
|
state++;
|
143 |
|
|
return sendProtocolElements(response);
|
144 |
|
|
case 1:
|
145 |
|
|
if (! complete)
|
146 |
|
|
{
|
147 |
|
|
state++;
|
148 |
|
|
return sendEvidence(response);
|
149 |
|
|
}
|
150 |
|
|
// else fall through
|
151 |
|
|
default:
|
152 |
|
|
throw new IllegalMechanismStateException("evaluateResponse()");
|
153 |
|
|
}
|
154 |
|
|
}
|
155 |
|
|
|
156 |
|
|
protected byte[] engineUnwrap(final byte[] incoming, final int offset,
|
157 |
|
|
final int len) throws SaslException
|
158 |
|
|
{
|
159 |
|
|
if (Configuration.DEBUG)
|
160 |
|
|
log.entering(this.getClass().getName(), "engineUnwrap");
|
161 |
|
|
if (inMac == null && inCipher == null)
|
162 |
|
|
throw new IllegalStateException("connection is not protected");
|
163 |
|
|
if (Configuration.DEBUG)
|
164 |
|
|
log.fine("Incoming buffer (before security): "
|
165 |
|
|
+ Util.dumpString(incoming, offset, len));
|
166 |
|
|
// at this point one, or both, of confidentiality and integrity protection
|
167 |
|
|
// services are active.
|
168 |
|
|
final byte[] result;
|
169 |
|
|
try
|
170 |
|
|
{
|
171 |
|
|
if (inMac != null)
|
172 |
|
|
{ // integrity bytes are at the end of the stream
|
173 |
|
|
final int macBytesCount = inMac.length();
|
174 |
|
|
final int payloadLength = len - macBytesCount;
|
175 |
|
|
final byte[] received_mac = new byte[macBytesCount];
|
176 |
|
|
System.arraycopy(incoming, offset + payloadLength, received_mac, 0,
|
177 |
|
|
macBytesCount);
|
178 |
|
|
if (Configuration.DEBUG)
|
179 |
|
|
log.fine("Got C (received MAC): " + Util.dumpString(received_mac));
|
180 |
|
|
inMac.update(incoming, offset, payloadLength);
|
181 |
|
|
if (replayDetection)
|
182 |
|
|
{
|
183 |
|
|
inCounter++;
|
184 |
|
|
if (Configuration.DEBUG)
|
185 |
|
|
log.fine("inCounter=" + String.valueOf(inCounter));
|
186 |
|
|
inMac.update(new byte[] {
|
187 |
|
|
(byte)(inCounter >>> 24),
|
188 |
|
|
(byte)(inCounter >>> 16),
|
189 |
|
|
(byte)(inCounter >>> 8),
|
190 |
|
|
(byte) inCounter });
|
191 |
|
|
}
|
192 |
|
|
final byte[] computed_mac = inMac.doFinal();
|
193 |
|
|
if (Configuration.DEBUG)
|
194 |
|
|
log.fine("Computed MAC: " + Util.dumpString(computed_mac));
|
195 |
|
|
if (! Arrays.equals(received_mac, computed_mac))
|
196 |
|
|
throw new IntegrityException("engineUnwrap()");
|
197 |
|
|
// deal with the payload, which can be either plain or encrypted
|
198 |
|
|
if (inCipher != null)
|
199 |
|
|
result = inCipher.doFinal(incoming, offset, payloadLength);
|
200 |
|
|
else
|
201 |
|
|
{
|
202 |
|
|
result = new byte[payloadLength];
|
203 |
|
|
System.arraycopy(incoming, offset, result, 0, result.length);
|
204 |
|
|
}
|
205 |
|
|
}
|
206 |
|
|
else // no integrity protection; just confidentiality
|
207 |
|
|
result = inCipher.doFinal(incoming, offset, len);
|
208 |
|
|
}
|
209 |
|
|
catch (IOException x)
|
210 |
|
|
{
|
211 |
|
|
if (x instanceof SaslException)
|
212 |
|
|
throw (SaslException) x;
|
213 |
|
|
throw new SaslException("engineUnwrap()", x);
|
214 |
|
|
}
|
215 |
|
|
if (Configuration.DEBUG)
|
216 |
|
|
{
|
217 |
|
|
log.fine("Incoming buffer (after security): " + Util.dumpString(result));
|
218 |
|
|
log.exiting(this.getClass().getName(), "engineUnwrap");
|
219 |
|
|
}
|
220 |
|
|
return result;
|
221 |
|
|
}
|
222 |
|
|
|
223 |
|
|
protected byte[] engineWrap(final byte[] outgoing, final int offset,
|
224 |
|
|
final int len) throws SaslException
|
225 |
|
|
{
|
226 |
|
|
if (Configuration.DEBUG)
|
227 |
|
|
log.entering(this.getClass().getName(), "engineWrap");
|
228 |
|
|
if (outMac == null && outCipher == null)
|
229 |
|
|
throw new IllegalStateException("connection is not protected");
|
230 |
|
|
if (Configuration.DEBUG)
|
231 |
|
|
{
|
232 |
|
|
log.fine("Outgoing buffer (before security) (hex): "
|
233 |
|
|
+ Util.dumpString(outgoing, offset, len));
|
234 |
|
|
log.fine("Outgoing buffer (before security) (str): \""
|
235 |
|
|
+ new String(outgoing, offset, len) + "\"");
|
236 |
|
|
}
|
237 |
|
|
// at this point one, or both, of confidentiality and integrity protection
|
238 |
|
|
// services are active.
|
239 |
|
|
byte[] result;
|
240 |
|
|
try
|
241 |
|
|
{
|
242 |
|
|
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
243 |
|
|
if (outCipher != null)
|
244 |
|
|
{
|
245 |
|
|
result = outCipher.doFinal(outgoing, offset, len);
|
246 |
|
|
if (Configuration.DEBUG)
|
247 |
|
|
log.fine("Encoding c (encrypted plaintext): "
|
248 |
|
|
+ Util.dumpString(result));
|
249 |
|
|
out.write(result);
|
250 |
|
|
if (outMac != null)
|
251 |
|
|
{
|
252 |
|
|
outMac.update(result);
|
253 |
|
|
if (replayDetection)
|
254 |
|
|
{
|
255 |
|
|
outCounter++;
|
256 |
|
|
if (Configuration.DEBUG)
|
257 |
|
|
log.fine("outCounter=" + outCounter);
|
258 |
|
|
outMac.update(new byte[] {
|
259 |
|
|
(byte)(outCounter >>> 24),
|
260 |
|
|
(byte)(outCounter >>> 16),
|
261 |
|
|
(byte)(outCounter >>> 8),
|
262 |
|
|
(byte) outCounter });
|
263 |
|
|
}
|
264 |
|
|
final byte[] C = outMac.doFinal();
|
265 |
|
|
out.write(C);
|
266 |
|
|
if (Configuration.DEBUG)
|
267 |
|
|
log.fine("Encoding C (integrity checksum): " + Util.dumpString(C));
|
268 |
|
|
}
|
269 |
|
|
// else ciphertext only; do nothing
|
270 |
|
|
}
|
271 |
|
|
else // no confidentiality; just integrity [+ replay detection]
|
272 |
|
|
{
|
273 |
|
|
if (Configuration.DEBUG)
|
274 |
|
|
log.fine("Encoding p (plaintext): "
|
275 |
|
|
+ Util.dumpString(outgoing, offset, len));
|
276 |
|
|
out.write(outgoing, offset, len);
|
277 |
|
|
outMac.update(outgoing, offset, len);
|
278 |
|
|
if (replayDetection)
|
279 |
|
|
{
|
280 |
|
|
outCounter++;
|
281 |
|
|
if (Configuration.DEBUG)
|
282 |
|
|
log.fine("outCounter=" + outCounter);
|
283 |
|
|
outMac.update(new byte[] {
|
284 |
|
|
(byte)(outCounter >>> 24),
|
285 |
|
|
(byte)(outCounter >>> 16),
|
286 |
|
|
(byte)(outCounter >>> 8),
|
287 |
|
|
(byte) outCounter });
|
288 |
|
|
}
|
289 |
|
|
final byte[] C = outMac.doFinal();
|
290 |
|
|
out.write(C);
|
291 |
|
|
if (Configuration.DEBUG)
|
292 |
|
|
log.fine("Encoding C (integrity checksum): " + Util.dumpString(C));
|
293 |
|
|
}
|
294 |
|
|
result = out.toByteArray();
|
295 |
|
|
}
|
296 |
|
|
catch (IOException x)
|
297 |
|
|
{
|
298 |
|
|
if (x instanceof SaslException)
|
299 |
|
|
throw (SaslException) x;
|
300 |
|
|
throw new SaslException("engineWrap()", x);
|
301 |
|
|
}
|
302 |
|
|
if (Configuration.DEBUG)
|
303 |
|
|
log.exiting(this.getClass().getName(), "engineWrap");
|
304 |
|
|
return result;
|
305 |
|
|
}
|
306 |
|
|
|
307 |
|
|
protected String getNegotiatedQOP()
|
308 |
|
|
{
|
309 |
|
|
if (inMac != null)
|
310 |
|
|
{
|
311 |
|
|
if (inCipher != null)
|
312 |
|
|
return Registry.QOP_AUTH_CONF;
|
313 |
|
|
return Registry.QOP_AUTH_INT;
|
314 |
|
|
}
|
315 |
|
|
return Registry.QOP_AUTH;
|
316 |
|
|
}
|
317 |
|
|
|
318 |
|
|
protected String getNegotiatedStrength()
|
319 |
|
|
{
|
320 |
|
|
if (inMac != null)
|
321 |
|
|
{
|
322 |
|
|
if (inCipher != null)
|
323 |
|
|
return Registry.STRENGTH_HIGH;
|
324 |
|
|
return Registry.STRENGTH_MEDIUM;
|
325 |
|
|
}
|
326 |
|
|
return Registry.STRENGTH_LOW;
|
327 |
|
|
}
|
328 |
|
|
|
329 |
|
|
protected String getNegotiatedRawSendSize()
|
330 |
|
|
{
|
331 |
|
|
return String.valueOf(rawSendSize);
|
332 |
|
|
}
|
333 |
|
|
|
334 |
|
|
protected String getReuse()
|
335 |
|
|
{
|
336 |
|
|
return Registry.REUSE_TRUE;
|
337 |
|
|
}
|
338 |
|
|
|
339 |
|
|
private byte[] sendProtocolElements(final byte[] input) throws SaslException
|
340 |
|
|
{
|
341 |
|
|
if (Configuration.DEBUG)
|
342 |
|
|
{
|
343 |
|
|
log.entering(this.getClass().getName(), "sendProtocolElements");
|
344 |
|
|
log.fine("C: " + Util.dumpString(input));
|
345 |
|
|
}
|
346 |
|
|
// Client send U, I, sid, cn
|
347 |
|
|
final InputBuffer frameIn = new InputBuffer(input);
|
348 |
|
|
try
|
349 |
|
|
{
|
350 |
|
|
U = frameIn.getText(); // Extract username
|
351 |
|
|
if (Configuration.DEBUG)
|
352 |
|
|
log.fine("Got U (username): \"" + U + "\"");
|
353 |
|
|
authorizationID = frameIn.getText(); // Extract authorisation ID
|
354 |
|
|
if (Configuration.DEBUG)
|
355 |
|
|
log.fine("Got I (userid): \"" + authorizationID + "\"");
|
356 |
|
|
sid = frameIn.getEOS();
|
357 |
|
|
if (Configuration.DEBUG)
|
358 |
|
|
log.fine("Got sid (session ID): " + new String(sid));
|
359 |
|
|
cn = frameIn.getOS();
|
360 |
|
|
if (Configuration.DEBUG)
|
361 |
|
|
log.fine("Got cn (client nonce): " + Util.dumpString(cn));
|
362 |
|
|
cCB = frameIn.getEOS();
|
363 |
|
|
if (Configuration.DEBUG)
|
364 |
|
|
log.fine("Got cCB (client channel binding): " + Util.dumpString(cCB));
|
365 |
|
|
}
|
366 |
|
|
catch (IOException x)
|
367 |
|
|
{
|
368 |
|
|
if (x instanceof SaslException)
|
369 |
|
|
throw (SaslException) x;
|
370 |
|
|
throw new AuthenticationException("sendProtocolElements()", x);
|
371 |
|
|
}
|
372 |
|
|
// do/can we re-use?
|
373 |
|
|
if (ServerStore.instance().isAlive(sid))
|
374 |
|
|
{
|
375 |
|
|
final SecurityContext ctx = ServerStore.instance().restoreSession(sid);
|
376 |
|
|
srp = SRP.instance(ctx.getMdName());
|
377 |
|
|
K = ctx.getK();
|
378 |
|
|
cIV = ctx.getClientIV();
|
379 |
|
|
sIV = ctx.getServerIV();
|
380 |
|
|
replayDetection = ctx.hasReplayDetection();
|
381 |
|
|
inCounter = ctx.getInCounter();
|
382 |
|
|
outCounter = ctx.getOutCounter();
|
383 |
|
|
inMac = ctx.getInMac();
|
384 |
|
|
outMac = ctx.getOutMac();
|
385 |
|
|
inCipher = ctx.getInCipher();
|
386 |
|
|
outCipher = ctx.getOutCipher();
|
387 |
|
|
if (sn == null || sn.length != 16)
|
388 |
|
|
sn = new byte[16];
|
389 |
|
|
getDefaultPRNG().nextBytes(sn);
|
390 |
|
|
setupSecurityServices(false);
|
391 |
|
|
final OutputBuffer frameOut = new OutputBuffer();
|
392 |
|
|
try
|
393 |
|
|
{
|
394 |
|
|
frameOut.setScalar(1, 0xFF);
|
395 |
|
|
frameOut.setOS(sn);
|
396 |
|
|
frameOut.setEOS(channelBinding);
|
397 |
|
|
}
|
398 |
|
|
catch (IOException x)
|
399 |
|
|
{
|
400 |
|
|
if (x instanceof SaslException)
|
401 |
|
|
throw (SaslException) x;
|
402 |
|
|
throw new AuthenticationException("sendProtocolElements()", x);
|
403 |
|
|
}
|
404 |
|
|
final byte[] result = frameOut.encode();
|
405 |
|
|
if (Configuration.DEBUG)
|
406 |
|
|
{
|
407 |
|
|
log.fine("Old session...");
|
408 |
|
|
log.fine("S: " + Util.dumpString(result));
|
409 |
|
|
log.fine(" sn = " + Util.dumpString(sn));
|
410 |
|
|
log.fine(" sCB = " + Util.dumpString(channelBinding));
|
411 |
|
|
log.exiting(this.getClass().getName(), "sendProtocolElements");
|
412 |
|
|
}
|
413 |
|
|
return result;
|
414 |
|
|
}
|
415 |
|
|
else
|
416 |
|
|
{ // new session
|
417 |
|
|
authenticator.activate(properties);
|
418 |
|
|
// -------------------------------------------------------------------
|
419 |
|
|
final HashMap mapB = new HashMap();
|
420 |
|
|
mapB.put(SRP6KeyAgreement.HASH_FUNCTION, srp.getAlgorithm());
|
421 |
|
|
mapB.put(SRP6KeyAgreement.HOST_PASSWORD_DB, authenticator);
|
422 |
|
|
try
|
423 |
|
|
{
|
424 |
|
|
serverHandler.init(mapB);
|
425 |
|
|
OutgoingMessage out = new OutgoingMessage();
|
426 |
|
|
out.writeString(U);
|
427 |
|
|
IncomingMessage in = new IncomingMessage(out.toByteArray());
|
428 |
|
|
out = serverHandler.processMessage(in);
|
429 |
|
|
in = new IncomingMessage(out.toByteArray());
|
430 |
|
|
N = in.readMPI();
|
431 |
|
|
g = in.readMPI();
|
432 |
|
|
s = in.readMPI().toByteArray();
|
433 |
|
|
B = in.readMPI();
|
434 |
|
|
}
|
435 |
|
|
catch (KeyAgreementException x)
|
436 |
|
|
{
|
437 |
|
|
throw new SaslException("sendProtocolElements()", x);
|
438 |
|
|
}
|
439 |
|
|
// -------------------------------------------------------------------
|
440 |
|
|
if (Configuration.DEBUG)
|
441 |
|
|
{
|
442 |
|
|
log.fine("Encoding N (modulus): " + Util.dump(N));
|
443 |
|
|
log.fine("Encoding g (generator): " + Util.dump(g));
|
444 |
|
|
log.fine("Encoding s (client's salt): " + Util.dumpString(s));
|
445 |
|
|
log.fine("Encoding B (server ephemeral public key): " + Util.dump(B));
|
446 |
|
|
}
|
447 |
|
|
// The server creates an options list (L), which consists of a
|
448 |
|
|
// comma-separated list of option strings that specify the security
|
449 |
|
|
// service options the server supports.
|
450 |
|
|
L = createL();
|
451 |
|
|
if (Configuration.DEBUG)
|
452 |
|
|
{
|
453 |
|
|
log.fine("Encoding L (available options): \"" + L + "\"");
|
454 |
|
|
log.fine("Encoding sIV (server IV): " + Util.dumpString(sIV));
|
455 |
|
|
}
|
456 |
|
|
final OutputBuffer frameOut = new OutputBuffer();
|
457 |
|
|
try
|
458 |
|
|
{
|
459 |
|
|
frameOut.setScalar(1, 0x00);
|
460 |
|
|
frameOut.setMPI(N);
|
461 |
|
|
frameOut.setMPI(g);
|
462 |
|
|
frameOut.setOS(s);
|
463 |
|
|
frameOut.setMPI(B);
|
464 |
|
|
frameOut.setText(L);
|
465 |
|
|
}
|
466 |
|
|
catch (IOException x)
|
467 |
|
|
{
|
468 |
|
|
if (x instanceof SaslException)
|
469 |
|
|
throw (SaslException) x;
|
470 |
|
|
throw new AuthenticationException("sendProtocolElements()", x);
|
471 |
|
|
}
|
472 |
|
|
final byte[] result = frameOut.encode();
|
473 |
|
|
if (Configuration.DEBUG)
|
474 |
|
|
{
|
475 |
|
|
log.fine("New session...");
|
476 |
|
|
log.fine("S: " + Util.dumpString(result));
|
477 |
|
|
log.fine(" N = 0x" + N.toString(16));
|
478 |
|
|
log.fine(" g = 0x" + g.toString(16));
|
479 |
|
|
log.fine(" s = " + Util.dumpString(s));
|
480 |
|
|
log.fine(" B = 0x" + B.toString(16));
|
481 |
|
|
log.fine(" L = " + L);
|
482 |
|
|
log.exiting(this.getClass().getName(), "sendProtocolElements");
|
483 |
|
|
}
|
484 |
|
|
return result;
|
485 |
|
|
}
|
486 |
|
|
}
|
487 |
|
|
|
488 |
|
|
private byte[] sendEvidence(final byte[] input) throws SaslException
|
489 |
|
|
{
|
490 |
|
|
if (Configuration.DEBUG)
|
491 |
|
|
{
|
492 |
|
|
log.entering(this.getClass().getName(), "sendEvidence");
|
493 |
|
|
log.fine("C: " + Util.dumpString(input));
|
494 |
|
|
}
|
495 |
|
|
// Client send A, M1, o, cIV
|
496 |
|
|
final InputBuffer frameIn = new InputBuffer(input);
|
497 |
|
|
final byte[] M1;
|
498 |
|
|
try
|
499 |
|
|
{
|
500 |
|
|
A = frameIn.getMPI(); // Extract client's ephemeral public key
|
501 |
|
|
if (Configuration.DEBUG)
|
502 |
|
|
log.fine("Got A (client ephemeral public key): " + Util.dump(A));
|
503 |
|
|
M1 = frameIn.getOS(); // Extract evidence
|
504 |
|
|
if (Configuration.DEBUG)
|
505 |
|
|
log.fine("Got M1 (client evidence): " + Util.dumpString(M1));
|
506 |
|
|
o = frameIn.getText(); // Extract client's options list
|
507 |
|
|
if (Configuration.DEBUG)
|
508 |
|
|
log.fine("Got o (client chosen options): \"" + o + "\"");
|
509 |
|
|
cIV = frameIn.getOS(); // Extract client's IV
|
510 |
|
|
if (Configuration.DEBUG)
|
511 |
|
|
log.fine("Got cIV (client IV): " + Util.dumpString(cIV));
|
512 |
|
|
}
|
513 |
|
|
catch (IOException x)
|
514 |
|
|
{
|
515 |
|
|
if (x instanceof SaslException)
|
516 |
|
|
throw (SaslException) x;
|
517 |
|
|
throw new AuthenticationException("sendEvidence()", x);
|
518 |
|
|
}
|
519 |
|
|
// Parse client's options and set security layer variables
|
520 |
|
|
parseO(o);
|
521 |
|
|
// ----------------------------------------------------------------------
|
522 |
|
|
try
|
523 |
|
|
{
|
524 |
|
|
final OutgoingMessage out = new OutgoingMessage();
|
525 |
|
|
out.writeMPI(A);
|
526 |
|
|
final IncomingMessage in = new IncomingMessage(out.toByteArray());
|
527 |
|
|
serverHandler.processMessage(in);
|
528 |
|
|
K = serverHandler.getSharedSecret();
|
529 |
|
|
}
|
530 |
|
|
catch (KeyAgreementException x)
|
531 |
|
|
{
|
532 |
|
|
throw new SaslException("sendEvidence()", x);
|
533 |
|
|
}
|
534 |
|
|
// ----------------------------------------------------------------------
|
535 |
|
|
if (Configuration.DEBUG)
|
536 |
|
|
log.fine("K: " + Util.dumpString(K));
|
537 |
|
|
final byte[] expected;
|
538 |
|
|
try
|
539 |
|
|
{
|
540 |
|
|
expected = srp.generateM1(N, g, U, s, A, B, K, authorizationID, L, cn,
|
541 |
|
|
cCB);
|
542 |
|
|
}
|
543 |
|
|
catch (UnsupportedEncodingException x)
|
544 |
|
|
{
|
545 |
|
|
throw new AuthenticationException("sendEvidence()", x);
|
546 |
|
|
}
|
547 |
|
|
// Verify client evidence
|
548 |
|
|
if (! Arrays.equals(M1, expected))
|
549 |
|
|
throw new AuthenticationException("M1 mismatch");
|
550 |
|
|
setupSecurityServices(true);
|
551 |
|
|
final byte[] M2;
|
552 |
|
|
try
|
553 |
|
|
{
|
554 |
|
|
M2 = srp.generateM2(A, M1, K, U, authorizationID, o, sid, ttl, cIV,
|
555 |
|
|
sIV, channelBinding);
|
556 |
|
|
}
|
557 |
|
|
catch (UnsupportedEncodingException x)
|
558 |
|
|
{
|
559 |
|
|
throw new AuthenticationException("sendEvidence()", x);
|
560 |
|
|
}
|
561 |
|
|
final OutputBuffer frameOut = new OutputBuffer();
|
562 |
|
|
try
|
563 |
|
|
{
|
564 |
|
|
frameOut.setOS(M2);
|
565 |
|
|
frameOut.setOS(sIV);
|
566 |
|
|
frameOut.setEOS(sid);
|
567 |
|
|
frameOut.setScalar(4, ttl);
|
568 |
|
|
frameOut.setEOS(channelBinding);
|
569 |
|
|
}
|
570 |
|
|
catch (IOException x)
|
571 |
|
|
{
|
572 |
|
|
if (x instanceof SaslException)
|
573 |
|
|
throw (SaslException) x;
|
574 |
|
|
throw new AuthenticationException("sendEvidence()", x);
|
575 |
|
|
}
|
576 |
|
|
final byte[] result = frameOut.encode();
|
577 |
|
|
if (Configuration.DEBUG)
|
578 |
|
|
{
|
579 |
|
|
log.fine("S: " + Util.dumpString(result));
|
580 |
|
|
log.fine(" M2 = " + Util.dumpString(M2));
|
581 |
|
|
log.fine(" sIV = " + Util.dumpString(sIV));
|
582 |
|
|
log.fine(" sid = " + new String(sid));
|
583 |
|
|
log.fine(" ttl = " + ttl);
|
584 |
|
|
log.fine(" sCB = " + Util.dumpString(channelBinding));
|
585 |
|
|
log.exiting(this.getClass().getName(), "sendEvidence");
|
586 |
|
|
}
|
587 |
|
|
return result;
|
588 |
|
|
}
|
589 |
|
|
|
590 |
|
|
private String createL()
|
591 |
|
|
{
|
592 |
|
|
if (Configuration.DEBUG)
|
593 |
|
|
log.entering(this.getClass().getName(), "createL()");
|
594 |
|
|
String s = (String) properties.get(SRPRegistry.SRP_MANDATORY);
|
595 |
|
|
if (s == null)
|
596 |
|
|
s = SRPRegistry.DEFAULT_MANDATORY;
|
597 |
|
|
|
598 |
|
|
if (! SRPRegistry.MANDATORY_NONE.equals(s)
|
599 |
|
|
&& ! SRPRegistry.OPTION_REPLAY_DETECTION.equals(s)
|
600 |
|
|
&& ! SRPRegistry.OPTION_INTEGRITY.equals(s)
|
601 |
|
|
&& ! SRPRegistry.OPTION_CONFIDENTIALITY.equals(s))
|
602 |
|
|
{
|
603 |
|
|
if (Configuration.DEBUG)
|
604 |
|
|
log.fine("Unrecognised mandatory option (" + s + "). Using default...");
|
605 |
|
|
s = SRPRegistry.DEFAULT_MANDATORY;
|
606 |
|
|
}
|
607 |
|
|
mandatory = s;
|
608 |
|
|
s = (String) properties.get(SRPRegistry.SRP_CONFIDENTIALITY);
|
609 |
|
|
final boolean confidentiality = (s == null ? SRPRegistry.DEFAULT_CONFIDENTIALITY
|
610 |
|
|
: Boolean.valueOf(s).booleanValue());
|
611 |
|
|
s = (String) properties.get(SRPRegistry.SRP_INTEGRITY_PROTECTION);
|
612 |
|
|
boolean integrity = (s == null ? SRPRegistry.DEFAULT_INTEGRITY
|
613 |
|
|
: Boolean.valueOf(s).booleanValue());
|
614 |
|
|
s = (String) properties.get(SRPRegistry.SRP_REPLAY_DETECTION);
|
615 |
|
|
final boolean replayDetection = (s == null ? SRPRegistry.DEFAULT_REPLAY_DETECTION
|
616 |
|
|
: Boolean.valueOf(s).booleanValue());
|
617 |
|
|
final CPStringBuilder sb = new CPStringBuilder();
|
618 |
|
|
sb.append(SRPRegistry.OPTION_SRP_DIGEST).append("=")
|
619 |
|
|
.append(srp.getAlgorithm()).append(",");
|
620 |
|
|
|
621 |
|
|
if (! SRPRegistry.MANDATORY_NONE.equals(mandatory))
|
622 |
|
|
sb.append(SRPRegistry.OPTION_MANDATORY)
|
623 |
|
|
.append("=").append(mandatory).append(",");
|
624 |
|
|
|
625 |
|
|
if (replayDetection)
|
626 |
|
|
{
|
627 |
|
|
sb.append(SRPRegistry.OPTION_REPLAY_DETECTION).append(",");
|
628 |
|
|
// if replay detection is on then force integrity protection
|
629 |
|
|
integrity = true;
|
630 |
|
|
}
|
631 |
|
|
int i;
|
632 |
|
|
if (integrity)
|
633 |
|
|
{
|
634 |
|
|
for (i = 0; i < SRPRegistry.INTEGRITY_ALGORITHMS.length; i++)
|
635 |
|
|
sb.append(SRPRegistry.OPTION_INTEGRITY).append("=")
|
636 |
|
|
.append(SRPRegistry.INTEGRITY_ALGORITHMS[i]).append(",");
|
637 |
|
|
}
|
638 |
|
|
if (confidentiality)
|
639 |
|
|
{
|
640 |
|
|
IBlockCipher cipher;
|
641 |
|
|
for (i = 0; i < SRPRegistry.CONFIDENTIALITY_ALGORITHMS.length; i++)
|
642 |
|
|
{
|
643 |
|
|
cipher = CipherFactory.getInstance(SRPRegistry.CONFIDENTIALITY_ALGORITHMS[i]);
|
644 |
|
|
if (cipher != null)
|
645 |
|
|
sb.append(SRPRegistry.OPTION_CONFIDENTIALITY).append("=")
|
646 |
|
|
.append(SRPRegistry.CONFIDENTIALITY_ALGORITHMS[i]).append(",");
|
647 |
|
|
}
|
648 |
|
|
}
|
649 |
|
|
final String result = sb.append(SRPRegistry.OPTION_MAX_BUFFER_SIZE)
|
650 |
|
|
.append("=").append(Registry.SASL_BUFFER_MAX_LIMIT)
|
651 |
|
|
.toString();
|
652 |
|
|
if (Configuration.DEBUG)
|
653 |
|
|
log.exiting(this.getClass().getName(), "createL");
|
654 |
|
|
return result;
|
655 |
|
|
}
|
656 |
|
|
|
657 |
|
|
// Parse client's options and set security layer variables
|
658 |
|
|
private void parseO(final String o) throws AuthenticationException
|
659 |
|
|
{
|
660 |
|
|
this.replayDetection = false;
|
661 |
|
|
boolean integrity = false;
|
662 |
|
|
boolean confidentiality = false;
|
663 |
|
|
String option;
|
664 |
|
|
int i;
|
665 |
|
|
|
666 |
|
|
final StringTokenizer st = new StringTokenizer(o.toLowerCase(), ",");
|
667 |
|
|
while (st.hasMoreTokens())
|
668 |
|
|
{
|
669 |
|
|
option = st.nextToken();
|
670 |
|
|
if (Configuration.DEBUG)
|
671 |
|
|
log.fine("option: <" + option + ">");
|
672 |
|
|
if (option.equals(SRPRegistry.OPTION_REPLAY_DETECTION))
|
673 |
|
|
replayDetection = true;
|
674 |
|
|
else if (option.startsWith(SRPRegistry.OPTION_INTEGRITY + "="))
|
675 |
|
|
{
|
676 |
|
|
if (integrity)
|
677 |
|
|
throw new AuthenticationException(
|
678 |
|
|
"Only one integrity algorithm may be chosen");
|
679 |
|
|
option = option.substring(option.indexOf('=') + 1);
|
680 |
|
|
if (Configuration.DEBUG)
|
681 |
|
|
log.fine("algorithm: <" + option + ">");
|
682 |
|
|
for (i = 0; i < SRPRegistry.INTEGRITY_ALGORITHMS.length; i++)
|
683 |
|
|
{
|
684 |
|
|
if (SRPRegistry.INTEGRITY_ALGORITHMS[i].equals(option))
|
685 |
|
|
{
|
686 |
|
|
chosenIntegrityAlgorithm = option;
|
687 |
|
|
integrity = true;
|
688 |
|
|
break;
|
689 |
|
|
}
|
690 |
|
|
}
|
691 |
|
|
if (! integrity)
|
692 |
|
|
throw new AuthenticationException("Unknown integrity algorithm: "
|
693 |
|
|
+ option);
|
694 |
|
|
}
|
695 |
|
|
else if (option.startsWith(SRPRegistry.OPTION_CONFIDENTIALITY + "="))
|
696 |
|
|
{
|
697 |
|
|
if (confidentiality)
|
698 |
|
|
throw new AuthenticationException(
|
699 |
|
|
"Only one confidentiality algorithm may be chosen");
|
700 |
|
|
option = option.substring(option.indexOf('=') + 1);
|
701 |
|
|
if (Configuration.DEBUG)
|
702 |
|
|
log.fine("algorithm: <" + option + ">");
|
703 |
|
|
for (i = 0; i < SRPRegistry.CONFIDENTIALITY_ALGORITHMS.length; i++)
|
704 |
|
|
{
|
705 |
|
|
if (SRPRegistry.CONFIDENTIALITY_ALGORITHMS[i].equals(option))
|
706 |
|
|
{
|
707 |
|
|
chosenConfidentialityAlgorithm = option;
|
708 |
|
|
confidentiality = true;
|
709 |
|
|
break;
|
710 |
|
|
}
|
711 |
|
|
}
|
712 |
|
|
if (! confidentiality)
|
713 |
|
|
throw new AuthenticationException("Unknown confidentiality algorithm: "
|
714 |
|
|
+ option);
|
715 |
|
|
}
|
716 |
|
|
else if (option.startsWith(SRPRegistry.OPTION_MAX_BUFFER_SIZE + "="))
|
717 |
|
|
{
|
718 |
|
|
final String maxBufferSize = option.substring(option.indexOf('=') + 1);
|
719 |
|
|
try
|
720 |
|
|
{
|
721 |
|
|
rawSendSize = Integer.parseInt(maxBufferSize);
|
722 |
|
|
if (rawSendSize > Registry.SASL_BUFFER_MAX_LIMIT
|
723 |
|
|
|| rawSendSize < 1)
|
724 |
|
|
throw new AuthenticationException(
|
725 |
|
|
"Illegal value for 'maxbuffersize' option");
|
726 |
|
|
}
|
727 |
|
|
catch (NumberFormatException x)
|
728 |
|
|
{
|
729 |
|
|
throw new AuthenticationException(
|
730 |
|
|
SRPRegistry.OPTION_MAX_BUFFER_SIZE + "=" + maxBufferSize, x);
|
731 |
|
|
}
|
732 |
|
|
}
|
733 |
|
|
}
|
734 |
|
|
// check if client did the right thing
|
735 |
|
|
if (replayDetection)
|
736 |
|
|
{
|
737 |
|
|
if (! integrity)
|
738 |
|
|
throw new AuthenticationException(
|
739 |
|
|
"Missing integrity protection algorithm but replay detection is chosen");
|
740 |
|
|
}
|
741 |
|
|
if (mandatory.equals(SRPRegistry.OPTION_REPLAY_DETECTION))
|
742 |
|
|
{
|
743 |
|
|
if (! replayDetection)
|
744 |
|
|
throw new AuthenticationException(
|
745 |
|
|
"Replay detection is mandatory but was not chosen");
|
746 |
|
|
}
|
747 |
|
|
if (mandatory.equals(SRPRegistry.OPTION_INTEGRITY))
|
748 |
|
|
{
|
749 |
|
|
if (! integrity)
|
750 |
|
|
throw new AuthenticationException(
|
751 |
|
|
"Integrity protection is mandatory but was not chosen");
|
752 |
|
|
}
|
753 |
|
|
if (mandatory.equals(SRPRegistry.OPTION_CONFIDENTIALITY))
|
754 |
|
|
{
|
755 |
|
|
if (! confidentiality)
|
756 |
|
|
throw new AuthenticationException(
|
757 |
|
|
"Confidentiality is mandatory but was not chosen");
|
758 |
|
|
}
|
759 |
|
|
int blockSize = 0;
|
760 |
|
|
if (chosenConfidentialityAlgorithm != null)
|
761 |
|
|
{
|
762 |
|
|
final IBlockCipher cipher = CipherFactory.getInstance(chosenConfidentialityAlgorithm);
|
763 |
|
|
if (cipher != null)
|
764 |
|
|
blockSize = cipher.defaultBlockSize();
|
765 |
|
|
else // should not happen
|
766 |
|
|
throw new AuthenticationException("Confidentiality algorithm ("
|
767 |
|
|
+ chosenConfidentialityAlgorithm
|
768 |
|
|
+ ") not available");
|
769 |
|
|
}
|
770 |
|
|
sIV = new byte[blockSize];
|
771 |
|
|
if (blockSize > 0)
|
772 |
|
|
getDefaultPRNG().nextBytes(sIV);
|
773 |
|
|
}
|
774 |
|
|
|
775 |
|
|
private void setupSecurityServices(final boolean newSession)
|
776 |
|
|
throws SaslException
|
777 |
|
|
{
|
778 |
|
|
complete = true; // signal end of authentication phase
|
779 |
|
|
if (newSession)
|
780 |
|
|
{
|
781 |
|
|
outCounter = inCounter = 0;
|
782 |
|
|
// instantiate cipher if confidentiality protection filter is active
|
783 |
|
|
if (chosenConfidentialityAlgorithm != null)
|
784 |
|
|
{
|
785 |
|
|
if (Configuration.DEBUG)
|
786 |
|
|
log.fine("Activating confidentiality protection filter");
|
787 |
|
|
inCipher = CALG.getInstance(chosenConfidentialityAlgorithm);
|
788 |
|
|
outCipher = CALG.getInstance(chosenConfidentialityAlgorithm);
|
789 |
|
|
}
|
790 |
|
|
// instantiate hmacs if integrity protection filter is active
|
791 |
|
|
if (chosenIntegrityAlgorithm != null)
|
792 |
|
|
{
|
793 |
|
|
if (Configuration.DEBUG)
|
794 |
|
|
log.fine("Activating integrity protection filter");
|
795 |
|
|
inMac = IALG.getInstance(chosenIntegrityAlgorithm);
|
796 |
|
|
outMac = IALG.getInstance(chosenIntegrityAlgorithm);
|
797 |
|
|
}
|
798 |
|
|
// generate a new sid if at least integrity is used
|
799 |
|
|
sid = (inMac != null ? ServerStore.getNewSessionID() : new byte[0]);
|
800 |
|
|
}
|
801 |
|
|
else // same session new keys
|
802 |
|
|
K = srp.generateKn(K, cn, sn);
|
803 |
|
|
|
804 |
|
|
final KDF kdf = KDF.getInstance(K);
|
805 |
|
|
// initialise in/out ciphers if confidentaility protection is used
|
806 |
|
|
if (inCipher != null)
|
807 |
|
|
{
|
808 |
|
|
outCipher.init(kdf, sIV, Direction.FORWARD);
|
809 |
|
|
inCipher.init(kdf, cIV, Direction.REVERSED);
|
810 |
|
|
}
|
811 |
|
|
// initialise in/out macs if integrity protection is used
|
812 |
|
|
if (inMac != null)
|
813 |
|
|
{
|
814 |
|
|
outMac.init(kdf);
|
815 |
|
|
inMac.init(kdf);
|
816 |
|
|
}
|
817 |
|
|
if (sid != null && sid.length != 0)
|
818 |
|
|
{ // update the security context and save in map
|
819 |
|
|
if (Configuration.DEBUG)
|
820 |
|
|
log.fine("Updating security context for sid = " + new String(sid));
|
821 |
|
|
ServerStore.instance().cacheSession(ttl,
|
822 |
|
|
new SecurityContext(srp.getAlgorithm(),
|
823 |
|
|
sid,
|
824 |
|
|
K,
|
825 |
|
|
cIV,
|
826 |
|
|
sIV,
|
827 |
|
|
replayDetection,
|
828 |
|
|
inCounter,
|
829 |
|
|
outCounter,
|
830 |
|
|
inMac, outMac,
|
831 |
|
|
inCipher,
|
832 |
|
|
outCipher));
|
833 |
|
|
}
|
834 |
|
|
}
|
835 |
|
|
|
836 |
|
|
private PRNG getDefaultPRNG()
|
837 |
|
|
{
|
838 |
|
|
if (prng == null)
|
839 |
|
|
prng = PRNG.getInstance();
|
840 |
|
|
return prng;
|
841 |
|
|
}
|
842 |
|
|
}
|