1 /*
   2  * Copyright (c) 2000, 2013, 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                 StringBuilder sb = new StringBuilder("\"");
 168                 sb.append(delegateTo.getName()).append('\"');
 169                 String realm = delegateTo.getRealmAsString();
 170                 sb.append(" \"krbtgt/").append(realm).append('@');
 171                 sb.append(realm).append('\"');
 172                 SecurityManager sm = System.getSecurityManager();
 173                 if (sm != null) {
 174                     DelegationPermission perm =
 175                         new DelegationPermission(sb.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 message 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, Checksum checksum,
 214                                   EncryptionKey key, EncryptionKey subKey)
 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                 KrbCred cred;
 281                 try {
 282                     cred = new KrbCred(credBytes, key);
 283                 } catch (KrbException ke) {
 284                     if (subKey != null) {
 285                         cred = new KrbCred(credBytes, subKey);
 286                     } else {
 287                         throw ke;
 288                     }
 289                 }
 290                 delegCreds = cred.getDelegatedCreds()[0];
 291             }
 292         }
 293 
 294         // check if KRB-CRED message should use NULL_KEY for encryption
 295         private boolean useNullKey(CipherHelper ch) {
 296             boolean flag = true;
 297             // for "newer" etypes and RC4-HMAC do not use NULL KEY
 298             if ((ch.getProto() == 1) || ch.isArcFour()) {
 299                 flag = false;
 300             }
 301             return flag;
 302         }
 303 
 304         public Checksum getChecksum() throws KrbException {
 305             return new Checksum(checksumBytes, CHECKSUM_TYPE);
 306         }
 307 
 308         public Credentials getDelegatedCreds() {
 309             return delegCreds;
 310         }
 311 
 312         // Only called by acceptor
 313         public void setContextFlags(Krb5Context context) {
 314                 // default for cred delegation is false
 315             if ((flags & CHECKSUM_DELEG_FLAG) > 0)
 316                 context.setCredDelegState(true);
 317                 // default for the following are true
 318             if ((flags & CHECKSUM_MUTUAL_FLAG) == 0) {
 319                 context.setMutualAuthState(false);
 320             }
 321             if ((flags & CHECKSUM_REPLAY_FLAG) == 0) {
 322                 context.setReplayDetState(false);
 323             }
 324             if ((flags & CHECKSUM_SEQUENCE_FLAG) == 0) {
 325                 context.setSequenceDetState(false);
 326             }
 327             if ((flags & CHECKSUM_CONF_FLAG) == 0) {
 328                 context.setConfState(false);
 329             }
 330             if ((flags & CHECKSUM_INTEG_FLAG) == 0) {
 331                 context.setIntegState(false);
 332             }
 333         }
 334     }
 335 
 336     private int getAddrType(InetAddress addr) {
 337         int addressType = CHANNEL_BINDING_AF_NULL_ADDR;
 338 
 339         if (addr instanceof Inet4Address)
 340             addressType = CHANNEL_BINDING_AF_INET;
 341         else if (addr instanceof Inet6Address)
 342             addressType = CHANNEL_BINDING_AF_INET6;
 343         return (addressType);
 344     }
 345 
 346     private byte[] getAddrBytes(InetAddress addr) throws GSSException {
 347         int addressType = getAddrType(addr);
 348         byte[] addressBytes = addr.getAddress();
 349         if (addressBytes != null) {
 350             switch (addressType) {
 351                 case CHANNEL_BINDING_AF_INET:
 352                     if (addressBytes.length != Inet4_ADDRSZ) {
 353                         throw new GSSException(GSSException.FAILURE, -1,
 354                         "Incorrect AF-INET address length in ChannelBinding.");
 355                     }
 356                     return (addressBytes);
 357                 case CHANNEL_BINDING_AF_INET6:
 358                     if (addressBytes.length != Inet6_ADDRSZ) {
 359                         throw new GSSException(GSSException.FAILURE, -1,
 360                         "Incorrect AF-INET6 address length in ChannelBinding.");
 361                     }
 362                     return (addressBytes);
 363                 default:
 364                     throw new GSSException(GSSException.FAILURE, -1,
 365                     "Cannot handle non AF-INET addresses in ChannelBinding.");
 366             }
 367         }
 368         return null;
 369     }
 370 
 371     private byte[] computeChannelBinding(ChannelBinding channelBinding)
 372         throws GSSException {
 373 
 374         InetAddress initiatorAddress = channelBinding.getInitiatorAddress();
 375         InetAddress acceptorAddress = channelBinding.getAcceptorAddress();
 376         int size = 5*4;
 377 
 378         int initiatorAddressType = getAddrType(initiatorAddress);
 379         int acceptorAddressType = getAddrType(acceptorAddress);
 380 
 381         byte[] initiatorAddressBytes = null;
 382         if (initiatorAddress != null) {
 383             initiatorAddressBytes = getAddrBytes(initiatorAddress);
 384             size += initiatorAddressBytes.length;
 385         }
 386 
 387         byte[] acceptorAddressBytes = null;
 388         if (acceptorAddress != null) {
 389             acceptorAddressBytes = getAddrBytes(acceptorAddress);
 390             size += acceptorAddressBytes.length;
 391         }
 392 
 393         byte[] appDataBytes = channelBinding.getApplicationData();
 394         if (appDataBytes != null) {
 395             size += appDataBytes.length;
 396         }
 397 
 398         byte[] data = new byte[size];
 399 
 400         int pos = 0;
 401 
 402         writeLittleEndian(initiatorAddressType, data, pos);
 403         pos += 4;
 404 
 405         if (initiatorAddressBytes != null) {
 406             writeLittleEndian(initiatorAddressBytes.length, data, pos);
 407             pos += 4;
 408             System.arraycopy(initiatorAddressBytes, 0,
 409                              data, pos, initiatorAddressBytes.length);
 410             pos += initiatorAddressBytes.length;
 411         } else {
 412             // Write length 0
 413             pos += 4;
 414         }
 415 
 416         writeLittleEndian(acceptorAddressType, data, pos);
 417         pos += 4;
 418 
 419         if (acceptorAddressBytes != null) {
 420             writeLittleEndian(acceptorAddressBytes.length, data, pos);
 421             pos += 4;
 422             System.arraycopy(acceptorAddressBytes, 0,
 423                              data, pos, acceptorAddressBytes.length);
 424             pos += acceptorAddressBytes.length;
 425         } else {
 426             // Write length 0
 427             pos += 4;
 428         }
 429 
 430         if (appDataBytes != null) {
 431             writeLittleEndian(appDataBytes.length, data, pos);
 432             pos += 4;
 433             System.arraycopy(appDataBytes, 0, data, pos,
 434                              appDataBytes.length);
 435             pos += appDataBytes.length;
 436         } else {
 437             // Write 0
 438             pos += 4;
 439         }
 440 
 441         try {
 442             MessageDigest md5 = MessageDigest.getInstance("MD5");
 443             return md5.digest(data);
 444         } catch (NoSuchAlgorithmException e) {
 445                 throw new GSSException(GSSException.FAILURE, -1,
 446                                        "Could not get MD5 Message Digest - "
 447                                        + e.getMessage());
 448         }
 449     }
 450 
 451     public abstract byte[] encode() throws IOException;
 452 
 453 }