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         byte[] hash;
 305         try {
 306             hash = MessageDigest.getInstance("MD5")
 307                     .digest(apReqMessg.authenticator.cipher);
 308         } catch (NoSuchAlgorithmException ex) {
 309             throw new AssertionError("Impossible");
 310         }
 311 
 312         char[] h = new char[hash.length * 2];
 313         for (int i=0; i<hash.length; i++) {
 314             h[2*i] = hexConst[(hash[i]&0xff)>>4];
 315             h[2*i+1] = hexConst[hash[i]&0xf];
 316         }
 317         AuthTimeWithHash time = new AuthTimeWithHash(
 318                 authenticator.cname.toString(),
 319                 apReqMessg.ticket.sname.toString(),
 320                 authenticator.ctime.getSeconds(),
 321                 authenticator.cusec,
 322                 new String(h));
 323         rcache.checkAndStore(KerberosTime.now(), time);
 324 
 325         if (initiator != null) {
 326             // sender host address
 327             HostAddress sender = new HostAddress(initiator);
 328             if (enc_ticketPart.caddr != null
 329                     && !enc_ticketPart.caddr.inList(sender)) {
 330                 if (DEBUG) {
 331                     System.out.println(">>> KrbApReq: initiator is "
 332                             + sender.getInetAddress()
 333                             + ", but caddr is "
 334                             + Arrays.toString(
 335                                 enc_ticketPart.caddr.getInetAddresses()));
 336                 }
 337                 throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR);
 338             }
 339         }
 340 
 341         // XXX check for repeated authenticator
 342         // if found
 343         //    throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
 344         // else
 345         //    save authenticator to check for later
 346 
 347         KerberosTime now = KerberosTime.now();
 348 
 349         if ((enc_ticketPart.starttime != null &&
 350              enc_ticketPart.starttime.greaterThanWRTClockSkew(now)) ||
 351             enc_ticketPart.flags.get(Krb5.TKT_OPTS_INVALID))
 352             throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_NYV);
 353 
 354         // if the current time is later than end time by more
 355         // than the allowable clock skew, throws ticket expired exception.
 356         if (enc_ticketPart.endtime != null &&
 357             now.greaterThanWRTClockSkew(enc_ticketPart.endtime)) {
 358             throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_EXPIRED);
 359         }
 360 
 361         creds = new Credentials(
 362                                 apReqMessg.ticket,
 363                                 authenticator.cname,
 364                                 apReqMessg.ticket.sname,
 365                                 enc_ticketPart.key,
 366                                 enc_ticketPart.flags,
 367                                 enc_ticketPart.authtime,
 368                                 enc_ticketPart.starttime,
 369                                 enc_ticketPart.endtime,
 370                                 enc_ticketPart.renewTill,
 371                                 enc_ticketPart.caddr,
 372                                 enc_ticketPart.authorizationData);
 373         if (DEBUG) {
 374             System.out.println(">>> KrbApReq: authenticate succeed.");
 375         }
 376     }
 377 
 378     /**
 379      * Returns the credentials that are contained in the ticket that
 380      * is part of this AP-REQ.
 381      */
 382     public Credentials getCreds() {
 383         return creds;
 384     }
 385 
 386     KerberosTime getCtime() {
 387         if (ctime != null)
 388             return ctime;
 389         return authenticator.ctime;
 390     }
 391 
 392     int cusec() {
 393         return cusec;
 394     }
 395 
 396     APOptions getAPOptions() throws KrbException, IOException {
 397         if (apReqMessg == null)
 398             decode();
 399         if (apReqMessg != null)
 400             return apReqMessg.apOptions;
 401         return null;
 402     }
 403 
 404     /**
 405      * Returns true if mutual authentication is required and hence an
 406      * AP-REP will need to be generated.
 407      * @throws KrbException
 408      * @throws IOException
 409      */
 410     public boolean getMutualAuthRequired() throws KrbException, IOException {
 411         if (apReqMessg == null)
 412             decode();
 413         if (apReqMessg != null)
 414             return apReqMessg.apOptions.get(Krb5.AP_OPTS_MUTUAL_REQUIRED);
 415         return false;
 416     }
 417 
 418     boolean useSessionKey() throws KrbException, IOException {
 419         if (apReqMessg == null)
 420             decode();
 421         if (apReqMessg != null)
 422             return apReqMessg.apOptions.get(Krb5.AP_OPTS_USE_SESSION_KEY);
 423         return false;
 424     }
 425 
 426     /**
 427      * Returns the optional subkey stored in the Authenticator for
 428      * this message. Returns null if none is stored.
 429      */
 430     public EncryptionKey getSubKey() {
 431         // XXX Can authenticator be null
 432         return authenticator.getSubKey();
 433     }
 434 
 435     /**
 436      * Returns the optional sequence number stored in the
 437      * Authenticator for this message. Returns null if none is
 438      * stored.
 439      */
 440     public Integer getSeqNumber() {
 441         // XXX Can authenticator be null
 442         return authenticator.getSeqNumber();
 443     }
 444 
 445     /**
 446      * Returns the optional Checksum stored in the
 447      * Authenticator for this message. Returns null if none is
 448      * stored.
 449      */
 450     public Checksum getChecksum() {
 451         return authenticator.getChecksum();
 452     }
 453 
 454     /**
 455      * Returns the ASN.1 encoding that should be sent to the peer.
 456      */
 457     public byte[] getMessage() {
 458         return obuf;
 459     }
 460 
 461     /**
 462      * Returns the principal name of the client that generated this
 463      * message.
 464      */
 465     public PrincipalName getClient() {
 466         return creds.getClient();
 467     }
 468 
 469     private void createMessage(APOptions apOptions,
 470                                Ticket ticket,
 471                                EncryptionKey key,
 472                                PrincipalName cname,
 473                                Checksum cksum,
 474                                KerberosTime ctime,
 475                                EncryptionKey subKey,
 476                                SeqNumber seqNumber,
 477                                AuthorizationData authorizationData,
 478         int usage)
 479         throws Asn1Exception, IOException,
 480                KdcErrException, KrbCryptoException {
 481 
 482         Integer seqno = null;
 483 
 484         if (seqNumber != null)
 485             seqno = seqNumber.current();
 486 
 487         authenticator =
 488             new Authenticator(cname,
 489                               cksum,
 490                               ctime.getMicroSeconds(),
 491                               ctime,
 492                               subKey,
 493                               seqno,
 494                               authorizationData);
 495 
 496         byte[] temp = authenticator.asn1Encode();
 497 
 498         EncryptedData encAuthenticator =
 499             new EncryptedData(key, temp, usage);
 500 
 501         apReqMessg =
 502             new APReq(apOptions, ticket, encAuthenticator);
 503     }
 504 
 505      // Check that key is one of the permitted types
 506      private static void checkPermittedEType(int target) throws KrbException {
 507         int[] etypes = EType.getDefaults("permitted_enctypes");
 508         if (!EType.isSupported(target, etypes)) {
 509             throw new KrbException(EType.toString(target) +
 510                 " encryption type not in permitted_enctypes list");
 511         }
 512      }
 513 }