/* * Copyright (c) 2000, 2013, 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.*; import sun.security.jgss.krb5.Krb5AcceptCredential; import java.net.InetAddress; import sun.security.util.*; import java.io.IOException; import java.util.Arrays; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import sun.security.krb5.internal.rcache.AuthTimeWithHash; /** * This class encapsulates a KRB-AP-REQ that a client sends to a * server for authentication. */ public class KrbApReq { private byte[] obuf; private KerberosTime ctime; private int cusec; private Authenticator authenticator; private Credentials creds; private APReq apReqMessg; // Used by acceptor side private static ReplayCache rcache = ReplayCache.getInstance(); private static boolean DEBUG = Krb5.DEBUG; private static final char[] hexConst = "0123456789ABCDEF".toCharArray(); /** * Constructs an AP-REQ message to send to the peer. * @param tgsCred the Credentials to be used to construct the * AP Request protocol message. * @param mutualRequired Whether mutual authentication is required * @param useSubkey Whether the subkey is to be used to protect this * specific application session. If this is not set then the * session key from the ticket will be used. * @throws KrbException for any Kerberos protocol specific error * @throws IOException for any IO related errors * (e.g. socket operations) */ /* // Not Used public KrbApReq(Credentials tgsCred, boolean mutualRequired, boolean useSubKey, boolean useSeqNumber) throws Asn1Exception, KrbCryptoException, KrbException, IOException { this(tgsCred, mutualRequired, useSubKey, useSeqNumber, null); } */ /** * Constructs an AP-REQ message to send to the peer. * @param tgsCred the Credentials to be used to construct the * AP Request protocol message. * @param mutualRequired Whether mutual authentication is required * @param useSubKey Whether the subkey is to be used to protect this * specific application session. If this is not set then the * session key from the ticket will be used. * @param cksum checksum of the application data that accompanies * the KRB_AP_REQ. * @throws KrbException for any Kerberos protocol specific error * @throws IOException for any IO related errors * (e.g. socket operations) */ // Used in InitSecContextToken public KrbApReq(Credentials tgsCred, boolean mutualRequired, boolean useSubKey, boolean useSeqNumber, Checksum cksum) throws Asn1Exception, KrbCryptoException, KrbException, IOException { APOptions apOptions = (mutualRequired? new APOptions(Krb5.AP_OPTS_MUTUAL_REQUIRED): new APOptions()); if (DEBUG) System.out.println(">>> KrbApReq: APOptions are " + apOptions); EncryptionKey subKey = (useSubKey? new EncryptionKey(tgsCred.getSessionKey()): null); SeqNumber seqNum = new LocalSeqNumber(); init(apOptions, tgsCred, cksum, subKey, seqNum, null, // AuthorizationData authzData KeyUsage.KU_AP_REQ_AUTHENTICATOR); } /** * Constructs an AP-REQ message from the bytes received from the * peer. * @param message The message received from the peer * @param cred KrbAcceptCredential containing keys to decrypt * the message; key selected will depend on etype used to encrypt data * @throws KrbException for any Kerberos protocol specific error * @throws IOException for any IO related errors * (e.g. socket operations) */ // Used in InitSecContextToken (for AP_REQ and not TGS REQ) public KrbApReq(byte[] message, Krb5AcceptCredential cred, InetAddress initiator) throws KrbException, IOException { obuf = message; if (apReqMessg == null) decode(); authenticate(cred, initiator); } /** * Constructs an AP-REQ message from the bytes received from the * peer. * @param value The DerValue that contains the * DER enoded AP-REQ protocol message * @param keys EncrtyptionKeys to decrypt the message; * * @throws KrbException for any Kerberos protocol specific error * @throws IOException for any IO related errors * (e.g. socket operations) */ /* public KrbApReq(DerValue value, EncryptionKey[] key, InetAddress initiator) throws KrbException, IOException { obuf = value.toByteArray(); if (apReqMessg == null) decode(value); authenticate(keys, initiator); } KrbApReq(APOptions options, Credentials tgs_creds, Checksum cksum, EncryptionKey subKey, SeqNumber seqNumber, AuthorizationData authorizationData) throws KrbException, IOException { init(options, tgs_creds, cksum, subKey, seqNumber, authorizationData); } */ /** used by KrbTgsReq **/ KrbApReq(APOptions apOptions, Ticket ticket, EncryptionKey key, PrincipalName cname, Checksum cksum, KerberosTime ctime, EncryptionKey subKey, SeqNumber seqNumber, AuthorizationData authorizationData) throws Asn1Exception, IOException, KdcErrException, KrbCryptoException { init(apOptions, ticket, key, cname, cksum, ctime, subKey, seqNumber, authorizationData, KeyUsage.KU_PA_TGS_REQ_AUTHENTICATOR); } private void init(APOptions options, Credentials tgs_creds, Checksum cksum, EncryptionKey subKey, SeqNumber seqNumber, AuthorizationData authorizationData, int usage) throws KrbException, IOException { ctime = KerberosTime.now(); init(options, tgs_creds.ticket, tgs_creds.key, tgs_creds.client, cksum, ctime, subKey, seqNumber, authorizationData, usage); } private void init(APOptions apOptions, Ticket ticket, EncryptionKey key, PrincipalName cname, Checksum cksum, KerberosTime ctime, EncryptionKey subKey, SeqNumber seqNumber, AuthorizationData authorizationData, int usage) throws Asn1Exception, IOException, KdcErrException, KrbCryptoException { createMessage(apOptions, ticket, key, cname, cksum, ctime, subKey, seqNumber, authorizationData, usage); obuf = apReqMessg.asn1Encode(); } void decode() throws KrbException, IOException { DerValue encoding = new DerValue(obuf); decode(encoding); } void decode(DerValue encoding) throws KrbException, IOException { apReqMessg = null; try { apReqMessg = new APReq(encoding); } catch (Asn1Exception e) { apReqMessg = null; KRBError err = new KRBError(encoding); String errStr = err.getErrorString(); String eText; if (errStr.charAt(errStr.length() - 1) == 0) eText = errStr.substring(0, errStr.length() - 1); else eText = errStr; KrbException ke = new KrbException(err.getErrorCode(), eText); ke.initCause(e); throw ke; } } private void authenticate(Krb5AcceptCredential cred, InetAddress initiator) throws KrbException, IOException { int encPartKeyType = apReqMessg.ticket.encPart.getEType(); Integer kvno = apReqMessg.ticket.encPart.getKeyVersionNumber(); EncryptionKey[] keys = cred.getKrb5EncryptionKeys(apReqMessg.ticket.sname); EncryptionKey dkey = EncryptionKey.findKey(encPartKeyType, kvno, keys); if (dkey == null) { throw new KrbException(Krb5.API_INVALID_ARG, "Cannot find key of appropriate type to decrypt AP-REQ - " + EType.toString(encPartKeyType)); } byte[] bytes = apReqMessg.ticket.encPart.decrypt(dkey, KeyUsage.KU_TICKET); byte[] temp = apReqMessg.ticket.encPart.reset(bytes); EncTicketPart enc_ticketPart = new EncTicketPart(temp); checkPermittedEType(enc_ticketPart.key.getEType()); byte[] bytes2 = apReqMessg.authenticator.decrypt(enc_ticketPart.key, KeyUsage.KU_AP_REQ_AUTHENTICATOR); byte[] temp2 = apReqMessg.authenticator.reset(bytes2); authenticator = new Authenticator(temp2); ctime = authenticator.ctime; cusec = authenticator.cusec; authenticator.ctime = authenticator.ctime.withMicroSeconds(authenticator.cusec); if (!authenticator.cname.equals(enc_ticketPart.cname)) { throw new KrbApErrException(Krb5.KRB_AP_ERR_BADMATCH); } if (!authenticator.ctime.inClockSkew()) throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW); String alg = AuthTimeWithHash.DEFAULT_HASH_ALG; byte[] hash; try { hash = MessageDigest.getInstance(AuthTimeWithHash.realAlg(alg)) .digest(apReqMessg.authenticator.cipher); } catch (NoSuchAlgorithmException ex) { throw new AssertionError("Impossible"); } char[] h = new char[hash.length * 2]; for (int i=0; i>4]; h[2*i+1] = hexConst[hash[i]&0xf]; } AuthTimeWithHash time = new AuthTimeWithHash( authenticator.cname.toString(), apReqMessg.ticket.sname.toString(), authenticator.ctime.getSeconds(), authenticator.cusec, alg, new String(h)); rcache.checkAndStore(KerberosTime.now(), time); if (initiator != null) { // sender host address HostAddress sender = new HostAddress(initiator); if (enc_ticketPart.caddr != null && !enc_ticketPart.caddr.inList(sender)) { if (DEBUG) { System.out.println(">>> KrbApReq: initiator is " + sender.getInetAddress() + ", but caddr is " + Arrays.toString( enc_ticketPart.caddr.getInetAddresses())); } throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR); } } // XXX check for repeated authenticator // if found // throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT); // else // save authenticator to check for later KerberosTime now = KerberosTime.now(); if ((enc_ticketPart.starttime != null && enc_ticketPart.starttime.greaterThanWRTClockSkew(now)) || enc_ticketPart.flags.get(Krb5.TKT_OPTS_INVALID)) throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_NYV); // if the current time is later than end time by more // than the allowable clock skew, throws ticket expired exception. if (enc_ticketPart.endtime != null && now.greaterThanWRTClockSkew(enc_ticketPart.endtime)) { throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_EXPIRED); } creds = new Credentials( apReqMessg.ticket, authenticator.cname, apReqMessg.ticket.sname, enc_ticketPart.key, enc_ticketPart.flags, enc_ticketPart.authtime, enc_ticketPart.starttime, enc_ticketPart.endtime, enc_ticketPart.renewTill, enc_ticketPart.caddr, enc_ticketPart.authorizationData); if (DEBUG) { System.out.println(">>> KrbApReq: authenticate succeed."); } } /** * Returns the credentials that are contained in the ticket that * is part of this AP-REQ. */ public Credentials getCreds() { return creds; } KerberosTime getCtime() { if (ctime != null) return ctime; return authenticator.ctime; } int cusec() { return cusec; } APOptions getAPOptions() throws KrbException, IOException { if (apReqMessg == null) decode(); if (apReqMessg != null) return apReqMessg.apOptions; return null; } /** * Returns true if mutual authentication is required and hence an * AP-REP will need to be generated. * @throws KrbException * @throws IOException */ public boolean getMutualAuthRequired() throws KrbException, IOException { if (apReqMessg == null) decode(); if (apReqMessg != null) return apReqMessg.apOptions.get(Krb5.AP_OPTS_MUTUAL_REQUIRED); return false; } boolean useSessionKey() throws KrbException, IOException { if (apReqMessg == null) decode(); if (apReqMessg != null) return apReqMessg.apOptions.get(Krb5.AP_OPTS_USE_SESSION_KEY); return false; } /** * Returns the optional subkey stored in the Authenticator for * this message. Returns null if none is stored. */ public EncryptionKey getSubKey() { // XXX Can authenticator be null return authenticator.getSubKey(); } /** * Returns the optional sequence number stored in the * Authenticator for this message. Returns null if none is * stored. */ public Integer getSeqNumber() { // XXX Can authenticator be null return authenticator.getSeqNumber(); } /** * Returns the optional Checksum stored in the * Authenticator for this message. Returns null if none is * stored. */ public Checksum getChecksum() { return authenticator.getChecksum(); } /** * Returns the ASN.1 encoding that should be sent to the peer. */ public byte[] getMessage() { return obuf; } /** * Returns the principal name of the client that generated this * message. */ public PrincipalName getClient() { return creds.getClient(); } private void createMessage(APOptions apOptions, Ticket ticket, EncryptionKey key, PrincipalName cname, Checksum cksum, KerberosTime ctime, EncryptionKey subKey, SeqNumber seqNumber, AuthorizationData authorizationData, int usage) throws Asn1Exception, IOException, KdcErrException, KrbCryptoException { Integer seqno = null; if (seqNumber != null) seqno = seqNumber.current(); authenticator = new Authenticator(cname, cksum, ctime.getMicroSeconds(), ctime, subKey, seqno, authorizationData); byte[] temp = authenticator.asn1Encode(); EncryptedData encAuthenticator = new EncryptedData(key, temp, usage); apReqMessg = new APReq(apOptions, ticket, encAuthenticator); } // Check that key is one of the permitted types private static void checkPermittedEType(int target) throws KrbException { int[] etypes = EType.getDefaults("permitted_enctypes"); if (!EType.isSupported(target, etypes)) { throw new KrbException(EType.toString(target) + " encryption type not in permitted_enctypes list"); } } }