/*
 * 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.
 */

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.
/** * 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 messsage 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.
/** * 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); CipherHelper cipherHelper = context.getCipherHelper(key); if (useNullKey(cipherHelper)) { delegCreds = new KrbCred(credBytes, EncryptionKey.NULL_KEY). getDelegatedCreds()[0]; } else { KrbCred cred; try { cred = new KrbCred(credBytes, key); } catch (KrbException e) { if (subKey != null) { cred = new KrbCred(credBytes, subKey); } else { throw e; } } 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; }