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