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

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