1 /*
   2  * Copyright (c) 2000, 2012, 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     private static final MessageDigest md;
  64 
  65     static {
  66         try {
  67             md = MessageDigest.getInstance("MD5");
  68         } catch (NoSuchAlgorithmException ex) {
  69             throw new RuntimeException("Impossible");
  70         }
  71     }
  72 
  73     /**
  74      * Constructs an AP-REQ message to send to the peer.
  75      * @param tgsCred the <code>Credentials</code> to be used to construct the
  76      *          AP Request  protocol message.
  77      * @param mutualRequired Whether mutual authentication is required
  78      * @param useSubkey Whether the subkey is to be used to protect this
  79      *        specific application session. If this is not set then the
  80      *        session key from the ticket will be used.
  81      * @throws KrbException for any Kerberos protocol specific error
  82      * @throws IOException for any IO related errors
  83      *          (e.g. socket operations)
  84      */
  85      /*
  86      // Not Used
  87     public KrbApReq(Credentials tgsCred,
  88                     boolean mutualRequired,
  89                     boolean useSubKey,
  90                     boolean useSeqNumber) throws Asn1Exception,
  91                     KrbCryptoException, KrbException, IOException {
  92 
  93         this(tgsCred, mutualRequired, useSubKey, useSeqNumber, null);
  94     }
  95 */
  96 
  97     /**
  98      * Constructs an AP-REQ message to send to the peer.
  99      * @param tgsCred the <code>Credentials</code> to be used to construct the
 100      *          AP Request  protocol message.
 101      * @param mutualRequired Whether mutual authentication is required
 102      * @param useSubkey Whether the subkey is to be used to protect this
 103      *        specific application session. If this is not set then the
 104      *        session key from the ticket will be used.
 105      * @param checksum checksum of the the application data that accompanies
 106      *        the KRB_AP_REQ.
 107      * @throws KrbException for any Kerberos protocol specific error
 108      * @throws IOException for any IO related errors
 109      *          (e.g. socket operations)
 110      */
 111      // Used in InitSecContextToken
 112     public KrbApReq(Credentials tgsCred,
 113                     boolean mutualRequired,
 114                     boolean useSubKey,
 115                     boolean useSeqNumber,
 116                     Checksum cksum) throws Asn1Exception,
 117                     KrbCryptoException, KrbException, IOException  {
 118 
 119         APOptions apOptions = (mutualRequired?
 120                                new APOptions(Krb5.AP_OPTS_MUTUAL_REQUIRED):
 121                                new APOptions());
 122         if (DEBUG)
 123             System.out.println(">>> KrbApReq: APOptions are " + apOptions);
 124 
 125         EncryptionKey subKey = (useSubKey?
 126                                 new EncryptionKey(tgsCred.getSessionKey()):
 127                                 null);
 128 
 129         SeqNumber seqNum = new LocalSeqNumber();
 130 
 131         init(apOptions,
 132              tgsCred,
 133              cksum,
 134              subKey,
 135              seqNum,
 136              null,   // AuthorizationData authzData
 137             KeyUsage.KU_AP_REQ_AUTHENTICATOR);
 138 
 139     }
 140 
 141     /**
 142      * Constructs an AP-REQ message from the bytes received from the
 143      * peer.
 144      * @param message The message received from the peer
 145      * @param keys <code>EncrtyptionKey</code>s to decrypt the message;
 146      *       key selected will depend on etype used to encrypte data
 147      * @throws KrbException for any Kerberos protocol specific error
 148      * @throws IOException for any IO related errors
 149      *          (e.g. socket operations)
 150      */
 151      // Used in InitSecContextToken (for AP_REQ and not TGS REQ)
 152     public KrbApReq(byte[] message,
 153                     Krb5AcceptCredential cred,
 154                     InetAddress initiator)
 155         throws KrbException, IOException {
 156         obuf = message;
 157         if (apReqMessg == null)
 158             decode();
 159         authenticate(cred, initiator);
 160     }
 161 
 162     /**
 163      * Constructs an AP-REQ message from the bytes received from the
 164      * peer.
 165      * @param value The <code>DerValue</code> that contains the
 166      *              DER enoded AP-REQ protocol message
 167      * @param keys <code>EncrtyptionKey</code>s to decrypt the message;
 168      *
 169      * @throws KrbException for any Kerberos protocol specific error
 170      * @throws IOException for any IO related errors
 171      *          (e.g. socket operations)
 172      */
 173      /*
 174     public KrbApReq(DerValue value, EncryptionKey[] key, InetAddress initiator)
 175         throws KrbException, IOException {
 176         obuf = value.toByteArray();
 177         if (apReqMessg == null)
 178             decode(value);
 179         authenticate(keys, initiator);
 180     }
 181 
 182     KrbApReq(APOptions options,
 183              Credentials tgs_creds,
 184              Checksum cksum,
 185              EncryptionKey subKey,
 186              SeqNumber seqNumber,
 187              AuthorizationData authorizationData)
 188         throws KrbException, IOException {
 189         init(options, tgs_creds, cksum, subKey, seqNumber, authorizationData);
 190     }
 191 */
 192 
 193      /** used by KrbTgsReq **/
 194     KrbApReq(APOptions apOptions,
 195              Ticket ticket,
 196              EncryptionKey key,
 197              PrincipalName cname,
 198              Checksum cksum,
 199              KerberosTime ctime,
 200              EncryptionKey subKey,
 201              SeqNumber seqNumber,
 202         AuthorizationData authorizationData)
 203         throws Asn1Exception, IOException,
 204                KdcErrException, KrbCryptoException {
 205 
 206         init(apOptions, ticket, key, cname,
 207              cksum, ctime, subKey, seqNumber, authorizationData,
 208             KeyUsage.KU_PA_TGS_REQ_AUTHENTICATOR);
 209 
 210     }
 211 
 212     private void init(APOptions options,
 213                       Credentials tgs_creds,
 214                       Checksum cksum,
 215                       EncryptionKey subKey,
 216                       SeqNumber seqNumber,
 217                       AuthorizationData authorizationData,
 218         int usage)
 219         throws KrbException, IOException {
 220 
 221         ctime = KerberosTime.now();
 222         init(options,
 223              tgs_creds.ticket,
 224              tgs_creds.key,
 225              tgs_creds.client,
 226              cksum,
 227              ctime,
 228              subKey,
 229              seqNumber,
 230              authorizationData,
 231             usage);
 232     }
 233 
 234     private void init(APOptions apOptions,
 235                       Ticket ticket,
 236                       EncryptionKey key,
 237                       PrincipalName cname,
 238                       Checksum cksum,
 239                       KerberosTime ctime,
 240                       EncryptionKey subKey,
 241                       SeqNumber seqNumber,
 242                       AuthorizationData authorizationData,
 243         int usage)
 244         throws Asn1Exception, IOException,
 245                KdcErrException, KrbCryptoException {
 246 
 247         createMessage(apOptions, ticket, key, cname,
 248                       cksum, ctime, subKey, seqNumber, authorizationData,
 249             usage);
 250         obuf = apReqMessg.asn1Encode();
 251     }
 252 
 253 
 254     void decode() throws KrbException, IOException {
 255         DerValue encoding = new DerValue(obuf);
 256         decode(encoding);
 257     }
 258 
 259     void decode(DerValue encoding) throws KrbException, IOException {
 260         apReqMessg = null;
 261         try {
 262             apReqMessg = new APReq(encoding);
 263         } catch (Asn1Exception e) {
 264             apReqMessg = null;
 265             KRBError err = new KRBError(encoding);
 266             String errStr = err.getErrorString();
 267             String eText;
 268             if (errStr.charAt(errStr.length() - 1) == 0)
 269                 eText = errStr.substring(0, errStr.length() - 1);
 270             else
 271                 eText = errStr;
 272             KrbException ke = new KrbException(err.getErrorCode(), eText);
 273             ke.initCause(e);
 274             throw ke;
 275         }
 276     }
 277 
 278     private void authenticate(Krb5AcceptCredential cred, InetAddress initiator)
 279         throws KrbException, IOException {
 280         int encPartKeyType = apReqMessg.ticket.encPart.getEType();
 281         Integer kvno = apReqMessg.ticket.encPart.getKeyVersionNumber();
 282         EncryptionKey[] keys = cred.getKrb5EncryptionKeys(apReqMessg.ticket.sname);
 283         EncryptionKey dkey = EncryptionKey.findKey(encPartKeyType, kvno, keys);
 284 
 285         if (dkey == null) {
 286             throw new KrbException(Krb5.API_INVALID_ARG,
 287                 "Cannot find key of appropriate type to decrypt AP REP - " +
 288                                    EType.toString(encPartKeyType));
 289         }
 290 
 291         byte[] bytes = apReqMessg.ticket.encPart.decrypt(dkey,
 292             KeyUsage.KU_TICKET);
 293         byte[] temp = apReqMessg.ticket.encPart.reset(bytes);
 294         EncTicketPart enc_ticketPart = new EncTicketPart(temp);
 295 
 296         checkPermittedEType(enc_ticketPart.key.getEType());
 297 
 298         byte[] bytes2 = apReqMessg.authenticator.decrypt(enc_ticketPart.key,
 299             KeyUsage.KU_AP_REQ_AUTHENTICATOR);
 300         byte[] temp2 = apReqMessg.authenticator.reset(bytes2);
 301         authenticator = new Authenticator(temp2);
 302         ctime = authenticator.ctime;
 303         cusec = authenticator.cusec;
 304         authenticator.ctime =
 305                 authenticator.ctime.withMicroSeconds(authenticator.cusec);
 306 
 307         if (!authenticator.cname.equals(enc_ticketPart.cname)) {
 308             throw new KrbApErrException(Krb5.KRB_AP_ERR_BADMATCH);
 309         }
 310 
 311         if (!authenticator.ctime.inClockSkew())
 312             throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW);
 313 
 314         byte[] hash = md.digest(apReqMessg.authenticator.cipher);
 315         char[] h = new char[hash.length * 2];
 316         for (int i=0; i<hash.length; i++) {
 317             h[2*i] = hexConst[(hash[i]&0xff)>>4];
 318             h[2*i+1] = hexConst[hash[i]&0xf];
 319         }
 320         AuthTimeWithHash time = new AuthTimeWithHash(
 321                 authenticator.cname.toString(),
 322                 apReqMessg.ticket.sname.toString(),
 323                 authenticator.ctime.getSeconds(),
 324                 authenticator.cusec,
 325                 new String(h));
 326         rcache.checkAndStore(KerberosTime.now(), time);
 327 
 328         if (initiator != null) {
 329             // sender host address
 330             HostAddress sender = new HostAddress(initiator);
 331             if (enc_ticketPart.caddr != null
 332                     && !enc_ticketPart.caddr.inList(sender)) {
 333                 if (DEBUG) {
 334                     System.out.println(">>> KrbApReq: initiator is "
 335                             + sender.getInetAddress()
 336                             + ", but caddr is "
 337                             + Arrays.toString(
 338                                 enc_ticketPart.caddr.getInetAddresses()));
 339                 }
 340                 throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR);
 341             }
 342         }
 343 
 344         // XXX check for repeated authenticator
 345         // if found
 346         //    throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
 347         // else
 348         //    save authenticator to check for later
 349 
 350         KerberosTime now = KerberosTime.now();
 351 
 352         if ((enc_ticketPart.starttime != null &&
 353              enc_ticketPart.starttime.greaterThanWRTClockSkew(now)) ||
 354             enc_ticketPart.flags.get(Krb5.TKT_OPTS_INVALID))
 355             throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_NYV);
 356 
 357         // if the current time is later than end time by more
 358         // than the allowable clock skew, throws ticket expired exception.
 359         if (enc_ticketPart.endtime != null &&
 360             now.greaterThanWRTClockSkew(enc_ticketPart.endtime)) {
 361             throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_EXPIRED);
 362         }
 363 
 364         creds = new Credentials(
 365                                 apReqMessg.ticket,
 366                                 authenticator.cname,
 367                                 apReqMessg.ticket.sname,
 368                                 enc_ticketPart.key,
 369                                 enc_ticketPart.flags,
 370                                 enc_ticketPart.authtime,
 371                                 enc_ticketPart.starttime,
 372                                 enc_ticketPart.endtime,
 373                                 enc_ticketPart.renewTill,
 374                                 enc_ticketPart.caddr,
 375                                 enc_ticketPart.authorizationData);
 376         if (DEBUG) {
 377             System.out.println(">>> KrbApReq: authenticate succeed.");
 378         }
 379     }
 380 
 381     /**
 382      * Returns the credentials that are contained in the ticket that
 383      * is part of this this AP-REP.
 384      */
 385     public Credentials getCreds() {
 386         return creds;
 387     }
 388 
 389     KerberosTime getCtime() {
 390         if (ctime != null)
 391             return ctime;
 392         return authenticator.ctime;
 393     }
 394 
 395     int cusec() {
 396         return cusec;
 397     }
 398 
 399     APOptions getAPOptions() throws KrbException, IOException {
 400         if (apReqMessg == null)
 401             decode();
 402         if (apReqMessg != null)
 403             return apReqMessg.apOptions;
 404         return null;
 405     }
 406 
 407     /**
 408      * Returns true if mutual authentication is required and hence an
 409      * AP-REP will need to be generated.
 410      * @throws KrbException
 411      * @throws IOException
 412      */
 413     public boolean getMutualAuthRequired() throws KrbException, IOException {
 414         if (apReqMessg == null)
 415             decode();
 416         if (apReqMessg != null)
 417             return apReqMessg.apOptions.get(Krb5.AP_OPTS_MUTUAL_REQUIRED);
 418         return false;
 419     }
 420 
 421     boolean useSessionKey() throws KrbException, IOException {
 422         if (apReqMessg == null)
 423             decode();
 424         if (apReqMessg != null)
 425             return apReqMessg.apOptions.get(Krb5.AP_OPTS_USE_SESSION_KEY);
 426         return false;
 427     }
 428 
 429     /**
 430      * Returns the optional subkey stored in the Authenticator for
 431      * this message. Returns null if none is stored.
 432      */
 433     public EncryptionKey getSubKey() {
 434         // XXX Can authenticator be null
 435         return authenticator.getSubKey();
 436     }
 437 
 438     /**
 439      * Returns the optional sequence number stored in the
 440      * Authenticator for this message. Returns null if none is
 441      * stored.
 442      */
 443     public Integer getSeqNumber() {
 444         // XXX Can authenticator be null
 445         return authenticator.getSeqNumber();
 446     }
 447 
 448     /**
 449      * Returns the optional Checksum stored in the
 450      * Authenticator for this message. Returns null if none is
 451      * stored.
 452      */
 453     public Checksum getChecksum() {
 454         return authenticator.getChecksum();
 455     }
 456 
 457     /**
 458      * Returns the ASN.1 encoding that should be sent to the peer.
 459      */
 460     public byte[] getMessage() {
 461         return obuf;
 462     }
 463 
 464     /**
 465      * Returns the principal name of the client that generated this
 466      * message.
 467      */
 468     public PrincipalName getClient() {
 469         return creds.getClient();
 470     }
 471 
 472     private void createMessage(APOptions apOptions,
 473                                Ticket ticket,
 474                                EncryptionKey key,
 475                                PrincipalName cname,
 476                                Checksum cksum,
 477                                KerberosTime ctime,
 478                                EncryptionKey subKey,
 479                                SeqNumber seqNumber,
 480                                AuthorizationData authorizationData,
 481         int usage)
 482         throws Asn1Exception, IOException,
 483                KdcErrException, KrbCryptoException {
 484 
 485         Integer seqno = null;
 486 
 487         if (seqNumber != null)
 488             seqno = new Integer(seqNumber.current());
 489 
 490         authenticator =
 491             new Authenticator(cname,
 492                               cksum,
 493                               ctime.getMicroSeconds(),
 494                               ctime,
 495                               subKey,
 496                               seqno,
 497                               authorizationData);
 498 
 499         byte[] temp = authenticator.asn1Encode();
 500 
 501         EncryptedData encAuthenticator =
 502             new EncryptedData(key, temp, usage);
 503 
 504         apReqMessg =
 505             new APReq(apOptions, ticket, encAuthenticator);
 506     }
 507 
 508      // Check that key is one of the permitted types
 509      private static void checkPermittedEType(int target) throws KrbException {
 510         int[] etypes = EType.getDefaults("permitted_enctypes");
 511         if (!EType.isSupported(target, etypes)) {
 512             throw new KrbException(EType.toString(target) +
 513                 " encryption type not in permitted_enctypes list");
 514         }
 515      }
 516 }