1 /*
   2  * Copyright (c) 2000, 2017, 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 /*
  27  *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
  28  *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
  29  */
  30 
  31 package sun.security.krb5;
  32 
  33 import java.util.Arrays;
  34 import sun.security.util.*;
  35 import sun.security.krb5.internal.*;
  36 import sun.security.krb5.internal.crypto.*;
  37 import java.io.IOException;
  38 import java.math.BigInteger;
  39 
  40 /**
  41  * This class encapsulates the concept of a Kerberos checksum.
  42  */
  43 public class Checksum {
  44 
  45     private int cksumType;
  46     private byte[] checksum;
  47 
  48     // ----------------------------------------------+-------------+-----------
  49     //                      Checksum type            |sumtype      |checksum
  50     //                                               |value        | size
  51     // ----------------------------------------------+-------------+-----------
  52     public static final int CKSUMTYPE_NULL          = 0;               // 0
  53     public static final int CKSUMTYPE_CRC32         = 1;               // 4
  54     public static final int CKSUMTYPE_RSA_MD4       = 2;               // 16
  55     public static final int CKSUMTYPE_RSA_MD4_DES   = 3;               // 24
  56     public static final int CKSUMTYPE_DES_MAC       = 4;               // 16
  57     public static final int CKSUMTYPE_DES_MAC_K     = 5;               // 8
  58     public static final int CKSUMTYPE_RSA_MD4_DES_K = 6;               // 16
  59     public static final int CKSUMTYPE_RSA_MD5       = 7;               // 16
  60     public static final int CKSUMTYPE_RSA_MD5_DES   = 8;               // 24
  61 
  62      // draft-ietf-krb-wg-crypto-07.txt
  63     public static final int CKSUMTYPE_HMAC_SHA1_DES3_KD = 12;          // 20
  64 
  65     // draft-raeburn-krb-rijndael-krb-07.txt
  66     public static final int CKSUMTYPE_HMAC_SHA1_96_AES128 = 15;        // 96
  67     public static final int CKSUMTYPE_HMAC_SHA1_96_AES256 = 16;        // 96
  68 
  69     // rfc8009
  70     public static final int CKSUMTYPE_HMAC_SHA256_128_AES128 = 19;        // 96
  71     public static final int CKSUMTYPE_HMAC_SHA384_192_AES256 = 20;        // 96
  72 
  73     // draft-brezak-win2k-krb-rc4-hmac-04.txt
  74     public static final int CKSUMTYPE_HMAC_MD5_ARCFOUR = -138;
  75 
  76     static int CKSUMTYPE_DEFAULT;
  77     static int SAFECKSUMTYPE_DEFAULT;
  78 
  79     private static boolean DEBUG = Krb5.DEBUG;
  80     static {
  81         initStatic();
  82     }
  83 
  84     public static void initStatic() {
  85         String temp = null;
  86         Config cfg = null;
  87         try {
  88             cfg = Config.getInstance();
  89             temp = cfg.get("libdefaults", "default_checksum");
  90             if (temp != null)
  91                 {
  92                     CKSUMTYPE_DEFAULT = Config.getType(temp);
  93                 } else {
  94                     /*
  95                      * If the default checksum is not
  96                      * specified in the configuration we
  97                      * set it to RSA_MD5. We follow the MIT and
  98                      * SEAM implementation.
  99                      */
 100                     CKSUMTYPE_DEFAULT = CKSUMTYPE_RSA_MD5;
 101                 }
 102         } catch (Exception exc) {
 103             if (DEBUG) {
 104                 System.out.println("Exception in getting default checksum "+
 105                                    "value from the configuration " +
 106                                    "Setting default checksum to be RSA-MD5");
 107                 exc.printStackTrace();
 108             }
 109             CKSUMTYPE_DEFAULT = CKSUMTYPE_RSA_MD5;
 110         }
 111 
 112 
 113         try {
 114             temp = cfg.get("libdefaults", "safe_checksum_type");
 115             if (temp != null)
 116                 {
 117                     SAFECKSUMTYPE_DEFAULT = Config.getType(temp);
 118                 } else {
 119                     SAFECKSUMTYPE_DEFAULT = CKSUMTYPE_RSA_MD5_DES;
 120                 }
 121         } catch (Exception exc) {
 122             if (DEBUG) {
 123                 System.out.println("Exception in getting safe default " +
 124                                    "checksum value " +
 125                                    "from the configuration Setting  " +
 126                                    "safe default checksum to be RSA-MD5");
 127                 exc.printStackTrace();
 128             }
 129             SAFECKSUMTYPE_DEFAULT = CKSUMTYPE_RSA_MD5_DES;
 130         }
 131     }
 132 
 133     /**
 134      * Constructs a new Checksum using the raw data and type.
 135      * @param data the byte array of checksum.
 136      * @param new_cksumType the type of checksum.
 137      *
 138      */
 139          // used in InitialToken
 140     public Checksum(byte[] data, int new_cksumType) {
 141         cksumType = new_cksumType;
 142         checksum = data;
 143     }
 144 
 145     /**
 146      * Constructs a new Checksum by calculating the checksum over the data
 147      * using specified checksum type.
 148      * @param new_cksumType the type of checksum.
 149      * @param data the data that needs to be performed a checksum calculation on.
 150      */
 151     public Checksum(int new_cksumType, byte[] data)
 152         throws KdcErrException, KrbCryptoException {
 153 
 154         cksumType = new_cksumType;
 155         CksumType cksumEngine = CksumType.getInstance(cksumType);
 156         if (!cksumEngine.isSafe()) {
 157             checksum = cksumEngine.calculateChecksum(data, data.length);
 158         } else {
 159             throw new KdcErrException(Krb5.KRB_AP_ERR_INAPP_CKSUM);
 160         }
 161     }
 162 
 163     /**
 164      * Constructs a new Checksum by calculating the keyed checksum
 165      * over the data using specified checksum type.
 166      * @param new_cksumType the type of checksum.
 167      * @param data the data that needs to be performed a checksum calculation on.
 168      */
 169          // KrbSafe, KrbTgsReq
 170     public Checksum(int new_cksumType, byte[] data,
 171                         EncryptionKey key, int usage)
 172         throws KdcErrException, KrbApErrException, KrbCryptoException {
 173         cksumType = new_cksumType;
 174         CksumType cksumEngine = CksumType.getInstance(cksumType);
 175         if (!cksumEngine.isSafe())
 176             throw new KrbApErrException(Krb5.KRB_AP_ERR_INAPP_CKSUM);
 177         checksum =
 178             cksumEngine.calculateKeyedChecksum(data,
 179                 data.length,
 180                 key.getBytes(),
 181                 usage);
 182     }
 183 
 184     /**
 185      * Verifies the keyed checksum over the data passed in.
 186      */
 187     public boolean verifyKeyedChecksum(byte[] data, EncryptionKey key,
 188                                         int usage)
 189         throws KdcErrException, KrbApErrException, KrbCryptoException {
 190         CksumType cksumEngine = CksumType.getInstance(cksumType);
 191         if (!cksumEngine.isSafe())
 192             throw new KrbApErrException(Krb5.KRB_AP_ERR_INAPP_CKSUM);
 193         return cksumEngine.verifyKeyedChecksum(data,
 194                                                data.length,
 195                                                key.getBytes(),
 196                                                checksum,
 197             usage);
 198     }
 199 
 200     /*
 201     public Checksum(byte[] data) throws KdcErrException, KrbCryptoException {
 202         this(Checksum.CKSUMTYPE_DEFAULT, data);
 203     }
 204     */
 205 
 206     boolean isEqual(Checksum cksum) throws KdcErrException {
 207         if (cksumType != cksum.cksumType)
 208             return false;
 209         CksumType cksumEngine = CksumType.getInstance(cksumType);
 210         return CksumType.isChecksumEqual(checksum, cksum.checksum);
 211     }
 212 
 213     /**
 214      * Constructs an instance of Checksum from an ASN.1 encoded representation.
 215      * @param encoding a single DER-encoded value.
 216      * @exception Asn1Exception if an error occurs while decoding an ASN1
 217      * encoded data.
 218      * @exception IOException if an I/O error occurs while reading encoded data.
 219      *
 220      */
 221     private Checksum(DerValue encoding) throws Asn1Exception, IOException {
 222         DerValue der;
 223         if (encoding.getTag() != DerValue.tag_Sequence) {
 224             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 225         }
 226         der = encoding.getData().getDerValue();
 227         if ((der.getTag() & (byte)0x1F) == (byte)0x00) {
 228             cksumType = der.getData().getBigInteger().intValue();
 229         }
 230         else
 231             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 232         der = encoding.getData().getDerValue();
 233         if ((der.getTag() & (byte)0x1F) == (byte)0x01) {
 234             checksum = der.getData().getOctetString();
 235         }
 236         else
 237             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 238         if (encoding.getData().available() > 0) {
 239             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 240         }
 241     }
 242 
 243     /**
 244      * Encodes a Checksum object.
 245      * <pre>{@code
 246      * Checksum    ::= SEQUENCE {
 247      *         cksumtype   [0] Int32,
 248      *         checksum    [1] OCTET STRING
 249      * }
 250      * }</pre>
 251      *
 252      * <p>
 253      * This definition reflects the Network Working Group RFC 4120
 254      * specification available at
 255      * <a href="http://www.ietf.org/rfc/rfc4120.txt">
 256      * http://www.ietf.org/rfc/rfc4120.txt</a>.
 257      * @return byte array of enocded Checksum.
 258      * @exception Asn1Exception if an error occurs while decoding an
 259      * ASN1 encoded data.
 260      * @exception IOException if an I/O error occurs while reading
 261      * encoded data.
 262      *
 263      */
 264     public byte[] asn1Encode() throws Asn1Exception, IOException {
 265         DerOutputStream bytes = new DerOutputStream();
 266         DerOutputStream temp = new DerOutputStream();
 267         temp.putInteger(BigInteger.valueOf(cksumType));
 268         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
 269                                        true, (byte)0x00), temp);
 270         temp = new DerOutputStream();
 271         temp.putOctetString(checksum);
 272         bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT,
 273                                        true, (byte)0x01), temp);
 274         temp = new DerOutputStream();
 275         temp.write(DerValue.tag_Sequence, bytes);
 276         return temp.toByteArray();
 277     }
 278 
 279 
 280     /**
 281      * Parse (unmarshal) a checksum object from a DER input stream.  This form
 282      * parsing might be used when expanding a value which is part of
 283      * a constructed sequence and uses explicitly tagged type.
 284      *
 285      * @exception Asn1Exception if an error occurs while decoding an
 286      * ASN1 encoded data.
 287      * @exception IOException if an I/O error occurs while reading
 288      * encoded data.
 289      * @param data the Der input stream value, which contains one or more
 290      * marshaled value.
 291      * @param explicitTag tag number.
 292      * @param optional indicates if this data field is optional
 293      * @return an instance of Checksum.
 294      *
 295      */
 296     public static Checksum parse(DerInputStream data,
 297                                  byte explicitTag, boolean optional)
 298         throws Asn1Exception, IOException {
 299 
 300         if ((optional) &&
 301             (((byte)data.peekByte() & (byte)0x1F) != explicitTag)) {
 302             return null;
 303         }
 304         DerValue der = data.getDerValue();
 305         if (explicitTag != (der.getTag() & (byte)0x1F))  {
 306             throw new Asn1Exception(Krb5.ASN1_BAD_ID);
 307         } else {
 308             DerValue subDer = der.getData().getDerValue();
 309             return new Checksum(subDer);
 310         }
 311     }
 312 
 313     /**
 314      * Returns the raw bytes of the checksum, not in ASN.1 encoded form.
 315      */
 316     public final byte[] getBytes() {
 317         return checksum;
 318     }
 319 
 320     public final int getType() {
 321         return cksumType;
 322     }
 323 
 324     @Override public boolean equals(Object obj) {
 325         if (this == obj) {
 326             return true;
 327         }
 328         if (!(obj instanceof Checksum)) {
 329             return false;
 330         }
 331 
 332         try {
 333             return isEqual((Checksum)obj);
 334         } catch (KdcErrException kee) {
 335             return false;
 336         }
 337     }
 338 
 339     @Override public int hashCode() {
 340         int result = 17;
 341         result = 37 * result + cksumType;
 342         if (checksum != null) {
 343             result = 37 * result + Arrays.hashCode(checksum);
 344         }
 345         return result;
 346     }
 347 }