jdk/src/share/classes/sun/security/jgss/krb5/InitialToken.java
changeset 2 90ce3da70b43
child 2942 37d9baeb7518
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/security/jgss/krb5/InitialToken.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2000-2006 Sun Microsystems, Inc.  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.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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 sun.security.krb5.*;
+import sun.security.jgss.GSSUtil;
+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 (context.getCredDelegState()) {
+                if (context.getCaller() == GSSUtil.CALLER_HTTP_NEGOTIATE &&
+                        !serviceTicket.getFlags()[Krb5.TKT_OPTS_DELEGATE]) {
+                    // When the caller is HTTP/SPNEGO and OK-AS-DELEGATE
+                    // is not present in the service ticket, delegation
+                    // is disabled.
+                    context.setCredDelegState(false);
+                } else if (!tgt.isForwardable()) {
+                    // XXX log this resetting of delegation state
+                    context.setCredDelegState(false);
+                } else {
+                    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.
+         */
+        // 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)
+            throws GSSException, KrbException, IOException {
+
+            int pos = 0;
+
+            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");
+            }
+
+            byte[] remoteBindingBytes = new byte[CHECKSUM_BINDINGS_SIZE];
+            System.arraycopy(checksumBytes, 4, remoteBindingBytes, 0,
+                             CHECKSUM_BINDINGS_SIZE);
+
+            byte[] noBindings = new byte[CHECKSUM_BINDINGS_SIZE];
+            boolean tokenContainsBindings =
+                (!java.util.Arrays.equals(noBindings, remoteBindingBytes));
+
+            ChannelBinding localBindings = context.getChannelBinding();
+
+            if (tokenContainsBindings ||
+                localBindings != null) {
+
+                boolean badBindings = false;
+                String errorMessage = null;
+
+                if (tokenContainsBindings &&
+                    localBindings != null) {
+                    byte[] localBindingsBytes =
+                        computeChannelBinding(localBindings);
+                    //              System.out.println("ChannelBinding hash: "
+                    //         + getHexBytes(localBindingsBytes));
+                    badBindings =
+                        (!java.util.Arrays.equals(localBindingsBytes,
+                                                remoteBindingBytes));
+                    errorMessage = "Bytes mismatch!";
+                } else if (localBindings == null) {
+                    errorMessage = "ChannelBinding not provided!";
+                    badBindings = true;
+                } else {
+                    errorMessage = "Token missing ChannelBinding!";
+                    badBindings = true;
+                }
+
+                if (badBindings)
+                    throw new GSSException(GSSException.BAD_BINDINGS, -1,
+                                           errorMessage);
+            }
+
+            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 {
+                    delegCreds =
+                        new KrbCred(credBytes, key).
+                        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;
+        }
+
+        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;
+
+}