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