/* * Copyright (c) 2000, 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * (C) Copyright IBM Corp. 1999 All Rights Reserved. * Copyright 1997 The Open Group Research Institute. All rights reserved. */ package sun.security.krb5; import sun.security.krb5.internal.*; import sun.security.krb5.internal.crypto.EType; import sun.security.krb5.internal.crypto.Nonce; import sun.security.krb5.internal.crypto.KeyUsage; import sun.security.util.*; import java.io.IOException; import java.io.ByteArrayInputStream; import java.net.UnknownHostException; import java.util.StringTokenizer; /** * This class encapsulates the KRB-AS-REQ message that the client * sends to the KDC. */ public class KrbAsReq extends KrbKdcReq { private PrincipalName princName; private ASReq asReqMessg; private boolean DEBUG = Krb5.DEBUG; private static KDCOptions defaultKDCOptions = new KDCOptions(); // pre-auth info private boolean PA_ENC_TIMESTAMP_REQUIRED = false; private boolean pa_exists = false; private int pa_etype = 0; private byte[] pa_salt = null; private byte[] pa_s2kparams = null; // default is address-less tickets private boolean KDC_EMPTY_ADDRESSES_ALLOWED = true; /** * Creates a KRB-AS-REQ to send to the default KDC * @throws KrbException * @throws IOException */ // Called by Credentials KrbAsReq(PrincipalName principal, EncryptionKey[] keys) throws KrbException, IOException { this(keys, // for pre-authentication false, 0, null, null, // pre-auth values defaultKDCOptions, principal, null, // PrincipalName sname null, // KerberosTime from null, // KerberosTime till null, // KerberosTime rtime null, // int[] eTypes null, // HostAddresses addresses null); // Ticket[] additionalTickets } /** * Creates a KRB-AS-REQ to send to the default KDC * with pre-authentication values */ KrbAsReq(PrincipalName principal, EncryptionKey[] keys, boolean pa_exists, int etype, byte[] salt, byte[] s2kparams) throws KrbException, IOException { this(keys, // for pre-authentication pa_exists, etype, salt, s2kparams, // pre-auth values defaultKDCOptions, principal, null, // PrincipalName sname null, // KerberosTime from null, // KerberosTime till null, // KerberosTime rtime null, // int[] eTypes null, // HostAddresses addresses null); // Ticket[] additionalTickets } private static int[] getETypesFromKeys(EncryptionKey[] keys) { int[] types = new int[keys.length]; for (int i = 0; i < keys.length; i++) { types[i] = keys[i].getEType(); } return types; } // update with pre-auth info public void updatePA(int etype, byte[] salt, byte[] params, PrincipalName name) { // set the pre-auth values pa_exists = true; pa_etype = etype; pa_salt = salt; pa_s2kparams = params; // update salt in PrincipalName if (salt != null && salt.length > 0) { String newSalt = new String(salt); name.setSalt(newSalt); if (DEBUG) { System.out.println("Updated salt from pre-auth = " + name.getSalt()); } } PA_ENC_TIMESTAMP_REQUIRED = true; } // Used by Kinit public KrbAsReq( char[] password, KDCOptions options, PrincipalName cname, PrincipalName sname, KerberosTime from, KerberosTime till, KerberosTime rtime, int[] eTypes, HostAddresses addresses, Ticket[] additionalTickets) throws KrbException, IOException { this(password, false, 0, null, null, // pre-auth values options, cname, sname, // PrincipalName sname from, // KerberosTime from till, // KerberosTime till rtime, // KerberosTime rtime eTypes, // int[] eTypes addresses, // HostAddresses addresses additionalTickets); // Ticket[] additionalTickets } // Used by Kinit public KrbAsReq( char[] password, boolean pa_exists, int etype, byte[] salt, byte[] s2kparams, KDCOptions options, PrincipalName cname, PrincipalName sname, KerberosTime from, KerberosTime till, KerberosTime rtime, int[] eTypes, HostAddresses addresses, Ticket[] additionalTickets) throws KrbException, IOException { EncryptionKey[] keys = null; // update with preauth info if (pa_exists) { updatePA(etype, salt, s2kparams, cname); } if (password != null) { keys = EncryptionKey.acquireSecretKeys(password, cname.getSalt(), pa_exists, pa_etype, pa_s2kparams); } if (DEBUG) { System.out.println(">>>KrbAsReq salt is " + cname.getSalt()); } try { init( keys, options, cname, sname, from, till, rtime, eTypes, addresses, additionalTickets); } finally { /* * Its ok to destroy the key here because we created it and are * now done with it. */ if (keys != null) { for (int i = 0; i < keys.length; i++) { keys[i].destroy(); } } } } // Used in Kinit public KrbAsReq( EncryptionKey[] keys, KDCOptions options, PrincipalName cname, PrincipalName sname, KerberosTime from, KerberosTime till, KerberosTime rtime, int[] eTypes, HostAddresses addresses, Ticket[] additionalTickets) throws KrbException, IOException { this(keys, false, 0, null, null, // pre-auth values options, cname, sname, // PrincipalName sname from, // KerberosTime from till, // KerberosTime till rtime, // KerberosTime rtime eTypes, // int[] eTypes addresses, // HostAddresses addresses additionalTickets); // Ticket[] additionalTickets } // Used by Kinit public KrbAsReq( EncryptionKey[] keys, boolean pa_exists, int etype, byte[] salt, byte[] s2kparams, KDCOptions options, PrincipalName cname, PrincipalName sname, KerberosTime from, KerberosTime till, KerberosTime rtime, int[] eTypes, HostAddresses addresses, Ticket[] additionalTickets) throws KrbException, IOException { // update with preauth info if (pa_exists) { // update pre-auth info updatePA(etype, salt, s2kparams, cname); if (DEBUG) { System.out.println(">>>KrbAsReq salt is " + cname.getSalt()); } } init( keys, options, cname, sname, from, till, rtime, eTypes, addresses, additionalTickets); } /* private KrbAsReq(KDCOptions options, PrincipalName cname, PrincipalName sname, KerberosTime from, KerberosTime till, KerberosTime rtime, int[] eTypes, HostAddresses addresses, Ticket[] additionalTickets) throws KrbException, IOException { init(null, options, cname, sname, from, till, rtime, eTypes, addresses, additionalTickets); } */ private void init(EncryptionKey[] keys, KDCOptions options, PrincipalName cname, PrincipalName sname, KerberosTime from, KerberosTime till, KerberosTime rtime, int[] eTypes, HostAddresses addresses, Ticket[] additionalTickets ) throws KrbException, IOException { // check if they are valid arguments. The optional fields should be // consistent with settings in KDCOptions. Mar 17 2000 if (options.get(KDCOptions.FORWARDED) || options.get(KDCOptions.PROXY) || options.get(KDCOptions.ENC_TKT_IN_SKEY) || options.get(KDCOptions.RENEW) || options.get(KDCOptions.VALIDATE)) { // this option is only specified in a request to the // ticket-granting server throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS); } if (options.get(KDCOptions.POSTDATED)) { // if (from == null) // throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS); } else { if (from != null) from = null; } if (options.get(KDCOptions.RENEWABLE)) { // if (rtime == null) // throw new KrbException(Krb5.KRB_AP_ERR_REQ_OPTIONS); } else { if (rtime != null) rtime = null; } princName = cname; EncryptionKey key = null; int[] tktETypes = null; if (pa_exists && pa_etype != EncryptedData.ETYPE_NULL) { if (DEBUG) { System.out.println("Pre-Authenticaton: find key for etype = " + pa_etype); } key = EncryptionKey.findKey(pa_etype, keys); tktETypes = new int[1]; tktETypes[0] = pa_etype; } else { tktETypes = EType.getDefaults("default_tkt_enctypes", keys); key = EncryptionKey.findKey(tktETypes[0], keys); } PAData[] paData = null; if (PA_ENC_TIMESTAMP_REQUIRED) { if (DEBUG) { System.out.println("AS-REQ: Add PA_ENC_TIMESTAMP now"); } PAEncTSEnc ts = new PAEncTSEnc(); byte[] temp = ts.asn1Encode(); if (key != null) { // Use first key in list EncryptedData encTs = new EncryptedData(key, temp, KeyUsage.KU_PA_ENC_TS); paData = new PAData[1]; paData[0] = new PAData( Krb5.PA_ENC_TIMESTAMP, encTs.asn1Encode()); } } if (DEBUG) { System.out.println(">>> KrbAsReq calling createMessage"); } if (eTypes == null) { eTypes = tktETypes; } // check to use addresses in tickets if (Config.getInstance().useAddresses()) { KDC_EMPTY_ADDRESSES_ALLOWED = false; } // get the local InetAddress if required if (addresses == null && !KDC_EMPTY_ADDRESSES_ALLOWED) { addresses = HostAddresses.getLocalAddresses(); } asReqMessg = createMessage( paData, options, cname, cname.getRealm(), sname, from, till, rtime, eTypes, addresses, additionalTickets); obuf = asReqMessg.asn1Encode(); } /** * Returns an AS-REP message corresponding to the AS-REQ that * was sent. * @param password The password that will be used to derive the * secret key that will decrypt the AS-REP from the KDC. * @exception KrbException if an error occurs while reading the data. * @exception IOException if an I/O error occurs while reading encoded data. */ public KrbAsRep getReply(char[] password) throws KrbException, IOException { if (password == null) throw new KrbException(Krb5.API_INVALID_ARG); KrbAsRep temp = null; EncryptionKey[] keys = null; try { keys = EncryptionKey.acquireSecretKeys(password, princName.getSalt(), pa_exists, pa_etype, pa_s2kparams); temp = getReply(keys); } finally { /* * Its ok to destroy the key here because we created it and are * now done with it. */ if (keys != null) { for (int i = 0; i < keys.length; i++) { keys[i].destroy(); } } } return temp; } /** * Sends an AS request to the realm of the client. * returns the KDC hostname that the request was sent to */ public String send() throws IOException, KrbException { String realmStr = null; if (princName != null) realmStr = princName.getRealmString(); return (send(realmStr)); } /** * Returns an AS-REP message corresponding to the AS-REQ that * was sent. * @param keys The secret keys that will decrypt the AS-REP from * the KDC; key selected depends on etype used to encrypt data. * @exception KrbException if an error occurs while reading the data. * @exception IOException if an I/O error occurs while reading encoded * data. * */ public KrbAsRep getReply(EncryptionKey[] keys) throws KrbException,IOException { return new KrbAsRep(ibuf, keys, this); } private ASReq createMessage( PAData[] paData, KDCOptions kdc_options, PrincipalName cname, Realm crealm, PrincipalName sname, KerberosTime from, KerberosTime till, KerberosTime rtime, int[] eTypes, HostAddresses addresses, Ticket[] additionalTickets ) throws Asn1Exception, KrbApErrException, RealmException, UnknownHostException, IOException { if (DEBUG) { System.out.println(">>> KrbAsReq in createMessage"); } PrincipalName req_sname = null; if (sname == null) { if (crealm == null) { throw new RealmException(Krb5.REALM_NULL, "default realm not specified "); } req_sname = new PrincipalName( "krbtgt" + PrincipalName.NAME_COMPONENT_SEPARATOR + crealm.toString(), PrincipalName.KRB_NT_SRV_INST); } else req_sname = sname; KerberosTime req_till = null; if (till == null) { req_till = new KerberosTime(); } else { req_till = till; } KDCReqBody kdc_req_body = new KDCReqBody(kdc_options, cname, crealm, req_sname, from, req_till, rtime, Nonce.value(), eTypes, addresses, null, additionalTickets); return new ASReq( paData, kdc_req_body); } ASReq getMessage() { return asReqMessg; } }