OSDN Git Service

libjava/ChangeLog:
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / javax / crypto / sasl / srp / SRPClient.java
1 /* SRPClient.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.hash.MD5;
46 import gnu.java.security.util.PRNG;
47 import gnu.java.security.util.Util;
48 import gnu.javax.crypto.assembly.Direction;
49 import gnu.javax.crypto.cipher.CipherFactory;
50 import gnu.javax.crypto.cipher.IBlockCipher;
51 import gnu.javax.crypto.key.IKeyAgreementParty;
52 import gnu.javax.crypto.key.IncomingMessage;
53 import gnu.javax.crypto.key.KeyAgreementException;
54 import gnu.javax.crypto.key.KeyAgreementFactory;
55 import gnu.javax.crypto.key.OutgoingMessage;
56 import gnu.javax.crypto.key.srp6.SRP6KeyAgreement;
57 import gnu.javax.crypto.sasl.ClientMechanism;
58 import gnu.javax.crypto.sasl.IllegalMechanismStateException;
59 import gnu.javax.crypto.sasl.InputBuffer;
60 import gnu.javax.crypto.sasl.IntegrityException;
61 import gnu.javax.crypto.sasl.OutputBuffer;
62 import gnu.javax.security.auth.Password;
63
64 import java.io.ByteArrayOutputStream;
65 import java.io.IOException;
66 import java.io.UnsupportedEncodingException;
67 import java.math.BigInteger;
68 import java.security.NoSuchAlgorithmException;
69 import java.util.Arrays;
70 import java.util.HashMap;
71 import java.util.StringTokenizer;
72 import java.util.logging.Logger;
73
74 import javax.security.auth.DestroyFailedException;
75 import javax.security.auth.callback.Callback;
76 import javax.security.auth.callback.NameCallback;
77 import javax.security.auth.callback.PasswordCallback;
78 import javax.security.auth.callback.UnsupportedCallbackException;
79 import javax.security.sasl.AuthenticationException;
80 import javax.security.sasl.SaslClient;
81 import javax.security.sasl.SaslException;
82
83 /**
84  * The SASL-SRP client-side mechanism.
85  */
86 public class SRPClient
87     extends ClientMechanism
88     implements SaslClient
89 {
90   private static final Logger log = Logger.getLogger(SRPClient.class.getName());
91   private String uid; // the unique key for this type of client
92   private String U; // the authentication identity
93   BigInteger N, g, A, B;
94   private Password password; // the authentication credentials
95   private byte[] s; // the user's salt
96   private byte[] cIV, sIV; // client+server IVs, when confidentiality is on
97   private byte[] M1, M2; // client+server evidences
98   private byte[] cn, sn; // client's and server's nonce
99   private SRP srp; // SRP algorithm instance used by this client
100   private byte[] sid; // session ID when re-used
101   private int ttl; // session time-to-live in seconds
102   private byte[] sCB; // the peer's channel binding data
103   private String L; // available options
104   private String o;
105   private String chosenIntegrityAlgorithm;
106   private String chosenConfidentialityAlgorithm;
107   private int rawSendSize = Registry.SASL_BUFFER_MAX_LIMIT;
108   private byte[] K; // shared session key
109   private boolean replayDetection = true; // whether Replay Detection is on
110   private int inCounter = 0; // messages sequence numbers
111   private int outCounter = 0;
112   private IALG inMac, outMac; // if !null, use for integrity
113   private CALG inCipher, outCipher; // if !null, use for confidentiality
114   private IKeyAgreementParty clientHandler =
115       KeyAgreementFactory.getPartyAInstance(Registry.SRP_SASL_KA);
116   /** Our default source of randomness. */
117   private PRNG prng = null;
118
119   public SRPClient()
120   {
121     super(Registry.SASL_SRP_MECHANISM);
122   }
123
124   protected void initMechanism() throws SaslException
125   {
126     // we shall keep track of the sid (and the security context of this SRP
127     // client) based on the initialisation parameters of an SRP session.
128     // we shall compute a unique key for those parameters and key the sid
129     // (and the security context) accordingly.
130     // 1. compute the mapping key. use MD5 (the fastest) for this purpose
131     final MD5 md = new MD5();
132     byte[] b;
133     b = authorizationID.getBytes();
134     md.update(b, 0, b.length);
135     b = serverName.getBytes();
136     md.update(b, 0, b.length);
137     b = protocol.getBytes();
138     md.update(b, 0, b.length);
139     if (channelBinding.length > 0)
140       md.update(channelBinding, 0, channelBinding.length);
141
142     uid = Util.toBase64(md.digest());
143     if (ClientStore.instance().isAlive(uid))
144       {
145         final SecurityContext ctx = ClientStore.instance().restoreSession(uid);
146         srp = SRP.instance(ctx.getMdName());
147         sid = ctx.getSID();
148         K = ctx.getK();
149         cIV = ctx.getClientIV();
150         sIV = ctx.getServerIV();
151         replayDetection = ctx.hasReplayDetection();
152         inCounter = ctx.getInCounter();
153         outCounter = ctx.getOutCounter();
154         inMac = ctx.getInMac();
155         outMac = ctx.getOutMac();
156         inCipher = ctx.getInCipher();
157         outCipher = ctx.getOutCipher();
158       }
159     else
160       {
161         sid = new byte[0];
162         ttl = 0;
163         K = null;
164         cIV = null;
165         sIV = null;
166         cn = null;
167         sn = null;
168       }
169   }
170
171   protected void resetMechanism() throws SaslException
172   {
173     try
174       {
175         password.destroy();
176       }
177     catch (DestroyFailedException dfe)
178       {
179         SaslException se = new SaslException("resetMechanism()");
180         se.initCause(dfe);
181         throw se;
182       }
183     password = null;
184     M1 = null;
185     K = null;
186     cIV = null;
187     sIV = null;
188     inMac = outMac = null;
189     inCipher = outCipher = null;
190     sid = null;
191     ttl = 0;
192     cn = null;
193     sn = null;
194   }
195
196   public boolean hasInitialResponse()
197   {
198     return true;
199   }
200
201   public byte[] evaluateChallenge(final byte[] challenge) throws SaslException
202   {
203     switch (state)
204       {
205       case 0:
206         state++;
207         return sendIdentities();
208       case 1:
209         state++;
210         final byte[] result = sendPublicKey(challenge);
211         try
212           {
213             password.destroy(); //don't need further this session
214           }
215         catch (DestroyFailedException x)
216           {
217             SaslException se = new SaslException("sendPublicKey()");
218             se.initCause(se);
219             throw se;
220           }
221         return result;
222       case 2: // should only occur if session re-use was rejected
223         if (! complete)
224           {
225             state++;
226             return receiveEvidence(challenge);
227           }
228       // else fall through
229       default:
230         throw new IllegalMechanismStateException("evaluateChallenge()");
231       }
232   }
233
234   protected byte[] engineUnwrap(final byte[] incoming, final int offset,
235                                 final int len) throws SaslException
236   {
237     if (Configuration.DEBUG)
238       log.entering(this.getClass().getName(), "engineUnwrap");
239     if (inMac == null && inCipher == null)
240       throw new IllegalStateException("connection is not protected");
241     // at this point one, or both, of confidentiality and integrity protection
242     // services are active.
243     final byte[] result;
244     try
245       {
246         if (inMac != null)
247           { // integrity bytes are at the end of the stream
248             final int macBytesCount = inMac.length();
249             final int payloadLength = len - macBytesCount;
250             final byte[] received_mac = new byte[macBytesCount];
251             System.arraycopy(incoming, offset + payloadLength, received_mac, 0,
252                              macBytesCount);
253             if (Configuration.DEBUG)
254               log.fine("Got C (received MAC): " + Util.dumpString(received_mac));
255             inMac.update(incoming, offset, payloadLength);
256             if (replayDetection)
257               {
258                 inCounter++;
259                 if (Configuration.DEBUG)
260                   log.fine("inCounter=" + inCounter);
261                 inMac.update(new byte[] {
262                     (byte)(inCounter >>> 24),
263                     (byte)(inCounter >>> 16),
264                     (byte)(inCounter >>> 8),
265                     (byte) inCounter });
266               }
267             final byte[] computed_mac = inMac.doFinal();
268             if (Configuration.DEBUG)
269               log.fine("Computed MAC: " + Util.dumpString(computed_mac));
270             if (! Arrays.equals(received_mac, computed_mac))
271               throw new IntegrityException("engineUnwrap()");
272             // deal with the payload, which can be either plain or encrypted
273             if (inCipher != null)
274               result = inCipher.doFinal(incoming, offset, payloadLength);
275             else
276               {
277                 result = new byte[len - macBytesCount];
278                 System.arraycopy(incoming, offset, result, 0, result.length);
279               }
280           }
281         else // no integrity protection; just confidentiality
282           result = inCipher.doFinal(incoming, offset, len);
283       }
284     catch (IOException x)
285       {
286         if (x instanceof SaslException)
287           throw (SaslException) x;
288         throw new SaslException("engineUnwrap()", x);
289       }
290     if (Configuration.DEBUG)
291       log.exiting(this.getClass().getName(), "engineUnwrap");
292     return result;
293   }
294
295   protected byte[] engineWrap(final byte[] outgoing, final int offset,
296                               final int len) throws SaslException
297   {
298     if (Configuration.DEBUG)
299       log.entering(this.getClass().getName(), "engineWrap");
300     if (outMac == null && outCipher == null)
301       throw new IllegalStateException("connection is not protected");
302     // at this point one, or both, of confidentiality and integrity protection
303     // services are active.
304     byte[] result;
305     try
306       {
307         final ByteArrayOutputStream out = new ByteArrayOutputStream();
308         // Process the data
309         if (outCipher != null)
310           {
311             result = outCipher.doFinal(outgoing, offset, len);
312             if (Configuration.DEBUG)
313               log.fine("Encoding c (encrypted plaintext): "
314                        + Util.dumpString(result));
315             out.write(result);
316             if (outMac != null)
317               {
318                 outMac.update(result);
319                 if (replayDetection)
320                   {
321                     outCounter++;
322                     if (Configuration.DEBUG)
323                       log.fine("outCounter=" + outCounter);
324                     outMac.update(new byte[] {
325                         (byte)(outCounter >>> 24),
326                         (byte)(outCounter >>> 16),
327                         (byte)(outCounter >>> 8),
328                         (byte) outCounter });
329                   }
330                 final byte[] C = outMac.doFinal();
331                 out.write(C);
332                 if (Configuration.DEBUG)
333                   log.fine("Encoding C (integrity checksum): " + Util.dumpString(C));
334               }
335             // else confidentiality only; do nothing
336           }
337         else // no confidentiality; just integrity [+ replay detection]
338           {
339             if (Configuration.DEBUG)
340               log.fine("Encoding p (plaintext): "
341                        + Util.dumpString(outgoing, offset, len));
342             out.write(outgoing, offset, len);
343             outMac.update(outgoing, offset, len);
344             if (replayDetection)
345               {
346                 outCounter++;
347                 if (Configuration.DEBUG)
348                   log.fine("outCounter=" + outCounter);
349                 outMac.update(new byte[] {
350                     (byte)(outCounter >>> 24),
351                     (byte)(outCounter >>> 16),
352                     (byte)(outCounter >>> 8),
353                     (byte) outCounter });
354               }
355             final byte[] C = outMac.doFinal();
356             out.write(C);
357             if (Configuration.DEBUG)
358               log.fine("Encoding C (integrity checksum): " + Util.dumpString(C));
359           }
360         result = out.toByteArray();
361       }
362     catch (IOException x)
363       {
364         if (x instanceof SaslException)
365           throw (SaslException) x;
366         throw new SaslException("engineWrap()", x);
367       }
368     if (Configuration.DEBUG)
369       log.exiting(this.getClass().getName(), "engineWrap");
370     return result;
371   }
372
373   protected String getNegotiatedQOP()
374   {
375     if (inMac != null)
376       {
377         if (inCipher != null)
378           return Registry.QOP_AUTH_CONF;
379         return Registry.QOP_AUTH_INT;
380       }
381     return Registry.QOP_AUTH;
382   }
383
384   protected String getNegotiatedStrength()
385   {
386     if (inMac != null)
387       {
388         if (inCipher != null)
389           return Registry.STRENGTH_HIGH;
390         return Registry.STRENGTH_MEDIUM;
391       }
392     return Registry.STRENGTH_LOW;
393   }
394
395   protected String getNegotiatedRawSendSize()
396   {
397     return String.valueOf(rawSendSize);
398   }
399
400   protected String getReuse()
401   {
402     return Registry.REUSE_TRUE;
403   }
404
405   private byte[] sendIdentities() throws SaslException
406   {
407     if (Configuration.DEBUG)
408       log.entering(this.getClass().getName(), "sendIdentities");
409     // If necessary, prompt the client for the username and password
410     getUsernameAndPassword();
411     if (Configuration.DEBUG)
412       {
413         log.fine("Password: \"" + new String(password.getPassword()) + "\"");
414         log.fine("Encoding U (username): \"" + U + "\"");
415         log.fine("Encoding I (userid): \"" + authorizationID + "\"");
416       }
417     // if session re-use generate new 16-byte nonce
418     if (sid.length != 0)
419       {
420         cn = new byte[16];
421         getDefaultPRNG().nextBytes(cn);
422       }
423     else
424       cn = new byte[0];
425     final OutputBuffer frameOut = new OutputBuffer();
426     try
427       {
428         frameOut.setText(U);
429         frameOut.setText(authorizationID);
430         frameOut.setEOS(sid); // session ID to re-use
431         frameOut.setOS(cn); // client nonce
432         frameOut.setEOS(channelBinding);
433       }
434     catch (IOException x)
435       {
436         if (x instanceof SaslException)
437           throw (SaslException) x;
438         throw new AuthenticationException("sendIdentities()", x);
439       }
440     final byte[] result = frameOut.encode();
441     if (Configuration.DEBUG)
442       {
443         log.fine("C: " + Util.dumpString(result));
444         log.fine("  U = " + U);
445         log.fine("  I = " + authorizationID);
446         log.fine("sid = " + new String(sid));
447         log.fine(" cn = " + Util.dumpString(cn));
448         log.fine("cCB = " + Util.dumpString(channelBinding));
449         log.exiting(this.getClass().getName(), "sendIdentities");
450       }
451     return result;
452   }
453
454   private byte[] sendPublicKey(final byte[] input) throws SaslException
455   {
456     if (Configuration.DEBUG)
457       {
458         log.entering(this.getClass().getName(), "sendPublicKey");
459         log.fine("S: " + Util.dumpString(input));
460       }
461     // Server sends [00], N, g, s, B, L
462     // or [FF], sn, sCB
463     final InputBuffer frameIn = new InputBuffer(input);
464     final int ack;
465     try
466       {
467         ack = (int) frameIn.getScalar(1);
468         if (ack == 0x00) // new session
469           {
470             N = frameIn.getMPI();
471             if (Configuration.DEBUG)
472               log.fine("Got N (modulus): " + Util.dump(N));
473             g = frameIn.getMPI();
474             if (Configuration.DEBUG)
475               log.fine("Got g (generator): " + Util.dump(g));
476             s = frameIn.getOS();
477             if (Configuration.DEBUG)
478               log.fine("Got s (salt): " + Util.dumpString(s));
479             B = frameIn.getMPI();
480             if (Configuration.DEBUG)
481               log.fine("Got B (server ephermeral public key): " + Util.dump(B));
482             L = frameIn.getText();
483             if (Configuration.DEBUG)
484               log.fine("Got L (available options): \"" + L + "\"");
485           }
486         else if (ack == 0xFF) // session re-use
487           {
488             sn = frameIn.getOS();
489             if (Configuration.DEBUG)
490               log.fine("Got sn (server nonce): " + Util.dumpString(sn));
491             sCB = frameIn.getEOS();
492             if (Configuration.DEBUG)
493               log.fine("Got sCB (server channel binding): " + Util.dumpString(sCB));
494           }
495         else // unexpected scalar
496           throw new SaslException("sendPublicKey(): Invalid scalar (" + ack
497                                   + ") in server's request");
498       }
499     catch (IOException x)
500       {
501         if (x instanceof SaslException)
502           throw (SaslException) x;
503         throw new SaslException("sendPublicKey()", x);
504       }
505     if (ack == 0x00)
506       { // new session ---------------------------------------
507         o = createO(L.toLowerCase()); // do this first to initialise the SRP hash
508         final byte[] pBytes; // use ASCII encoding to inter-operate w/ non-java
509         pBytes = password.getBytes();
510         // ----------------------------------------------------------------------
511         final HashMap mapA = new HashMap();
512         mapA.put(SRP6KeyAgreement.HASH_FUNCTION, srp.getAlgorithm());
513         mapA.put(SRP6KeyAgreement.USER_IDENTITY, U);
514         mapA.put(SRP6KeyAgreement.USER_PASSWORD, pBytes);
515         try
516           {
517             clientHandler.init(mapA);
518             clientHandler.processMessage(null);
519           }
520         catch (KeyAgreementException x)
521           {
522             throw new SaslException("sendPublicKey()", x);
523           }
524         // -------------------------------------------------------------------
525         try
526           {
527             OutgoingMessage out = new OutgoingMessage();
528             out.writeMPI(N);
529             out.writeMPI(g);
530             out.writeMPI(new BigInteger(1, s));
531             out.writeMPI(B);
532             IncomingMessage in = new IncomingMessage(out.toByteArray());
533             out = clientHandler.processMessage(in);
534             in = new IncomingMessage(out.toByteArray());
535             A = in.readMPI();
536             K = clientHandler.getSharedSecret();
537           }
538         catch (KeyAgreementException x)
539           {
540             throw new SaslException("sendPublicKey()", x);
541           }
542         // -------------------------------------------------------------------
543         if (Configuration.DEBUG)
544           {
545             log.fine("K: " + Util.dumpString(K));
546             log.fine("Encoding A (client ephemeral public key): " + Util.dump(A));
547           }
548         try
549           {
550             M1 = srp.generateM1(N, g, U, s, A, B, K, authorizationID, L, cn,
551                                 channelBinding);
552           }
553         catch (UnsupportedEncodingException x)
554           {
555             throw new AuthenticationException("sendPublicKey()", x);
556           }
557         if (Configuration.DEBUG)
558           {
559             log.fine("Encoding o (client chosen options): \"" + o + "\"");
560             log.fine("Encoding cIV (client IV): \"" + Util.dumpString(cIV) + "\"");
561           }
562         final OutputBuffer frameOut = new OutputBuffer();
563         try
564           {
565             frameOut.setMPI(A);
566             frameOut.setOS(M1);
567             frameOut.setText(o);
568             frameOut.setOS(cIV);
569           }
570         catch (IOException x)
571           {
572             if (x instanceof SaslException)
573               throw (SaslException) x;
574             throw new AuthenticationException("sendPublicKey()", x);
575           }
576         final byte[] result = frameOut.encode();
577         if (Configuration.DEBUG)
578           {
579             log.fine("New session, or session re-use rejected...");
580             log.fine("C: " + Util.dumpString(result));
581             log.fine("  A = 0x" + A.toString(16));
582             log.fine(" M1 = " + Util.dumpString(M1));
583             log.fine("  o = " + o);
584             log.fine("cIV = " + Util.dumpString(cIV));
585             log.exiting(this.getClass().getName(), "sendPublicKey");
586           }
587         return result;
588       }
589     else // session re-use accepted -------------------------------------------
590       {
591         setupSecurityServices(true);
592         if (Configuration.DEBUG)
593           {
594             log.fine("Session re-use accepted...");
595             log.exiting(this.getClass().getName(), "sendPublicKey");
596           }
597         return null;
598       }
599   }
600
601   private byte[] receiveEvidence(byte[] input) throws SaslException
602   {
603     if (Configuration.DEBUG)
604       {
605         log.entering(this.getClass().getName(), "receiveEvidence");
606         log.fine("S: " + Util.dumpString(input));
607       }
608     // Server send M2, sIV, sCB, sid, ttl
609     final InputBuffer frameIn = new InputBuffer(input);
610     try
611       {
612         M2 = frameIn.getOS();
613         if (Configuration.DEBUG)
614           log.fine("Got M2 (server evidence): " + Util.dumpString(M2));
615         sIV = frameIn.getOS();
616         if (Configuration.DEBUG)
617           log.fine("Got sIV (server IV): " + Util.dumpString(sIV));
618         sid = frameIn.getEOS();
619         if (Configuration.DEBUG)
620           log.fine("Got sid (session ID): " + new String(sid));
621         ttl = (int) frameIn.getScalar(4);
622         if (Configuration.DEBUG)
623           log.fine("Got ttl (session time-to-live): " + ttl + "sec.");
624         sCB = frameIn.getEOS();
625         if (Configuration.DEBUG)
626           log.fine("Got sCB (server channel binding): " + Util.dumpString(sCB));
627       }
628     catch (IOException x)
629       {
630         if (x instanceof SaslException)
631           throw (SaslException) x;
632         throw new AuthenticationException("receiveEvidence()", x);
633       }
634
635     final byte[] expected;
636     try
637       {
638         expected = srp.generateM2(A, M1, K, U, authorizationID, o, sid, ttl,
639                                   cIV, sIV, sCB);
640       }
641     catch (UnsupportedEncodingException x)
642       {
643         throw new AuthenticationException("receiveEvidence()", x);
644       }
645     if (Configuration.DEBUG)
646       log.fine("Expected: " + Util.dumpString(expected));
647     if (! Arrays.equals(M2, expected))
648       throw new AuthenticationException("M2 mismatch");
649     setupSecurityServices(false);
650     if (Configuration.DEBUG)
651       log.exiting(this.getClass().getName(), "receiveEvidence");
652     return null;
653   }
654
655   private void getUsernameAndPassword() throws AuthenticationException
656   {
657     try
658       {
659         if ((! properties.containsKey(Registry.SASL_USERNAME))
660             && (! properties.containsKey(Registry.SASL_PASSWORD)))
661           {
662             final NameCallback nameCB;
663             final String defaultName = System.getProperty("user.name");
664             if (defaultName == null)
665               nameCB = new NameCallback("username: ");
666             else
667               nameCB = new NameCallback("username: ", defaultName);
668             final PasswordCallback pwdCB = new PasswordCallback("password: ",
669                                                                 false);
670             handler.handle(new Callback[] { nameCB, pwdCB });
671             U = nameCB.getName();
672             password = new Password(pwdCB.getPassword());
673           }
674         else
675           {
676             if (properties.containsKey(Registry.SASL_USERNAME))
677               this.U = (String) properties.get(Registry.SASL_USERNAME);
678             else
679               {
680                 final NameCallback nameCB;
681                 final String defaultName = System.getProperty("user.name");
682                 if (defaultName == null)
683                   nameCB = new NameCallback("username: ");
684                 else
685                   nameCB = new NameCallback("username: ", defaultName);
686                 this.handler.handle(new Callback[] { nameCB });
687                 this.U = nameCB.getName();
688               }
689
690             if (properties.containsKey(Registry.SASL_PASSWORD))
691               {
692                 Object pw = properties.get(Registry.SASL_PASSWORD);
693                 if (pw instanceof char[])
694                   password = new Password((char[]) pw);
695                 else if (pw instanceof Password)
696                   password = (Password) pw;
697                 else if (pw instanceof String)
698                   password = new Password(((String) pw).toCharArray());
699                 else
700                   throw new IllegalArgumentException(pw.getClass().getName()
701                                                      + "is not a valid password class");
702               }
703             else
704               {
705                 final PasswordCallback pwdCB = new PasswordCallback("password: ",
706                                                                     false);
707                 this.handler.handle(new Callback[] { pwdCB });
708                 password = new Password(pwdCB.getPassword());
709               }
710           }
711
712         if (U == null)
713           throw new AuthenticationException("null username supplied");
714         if (password == null)
715           throw new AuthenticationException("null password supplied");
716       }
717     catch (UnsupportedCallbackException x)
718       {
719         throw new AuthenticationException("getUsernameAndPassword()", x);
720       }
721     catch (IOException x)
722       {
723         throw new AuthenticationException("getUsernameAndPassword()", x);
724       }
725   }
726
727   // We go through the list of available services and for each available one
728   // we decide whether or not we want it enabled, based on properties passed
729   // to us by the client.
730   private String createO(final String aol) throws AuthenticationException
731   {
732     if (Configuration.DEBUG)
733       log.entering(this.getClass().getName(), "createO", aol);
734     boolean replaydetectionAvailable = false;
735     boolean integrityAvailable = false;
736     boolean confidentialityAvailable = false;
737     String option, mandatory = SRPRegistry.DEFAULT_MANDATORY;
738     int i;
739
740     String mdName = SRPRegistry.SRP_DEFAULT_DIGEST_NAME;
741     final StringTokenizer st = new StringTokenizer(aol, ",");
742     while (st.hasMoreTokens())
743       {
744         option = st.nextToken();
745         if (option.startsWith(SRPRegistry.OPTION_SRP_DIGEST + "="))
746           {
747             option = option.substring(option.indexOf('=') + 1);
748             if (Configuration.DEBUG)
749               log.fine("mda: <" + option + ">");
750             for (i = 0; i < SRPRegistry.INTEGRITY_ALGORITHMS.length; i++)
751               if (SRPRegistry.SRP_ALGORITHMS[i].equals(option))
752                 {
753                   mdName = option;
754                   break;
755                 }
756           }
757         else if (option.equals(SRPRegistry.OPTION_REPLAY_DETECTION))
758           replaydetectionAvailable = true;
759         else if (option.startsWith(SRPRegistry.OPTION_INTEGRITY + "="))
760           {
761             option = option.substring(option.indexOf('=') + 1);
762             if (Configuration.DEBUG)
763               log.fine("ialg: <" + option + ">");
764             for (i = 0; i < SRPRegistry.INTEGRITY_ALGORITHMS.length; i++)
765               if (SRPRegistry.INTEGRITY_ALGORITHMS[i].equals(option))
766                 {
767                   chosenIntegrityAlgorithm = option;
768                   integrityAvailable = true;
769                   break;
770                 }
771           }
772         else if (option.startsWith(SRPRegistry.OPTION_CONFIDENTIALITY + "="))
773           {
774             option = option.substring(option.indexOf('=') + 1);
775             if (Configuration.DEBUG)
776               log.fine("calg: <" + option + ">");
777             for (i = 0; i < SRPRegistry.CONFIDENTIALITY_ALGORITHMS.length; i++)
778               if (SRPRegistry.CONFIDENTIALITY_ALGORITHMS[i].equals(option))
779                 {
780                   chosenConfidentialityAlgorithm = option;
781                   confidentialityAvailable = true;
782                   break;
783                 }
784           }
785         else if (option.startsWith(SRPRegistry.OPTION_MANDATORY + "="))
786           mandatory = option.substring(option.indexOf('=') + 1);
787         else if (option.startsWith(SRPRegistry.OPTION_MAX_BUFFER_SIZE + "="))
788           {
789             final String maxBufferSize = option.substring(option.indexOf('=') + 1);
790             try
791               {
792                 rawSendSize = Integer.parseInt(maxBufferSize);
793                 if (rawSendSize > Registry.SASL_BUFFER_MAX_LIMIT
794                     || rawSendSize < 1)
795                   throw new AuthenticationException(
796                       "Illegal value for 'maxbuffersize' option");
797               }
798             catch (NumberFormatException x)
799               {
800                 throw new AuthenticationException(
801                     SRPRegistry.OPTION_MAX_BUFFER_SIZE + "=" + maxBufferSize, x);
802               }
803           }
804       }
805     String s;
806     Boolean flag;
807     s = (String) properties.get(SRPRegistry.SRP_REPLAY_DETECTION);
808     flag = Boolean.valueOf(s);
809     replayDetection = replaydetectionAvailable && flag.booleanValue();
810     s = (String) properties.get(SRPRegistry.SRP_INTEGRITY_PROTECTION);
811     flag = Boolean.valueOf(s);
812     boolean integrity = integrityAvailable && flag.booleanValue();
813     s = (String) properties.get(SRPRegistry.SRP_CONFIDENTIALITY);
814     flag = Boolean.valueOf(s);
815     boolean confidentiality = confidentialityAvailable && flag.booleanValue();
816     // make sure we do the right thing
817     if (SRPRegistry.OPTION_REPLAY_DETECTION.equals(mandatory))
818       {
819         replayDetection = true;
820         integrity = true;
821       }
822     else if (SRPRegistry.OPTION_INTEGRITY.equals(mandatory))
823       integrity = true;
824     else if (SRPRegistry.OPTION_CONFIDENTIALITY.equals(mandatory))
825       confidentiality = true;
826
827     if (replayDetection)
828       {
829         if (chosenIntegrityAlgorithm == null)
830           throw new AuthenticationException(
831               "Replay detection is required but no integrity protection "
832               + "algorithm was chosen");
833       }
834     if (integrity)
835       {
836         if (chosenIntegrityAlgorithm == null)
837           throw new AuthenticationException(
838               "Integrity protection is required but no algorithm was chosen");
839       }
840     if (confidentiality)
841       {
842         if (chosenConfidentialityAlgorithm == null)
843           throw new AuthenticationException(
844               "Confidentiality protection is required but no algorithm was chosen");
845       }
846     // 1. check if we'll be using confidentiality; if not set IV to 0-byte
847     if (chosenConfidentialityAlgorithm == null)
848       cIV = new byte[0];
849     else
850       {
851         // 2. get the block size of the cipher
852         final IBlockCipher cipher = CipherFactory.getInstance(chosenConfidentialityAlgorithm);
853         if (cipher == null)
854           throw new AuthenticationException("createO()",
855                                             new NoSuchAlgorithmException());
856         final int blockSize = cipher.defaultBlockSize();
857         // 3. generate random iv
858         cIV = new byte[blockSize];
859         getDefaultPRNG().nextBytes(cIV);
860       }
861     srp = SRP.instance(mdName);
862     // Now create the options list specifying which of the available options
863     // we have chosen.
864
865     // For now we just select the defaults. Later we need to add support for
866     // properties (perhaps in a file) where a user can specify the list of
867     // algorithms they would prefer to use.
868     final CPStringBuilder sb = new CPStringBuilder();
869     sb.append(SRPRegistry.OPTION_SRP_DIGEST)
870       .append("=").append(mdName).append(",");
871     if (replayDetection)
872       sb.append(SRPRegistry.OPTION_REPLAY_DETECTION).append(",");
873     if (integrity)
874       sb.append(SRPRegistry.OPTION_INTEGRITY)
875         .append("=").append(chosenIntegrityAlgorithm).append(",");
876     if (confidentiality)
877       sb.append(SRPRegistry.OPTION_CONFIDENTIALITY)
878         .append("=").append(chosenConfidentialityAlgorithm).append(",");
879
880     final String result = sb.append(SRPRegistry.OPTION_MAX_BUFFER_SIZE)
881                             .append("=").append(Registry.SASL_BUFFER_MAX_LIMIT)
882                             .toString();
883     if (Configuration.DEBUG)
884       log.exiting(this.getClass().getName(), "createO", result);
885     return result;
886   }
887
888   private void setupSecurityServices(final boolean sessionReUse)
889       throws SaslException
890   {
891     complete = true; // signal end of authentication phase
892     if (! sessionReUse)
893       {
894         outCounter = inCounter = 0;
895         // instantiate cipher if confidentiality protection filter is active
896         if (chosenConfidentialityAlgorithm != null)
897           {
898             if (Configuration.DEBUG)
899               log.fine("Activating confidentiality protection filter");
900             inCipher = CALG.getInstance(chosenConfidentialityAlgorithm);
901             outCipher = CALG.getInstance(chosenConfidentialityAlgorithm);
902           }
903         // instantiate hmacs if integrity protection filter is active
904         if (chosenIntegrityAlgorithm != null)
905           {
906             if (Configuration.DEBUG)
907               log.fine("Activating integrity protection filter");
908             inMac = IALG.getInstance(chosenIntegrityAlgorithm);
909             outMac = IALG.getInstance(chosenIntegrityAlgorithm);
910           }
911       }
912     else // same session new Keys
913       K = srp.generateKn(K, cn, sn);
914
915     final KDF kdf = KDF.getInstance(K);
916     // initialise in/out ciphers if confidentiality protection is used
917     if (inCipher != null)
918       {
919         inCipher.init(kdf, sIV, Direction.REVERSED);
920         outCipher.init(kdf, cIV, Direction.FORWARD);
921       }
922     // initialise in/out macs if integrity protection is used
923     if (inMac != null)
924       {
925         inMac.init(kdf);
926         outMac.init(kdf);
927       }
928     if (sid != null && sid.length != 0)
929       { // update the security context and save in map
930         if (Configuration.DEBUG)
931           log.fine("Updating security context for UID = " + uid);
932         ClientStore.instance().cacheSession(uid,
933                                             ttl,
934                                             new SecurityContext(srp.getAlgorithm(),
935                                                                 sid,
936                                                                 K,
937                                                                 cIV,
938                                                                 sIV,
939                                                                 replayDetection,
940                                                                 inCounter,
941                                                                 outCounter,
942                                                                 inMac, outMac,
943                                                                 inCipher,
944                                                                 outCipher));
945       }
946   }
947
948   private PRNG getDefaultPRNG()
949   {
950     if (prng == null)
951       prng = PRNG.getInstance();
952     return prng;
953   }
954 }