1 /*
   2  * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.jgss.krb5;
  27 
  28 import org.ietf.jgss.*;
  29 import javax.security.auth.kerberos.DelegationPermission;
  30 import java.io.IOException;
  31 import java.net.InetAddress;
  32 import java.net.Inet4Address;
  33 import java.net.Inet6Address;
  34 import java.security.MessageDigest;
  35 import java.security.NoSuchAlgorithmException;
  36 import java.util.Arrays;
  37 import sun.security.krb5.*;
  38 import sun.security.krb5.internal.Krb5;
  39 
  40 abstract class InitialToken extends Krb5Token {
  41 
  42     private static final int CHECKSUM_TYPE = 0x8003;
  43 
  44     private static final int CHECKSUM_LENGTH_SIZE     = 4;
  45     private static final int CHECKSUM_BINDINGS_SIZE   = 16;
  46     private static final int CHECKSUM_FLAGS_SIZE      = 4;
  47     private static final int CHECKSUM_DELEG_OPT_SIZE  = 2;
  48     private static final int CHECKSUM_DELEG_LGTH_SIZE = 2;
  49 
  50     private static final int CHECKSUM_DELEG_FLAG    = 1;
  51     private static final int CHECKSUM_MUTUAL_FLAG   = 2;
  52     private static final int CHECKSUM_REPLAY_FLAG   = 4;
  53     private static final int CHECKSUM_SEQUENCE_FLAG = 8;
  54     private static final int CHECKSUM_CONF_FLAG     = 16;
  55     private static final int CHECKSUM_INTEG_FLAG    = 32;
  56 
  57     private final byte[] CHECKSUM_FIRST_BYTES =
  58     {(byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00};
  59 
  60     private static final int CHANNEL_BINDING_AF_INET = 2;
  61     private static final int CHANNEL_BINDING_AF_INET6 = 24;
  62     private static final int CHANNEL_BINDING_AF_NULL_ADDR = 255;
  63 
  64     private static final int Inet4_ADDRSZ = 4;
  65     private static final int Inet6_ADDRSZ = 16;
  66 
  67     protected class OverloadedChecksum {
  68 
  69         private byte[] checksumBytes = null;
  70         private Credentials delegCreds = null;
  71         private int flags = 0;
  72 
  73         /**
  74          * Called on the initiator side when creating the
  75          * InitSecContextToken.
  76          */
  77         public OverloadedChecksum(Krb5Context context,
  78                                   Credentials tgt,
  79                                   Credentials serviceTicket)
  80             throws KrbException, IOException, GSSException {
  81 
  82             byte[] krbCredMessage = null;
  83             int pos = 0;
  84             int size = CHECKSUM_LENGTH_SIZE + CHECKSUM_BINDINGS_SIZE +
  85                 CHECKSUM_FLAGS_SIZE;
  86 
  87             if (!tgt.isForwardable()) {
  88                 context.setCredDelegState(false);
  89                 context.setDelegPolicyState(false);
  90             } else if (context.getCredDelegState()) {
  91                 if (context.getDelegPolicyState()) {
  92                     if (!serviceTicket.checkDelegate()) {
  93                         // delegation not permitted by server policy, mark it
  94                         context.setDelegPolicyState(false);
  95                     }
  96                 }
  97             } else if (context.getDelegPolicyState()) {
  98                 if (serviceTicket.checkDelegate()) {
  99                     context.setCredDelegState(true);
 100                 } else {
 101                     context.setDelegPolicyState(false);
 102                 }
 103             }
 104 
 105             if (context.getCredDelegState()) {
 106                 KrbCred krbCred = null;
 107                 CipherHelper cipherHelper =
 108                     context.getCipherHelper(serviceTicket.getSessionKey());
 109                 if (useNullKey(cipherHelper)) {
 110                     krbCred = new KrbCred(tgt, serviceTicket,
 111                                               EncryptionKey.NULL_KEY);
 112                 } else {
 113                     krbCred = new KrbCred(tgt, serviceTicket,
 114                                     serviceTicket.getSessionKey());
 115                 }
 116                 krbCredMessage = krbCred.getMessage();
 117                 size += CHECKSUM_DELEG_OPT_SIZE +
 118                         CHECKSUM_DELEG_LGTH_SIZE +
 119                         krbCredMessage.length;
 120             }
 121 
 122             checksumBytes = new byte[size];
 123 
 124             checksumBytes[pos++] = CHECKSUM_FIRST_BYTES[0];
 125             checksumBytes[pos++] = CHECKSUM_FIRST_BYTES[1];
 126             checksumBytes[pos++] = CHECKSUM_FIRST_BYTES[2];
 127             checksumBytes[pos++] = CHECKSUM_FIRST_BYTES[3];
 128 
 129             ChannelBinding localBindings = context.getChannelBinding();
 130             if (localBindings != null) {
 131                 byte[] localBindingsBytes =
 132                     computeChannelBinding(context.getChannelBinding());
 133                 System.arraycopy(localBindingsBytes, 0,
 134                              checksumBytes, pos, localBindingsBytes.length);
 135                 //              System.out.println("ChannelBinding hash: "
 136                 //         + getHexBytes(localBindingsBytes));
 137             }
 138 
 139             pos += CHECKSUM_BINDINGS_SIZE;
 140 
 141             if (context.getCredDelegState())
 142                 flags |= CHECKSUM_DELEG_FLAG;
 143             if (context.getMutualAuthState())
 144                 flags |= CHECKSUM_MUTUAL_FLAG;
 145             if (context.getReplayDetState())
 146                 flags |= CHECKSUM_REPLAY_FLAG;
 147             if (context.getSequenceDetState())
 148                 flags |= CHECKSUM_SEQUENCE_FLAG;
 149             if (context.getIntegState())
 150                 flags |= CHECKSUM_INTEG_FLAG;
 151             if (context.getConfState())
 152                 flags |= CHECKSUM_CONF_FLAG;
 153 
 154             byte[] temp = new byte[4];
 155             writeLittleEndian(flags, temp);
 156             checksumBytes[pos++] = temp[0];
 157             checksumBytes[pos++] = temp[1];
 158             checksumBytes[pos++] = temp[2];
 159             checksumBytes[pos++] = temp[3];
 160 
 161             if (context.getCredDelegState()) {
 162 
 163                 PrincipalName delegateTo =
 164                     serviceTicket.getServer();
 165                 // Cannot use '\"' instead of "\"" in constructor because
 166                 // it is interpreted as suggested length!
 167                 StringBuffer buf = new StringBuffer("\"");
 168                 buf.append(delegateTo.getName()).append('\"');
 169                 String realm = delegateTo.getRealmAsString();
 170                 buf.append(" \"krbtgt/").append(realm).append('@');
 171                 buf.append(realm).append('\"');
 172                 SecurityManager sm = System.getSecurityManager();
 173                 if (sm != null) {
 174                     DelegationPermission perm =
 175                         new DelegationPermission(buf.toString());
 176                     sm.checkPermission(perm);
 177                 }
 178 
 179 
 180                 /*
 181                  * Write 1 in little endian but in two bytes
 182                  * for DlgOpt
 183                  */
 184 
 185                 checksumBytes[pos++] = (byte)0x01;
 186                 checksumBytes[pos++] = (byte)0x00;
 187 
 188                 /*
 189                  * Write the length of the delegated credential in little
 190                  * endian but in two bytes for Dlgth
 191                  */
 192 
 193                 if (krbCredMessage.length > 0x0000ffff)
 194                     throw new GSSException(GSSException.FAILURE, -1,
 195                         "Incorrect messsage length");
 196 
 197                 writeLittleEndian(krbCredMessage.length, temp);
 198                 checksumBytes[pos++] = temp[0];
 199                 checksumBytes[pos++] = temp[1];
 200                 System.arraycopy(krbCredMessage, 0,
 201                                  checksumBytes, pos, krbCredMessage.length);
 202             }
 203 
 204         }
 205 
 206         /**
 207          * Called on the acceptor side when reading an InitSecContextToken.
 208          */
 209         // XXX Passing in Checksum is not required. byte[] can
 210         // be passed in if this checksum type denotes a
 211         // raw_checksum. In that case, make Checksum class krb5
 212         // internal.
 213         public OverloadedChecksum(Krb5Context context,
 214                                   Checksum checksum, EncryptionKey key)
 215             throws GSSException, KrbException, IOException {
 216 
 217             int pos = 0;
 218 
 219             if (checksum == null) {
 220                 GSSException ge = new GSSException(GSSException.FAILURE, -1,
 221                         "No cksum in AP_REQ's authenticator");
 222                 ge.initCause(new KrbException(Krb5.KRB_AP_ERR_INAPP_CKSUM));
 223                 throw ge;
 224             }
 225             checksumBytes = checksum.getBytes();
 226 
 227             if ((checksumBytes[0] != CHECKSUM_FIRST_BYTES[0]) ||
 228                 (checksumBytes[1] != CHECKSUM_FIRST_BYTES[1]) ||
 229                 (checksumBytes[2] != CHECKSUM_FIRST_BYTES[2]) ||
 230                 (checksumBytes[3] != CHECKSUM_FIRST_BYTES[3])) {
 231                 throw new GSSException(GSSException.FAILURE, -1,
 232                         "Incorrect checksum");
 233             }
 234 
 235             ChannelBinding localBindings = context.getChannelBinding();
 236 
 237             // Ignore remote channel binding info when not requested at
 238             // local side (RFC 4121 4.1.1.2: the acceptor MAY ignore...).
 239             //
 240             // All major krb5 implementors implement this "MAY",
 241             // and some applications depend on it as a workaround
 242             // for not having a way to negotiate the use of channel
 243             // binding -- the initiator application always uses CB
 244             // and hopes the acceptor will ignore the CB if the
 245             // acceptor doesn't support CB.
 246             if (localBindings != null) {
 247                 byte[] remoteBindingBytes = new byte[CHECKSUM_BINDINGS_SIZE];
 248                 System.arraycopy(checksumBytes, 4, remoteBindingBytes, 0,
 249                                  CHECKSUM_BINDINGS_SIZE);
 250 
 251                 byte[] noBindings = new byte[CHECKSUM_BINDINGS_SIZE];
 252                 if (!Arrays.equals(noBindings, remoteBindingBytes)) {
 253                     byte[] localBindingsBytes =
 254                         computeChannelBinding(localBindings);
 255                     if (!Arrays.equals(localBindingsBytes,
 256                                                 remoteBindingBytes)) {
 257                         throw new GSSException(GSSException.BAD_BINDINGS, -1,
 258                                                "Bytes mismatch!");
 259                     }
 260                 } else {
 261                     throw new GSSException(GSSException.BAD_BINDINGS, -1,
 262                                            "Token missing ChannelBinding!");
 263                 }
 264             }
 265 
 266             flags = readLittleEndian(checksumBytes, 20, 4);
 267 
 268             if ((flags & CHECKSUM_DELEG_FLAG) > 0) {
 269 
 270                 /*
 271                  * XXX
 272                  * if ((checksumBytes[24] != (byte)0x01) &&
 273                  * (checksumBytes[25] != (byte)0x00))
 274                  */
 275 
 276                 int credLen = readLittleEndian(checksumBytes, 26, 2);
 277                 byte[] credBytes = new byte[credLen];
 278                 System.arraycopy(checksumBytes, 28, credBytes, 0, credLen);
 279 
 280                 CipherHelper cipherHelper = context.getCipherHelper(key);
 281                 if (useNullKey(cipherHelper)) {
 282                     delegCreds =
 283                         new KrbCred(credBytes, EncryptionKey.NULL_KEY).
 284                         getDelegatedCreds()[0];
 285                 } else {
 286                     delegCreds =
 287                         new KrbCred(credBytes, key).
 288                         getDelegatedCreds()[0];
 289                 }
 290             }
 291         }
 292 
 293         // check if KRB-CRED message should use NULL_KEY for encryption
 294         private boolean useNullKey(CipherHelper ch) {
 295             boolean flag = true;
 296             // for "newer" etypes and RC4-HMAC do not use NULL KEY
 297             if ((ch.getProto() == 1) || ch.isArcFour()) {
 298                 flag = false;
 299             }
 300             return flag;
 301         }
 302 
 303         public Checksum getChecksum() throws KrbException {
 304             return new Checksum(checksumBytes, CHECKSUM_TYPE);
 305         }
 306 
 307         public Credentials getDelegatedCreds() {
 308             return delegCreds;
 309         }
 310 
 311         // Only called by acceptor
 312         public void setContextFlags(Krb5Context context) {
 313                 // default for cred delegation is false
 314             if ((flags & CHECKSUM_DELEG_FLAG) > 0)
 315                 context.setCredDelegState(true);
 316                 // default for the following are true
 317             if ((flags & CHECKSUM_MUTUAL_FLAG) == 0) {
 318                 context.setMutualAuthState(false);
 319             }
 320             if ((flags & CHECKSUM_REPLAY_FLAG) == 0) {
 321                 context.setReplayDetState(false);
 322             }
 323             if ((flags & CHECKSUM_SEQUENCE_FLAG) == 0) {
 324                 context.setSequenceDetState(false);
 325             }
 326             if ((flags & CHECKSUM_CONF_FLAG) == 0) {
 327                 context.setConfState(false);
 328             }
 329             if ((flags & CHECKSUM_INTEG_FLAG) == 0) {
 330                 context.setIntegState(false);
 331             }
 332         }
 333     }
 334 
 335     private int getAddrType(InetAddress addr) {
 336         int addressType = CHANNEL_BINDING_AF_NULL_ADDR;
 337 
 338         if (addr instanceof Inet4Address)
 339             addressType = CHANNEL_BINDING_AF_INET;
 340         else if (addr instanceof Inet6Address)
 341             addressType = CHANNEL_BINDING_AF_INET6;
 342         return (addressType);
 343     }
 344 
 345     private byte[] getAddrBytes(InetAddress addr) throws GSSException {
 346         int addressType = getAddrType(addr);
 347         byte[] addressBytes = addr.getAddress();
 348         if (addressBytes != null) {
 349             switch (addressType) {
 350                 case CHANNEL_BINDING_AF_INET:
 351                     if (addressBytes.length != Inet4_ADDRSZ) {
 352                         throw new GSSException(GSSException.FAILURE, -1,
 353                         "Incorrect AF-INET address length in ChannelBinding.");
 354                     }
 355                     return (addressBytes);
 356                 case CHANNEL_BINDING_AF_INET6:
 357                     if (addressBytes.length != Inet6_ADDRSZ) {
 358                         throw new GSSException(GSSException.FAILURE, -1,
 359                         "Incorrect AF-INET6 address length in ChannelBinding.");
 360                     }
 361                     return (addressBytes);
 362                 default:
 363                     throw new GSSException(GSSException.FAILURE, -1,
 364                     "Cannot handle non AF-INET addresses in ChannelBinding.");
 365             }
 366         }
 367         return null;
 368     }
 369 
 370     private byte[] computeChannelBinding(ChannelBinding channelBinding)
 371         throws GSSException {
 372 
 373         InetAddress initiatorAddress = channelBinding.getInitiatorAddress();
 374         InetAddress acceptorAddress = channelBinding.getAcceptorAddress();
 375         int size = 5*4;
 376 
 377         int initiatorAddressType = getAddrType(initiatorAddress);
 378         int acceptorAddressType = getAddrType(acceptorAddress);
 379 
 380         byte[] initiatorAddressBytes = null;
 381         if (initiatorAddress != null) {
 382             initiatorAddressBytes = getAddrBytes(initiatorAddress);
 383             size += initiatorAddressBytes.length;
 384         }
 385 
 386         byte[] acceptorAddressBytes = null;
 387         if (acceptorAddress != null) {
 388             acceptorAddressBytes = getAddrBytes(acceptorAddress);
 389             size += acceptorAddressBytes.length;
 390         }
 391 
 392         byte[] appDataBytes = channelBinding.getApplicationData();
 393         if (appDataBytes != null) {
 394             size += appDataBytes.length;
 395         }
 396 
 397         byte[] data = new byte[size];
 398 
 399         int pos = 0;
 400 
 401         writeLittleEndian(initiatorAddressType, data, pos);
 402         pos += 4;
 403 
 404         if (initiatorAddressBytes != null) {
 405             writeLittleEndian(initiatorAddressBytes.length, data, pos);
 406             pos += 4;
 407             System.arraycopy(initiatorAddressBytes, 0,
 408                              data, pos, initiatorAddressBytes.length);
 409             pos += initiatorAddressBytes.length;
 410         } else {
 411             // Write length 0
 412             pos += 4;
 413         }
 414 
 415         writeLittleEndian(acceptorAddressType, data, pos);
 416         pos += 4;
 417 
 418         if (acceptorAddressBytes != null) {
 419             writeLittleEndian(acceptorAddressBytes.length, data, pos);
 420             pos += 4;
 421             System.arraycopy(acceptorAddressBytes, 0,
 422                              data, pos, acceptorAddressBytes.length);
 423             pos += acceptorAddressBytes.length;
 424         } else {
 425             // Write length 0
 426             pos += 4;
 427         }
 428 
 429         if (appDataBytes != null) {
 430             writeLittleEndian(appDataBytes.length, data, pos);
 431             pos += 4;
 432             System.arraycopy(appDataBytes, 0, data, pos,
 433                              appDataBytes.length);
 434             pos += appDataBytes.length;
 435         } else {
 436             // Write 0
 437             pos += 4;
 438         }
 439 
 440         try {
 441             MessageDigest md5 = MessageDigest.getInstance("MD5");
 442             return md5.digest(data);
 443         } catch (NoSuchAlgorithmException e) {
 444                 throw new GSSException(GSSException.FAILURE, -1,
 445                                        "Could not get MD5 Message Digest - "
 446                                        + e.getMessage());
 447         }
 448     }
 449 
 450     public abstract byte[] encode() throws IOException;
 451 
 452 }