1 /*
   2  * Copyright (c) 2000, 2019, 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 java.io.IOException;
  37 import java.net.UnknownHostException;
  38 import java.time.Instant;
  39 import java.util.Arrays;
  40 
  41 /**
  42  * This class encapsulates a Kerberos TGS-REQ that is sent from the
  43  * client to the KDC.
  44  */
  45 public class KrbTgsReq {
  46 
  47     private PrincipalName princName;
  48     private PrincipalName servName;
  49     private TGSReq tgsReqMessg;
  50     private KerberosTime ctime;
  51     private Ticket secondTicket = null;
  52     private boolean useSubkey = false;
  53     EncryptionKey tgsReqKey;
  54 
  55     private static final boolean DEBUG = Krb5.DEBUG;
  56 
  57     private byte[] obuf;
  58     private byte[] ibuf;
  59 
  60     // Used in CredentialsUtil
  61     public KrbTgsReq(KDCOptions options, Credentials asCreds,
  62             PrincipalName cname, PrincipalName sname,
  63             Ticket[] additionalTickets, PAData[] extraPAs)
  64         throws KrbException, IOException {
  65         this(options,
  66              asCreds,
  67              cname,
  68              sname,
  69              null, // KerberosTime from
  70              null, // KerberosTime till
  71              null, // KerberosTime rtime
  72              null, // int[] eTypes
  73              null, // HostAddresses addresses
  74              null, // AuthorizationData authorizationData
  75              additionalTickets,
  76              null, // EncryptionKey subKey
  77              extraPAs);
  78     }
  79 
  80     // Called by Credentials, KrbCred
  81     KrbTgsReq(
  82             KDCOptions options,
  83             Credentials asCreds,
  84             PrincipalName sname,
  85             KerberosTime from,
  86             KerberosTime till,
  87             KerberosTime rtime,
  88             int[] eTypes,
  89             HostAddresses addresses,
  90             AuthorizationData authorizationData,
  91             Ticket[] additionalTickets,
  92             EncryptionKey subKey) throws KrbException, IOException {
  93         this(options, asCreds, asCreds.getClient(), sname,
  94                 from, till, rtime, eTypes, addresses,
  95                 authorizationData, additionalTickets, subKey, null);
  96     }
  97 
  98     private KrbTgsReq(
  99             KDCOptions options,
 100             Credentials asCreds,
 101             PrincipalName cname,
 102             PrincipalName sname,
 103             KerberosTime from,
 104             KerberosTime till,
 105             KerberosTime rtime,
 106             int[] eTypes,
 107             HostAddresses addresses,
 108             AuthorizationData authorizationData,
 109             Ticket[] additionalTickets,
 110             EncryptionKey subKey,
 111             PAData[] extraPAs) throws KrbException, IOException {
 112 
 113         princName = cname;
 114         servName = sname;
 115         ctime = KerberosTime.now();
 116 
 117         // check if they are valid arguments. The optional fields
 118         // should be consistent with settings in KDCOptions.
 119 
 120         if (options.get(KDCOptions.FORWARDABLE) &&
 121                 (!(asCreds.flags.get(Krb5.TKT_OPTS_FORWARDABLE)))) {
 122             options.set(KDCOptions.FORWARDABLE, false);
 123         }
 124         if (options.get(KDCOptions.FORWARDED)) {
 125             if (!(asCreds.flags.get(KDCOptions.FORWARDABLE)))
 126                 throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
 127         }
 128         if (options.get(KDCOptions.PROXIABLE) &&
 129                 (!(asCreds.flags.get(Krb5.TKT_OPTS_PROXIABLE)))) {
 130             throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
 131         }
 132         if (options.get(KDCOptions.PROXY)) {
 133             if (!(asCreds.flags.get(KDCOptions.PROXIABLE)))
 134                 throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
 135         }
 136         if (options.get(KDCOptions.ALLOW_POSTDATE) &&
 137                 (!(asCreds.flags.get(Krb5.TKT_OPTS_MAY_POSTDATE)))) {
 138             throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
 139         }
 140         if (options.get(KDCOptions.RENEWABLE) &&
 141                 (!(asCreds.flags.get(Krb5.TKT_OPTS_RENEWABLE)))) {
 142             throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
 143         }
 144 
 145         if (options.get(KDCOptions.POSTDATED)) {
 146             if (!(asCreds.flags.get(KDCOptions.POSTDATED)))
 147                 throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
 148         } else {
 149             if (from != null)  from = null;
 150         }
 151         if (options.get(KDCOptions.RENEWABLE)) {
 152             if (!(asCreds.flags.get(KDCOptions.RENEWABLE)))
 153                 throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
 154         } else {
 155             if (rtime != null)  rtime = null;
 156         }
 157         if (options.get(KDCOptions.ENC_TKT_IN_SKEY) || options.get(KDCOptions.CNAME_IN_ADDL_TKT)) {
 158             if (additionalTickets == null)
 159                 throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS);
 160             // in TGS_REQ there could be more than one additional
 161             // tickets,  but in file-based credential cache,
 162             // there is only one additional ticket field.
 163             secondTicket = additionalTickets[0];
 164         } else {
 165             if (additionalTickets != null)
 166                 additionalTickets = null;
 167         }
 168 
 169         tgsReqMessg = createRequest(
 170                 options,
 171                 asCreds.ticket,
 172                 asCreds.key,
 173                 ctime,
 174                 princName,
 175                 servName,
 176                 from,
 177                 till,
 178                 rtime,
 179                 eTypes,
 180                 addresses,
 181                 authorizationData,
 182                 additionalTickets,
 183                 subKey,
 184                 extraPAs);
 185         obuf = tgsReqMessg.asn1Encode();
 186 
 187         // XXX We need to revisit this to see if can't move it
 188         // up such that FORWARDED flag set in the options
 189         // is included in the marshaled request.
 190         /*
 191          * If this is based on a forwarded ticket, record that in the
 192          * options, because the returned TgsRep will contain the
 193          * FORWARDED flag set.
 194          */
 195         if (asCreds.flags.get(KDCOptions.FORWARDED))
 196             options.set(KDCOptions.FORWARDED, true);
 197 
 198 
 199     }
 200 
 201     /**
 202      * Sends a TGS request to the realm of the target.
 203      * @throws KrbException
 204      * @throws IOException
 205      */
 206     public void send() throws IOException, KrbException {
 207         String realmStr = null;
 208         if (servName != null)
 209             realmStr = servName.getRealmString();
 210         KdcComm comm = new KdcComm(realmStr);
 211         ibuf = comm.send(obuf);
 212     }
 213 
 214     public KrbTgsRep getReply()
 215         throws KrbException, IOException {
 216         return new KrbTgsRep(ibuf, this);
 217     }
 218 
 219     /**
 220      * Sends the request, waits for a reply, and returns the Credentials.
 221      * Used in Credentials, KrbCred, and internal/CredentialsUtil.
 222      */
 223     public Credentials sendAndGetCreds() throws IOException, KrbException {
 224         KrbTgsRep tgs_rep = null;
 225         String kdc = null;
 226         send();
 227         tgs_rep = getReply();
 228         return tgs_rep.getCreds();
 229     }
 230 
 231     KerberosTime getCtime() {
 232         return ctime;
 233     }
 234 
 235     private TGSReq createRequest(
 236                          KDCOptions kdc_options,
 237                          Ticket ticket,
 238                          EncryptionKey key,
 239                          KerberosTime ctime,
 240                          PrincipalName cname,
 241                          PrincipalName sname,
 242                          KerberosTime from,
 243                          KerberosTime till,
 244                          KerberosTime rtime,
 245                          int[] eTypes,
 246                          HostAddresses addresses,
 247                          AuthorizationData authorizationData,
 248                          Ticket[] additionalTickets,
 249                          EncryptionKey subKey,
 250                          PAData[] extraPAs)
 251         throws IOException, KrbException, UnknownHostException {
 252         KerberosTime req_till = null;
 253         if (till == null) {
 254             String d = Config.getInstance().get("libdefaults", "ticket_lifetime");
 255             if (d != null) {
 256                 req_till = new KerberosTime(Instant.now().plusSeconds(Config.duration(d)));
 257             } else {
 258                 req_till = new KerberosTime(0); // Choose KDC maximum allowed
 259             }
 260         } else {
 261             req_till = till;
 262         }
 263 
 264         /*
 265          * RFC 4120, Section 5.4.2.
 266          * For KRB_TGS_REP, the ciphertext is encrypted in the
 267          * sub-session key from the Authenticator, or if absent,
 268          * the session key from the ticket-granting ticket used
 269          * in the request.
 270          *
 271          * To support this, use tgsReqKey to remember which key to use.
 272          */
 273         tgsReqKey = key;
 274 
 275         int[] req_eTypes = null;
 276         if (eTypes == null) {
 277             req_eTypes = EType.getDefaults("default_tgs_enctypes");
 278         } else {
 279             req_eTypes = eTypes;
 280         }
 281 
 282         EncryptionKey reqKey = null;
 283         EncryptedData encAuthorizationData = null;
 284         if (authorizationData != null) {
 285             byte[] ad = authorizationData.asn1Encode();
 286             if (subKey != null) {
 287                 reqKey = subKey;
 288                 tgsReqKey = subKey;    // Key to use to decrypt reply
 289                 useSubkey = true;
 290                 encAuthorizationData = new EncryptedData(reqKey, ad,
 291                     KeyUsage.KU_TGS_REQ_AUTH_DATA_SUBKEY);
 292             } else
 293                 encAuthorizationData = new EncryptedData(key, ad,
 294                     KeyUsage.KU_TGS_REQ_AUTH_DATA_SESSKEY);
 295         }
 296 
 297         KDCReqBody reqBody = new KDCReqBody(
 298                                             kdc_options,
 299                                             cname,
 300                                             sname,
 301                                             from,
 302                                             req_till,
 303                                             rtime,
 304                                             Nonce.value(),
 305                                             req_eTypes,
 306                                             addresses,
 307                                             encAuthorizationData,
 308                                             additionalTickets);
 309 
 310         byte[] temp = reqBody.asn1Encode(Krb5.KRB_TGS_REQ);
 311         // if the checksum type is one of the keyed checksum types,
 312         // use session key.
 313         Checksum cksum;
 314         switch (Checksum.CKSUMTYPE_DEFAULT) {
 315         case Checksum.CKSUMTYPE_RSA_MD4_DES:
 316         case Checksum.CKSUMTYPE_DES_MAC:
 317         case Checksum.CKSUMTYPE_DES_MAC_K:
 318         case Checksum.CKSUMTYPE_RSA_MD4_DES_K:
 319         case Checksum.CKSUMTYPE_RSA_MD5_DES:
 320         case Checksum.CKSUMTYPE_HMAC_SHA1_DES3_KD:
 321         case Checksum.CKSUMTYPE_HMAC_MD5_ARCFOUR:
 322         case Checksum.CKSUMTYPE_HMAC_SHA1_96_AES128:
 323         case Checksum.CKSUMTYPE_HMAC_SHA1_96_AES256:
 324         case Checksum.CKSUMTYPE_HMAC_SHA256_128_AES128:
 325         case Checksum.CKSUMTYPE_HMAC_SHA384_192_AES256:
 326             cksum = new Checksum(Checksum.CKSUMTYPE_DEFAULT, temp, key,
 327                 KeyUsage.KU_PA_TGS_REQ_CKSUM);
 328             break;
 329         case Checksum.CKSUMTYPE_CRC32:
 330         case Checksum.CKSUMTYPE_RSA_MD4:
 331         case Checksum.CKSUMTYPE_RSA_MD5:
 332         default:
 333             cksum = new Checksum(Checksum.CKSUMTYPE_DEFAULT, temp);
 334         }
 335 
 336         // Usage will be KeyUsage.KU_PA_TGS_REQ_AUTHENTICATOR
 337 
 338         byte[] tgs_ap_req = new KrbApReq(
 339                                          new APOptions(),
 340                                          ticket,
 341                                          key,
 342                                          cname,
 343                                          cksum,
 344                                          ctime,
 345                                          reqKey,
 346                                          null,
 347                                          null).getMessage();
 348 
 349         PAData tgsPAData = new PAData(Krb5.PA_TGS_REQ, tgs_ap_req);
 350         PAData[] pa;
 351         if (extraPAs != null) {
 352             pa = Arrays.copyOf(extraPAs, extraPAs.length + 1);
 353             pa[extraPAs.length] = tgsPAData;
 354         } else {
 355             pa = new PAData[] {tgsPAData};
 356         }
 357         return new TGSReq(pa, reqBody);
 358     }
 359 
 360     TGSReq getMessage() {
 361         return tgsReqMessg;
 362     }
 363 
 364     Ticket getSecondTicket() {
 365         return secondTicket;
 366     }
 367 
 368     private static void debug(String message) {
 369         //      System.err.println(">>> KrbTgsReq: " + message);
 370     }
 371 
 372     boolean usedSubkey() {
 373         return useSubkey;
 374     }
 375 
 376 }