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