Source for gnu.javax.crypto.sasl.srp.SRPClient

   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: }