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