3054N/A/*
4102N/A * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
3054N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3054N/A *
3054N/A * This code is free software; you can redistribute it and/or modify it
3054N/A * under the terms of the GNU General Public License version 2 only, as
3054N/A * published by the Free Software Foundation. Oracle designates this
3054N/A * particular file as subject to the "Classpath" exception as provided
3054N/A * by Oracle in the LICENSE file that accompanied this code.
3054N/A *
3054N/A * This code is distributed in the hope that it will be useful, but WITHOUT
3054N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
3054N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
3054N/A * version 2 for more details (a copy is included in the LICENSE file that
3054N/A * accompanied this code).
3054N/A *
3054N/A * You should have received a copy of the GNU General Public License version
3054N/A * 2 along with this work; if not, write to the Free Software Foundation,
3054N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
3054N/A *
3054N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
3054N/A * or visit www.oracle.com if you need additional information or have any
3054N/A * questions.
3054N/A */
3054N/A
3054N/Apackage sun.security.krb5;
3054N/A
3054N/Aimport java.io.IOException;
3054N/Aimport java.util.Arrays;
4102N/Aimport javax.security.auth.kerberos.KeyTab;
4102N/Aimport sun.security.jgss.krb5.Krb5Util;
3054N/Aimport sun.security.krb5.internal.HostAddresses;
3054N/Aimport sun.security.krb5.internal.KDCOptions;
3054N/Aimport sun.security.krb5.internal.KRBError;
3054N/Aimport sun.security.krb5.internal.KerberosTime;
3054N/Aimport sun.security.krb5.internal.Krb5;
3054N/Aimport sun.security.krb5.internal.PAData;
3054N/Aimport sun.security.krb5.internal.crypto.EType;
3054N/A
3054N/A/**
3054N/A * A manager class for AS-REQ communications.
3054N/A *
3054N/A * This class does:
3054N/A * 1. Gather information to create AS-REQ
3054N/A * 2. Create and send AS-REQ
3054N/A * 3. Receive AS-REP and KRB-ERROR (-KRB_ERR_RESPONSE_TOO_BIG) and parse them
4102N/A * 4. Emit credentials and secret keys (for JAAS storeKey=true with password)
3054N/A *
3054N/A * This class does not:
3054N/A * 1. Deal with real communications (KdcComm does it, and TGS-REQ)
3054N/A * a. Name of KDCs for a realm
3054N/A * b. Server availability, timeout, UDP or TCP
3054N/A * d. KRB_ERR_RESPONSE_TOO_BIG
4102N/A * 2. Stores its own copy of password, this means:
4102N/A * a. Do not change/wipe it before Builder finish
4102N/A * b. Builder will not wipe it for you
3054N/A *
3054N/A * With this class:
3054N/A * 1. KrbAsReq has only one constructor
3054N/A * 2. Krb5LoginModule and Kinit call a single builder
3054N/A * 3. Better handling of sensitive info
3054N/A *
3054N/A * @since 1.7
3054N/A */
3054N/A
3054N/Apublic final class KrbAsReqBuilder {
3054N/A
3054N/A // Common data for AS-REQ fields
3054N/A private KDCOptions options;
3054N/A private PrincipalName cname;
3054N/A private PrincipalName sname;
3054N/A private KerberosTime from;
3054N/A private KerberosTime till;
3054N/A private KerberosTime rtime;
3054N/A private HostAddresses addresses;
3054N/A
3054N/A // Secret source: can't be changed once assigned, only one (of the two
4102N/A // sources) can be set to non-null
4102N/A private final char[] password;
4102N/A private final KeyTab ktab;
3054N/A
3054N/A // Used to create a ENC-TIMESTAMP in the 2nd AS-REQ
3054N/A private PAData[] paList; // PA-DATA from both KRB-ERROR and AS-REP.
3054N/A // Used by getKeys() only.
3054N/A // Only AS-REP should be enough per RFC,
3054N/A // combined in case etypes are different.
3054N/A
3054N/A // The generated and received:
3054N/A private KrbAsReq req;
3054N/A private KrbAsRep rep;
3054N/A
3054N/A private static enum State {
3054N/A INIT, // Initialized, can still add more initialization info
3054N/A REQ_OK, // AS-REQ performed
3054N/A DESTROYED, // Destroyed, not usable anymore
3054N/A }
3054N/A private State state;
3054N/A
3054N/A // Called by other constructors
4102N/A private void init(PrincipalName cname)
3054N/A throws KrbException {
3054N/A if (cname.getRealm() == null) {
3054N/A cname.setRealm(Config.getInstance().getDefaultRealm());
3054N/A }
3054N/A this.cname = cname;
3054N/A state = State.INIT;
3054N/A }
3054N/A
3054N/A /**
3054N/A * Creates a builder to be used by {@code cname} with existing keys.
3054N/A *
3054N/A * @param cname the client of the AS-REQ. Must not be null. Might have no
3054N/A * realm, where default realm will be used. This realm will be the target
3054N/A * realm for AS-REQ. I believe a client should only get initial TGT from
3054N/A * its own realm.
3054N/A * @param keys must not be null. if empty, might be quite useless.
3054N/A * This argument will neither be modified nor stored by the method.
3054N/A * @throws KrbException
3054N/A */
4102N/A public KrbAsReqBuilder(PrincipalName cname, KeyTab ktab)
3054N/A throws KrbException {
4102N/A init(cname);
4102N/A this.ktab = ktab;
4102N/A this.password = null;
3054N/A }
3054N/A
3054N/A /**
3054N/A * Creates a builder to be used by {@code cname} with a known password.
3054N/A *
3054N/A * @param cname the client of the AS-REQ. Must not be null. Might have no
3054N/A * realm, where default realm will be used. This realm will be the target
3054N/A * realm for AS-REQ. I believe a client should only get initial TGT from
3054N/A * its own realm.
3054N/A * @param pass must not be null. This argument will neither be modified
3054N/A * nor stored by the method.
3054N/A * @throws KrbException
3054N/A */
3054N/A public KrbAsReqBuilder(PrincipalName cname, char[] pass)
3054N/A throws KrbException {
4102N/A init(cname);
3054N/A this.password = pass.clone();
4102N/A this.ktab = null;
3054N/A }
3054N/A
3054N/A /**
4102N/A * Retrieves an array of secret keys for the client. This is used when
4415N/A * the client supplies password but need keys to act as an acceptor. For
4415N/A * an initiator, it must be called after AS-REQ is performed (state is OK).
4415N/A * For an acceptor, it can be called when this KrbAsReqBuilder object is
4415N/A * constructed (state is INIT).
4415N/A * @param isInitiator if the caller is an initiator
4102N/A * @return generated keys from password. PA-DATA from server might be used.
4102N/A * All "default_tkt_enctypes" keys will be generated, Never null.
4102N/A * @throws IllegalStateException if not constructed from a password
3054N/A * @throws KrbException
3054N/A */
4415N/A public EncryptionKey[] getKeys(boolean isInitiator) throws KrbException {
4415N/A checkState(isInitiator?State.REQ_OK:State.INIT, "Cannot get keys");
4102N/A if (password != null) {
4102N/A int[] eTypes = EType.getDefaults("default_tkt_enctypes");
3054N/A EncryptionKey[] result = new EncryptionKey[eTypes.length];
3054N/A
3054N/A /*
3054N/A * Returns an array of keys. Before KrbAsReqBuilder, all etypes
3054N/A * use the same salt which is either the default one or a new salt
3054N/A * coming from PA-DATA. After KrbAsReqBuilder, each etype uses its
3054N/A * own new salt from PA-DATA. For an etype with no PA-DATA new salt
3054N/A * at all, what salt should it use?
3054N/A *
3054N/A * Commonly, the stored keys are only to be used by an acceptor to
3054N/A * decrypt service ticket in AP-REQ. Most impls only allow keys
3054N/A * from a keytab on acceptor, but unfortunately (?) Java supports
3054N/A * acceptor using password. In this case, if the service ticket is
3054N/A * encrypted using an etype which we don't have PA-DATA new salt,
4391N/A * using the default salt might be wrong (say, case-insensitive
3054N/A * user name). Instead, we would use the new salt of another etype.
3054N/A */
3054N/A
3054N/A String salt = null; // the saved new salt
4391N/A try {
4391N/A for (int i=0; i<eTypes.length; i++) {
4391N/A // First round, only calculate those have a PA entry
3054N/A PAData.SaltAndParams snp =
3054N/A PAData.getSaltAndParams(eTypes[i], paList);
4391N/A if (snp != null) {
4391N/A // Never uses a salt for rc4-hmac, it does not use
4391N/A // a salt at all
4391N/A if (eTypes[i] != EncryptedData.ETYPE_ARCFOUR_HMAC &&
4391N/A snp.salt != null) {
4391N/A salt = snp.salt;
4391N/A }
4391N/A result[i] = EncryptionKey.acquireSecretKey(cname,
4391N/A password,
4391N/A eTypes[i],
4391N/A snp);
4391N/A }
3054N/A }
4391N/A // No new salt from PA, maybe empty, maybe only rc4-hmac
4391N/A if (salt == null) salt = cname.getSalt();
4391N/A for (int i=0; i<eTypes.length; i++) {
4391N/A // Second round, calculate those with no PA entry
4391N/A if (result[i] == null) {
4391N/A result[i] = EncryptionKey.acquireSecretKey(password,
4391N/A salt,
4391N/A eTypes[i],
4391N/A null);
4391N/A }
4391N/A }
4391N/A } catch (IOException ioe) {
4391N/A KrbException ke = new KrbException(Krb5.ASN1_PARSE_ERROR);
4391N/A ke.initCause(ioe);
4391N/A throw ke;
3054N/A }
3054N/A return result;
4102N/A } else {
4102N/A throw new IllegalStateException("Required password not provided");
3054N/A }
3054N/A }
3054N/A
3054N/A /**
3054N/A * Sets or clears options. If cleared, default options will be used
3054N/A * at creation time.
3054N/A * @param options
3054N/A */
3054N/A public void setOptions(KDCOptions options) {
3054N/A checkState(State.INIT, "Cannot specify options");
3054N/A this.options = options;
3054N/A }
3054N/A
3054N/A /**
3054N/A * Sets or clears target. If cleared, KrbAsReq might choose krbtgt
3054N/A * for cname realm
3054N/A * @param sname
3054N/A */
3054N/A public void setTarget(PrincipalName sname) {
3054N/A checkState(State.INIT, "Cannot specify target");
3054N/A this.sname = sname;
3054N/A }
3054N/A
3054N/A /**
3054N/A * Adds or clears addresses. KrbAsReq might add some if empty
3054N/A * field not allowed
3054N/A * @param addresses
3054N/A */
3054N/A public void setAddresses(HostAddresses addresses) {
3054N/A checkState(State.INIT, "Cannot specify addresses");
3054N/A this.addresses = addresses;
3054N/A }
3054N/A
3054N/A /**
3054N/A * Build a KrbAsReq object from all info fed above. Normally this method
3054N/A * will be called twice: initial AS-REQ and second with pakey
4102N/A * @param key null (initial AS-REQ) or pakey (with preauth)
3054N/A * @return the KrbAsReq object
3054N/A * @throws KrbException
3054N/A * @throws IOException
3054N/A */
4102N/A private KrbAsReq build(EncryptionKey key) throws KrbException, IOException {
4102N/A int[] eTypes;
4102N/A if (password != null) {
4102N/A eTypes = EType.getDefaults("default_tkt_enctypes");
4102N/A } else {
4102N/A EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname);
4102N/A eTypes = EType.getDefaults("default_tkt_enctypes",
4102N/A ks);
4102N/A for (EncryptionKey k: ks) k.destroy();
4102N/A }
4102N/A return new KrbAsReq(key,
3054N/A options,
3054N/A cname,
3054N/A sname,
3054N/A from,
3054N/A till,
3054N/A rtime,
3054N/A eTypes,
3054N/A addresses);
3054N/A }
3054N/A
3054N/A /**
3054N/A * Parses AS-REP, decrypts enc-part, retrieves ticket and session key
3054N/A * @throws KrbException
3054N/A * @throws Asn1Exception
3054N/A * @throws IOException
3054N/A */
4102N/A private KrbAsReqBuilder resolve()
4102N/A throws KrbException, Asn1Exception, IOException {
4102N/A if (ktab != null) {
4102N/A rep.decryptUsingKeyTab(ktab, req, cname);
3054N/A } else {
3054N/A rep.decryptUsingPassword(password, req, cname);
3054N/A }
3054N/A if (rep.getPA() != null) {
3054N/A if (paList == null || paList.length == 0) {
3054N/A paList = rep.getPA();
3054N/A } else {
3054N/A int extraLen = rep.getPA().length;
3054N/A if (extraLen > 0) {
3054N/A int oldLen = paList.length;
3054N/A paList = Arrays.copyOf(paList, paList.length + extraLen);
3054N/A System.arraycopy(rep.getPA(), 0, paList, oldLen, extraLen);
3054N/A }
3054N/A }
3054N/A }
3054N/A return this;
3054N/A }
3054N/A
3054N/A /**
3054N/A * Communication until AS-REP or non preauth-related KRB-ERROR received
3054N/A * @throws KrbException
3054N/A * @throws IOException
3054N/A */
3054N/A private KrbAsReqBuilder send() throws KrbException, IOException {
3054N/A boolean preAuthFailedOnce = false;
3054N/A KdcComm comm = new KdcComm(cname.getRealmAsString());
4102N/A EncryptionKey pakey = null;
3054N/A while (true) {
3054N/A try {
4102N/A req = build(pakey);
3054N/A rep = new KrbAsRep(comm.send(req.encoding()));
3054N/A return this;
3054N/A } catch (KrbException ke) {
3054N/A if (!preAuthFailedOnce && (
3054N/A ke.returnCode() == Krb5.KDC_ERR_PREAUTH_FAILED ||
3054N/A ke.returnCode() == Krb5.KDC_ERR_PREAUTH_REQUIRED)) {
3054N/A if (Krb5.DEBUG) {
3054N/A System.out.println("KrbAsReqBuilder: " +
3054N/A "PREAUTH FAILED/REQ, re-send AS-REQ");
3054N/A }
3054N/A preAuthFailedOnce = true;
3054N/A KRBError kerr = ke.getError();
4391N/A int paEType = PAData.getPreferredEType(kerr.getPA(),
4391N/A EType.getDefaults("default_tkt_enctypes")[0]);
3054N/A if (password == null) {
4102N/A EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname);
4391N/A pakey = EncryptionKey.findKey(paEType, ks);
4102N/A if (pakey != null) pakey = (EncryptionKey)pakey.clone();
4102N/A for (EncryptionKey k: ks) k.destroy();
3054N/A } else {
4391N/A pakey = EncryptionKey.acquireSecretKey(cname,
4391N/A password,
4391N/A paEType,
4391N/A PAData.getSaltAndParams(
4391N/A paEType, kerr.getPA()));
3054N/A }
3054N/A paList = kerr.getPA(); // Update current paList
3054N/A } else {
3054N/A throw ke;
3054N/A }
3054N/A }
3054N/A }
3054N/A }
3054N/A
3054N/A /**
3054N/A * Performs AS-REQ send and AS-REP receive.
3054N/A * Maybe a state is needed here, to divide prepare process and getCreds.
3054N/A * @throws KrbException
3054N/A * @throws Asn1Exception
3054N/A * @throws IOException
3054N/A */
3054N/A public KrbAsReqBuilder action()
3054N/A throws KrbException, Asn1Exception, IOException {
3054N/A checkState(State.INIT, "Cannot call action");
3054N/A state = State.REQ_OK;
3054N/A return send().resolve();
3054N/A }
3054N/A
3054N/A /**
3054N/A * Gets Credentials object after action
3054N/A */
3054N/A public Credentials getCreds() {
3054N/A checkState(State.REQ_OK, "Cannot retrieve creds");
3054N/A return rep.getCreds();
3054N/A }
3054N/A
3054N/A /**
3054N/A * Gets another type of Credentials after action
3054N/A */
3054N/A public sun.security.krb5.internal.ccache.Credentials getCCreds() {
3054N/A checkState(State.REQ_OK, "Cannot retrieve CCreds");
3054N/A return rep.getCCreds();
3054N/A }
3054N/A
3054N/A /**
3054N/A * Destroys the object and clears keys and password info.
3054N/A */
3054N/A public void destroy() {
3054N/A state = State.DESTROYED;
3054N/A if (password != null) {
3054N/A Arrays.fill(password, (char)0);
3054N/A }
3054N/A }
3054N/A
3054N/A /**
3054N/A * Checks if the current state is the specified one.
3054N/A * @param st the expected state
3054N/A * @param msg error message if state is not correct
3054N/A * @throws IllegalStateException if state is not correct
3054N/A */
3054N/A private void checkState(State st, String msg) {
3054N/A if (state != st) {
3054N/A throw new IllegalStateException(msg + " at " + st + " state");
3054N/A }
3054N/A }
3054N/A}