454 lines
18 KiB
Java
454 lines
18 KiB
Java
/*
|
|
* 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.
|
|
*/
|
|
|
|
package sun.security.jgss.krb5;
|
|
|
|
import org.ietf.jgss.*;
|
|
import javax.security.auth.kerberos.DelegationPermission;
|
|
import java.io.IOException;
|
|
import java.net.InetAddress;
|
|
import java.net.Inet4Address;
|
|
import java.net.Inet6Address;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.Arrays;
|
|
import sun.security.krb5.*;
|
|
import sun.security.krb5.internal.Krb5;
|
|
|
|
abstract class InitialToken extends Krb5Token {
|
|
|
|
private static final int CHECKSUM_TYPE = 0x8003;
|
|
|
|
private static final int CHECKSUM_LENGTH_SIZE = 4;
|
|
private static final int CHECKSUM_BINDINGS_SIZE = 16;
|
|
private static final int CHECKSUM_FLAGS_SIZE = 4;
|
|
private static final int CHECKSUM_DELEG_OPT_SIZE = 2;
|
|
private static final int CHECKSUM_DELEG_LGTH_SIZE = 2;
|
|
|
|
private static final int CHECKSUM_DELEG_FLAG = 1;
|
|
private static final int CHECKSUM_MUTUAL_FLAG = 2;
|
|
private static final int CHECKSUM_REPLAY_FLAG = 4;
|
|
private static final int CHECKSUM_SEQUENCE_FLAG = 8;
|
|
private static final int CHECKSUM_CONF_FLAG = 16;
|
|
private static final int CHECKSUM_INTEG_FLAG = 32;
|
|
|
|
private final byte[] CHECKSUM_FIRST_BYTES =
|
|
{(byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00};
|
|
|
|
private static final int CHANNEL_BINDING_AF_INET = 2;
|
|
private static final int CHANNEL_BINDING_AF_INET6 = 24;
|
|
private static final int CHANNEL_BINDING_AF_NULL_ADDR = 255;
|
|
|
|
private static final int Inet4_ADDRSZ = 4;
|
|
private static final int Inet6_ADDRSZ = 16;
|
|
|
|
protected class OverloadedChecksum {
|
|
|
|
private byte[] checksumBytes = null;
|
|
private Credentials delegCreds = null;
|
|
private int flags = 0;
|
|
|
|
/**
|
|
* Called on the initiator side when creating the
|
|
* InitSecContextToken.
|
|
*/
|
|
public OverloadedChecksum(Krb5Context context,
|
|
Credentials tgt,
|
|
Credentials serviceTicket)
|
|
throws KrbException, IOException, GSSException {
|
|
|
|
byte[] krbCredMessage = null;
|
|
int pos = 0;
|
|
int size = CHECKSUM_LENGTH_SIZE + CHECKSUM_BINDINGS_SIZE +
|
|
CHECKSUM_FLAGS_SIZE;
|
|
|
|
if (!tgt.isForwardable()) {
|
|
context.setCredDelegState(false);
|
|
context.setDelegPolicyState(false);
|
|
} else if (context.getCredDelegState()) {
|
|
if (context.getDelegPolicyState()) {
|
|
if (!serviceTicket.checkDelegate()) {
|
|
// delegation not permitted by server policy, mark it
|
|
context.setDelegPolicyState(false);
|
|
}
|
|
}
|
|
} else if (context.getDelegPolicyState()) {
|
|
if (serviceTicket.checkDelegate()) {
|
|
context.setCredDelegState(true);
|
|
} else {
|
|
context.setDelegPolicyState(false);
|
|
}
|
|
}
|
|
|
|
if (context.getCredDelegState()) {
|
|
KrbCred krbCred = null;
|
|
CipherHelper cipherHelper =
|
|
context.getCipherHelper(serviceTicket.getSessionKey());
|
|
if (useNullKey(cipherHelper)) {
|
|
krbCred = new KrbCred(tgt, serviceTicket,
|
|
EncryptionKey.NULL_KEY);
|
|
} else {
|
|
krbCred = new KrbCred(tgt, serviceTicket,
|
|
serviceTicket.getSessionKey());
|
|
}
|
|
krbCredMessage = krbCred.getMessage();
|
|
size += CHECKSUM_DELEG_OPT_SIZE +
|
|
CHECKSUM_DELEG_LGTH_SIZE +
|
|
krbCredMessage.length;
|
|
}
|
|
|
|
checksumBytes = new byte[size];
|
|
|
|
checksumBytes[pos++] = CHECKSUM_FIRST_BYTES[0];
|
|
checksumBytes[pos++] = CHECKSUM_FIRST_BYTES[1];
|
|
checksumBytes[pos++] = CHECKSUM_FIRST_BYTES[2];
|
|
checksumBytes[pos++] = CHECKSUM_FIRST_BYTES[3];
|
|
|
|
ChannelBinding localBindings = context.getChannelBinding();
|
|
if (localBindings != null) {
|
|
byte[] localBindingsBytes =
|
|
computeChannelBinding(context.getChannelBinding());
|
|
System.arraycopy(localBindingsBytes, 0,
|
|
checksumBytes, pos, localBindingsBytes.length);
|
|
// System.out.println("ChannelBinding hash: "
|
|
// + getHexBytes(localBindingsBytes));
|
|
}
|
|
|
|
pos += CHECKSUM_BINDINGS_SIZE;
|
|
|
|
if (context.getCredDelegState())
|
|
flags |= CHECKSUM_DELEG_FLAG;
|
|
if (context.getMutualAuthState())
|
|
flags |= CHECKSUM_MUTUAL_FLAG;
|
|
if (context.getReplayDetState())
|
|
flags |= CHECKSUM_REPLAY_FLAG;
|
|
if (context.getSequenceDetState())
|
|
flags |= CHECKSUM_SEQUENCE_FLAG;
|
|
if (context.getIntegState())
|
|
flags |= CHECKSUM_INTEG_FLAG;
|
|
if (context.getConfState())
|
|
flags |= CHECKSUM_CONF_FLAG;
|
|
|
|
byte[] temp = new byte[4];
|
|
writeLittleEndian(flags, temp);
|
|
checksumBytes[pos++] = temp[0];
|
|
checksumBytes[pos++] = temp[1];
|
|
checksumBytes[pos++] = temp[2];
|
|
checksumBytes[pos++] = temp[3];
|
|
|
|
if (context.getCredDelegState()) {
|
|
|
|
PrincipalName delegateTo =
|
|
serviceTicket.getServer();
|
|
// Cannot use '\"' instead of "\"" in constructor because
|
|
// it is interpreted as suggested length!
|
|
StringBuffer buf = new StringBuffer("\"");
|
|
buf.append(delegateTo.getName()).append('\"');
|
|
String realm = delegateTo.getRealmAsString();
|
|
buf.append(" \"krbtgt/").append(realm).append('@');
|
|
buf.append(realm).append('\"');
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null) {
|
|
DelegationPermission perm =
|
|
new DelegationPermission(buf.toString());
|
|
sm.checkPermission(perm);
|
|
}
|
|
|
|
|
|
/*
|
|
* Write 1 in little endian but in two bytes
|
|
* for DlgOpt
|
|
*/
|
|
|
|
checksumBytes[pos++] = (byte)0x01;
|
|
checksumBytes[pos++] = (byte)0x00;
|
|
|
|
/*
|
|
* Write the length of the delegated credential in little
|
|
* endian but in two bytes for Dlgth
|
|
*/
|
|
|
|
if (krbCredMessage.length > 0x0000ffff)
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"Incorrect message length");
|
|
|
|
writeLittleEndian(krbCredMessage.length, temp);
|
|
checksumBytes[pos++] = temp[0];
|
|
checksumBytes[pos++] = temp[1];
|
|
System.arraycopy(krbCredMessage, 0,
|
|
checksumBytes, pos, krbCredMessage.length);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Called on the acceptor side when reading an InitSecContextToken.
|
|
*/
|
|
// XXX Passing in Checksum is not required. byte[] can
|
|
// be passed in if this checksum type denotes a
|
|
// raw_checksum. In that case, make Checksum class krb5
|
|
// internal.
|
|
public OverloadedChecksum(Krb5Context context, Checksum checksum,
|
|
EncryptionKey key, EncryptionKey subKey)
|
|
throws GSSException, KrbException, IOException {
|
|
|
|
int pos = 0;
|
|
|
|
if (checksum == null) {
|
|
GSSException ge = new GSSException(GSSException.FAILURE, -1,
|
|
"No cksum in AP_REQ's authenticator");
|
|
ge.initCause(new KrbException(Krb5.KRB_AP_ERR_INAPP_CKSUM));
|
|
throw ge;
|
|
}
|
|
checksumBytes = checksum.getBytes();
|
|
|
|
if ((checksumBytes[0] != CHECKSUM_FIRST_BYTES[0]) ||
|
|
(checksumBytes[1] != CHECKSUM_FIRST_BYTES[1]) ||
|
|
(checksumBytes[2] != CHECKSUM_FIRST_BYTES[2]) ||
|
|
(checksumBytes[3] != CHECKSUM_FIRST_BYTES[3])) {
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"Incorrect checksum");
|
|
}
|
|
|
|
ChannelBinding localBindings = context.getChannelBinding();
|
|
|
|
// Ignore remote channel binding info when not requested at
|
|
// local side (RFC 4121 4.1.1.2: the acceptor MAY ignore...).
|
|
//
|
|
// All major krb5 implementors implement this "MAY",
|
|
// and some applications depend on it as a workaround
|
|
// for not having a way to negotiate the use of channel
|
|
// binding -- the initiator application always uses CB
|
|
// and hopes the acceptor will ignore the CB if the
|
|
// acceptor doesn't support CB.
|
|
if (localBindings != null) {
|
|
byte[] remoteBindingBytes = new byte[CHECKSUM_BINDINGS_SIZE];
|
|
System.arraycopy(checksumBytes, 4, remoteBindingBytes, 0,
|
|
CHECKSUM_BINDINGS_SIZE);
|
|
|
|
byte[] noBindings = new byte[CHECKSUM_BINDINGS_SIZE];
|
|
if (!Arrays.equals(noBindings, remoteBindingBytes)) {
|
|
byte[] localBindingsBytes =
|
|
computeChannelBinding(localBindings);
|
|
if (!Arrays.equals(localBindingsBytes,
|
|
remoteBindingBytes)) {
|
|
throw new GSSException(GSSException.BAD_BINDINGS, -1,
|
|
"Bytes mismatch!");
|
|
}
|
|
} else {
|
|
throw new GSSException(GSSException.BAD_BINDINGS, -1,
|
|
"Token missing ChannelBinding!");
|
|
}
|
|
}
|
|
|
|
flags = readLittleEndian(checksumBytes, 20, 4);
|
|
|
|
if ((flags & CHECKSUM_DELEG_FLAG) > 0) {
|
|
|
|
/*
|
|
* XXX
|
|
* if ((checksumBytes[24] != (byte)0x01) &&
|
|
* (checksumBytes[25] != (byte)0x00))
|
|
*/
|
|
|
|
int credLen = readLittleEndian(checksumBytes, 26, 2);
|
|
byte[] credBytes = new byte[credLen];
|
|
System.arraycopy(checksumBytes, 28, credBytes, 0, credLen);
|
|
|
|
KrbCred cred;
|
|
try {
|
|
cred = new KrbCred(credBytes, key);
|
|
} catch (KrbException ke) {
|
|
if (subKey != null) {
|
|
cred = new KrbCred(credBytes, subKey);
|
|
} else {
|
|
throw ke;
|
|
}
|
|
}
|
|
delegCreds = cred.getDelegatedCreds()[0];
|
|
}
|
|
}
|
|
|
|
// check if KRB-CRED message should use NULL_KEY for encryption
|
|
private boolean useNullKey(CipherHelper ch) {
|
|
boolean flag = true;
|
|
// for "newer" etypes and RC4-HMAC do not use NULL KEY
|
|
if ((ch.getProto() == 1) || ch.isArcFour()) {
|
|
flag = false;
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
public Checksum getChecksum() throws KrbException {
|
|
return new Checksum(checksumBytes, CHECKSUM_TYPE);
|
|
}
|
|
|
|
public Credentials getDelegatedCreds() {
|
|
return delegCreds;
|
|
}
|
|
|
|
// Only called by acceptor
|
|
public void setContextFlags(Krb5Context context) {
|
|
// default for cred delegation is false
|
|
if ((flags & CHECKSUM_DELEG_FLAG) > 0)
|
|
context.setCredDelegState(true);
|
|
// default for the following are true
|
|
if ((flags & CHECKSUM_MUTUAL_FLAG) == 0) {
|
|
context.setMutualAuthState(false);
|
|
}
|
|
if ((flags & CHECKSUM_REPLAY_FLAG) == 0) {
|
|
context.setReplayDetState(false);
|
|
}
|
|
if ((flags & CHECKSUM_SEQUENCE_FLAG) == 0) {
|
|
context.setSequenceDetState(false);
|
|
}
|
|
if ((flags & CHECKSUM_CONF_FLAG) == 0) {
|
|
context.setConfState(false);
|
|
}
|
|
if ((flags & CHECKSUM_INTEG_FLAG) == 0) {
|
|
context.setIntegState(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private int getAddrType(InetAddress addr) {
|
|
int addressType = CHANNEL_BINDING_AF_NULL_ADDR;
|
|
|
|
if (addr instanceof Inet4Address)
|
|
addressType = CHANNEL_BINDING_AF_INET;
|
|
else if (addr instanceof Inet6Address)
|
|
addressType = CHANNEL_BINDING_AF_INET6;
|
|
return (addressType);
|
|
}
|
|
|
|
private byte[] getAddrBytes(InetAddress addr) throws GSSException {
|
|
int addressType = getAddrType(addr);
|
|
byte[] addressBytes = addr.getAddress();
|
|
if (addressBytes != null) {
|
|
switch (addressType) {
|
|
case CHANNEL_BINDING_AF_INET:
|
|
if (addressBytes.length != Inet4_ADDRSZ) {
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"Incorrect AF-INET address length in ChannelBinding.");
|
|
}
|
|
return (addressBytes);
|
|
case CHANNEL_BINDING_AF_INET6:
|
|
if (addressBytes.length != Inet6_ADDRSZ) {
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"Incorrect AF-INET6 address length in ChannelBinding.");
|
|
}
|
|
return (addressBytes);
|
|
default:
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"Cannot handle non AF-INET addresses in ChannelBinding.");
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private byte[] computeChannelBinding(ChannelBinding channelBinding)
|
|
throws GSSException {
|
|
|
|
InetAddress initiatorAddress = channelBinding.getInitiatorAddress();
|
|
InetAddress acceptorAddress = channelBinding.getAcceptorAddress();
|
|
int size = 5*4;
|
|
|
|
int initiatorAddressType = getAddrType(initiatorAddress);
|
|
int acceptorAddressType = getAddrType(acceptorAddress);
|
|
|
|
byte[] initiatorAddressBytes = null;
|
|
if (initiatorAddress != null) {
|
|
initiatorAddressBytes = getAddrBytes(initiatorAddress);
|
|
size += initiatorAddressBytes.length;
|
|
}
|
|
|
|
byte[] acceptorAddressBytes = null;
|
|
if (acceptorAddress != null) {
|
|
acceptorAddressBytes = getAddrBytes(acceptorAddress);
|
|
size += acceptorAddressBytes.length;
|
|
}
|
|
|
|
byte[] appDataBytes = channelBinding.getApplicationData();
|
|
if (appDataBytes != null) {
|
|
size += appDataBytes.length;
|
|
}
|
|
|
|
byte[] data = new byte[size];
|
|
|
|
int pos = 0;
|
|
|
|
writeLittleEndian(initiatorAddressType, data, pos);
|
|
pos += 4;
|
|
|
|
if (initiatorAddressBytes != null) {
|
|
writeLittleEndian(initiatorAddressBytes.length, data, pos);
|
|
pos += 4;
|
|
System.arraycopy(initiatorAddressBytes, 0,
|
|
data, pos, initiatorAddressBytes.length);
|
|
pos += initiatorAddressBytes.length;
|
|
} else {
|
|
// Write length 0
|
|
pos += 4;
|
|
}
|
|
|
|
writeLittleEndian(acceptorAddressType, data, pos);
|
|
pos += 4;
|
|
|
|
if (acceptorAddressBytes != null) {
|
|
writeLittleEndian(acceptorAddressBytes.length, data, pos);
|
|
pos += 4;
|
|
System.arraycopy(acceptorAddressBytes, 0,
|
|
data, pos, acceptorAddressBytes.length);
|
|
pos += acceptorAddressBytes.length;
|
|
} else {
|
|
// Write length 0
|
|
pos += 4;
|
|
}
|
|
|
|
if (appDataBytes != null) {
|
|
writeLittleEndian(appDataBytes.length, data, pos);
|
|
pos += 4;
|
|
System.arraycopy(appDataBytes, 0, data, pos,
|
|
appDataBytes.length);
|
|
pos += appDataBytes.length;
|
|
} else {
|
|
// Write 0
|
|
pos += 4;
|
|
}
|
|
|
|
try {
|
|
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
|
return md5.digest(data);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"Could not get MD5 Message Digest - "
|
|
+ e.getMessage());
|
|
}
|
|
}
|
|
|
|
public abstract byte[] encode() throws IOException;
|
|
|
|
}
|