1 /*
   2  * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  *
  28  *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
  29  *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
  30  */
  31 
  32 package sun.security.krb5;
  33 
  34 import sun.security.krb5.internal.*;
  35 import sun.security.krb5.internal.crypto.*;
  36 import sun.security.jgss.krb5.Krb5AcceptCredential;
  37 import java.net.InetAddress;
  38 import sun.security.util.*;
  39 import java.io.IOException;
  40 import java.util.Arrays;
  41 import java.security.MessageDigest;
  42 import java.security.NoSuchAlgorithmException;
  43 import sun.security.krb5.internal.rcache.AuthTimeWithHash;
  44 
  45 /**
  46  * This class encapsulates a KRB-AP-REQ that a client sends to a
  47  * server for authentication.
  48  */
  49 public class KrbApReq {
  50 
  51     private byte[] obuf;
  52     private KerberosTime ctime;
  53     private int cusec;
  54     private Authenticator authenticator;
  55     private Credentials creds;
  56     private APReq apReqMessg;
  57 
  58     // Used by acceptor side
  59     private static ReplayCache rcache = ReplayCache.getInstance();
  60     private static boolean DEBUG = Krb5.DEBUG;
  61     private static final char[] hexConst = "0123456789ABCDEF".toCharArray();
  62 
  63     /**
  64      * Constructs an AP-REQ message to send to the peer.
  65      * @param tgsCred the <code>Credentials</code> to be used to construct the
  66      *          AP Request  protocol message.
  67      * @param mutualRequired Whether mutual authentication is required
  68      * @param useSubkey Whether the subkey is to be used to protect this
  69      *        specific application session. If this is not set then the
  70      *        session key from the ticket will be used.
  71      * @throws KrbException for any Kerberos protocol specific error
  72      * @throws IOException for any IO related errors
  73      *          (e.g. socket operations)
  74      */
  75      /*
  76      // Not Used
  77     public KrbApReq(Credentials tgsCred,
  78                     boolean mutualRequired,
  79                     boolean useSubKey,
  80                     boolean useSeqNumber) throws Asn1Exception,
  81                     KrbCryptoException, KrbException, IOException {
  82 
  83         this(tgsCred, mutualRequired, useSubKey, useSeqNumber, null);
  84     }
  85 */
  86 
  87     /**
  88      * Constructs an AP-REQ message to send to the peer.
  89      * @param tgsCred the <code>Credentials</code> to be used to construct the
  90      *          AP Request  protocol message.
  91      * @param mutualRequired Whether mutual authentication is required
  92      * @param useSubKey Whether the subkey is to be used to protect this
  93      *        specific application session. If this is not set then the
  94      *        session key from the ticket will be used.
  95      * @param cksum checksum of the application data that accompanies
  96      *        the KRB_AP_REQ.
  97      * @throws KrbException for any Kerberos protocol specific error
  98      * @throws IOException for any IO related errors
  99      *          (e.g. socket operations)
 100      */
 101      // Used in InitSecContextToken
 102     public KrbApReq(Credentials tgsCred,
 103                     boolean mutualRequired,
 104                     boolean useSubKey,
 105                     boolean useSeqNumber,
 106                     Checksum cksum) throws Asn1Exception,
 107                     KrbCryptoException, KrbException, IOException  {
 108 
 109         APOptions apOptions = (mutualRequired?
 110                                new APOptions(Krb5.AP_OPTS_MUTUAL_REQUIRED):
 111                                new APOptions());
 112         if (DEBUG)
 113             System.out.println(">>> KrbApReq: APOptions are " + apOptions);
 114 
 115         EncryptionKey subKey = (useSubKey?
 116                                 new EncryptionKey(tgsCred.getSessionKey()):
 117                                 null);
 118 
 119         SeqNumber seqNum = new LocalSeqNumber();
 120 
 121         init(apOptions,
 122              tgsCred,
 123              cksum,
 124              subKey,
 125              seqNum,
 126              null,   // AuthorizationData authzData
 127             KeyUsage.KU_AP_REQ_AUTHENTICATOR);
 128 
 129     }
 130 
 131     /**
 132      * Constructs an AP-REQ message from the bytes received from the
 133      * peer.
 134      * @param message The message received from the peer
 135      * @param cred <code>KrbAcceptCredential</code> containing keys to decrypt
 136      *    the message; key selected will depend on etype used to encrypt data
 137      * @throws KrbException for any Kerberos protocol specific error
 138      * @throws IOException for any IO related errors
 139      *          (e.g. socket operations)
 140      */
 141      // Used in InitSecContextToken (for AP_REQ and not TGS REQ)
 142     public KrbApReq(byte[] message,
 143                     Krb5AcceptCredential cred,
 144                     InetAddress initiator)
 145         throws KrbException, IOException {
 146         obuf = message;
 147         if (apReqMessg == null)
 148             decode();
 149         authenticate(cred, initiator);
 150     }
 151 
 152     /**
 153      * Constructs an AP-REQ message from the bytes received from the
 154      * peer.
 155      * @param value The <code>DerValue</code> that contains the
 156      *              DER enoded AP-REQ protocol message
 157      * @param keys <code>EncrtyptionKey</code>s to decrypt the message;
 158      *
 159      * @throws KrbException for any Kerberos protocol specific error
 160      * @throws IOException for any IO related errors
 161      *          (e.g. socket operations)
 162      */
 163      /*
 164     public KrbApReq(DerValue value, EncryptionKey[] key, InetAddress initiator)
 165         throws KrbException, IOException {
 166         obuf = value.toByteArray();
 167         if (apReqMessg == null)
 168             decode(value);
 169         authenticate(keys, initiator);
 170     }
 171 
 172     KrbApReq(APOptions options,
 173              Credentials tgs_creds,
 174              Checksum cksum,
 175              EncryptionKey subKey,
 176              SeqNumber seqNumber,
 177              AuthorizationData authorizationData)
 178         throws KrbException, IOException {
 179         init(options, tgs_creds, cksum, subKey, seqNumber, authorizationData);
 180     }
 181 */
 182 
 183      /** used by KrbTgsReq **/
 184     KrbApReq(APOptions apOptions,
 185              Ticket ticket,
 186              EncryptionKey key,
 187              PrincipalName cname,
 188              Checksum cksum,
 189              KerberosTime ctime,
 190              EncryptionKey subKey,
 191              SeqNumber seqNumber,
 192         AuthorizationData authorizationData)
 193         throws Asn1Exception, IOException,
 194                KdcErrException, KrbCryptoException {
 195 
 196         init(apOptions, ticket, key, cname,
 197              cksum, ctime, subKey, seqNumber, authorizationData,
 198             KeyUsage.KU_PA_TGS_REQ_AUTHENTICATOR);
 199 
 200     }
 201 
 202     private void init(APOptions options,
 203                       Credentials tgs_creds,
 204                       Checksum cksum,
 205                       EncryptionKey subKey,
 206                       SeqNumber seqNumber,
 207                       AuthorizationData authorizationData,
 208         int usage)
 209         throws KrbException, IOException {
 210 
 211         ctime = KerberosTime.now();
 212         init(options,
 213              tgs_creds.ticket,
 214              tgs_creds.key,
 215              tgs_creds.client,
 216              cksum,
 217              ctime,
 218              subKey,
 219              seqNumber,
 220              authorizationData,
 221             usage);
 222     }
 223 
 224     private void init(APOptions apOptions,
 225                       Ticket ticket,
 226                       EncryptionKey key,
 227                       PrincipalName cname,
 228                       Checksum cksum,
 229                       KerberosTime ctime,
 230                       EncryptionKey subKey,
 231                       SeqNumber seqNumber,
 232                       AuthorizationData authorizationData,
 233         int usage)
 234         throws Asn1Exception, IOException,
 235                KdcErrException, KrbCryptoException {
 236 
 237         createMessage(apOptions, ticket, key, cname,
 238                       cksum, ctime, subKey, seqNumber, authorizationData,
 239             usage);
 240         obuf = apReqMessg.asn1Encode();
 241     }
 242 
 243 
 244     void decode() throws KrbException, IOException {
 245         DerValue encoding = new DerValue(obuf);
 246         decode(encoding);
 247     }
 248 
 249     void decode(DerValue encoding) throws KrbException, IOException {
 250         apReqMessg = null;
 251         try {
 252             apReqMessg = new APReq(encoding);
 253         } catch (Asn1Exception e) {
 254             apReqMessg = null;
 255             KRBError err = new KRBError(encoding);
 256             String errStr = err.getErrorString();
 257             String eText;
 258             if (errStr.charAt(errStr.length() - 1) == 0)
 259                 eText = errStr.substring(0, errStr.length() - 1);
 260             else
 261                 eText = errStr;
 262             KrbException ke = new KrbException(err.getErrorCode(), eText);
 263             ke.initCause(e);
 264             throw ke;
 265         }
 266     }
 267 
 268     private void authenticate(Krb5AcceptCredential cred, InetAddress initiator)
 269         throws KrbException, IOException {
 270         int encPartKeyType = apReqMessg.ticket.encPart.getEType();
 271         Integer kvno = apReqMessg.ticket.encPart.getKeyVersionNumber();
 272         EncryptionKey[] keys = cred.getKrb5EncryptionKeys(apReqMessg.ticket.sname);
 273         EncryptionKey dkey = EncryptionKey.findKey(encPartKeyType, kvno, keys);
 274 
 275         if (dkey == null) {
 276             throw new KrbException(Krb5.API_INVALID_ARG,
 277                 "Cannot find key of appropriate type to decrypt AP-REQ - " +
 278                                    EType.toString(encPartKeyType));
 279         }
 280 
 281         byte[] bytes = apReqMessg.ticket.encPart.decrypt(dkey,
 282             KeyUsage.KU_TICKET);
 283         byte[] temp = apReqMessg.ticket.encPart.reset(bytes);
 284         EncTicketPart enc_ticketPart = new EncTicketPart(temp);
 285 
 286         checkPermittedEType(enc_ticketPart.key.getEType());
 287 
 288         byte[] bytes2 = apReqMessg.authenticator.decrypt(enc_ticketPart.key,
 289             KeyUsage.KU_AP_REQ_AUTHENTICATOR);
 290         byte[] temp2 = apReqMessg.authenticator.reset(bytes2);
 291         authenticator = new Authenticator(temp2);
 292         ctime = authenticator.ctime;
 293         cusec = authenticator.cusec;
 294         authenticator.ctime =
 295                 authenticator.ctime.withMicroSeconds(authenticator.cusec);
 296 
 297         if (!authenticator.cname.equals(enc_ticketPart.cname)) {
 298             throw new KrbApErrException(Krb5.KRB_AP_ERR_BADMATCH);
 299         }
 300 
 301         if (!authenticator.ctime.inClockSkew())
 302             throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW);
 303 
 304         String alg = AuthTimeWithHash.DEFAULT_HASH_ALG;
 305         byte[] hash;
 306         try {
 307             hash = MessageDigest.getInstance(AuthTimeWithHash.realAlg(alg))
 308                     .digest(apReqMessg.authenticator.cipher);
 309         } catch (NoSuchAlgorithmException ex) {
 310             throw new AssertionError("Impossible");
 311         }
 312 
 313         char[] h = new char[hash.length * 2];
 314         for (int i=0; i<hash.length; i++) {
 315             h[2*i] = hexConst[(hash[i]&0xff)>>4];
 316             h[2*i+1] = hexConst[hash[i]&0xf];
 317         }
 318         AuthTimeWithHash time = new AuthTimeWithHash(
 319                 authenticator.cname.toString(),
 320                 apReqMessg.ticket.sname.toString(),
 321                 authenticator.ctime.getSeconds(),
 322                 authenticator.cusec,
 323                 alg,
 324                 new String(h));
 325         rcache.checkAndStore(KerberosTime.now(), time);
 326 
 327         if (initiator != null) {
 328             // sender host address
 329             HostAddress sender = new HostAddress(initiator);
 330             if (enc_ticketPart.caddr != null
 331                     && !enc_ticketPart.caddr.inList(sender)) {
 332                 if (DEBUG) {
 333                     System.out.println(">>> KrbApReq: initiator is "
 334                             + sender.getInetAddress()
 335                             + ", but caddr is "
 336                             + Arrays.toString(
 337                                 enc_ticketPart.caddr.getInetAddresses()));
 338                 }
 339                 throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR);
 340             }
 341         }
 342 
 343         // XXX check for repeated authenticator
 344         // if found
 345         //    throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
 346         // else
 347         //    save authenticator to check for later
 348 
 349         KerberosTime now = KerberosTime.now();
 350 
 351         if ((enc_ticketPart.starttime != null &&
 352              enc_ticketPart.starttime.greaterThanWRTClockSkew(now)) ||
 353             enc_ticketPart.flags.get(Krb5.TKT_OPTS_INVALID))
 354             throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_NYV);
 355 
 356         // if the current time is later than end time by more
 357         // than the allowable clock skew, throws ticket expired exception.
 358         if (enc_ticketPart.endtime != null &&
 359             now.greaterThanWRTClockSkew(enc_ticketPart.endtime)) {
 360             throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_EXPIRED);
 361         }
 362 
 363         creds = new Credentials(
 364                                 apReqMessg.ticket,
 365                                 authenticator.cname,
 366                                 apReqMessg.ticket.sname,
 367                                 enc_ticketPart.key,
 368                                 enc_ticketPart.flags,
 369                                 enc_ticketPart.authtime,
 370                                 enc_ticketPart.starttime,
 371                                 enc_ticketPart.endtime,
 372                                 enc_ticketPart.renewTill,
 373                                 enc_ticketPart.caddr,
 374                                 enc_ticketPart.authorizationData);
 375         if (DEBUG) {
 376             System.out.println(">>> KrbApReq: authenticate succeed.");
 377         }
 378     }
 379 
 380     /**
 381      * Returns the credentials that are contained in the ticket that
 382      * is part of this AP-REQ.
 383      */
 384     public Credentials getCreds() {
 385         return creds;
 386     }
 387 
 388     KerberosTime getCtime() {
 389         if (ctime != null)
 390             return ctime;
 391         return authenticator.ctime;
 392     }
 393 
 394     int cusec() {
 395         return cusec;
 396     }
 397 
 398     APOptions getAPOptions() throws KrbException, IOException {
 399         if (apReqMessg == null)
 400             decode();
 401         if (apReqMessg != null)
 402             return apReqMessg.apOptions;
 403         return null;
 404     }
 405 
 406     /**
 407      * Returns true if mutual authentication is required and hence an
 408      * AP-REP will need to be generated.
 409      * @throws KrbException
 410      * @throws IOException
 411      */
 412     public boolean getMutualAuthRequired() throws KrbException, IOException {
 413         if (apReqMessg == null)
 414             decode();
 415         if (apReqMessg != null)
 416             return apReqMessg.apOptions.get(Krb5.AP_OPTS_MUTUAL_REQUIRED);
 417         return false;
 418     }
 419 
 420     boolean useSessionKey() throws KrbException, IOException {
 421         if (apReqMessg == null)
 422             decode();
 423         if (apReqMessg != null)
 424             return apReqMessg.apOptions.get(Krb5.AP_OPTS_USE_SESSION_KEY);
 425         return false;
 426     }
 427 
 428     /**
 429      * Returns the optional subkey stored in the Authenticator for
 430      * this message. Returns null if none is stored.
 431      */
 432     public EncryptionKey getSubKey() {
 433         // XXX Can authenticator be null
 434         return authenticator.getSubKey();
 435     }
 436 
 437     /**
 438      * Returns the optional sequence number stored in the
 439      * Authenticator for this message. Returns null if none is
 440      * stored.
 441      */
 442     public Integer getSeqNumber() {
 443         // XXX Can authenticator be null
 444         return authenticator.getSeqNumber();
 445     }
 446 
 447     /**
 448      * Returns the optional Checksum stored in the
 449      * Authenticator for this message. Returns null if none is
 450      * stored.
 451      */
 452     public Checksum getChecksum() {
 453         return authenticator.getChecksum();
 454     }
 455 
 456     /**
 457      * Returns the ASN.1 encoding that should be sent to the peer.
 458      */
 459     public byte[] getMessage() {
 460         return obuf;
 461     }
 462 
 463     /**
 464      * Returns the principal name of the client that generated this
 465      * message.
 466      */
 467     public PrincipalName getClient() {
 468         return creds.getClient();
 469     }
 470 
 471     private void createMessage(APOptions apOptions,
 472                                Ticket ticket,
 473                                EncryptionKey key,
 474                                PrincipalName cname,
 475                                Checksum cksum,
 476                                KerberosTime ctime,
 477                                EncryptionKey subKey,
 478                                SeqNumber seqNumber,
 479                                AuthorizationData authorizationData,
 480         int usage)
 481         throws Asn1Exception, IOException,
 482                KdcErrException, KrbCryptoException {
 483 
 484         Integer seqno = null;
 485 
 486         if (seqNumber != null)
 487             seqno = seqNumber.current();
 488 
 489         authenticator =
 490             new Authenticator(cname,
 491                               cksum,
 492                               ctime.getMicroSeconds(),
 493                               ctime,
 494                               subKey,
 495                               seqno,
 496                               authorizationData);
 497 
 498         byte[] temp = authenticator.asn1Encode();
 499 
 500         EncryptedData encAuthenticator =
 501             new EncryptedData(key, temp, usage);
 502 
 503         apReqMessg =
 504             new APReq(apOptions, ticket, encAuthenticator);
 505     }
 506 
 507      // Check that key is one of the permitted types
 508      private static void checkPermittedEType(int target) throws KrbException {
 509         int[] etypes = EType.getDefaults("permitted_enctypes");
 510         if (!EType.isSupported(target, etypes)) {
 511             throw new KrbException(EType.toString(target) +
 512                 " encryption type not in permitted_enctypes list");
 513         }
 514      }
 515 }