/*
* Copyright (c) 2000, 2011, 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 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.
/**
* 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();
private static final MessageDigest md;
static {
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("Impossible");
}
}
/**
* Constructs an AP-REQ message to send to the peer.
* @param tgsCred the <code>Credentials</code> 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.
Params: - tgsCred – the
Credentials
to be used to construct the
AP Request protocol message. - mutualRequired – Whether mutual authentication is required
- 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.
- checksum – checksum of the the application data that accompanies
the KRB_AP_REQ.
Throws: - KrbException – for any Kerberos protocol specific error
- IOException – for any IO related errors
(e.g. socket operations)
/**
* Constructs an AP-REQ message to send to the peer.
* @param tgsCred the <code>Credentials</code> 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 checksum checksum of the 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.
Params: - message – The message received from the peer
- keys –
EncrtyptionKey
s to decrypt the message;
key selected will depend on etype used to encrypte data
Throws: - KrbException – for any Kerberos protocol specific error
- IOException – for any IO related errors
(e.g. socket operations)
/**
* Constructs an AP-REQ message from the bytes received from the
* peer.
* @param message The message received from the peer
* @param keys <code>EncrtyptionKey</code>s to decrypt the message;
* key selected will depend on etype used to encrypte 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,
EncryptionKey[] keys,
InetAddress initiator)
throws KrbException, IOException {
obuf = message;
if (apReqMessg == null)
decode();
authenticate(keys, initiator);
}
/**
* Constructs an AP-REQ message from the bytes received from the
* peer.
* @param value The <code>DerValue</code> that contains the
* DER enoded AP-REQ protocol message
* @param keys <code>EncrtyptionKey</code>s 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 /** 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(EncryptionKey[] keys, InetAddress initiator)
throws KrbException, IOException {
int encPartKeyType = apReqMessg.ticket.encPart.getEType();
Integer kvno = apReqMessg.ticket.encPart.getKeyVersionNumber();
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 REP - " +
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);
byte[] hash = md.digest(apReqMessg.authenticator.cipher);
char[] h = new char[hash.length * 2];
for (int i=0; i<hash.length; i++) {
h[2*i] = hexConst[(hash[i]&0xff)>>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,
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 this AP-REP.
/**
* Returns the credentials that are contained in the ticket that
* is part of this this AP-REP.
*/
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 –
- IOException –
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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 = new Integer(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");
}
}
}