/*
 * Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.jgss.krb5;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import org.ietf.jgss.*;

import java.security.MessageDigest;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import sun.security.krb5.*;
import sun.security.krb5.internal.crypto.Des3;
import sun.security.krb5.internal.crypto.Aes128;
import sun.security.krb5.internal.crypto.Aes256;
import sun.security.krb5.internal.crypto.ArcFourHmac;

class CipherHelper {

    // From draft-raeburn-cat-gssapi-krb5-3des-00
    // Key usage values when deriving keys
    private static final int KG_USAGE_SEAL = 22;
    private static final int KG_USAGE_SIGN = 23;
    private static final int KG_USAGE_SEQ = 24;

    private static final int DES_CHECKSUM_SIZE = 8;
    private static final int DES_IV_SIZE = 8;
    private static final int AES_IV_SIZE = 16;

    // ARCFOUR-HMAC
    // Save first 8 octets of HMAC Sgn_Cksum
    private static final int HMAC_CHECKSUM_SIZE = 8;
    // key usage for MIC tokens used by MS
    private static final int KG_USAGE_SIGN_MS = 15;

    // debug flag
    private static final boolean DEBUG = Krb5Util.DEBUG;

    
A zero initial vector to be used for checksum calculation and for DesCbc application data encryption/decryption.
/** * A zero initial vector to be used for checksum calculation and for * DesCbc application data encryption/decryption. */
private static final byte[] ZERO_IV = new byte[DES_IV_SIZE]; private static final byte[] ZERO_IV_AES = new byte[AES_IV_SIZE]; private int etype; private int sgnAlg, sealAlg; private byte[] keybytes; // new token format from draft-ietf-krb-wg-gssapi-cfx-07 // proto is used to determine new GSS token format for "newer" etypes private int proto = 0; CipherHelper(EncryptionKey key) throws GSSException { etype = key.getEType(); keybytes = key.getBytes(); switch (etype) { case EncryptedData.ETYPE_DES_CBC_CRC: case EncryptedData.ETYPE_DES_CBC_MD5: sgnAlg = MessageToken.SGN_ALG_DES_MAC_MD5; sealAlg = MessageToken.SEAL_ALG_DES; break; case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: sgnAlg = MessageToken.SGN_ALG_HMAC_SHA1_DES3_KD; sealAlg = MessageToken.SEAL_ALG_DES3_KD; break; case EncryptedData.ETYPE_ARCFOUR_HMAC: sgnAlg = MessageToken.SGN_ALG_HMAC_MD5_ARCFOUR; sealAlg = MessageToken.SEAL_ALG_ARCFOUR_HMAC; break; case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: sgnAlg = -1; sealAlg = -1; proto = 1; break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported encryption type: " + etype); } } int getSgnAlg() { return sgnAlg; } int getSealAlg() { return sealAlg; } int getProto() { return proto; } int getEType() { return etype; } boolean isArcFour() { boolean flag = false; if (etype == EncryptedData.ETYPE_ARCFOUR_HMAC) { flag = true; } return flag; } @SuppressWarnings("fallthrough") byte[] calculateChecksum(int alg, byte[] header, byte[] trailer, byte[] data, int start, int len, int tokenId) throws GSSException { switch (alg) { case MessageToken.SGN_ALG_DES_MAC_MD5: /* * With this sign algorithm, first an MD5 hash is computed on the * application data. The 16 byte hash is then DesCbc encrypted. */ try { MessageDigest md5 = MessageDigest.getInstance("MD5"); // debug("\t\tdata=["); // debug(getHexBytes(checksumDataHeader, // checksumDataHeader.length) + " "); md5.update(header); // debug(getHexBytes(data, start, len)); md5.update(data, start, len); if (trailer != null) { // debug(" " + // getHexBytes(trailer, // optionalTrailer.length)); md5.update(trailer); } // debug("]\n"); data = md5.digest(); start = 0; len = data.length; // System.out.println("\tMD5 Checksum is [" + // getHexBytes(data) + "]\n"); header = null; trailer = null; } catch (NoSuchAlgorithmException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not get MD5 Message Digest - " + e.getMessage()); ge.initCause(e); throw ge; } // fall through to encrypt checksum case MessageToken.SGN_ALG_DES_MAC: return getDesCbcChecksum(keybytes, header, data, start, len); case MessageToken.SGN_ALG_HMAC_SHA1_DES3_KD: byte[] buf; int offset, total; if (header == null && trailer == null) { buf = data; total = len; offset = start; } else { total = ((header != null ? header.length : 0) + len + (trailer != null ? trailer.length : 0)); buf = new byte[total]; int pos = 0; if (header != null) { System.arraycopy(header, 0, buf, 0, header.length); pos = header.length; } System.arraycopy(data, start, buf, pos, len); pos += len; if (trailer != null) { System.arraycopy(trailer, 0, buf, pos, trailer.length); } offset = 0; } try { /* Krb5Token.debug("\nkeybytes: " + Krb5Token.getHexBytes(keybytes)); Krb5Token.debug("\nheader: " + (header == null ? "NONE" : Krb5Token.getHexBytes(header))); Krb5Token.debug("\ntrailer: " + (trailer == null ? "NONE" : Krb5Token.getHexBytes(trailer))); Krb5Token.debug("\ndata: " + Krb5Token.getHexBytes(data, start, len)); Krb5Token.debug("\nbuf: " + Krb5Token.getHexBytes(buf, offset, total)); */ byte[] answer = Des3.calculateChecksum(keybytes, KG_USAGE_SIGN, buf, offset, total); // Krb5Token.debug("\nanswer: " + // Krb5Token.getHexBytes(answer)); return answer; } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use HMAC-SHA1-DES3-KD signing algorithm - " + e.getMessage()); ge.initCause(e); throw ge; } case MessageToken.SGN_ALG_HMAC_MD5_ARCFOUR: byte[] buffer; int off, tot; if (header == null && trailer == null) { buffer = data; tot = len; off = start; } else { tot = ((header != null ? header.length : 0) + len + (trailer != null ? trailer.length : 0)); buffer = new byte[tot]; int pos = 0; if (header != null) { System.arraycopy(header, 0, buffer, 0, header.length); pos = header.length; } System.arraycopy(data, start, buffer, pos, len); pos += len; if (trailer != null) { System.arraycopy(trailer, 0, buffer, pos, trailer.length); } off = 0; } try { /* Krb5Token.debug("\nkeybytes: " + Krb5Token.getHexBytes(keybytes)); Krb5Token.debug("\nheader: " + (header == null ? "NONE" : Krb5Token.getHexBytes(header))); Krb5Token.debug("\ntrailer: " + (trailer == null ? "NONE" : Krb5Token.getHexBytes(trailer))); Krb5Token.debug("\ndata: " + Krb5Token.getHexBytes(data, start, len)); Krb5Token.debug("\nbuffer: " + Krb5Token.getHexBytes(buffer, off, tot)); */ // for MIC tokens, key derivation salt is 15 // NOTE: Required for interoperability. The RC4-HMAC spec // defines key_usage of 23, however all Kerberos impl. // MS/Solaris/MIT all use key_usage of 15 for MIC tokens int key_usage = KG_USAGE_SIGN; if (tokenId == Krb5Token.MIC_ID) { key_usage = KG_USAGE_SIGN_MS; } byte[] answer = ArcFourHmac.calculateChecksum(keybytes, key_usage, buffer, off, tot); // Krb5Token.debug("\nanswer: " + // Krb5Token.getHexBytes(answer)); // Save first 8 octets of HMAC Sgn_Cksum byte[] output = new byte[getChecksumLength()]; System.arraycopy(answer, 0, output, 0, output.length); // Krb5Token.debug("\nanswer (trimmed): " + // Krb5Token.getHexBytes(output)); return output; } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use HMAC_MD5_ARCFOUR signing algorithm - " + e.getMessage()); ge.initCause(e); throw ge; } default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported signing algorithm: " + sgnAlg); } } // calculate Checksum for the new GSS tokens byte[] calculateChecksum(byte[] header, byte[] data, int start, int len, int key_usage) throws GSSException { // total length int total = ((header != null ? header.length : 0) + len); // get_mic("plaintext-data" | "header") byte[] buf = new byte[total]; // data System.arraycopy(data, start, buf, 0, len); // token header if (header != null) { System.arraycopy(header, 0, buf, len, header.length); } // Krb5Token.debug("\nAES calculate checksum on: " + // Krb5Token.getHexBytes(buf)); switch (etype) { case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: try { byte[] answer = Aes128.calculateChecksum(keybytes, key_usage, buf, 0, total); // Krb5Token.debug("\nAES128 checksum: " + // Krb5Token.getHexBytes(answer)); return answer; } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use AES128 signing algorithm - " + e.getMessage()); ge.initCause(e); throw ge; } case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: try { byte[] answer = Aes256.calculateChecksum(keybytes, key_usage, buf, 0, total); // Krb5Token.debug("\nAES256 checksum: " + // Krb5Token.getHexBytes(answer)); return answer; } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use AES256 signing algorithm - " + e.getMessage()); ge.initCause(e); throw ge; } default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported encryption type: " + etype); } } byte[] encryptSeq(byte[] ivec, byte[] plaintext, int start, int len) throws GSSException { switch (sgnAlg) { case MessageToken.SGN_ALG_DES_MAC_MD5: case MessageToken.SGN_ALG_DES_MAC: try { Cipher des = getInitializedDes(true, keybytes, ivec); return des.doFinal(plaintext, start, len); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not encrypt sequence number using DES - " + e.getMessage()); ge.initCause(e); throw ge; } case MessageToken.SGN_ALG_HMAC_SHA1_DES3_KD: byte[] iv; if (ivec.length == DES_IV_SIZE) { iv = ivec; } else { iv = new byte[DES_IV_SIZE]; System.arraycopy(ivec, 0, iv, 0, DES_IV_SIZE); } try { return Des3.encryptRaw(keybytes, KG_USAGE_SEQ, iv, plaintext, start, len); } catch (Exception e) { // GeneralSecurityException, KrbCryptoException GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not encrypt sequence number using DES3-KD - " + e.getMessage()); ge.initCause(e); throw ge; } case MessageToken.SGN_ALG_HMAC_MD5_ARCFOUR: // ivec passed is the checksum byte[] checksum; if (ivec.length == HMAC_CHECKSUM_SIZE) { checksum = ivec; } else { checksum = new byte[HMAC_CHECKSUM_SIZE]; System.arraycopy(ivec, 0, checksum, 0, HMAC_CHECKSUM_SIZE); } try { return ArcFourHmac.encryptSeq(keybytes, KG_USAGE_SEQ, checksum, plaintext, start, len); } catch (Exception e) { // GeneralSecurityException, KrbCryptoException GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not encrypt sequence number using RC4-HMAC - " + e.getMessage()); ge.initCause(e); throw ge; } default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported signing algorithm: " + sgnAlg); } } byte[] decryptSeq(byte[] ivec, byte[] ciphertext, int start, int len) throws GSSException { switch (sgnAlg) { case MessageToken.SGN_ALG_DES_MAC_MD5: case MessageToken.SGN_ALG_DES_MAC: try { Cipher des = getInitializedDes(false, keybytes, ivec); return des.doFinal(ciphertext, start, len); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not decrypt sequence number using DES - " + e.getMessage()); ge.initCause(e); throw ge; } case MessageToken.SGN_ALG_HMAC_SHA1_DES3_KD: byte[] iv; if (ivec.length == DES_IV_SIZE) { iv = ivec; } else { iv = new byte[8]; System.arraycopy(ivec, 0, iv, 0, DES_IV_SIZE); } try { return Des3.decryptRaw(keybytes, KG_USAGE_SEQ, iv, ciphertext, start, len); } catch (Exception e) { // GeneralSecurityException, KrbCryptoException GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not decrypt sequence number using DES3-KD - " + e.getMessage()); ge.initCause(e); throw ge; } case MessageToken.SGN_ALG_HMAC_MD5_ARCFOUR: // ivec passed is the checksum byte[] checksum; if (ivec.length == HMAC_CHECKSUM_SIZE) { checksum = ivec; } else { checksum = new byte[HMAC_CHECKSUM_SIZE]; System.arraycopy(ivec, 0, checksum, 0, HMAC_CHECKSUM_SIZE); } try { return ArcFourHmac.decryptSeq(keybytes, KG_USAGE_SEQ, checksum, ciphertext, start, len); } catch (Exception e) { // GeneralSecurityException, KrbCryptoException GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not decrypt sequence number using RC4-HMAC - " + e.getMessage()); ge.initCause(e); throw ge; } default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported signing algorithm: " + sgnAlg); } } int getChecksumLength() throws GSSException { switch (etype) { case EncryptedData.ETYPE_DES_CBC_CRC: case EncryptedData.ETYPE_DES_CBC_MD5: return DES_CHECKSUM_SIZE; case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: return Des3.getChecksumLength(); case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: return Aes128.getChecksumLength(); case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: return Aes256.getChecksumLength(); case EncryptedData.ETYPE_ARCFOUR_HMAC: // only first 8 octets of HMAC Sgn_Cksum are used return HMAC_CHECKSUM_SIZE; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported encryption type: " + etype); } } void decryptData(WrapToken token, byte[] ciphertext, int cStart, int cLen, byte[] plaintext, int pStart) throws GSSException { /* Krb5Token.debug("decryptData : ciphertext = " + Krb5Token.getHexBytes(ciphertext)); */ switch (sealAlg) { case MessageToken.SEAL_ALG_DES: desCbcDecrypt(token, getDesEncryptionKey(keybytes), ciphertext, cStart, cLen, plaintext, pStart); break; case MessageToken.SEAL_ALG_DES3_KD: des3KdDecrypt(token, ciphertext, cStart, cLen, plaintext, pStart); break; case MessageToken.SEAL_ALG_ARCFOUR_HMAC: arcFourDecrypt(token, ciphertext, cStart, cLen, plaintext, pStart); break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported seal algorithm: " + sealAlg); } } // decrypt data in the new GSS tokens void decryptData(WrapToken_v2 token, byte[] ciphertext, int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) throws GSSException { /* Krb5Token.debug("decryptData : ciphertext = " + Krb5Token.getHexBytes(ciphertext)); */ switch (etype) { case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: aes128Decrypt(token, ciphertext, cStart, cLen, plaintext, pStart, key_usage); break; case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: aes256Decrypt(token, ciphertext, cStart, cLen, plaintext, pStart, key_usage); break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported etype: " + etype); } } void decryptData(WrapToken token, InputStream cipherStream, int cLen, byte[] plaintext, int pStart) throws GSSException, IOException { switch (sealAlg) { case MessageToken.SEAL_ALG_DES: desCbcDecrypt(token, getDesEncryptionKey(keybytes), cipherStream, cLen, plaintext, pStart); break; case MessageToken.SEAL_ALG_DES3_KD: // Read encrypted data from stream byte[] ciphertext = new byte[cLen]; try { Krb5Token.readFully(cipherStream, ciphertext, 0, cLen); } catch (IOException e) { GSSException ge = new GSSException( GSSException.DEFECTIVE_TOKEN, -1, "Cannot read complete token"); ge.initCause(e); throw ge; } des3KdDecrypt(token, ciphertext, 0, cLen, plaintext, pStart); break; case MessageToken.SEAL_ALG_ARCFOUR_HMAC: // Read encrypted data from stream byte[] ctext = new byte[cLen]; try { Krb5Token.readFully(cipherStream, ctext, 0, cLen); } catch (IOException e) { GSSException ge = new GSSException( GSSException.DEFECTIVE_TOKEN, -1, "Cannot read complete token"); ge.initCause(e); throw ge; } arcFourDecrypt(token, ctext, 0, cLen, plaintext, pStart); break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported seal algorithm: " + sealAlg); } } void decryptData(WrapToken_v2 token, InputStream cipherStream, int cLen, byte[] plaintext, int pStart, int key_usage) throws GSSException, IOException { // Read encrypted data from stream byte[] ciphertext = new byte[cLen]; try { Krb5Token.readFully(cipherStream, ciphertext, 0, cLen); } catch (IOException e) { GSSException ge = new GSSException( GSSException.DEFECTIVE_TOKEN, -1, "Cannot read complete token"); ge.initCause(e); throw ge; } switch (etype) { case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: aes128Decrypt(token, ciphertext, 0, cLen, plaintext, pStart, key_usage); break; case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: aes256Decrypt(token, ciphertext, 0, cLen, plaintext, pStart, key_usage); break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported etype: " + etype); } } void encryptData(WrapToken token, byte[] confounder, byte[] plaintext, int start, int len, byte[] padding, OutputStream os) throws GSSException, IOException { switch (sealAlg) { case MessageToken.SEAL_ALG_DES: // Encrypt on the fly and write Cipher des = getInitializedDes(true, getDesEncryptionKey(keybytes), ZERO_IV); CipherOutputStream cos = new CipherOutputStream(os, des); // debug(getHexBytes(confounder, confounder.length)); cos.write(confounder); // debug(" " + getHexBytes(plaintext, start, len)); cos.write(plaintext, start, len); // debug(" " + getHexBytes(padding, padding.length)); cos.write(padding); break; case MessageToken.SEAL_ALG_DES3_KD: byte[] ctext = des3KdEncrypt(confounder, plaintext, start, len, padding); // Write to stream os.write(ctext); break; case MessageToken.SEAL_ALG_ARCFOUR_HMAC: byte[] ciphertext = arcFourEncrypt(token, confounder, plaintext, start, len, padding); // Write to stream os.write(ciphertext); break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported seal algorithm: " + sealAlg); } } /* * Encrypt data in the new GSS tokens * * Wrap Tokens (with confidentiality) * { Encrypt(16-byte confounder | plaintext | 16-byte token_header) | * 12-byte HMAC } * where HMAC is on {16-byte confounder | plaintext | 16-byte token_header} * HMAC is not encrypted; it is appended at the end. */ byte[] encryptData(WrapToken_v2 token, byte[] confounder, byte[] tokenHeader, byte[] plaintext, int start, int len, int key_usage) throws GSSException { switch (etype) { case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: return aes128Encrypt(confounder, tokenHeader, plaintext, start, len, key_usage); case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: return aes256Encrypt(confounder, tokenHeader, plaintext, start, len, key_usage); default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported etype: " + etype); } } void encryptData(WrapToken token, byte[] confounder, byte[] plaintext, int pStart, int pLen, byte[] padding, byte[] ciphertext, int cStart) throws GSSException { switch (sealAlg) { case MessageToken.SEAL_ALG_DES: int pos = cStart; // Encrypt and write Cipher des = getInitializedDes(true, getDesEncryptionKey(keybytes), ZERO_IV); try { // debug(getHexBytes(confounder, confounder.length)); pos += des.update(confounder, 0, confounder.length, ciphertext, pos); // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen)); pos += des.update(plaintext, pStart, pLen, ciphertext, pos); // debug(" " + getHexBytes(padding, padding.length)); des.update(padding, 0, padding.length, ciphertext, pos); des.doFinal(); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use DES Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } break; case MessageToken.SEAL_ALG_DES3_KD: byte[] ctext = des3KdEncrypt(confounder, plaintext, pStart, pLen, padding); System.arraycopy(ctext, 0, ciphertext, cStart, ctext.length); break; case MessageToken.SEAL_ALG_ARCFOUR_HMAC: byte[] ctext2 = arcFourEncrypt(token, confounder, plaintext, pStart, pLen, padding); System.arraycopy(ctext2, 0, ciphertext, cStart, ctext2.length); break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported seal algorithm: " + sealAlg); } } /* * Encrypt data in the new GSS tokens * * Wrap Tokens (with confidentiality) * { Encrypt(16-byte confounder | plaintext | 16-byte token_header) | * 12-byte HMAC } * where HMAC is on {16-byte confounder | plaintext | 16-byte token_header} * HMAC is not encrypted; it is appended at the end. */ int encryptData(WrapToken_v2 token, byte[] confounder, byte[] tokenHeader, byte[] plaintext, int pStart, int pLen, byte[] ciphertext, int cStart, int key_usage) throws GSSException { byte[] ctext = null; switch (etype) { case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: ctext = aes128Encrypt(confounder, tokenHeader, plaintext, pStart, pLen, key_usage); break; case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: ctext = aes256Encrypt(confounder, tokenHeader, plaintext, pStart, pLen, key_usage); break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported etype: " + etype); } System.arraycopy(ctext, 0, ciphertext, cStart, ctext.length); return ctext.length; } // --------------------- DES methods
Computes the DesCbc checksum based on the algorithm published in FIPS Publication 113. This involves applying padding to the data passed in, then performing DesCbc encryption on the data with a zero initial vector, and finally returning the last 8 bytes of the encryption result.
Params:
  • key – the bytes for the DES key
  • header – a header to process first before the data is.
  • data – the data to checksum
  • offset – the offset where the data begins
  • len – the length of the data
Throws:
/** * Computes the DesCbc checksum based on the algorithm published in FIPS * Publication 113. This involves applying padding to the data passed * in, then performing DesCbc encryption on the data with a zero initial * vector, and finally returning the last 8 bytes of the encryption * result. * * @param key the bytes for the DES key * @param header a header to process first before the data is. * @param data the data to checksum * @param offset the offset where the data begins * @param len the length of the data * @throws GSSException when an error occuse in the encryption */
private byte[] getDesCbcChecksum(byte key[], byte[] header, byte[] data, int offset, int len) throws GSSException { Cipher des = getInitializedDes(true, key, ZERO_IV); int blockSize = des.getBlockSize(); /* * Here the data need not be a multiple of the blocksize * (8). Encrypt and throw away results for all blocks except for * the very last block. */ byte[] finalBlock = new byte[blockSize]; int numBlocks = len / blockSize; int lastBytes = len % blockSize; if (lastBytes == 0) { // No need for padding. Save last block from application data numBlocks -= 1; System.arraycopy(data, offset + numBlocks*blockSize, finalBlock, 0, blockSize); } else { System.arraycopy(data, offset + numBlocks*blockSize, finalBlock, 0, lastBytes); // Zero padding automatically done } try { byte[] temp = new byte[Math.max(blockSize, (header == null? blockSize : header.length))]; if (header != null) { // header will be null when doing DES-MD5 Checksum des.update(header, 0, header.length, temp, 0); } // Iterate over all but the last block for (int i = 0; i < numBlocks; i++) { des.update(data, offset, blockSize, temp, 0); offset += blockSize; } // Now process the final block byte[] retVal = new byte[blockSize]; des.update(finalBlock, 0, blockSize, retVal, 0); des.doFinal(); return retVal; } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use DES Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } }
Obtains an initialized DES cipher.
Params:
  • encryptMode – true if encryption is desired, false is decryption is desired.
  • key – the bytes for the DES key
  • ivBytes – the initial vector bytes
/** * Obtains an initialized DES cipher. * * @param encryptMode true if encryption is desired, false is decryption * is desired. * @param key the bytes for the DES key * @param ivBytes the initial vector bytes */
private final Cipher getInitializedDes(boolean encryptMode, byte[] key, byte[] ivBytes) throws GSSException { try { IvParameterSpec iv = new IvParameterSpec(ivBytes); SecretKey jceKey = (SecretKey) (new SecretKeySpec(key, "DES")); Cipher desCipher = Cipher.getInstance("DES/CBC/NoPadding"); desCipher.init( (encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE), jceKey, iv); return desCipher; } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, e.getMessage()); ge.initCause(e); throw ge; } }
Helper routine to decrypt fromm a byte array and write the application data straight to an output array with minimal buffer copies. The confounder and the padding are stored separately and not copied into this output array.
Params:
  • key – the DES key to use
  • cipherText – the encrypted data
  • offset – the offset for the encrypted data
  • len – the length of the encrypted data
  • dataOutBuf – the output buffer where the application data should be writte
  • dataOffset – the offser where the application data should be written.
Throws:
  • GSSException – is an error occurs while decrypting the data
/** * Helper routine to decrypt fromm a byte array and write the * application data straight to an output array with minimal * buffer copies. The confounder and the padding are stored * separately and not copied into this output array. * @param key the DES key to use * @param cipherText the encrypted data * @param offset the offset for the encrypted data * @param len the length of the encrypted data * @param dataOutBuf the output buffer where the application data * should be writte * @param dataOffset the offser where the application data should * be written. * @throws GSSException is an error occurs while decrypting the * data */
private void desCbcDecrypt(WrapToken token, byte[] key, byte[] cipherText, int offset, int len, byte[] dataOutBuf, int dataOffset) throws GSSException { try { int temp = 0; Cipher des = getInitializedDes(false, key, ZERO_IV); /* * Remove the counfounder first. * CONFOUNDER_SIZE is one DES block ie 8 bytes. */ temp = des.update(cipherText, offset, WrapToken.CONFOUNDER_SIZE, token.confounder); // temp should be CONFOUNDER_SIZE // debug("\n\ttemp is " + temp + " and CONFOUNDER_SIZE is " // + CONFOUNDER_SIZE); offset += WrapToken.CONFOUNDER_SIZE; len -= WrapToken.CONFOUNDER_SIZE; /* * len is a multiple of 8 due to padding. * Decrypt all blocks directly into the output buffer except for * the very last block. Remove the trailing padding bytes from the * very last block and copy that into the output buffer. */ int blockSize = des.getBlockSize(); int numBlocks = len / blockSize - 1; // Iterate over all but the last block for (int i = 0; i < numBlocks; i++) { temp = des.update(cipherText, offset, blockSize, dataOutBuf, dataOffset); // temp should be blockSize // debug("\n\ttemp is " + temp + " and blockSize is " // + blockSize); offset += blockSize; dataOffset += blockSize; } // Now process the last block byte[] finalBlock = new byte[blockSize]; des.update(cipherText, offset, blockSize, finalBlock); des.doFinal(); /* * There is always at least one padding byte. The padding bytes * are all the value of the number of padding bytes. */ int padSize = finalBlock[blockSize - 1]; if (padSize < 1 || padSize > 8) throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, "Invalid padding on Wrap Token"); token.padding = WrapToken.pads[padSize]; blockSize -= padSize; // Copy this last block into the output buffer System.arraycopy(finalBlock, 0, dataOutBuf, dataOffset, blockSize); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use DES cipher - " + e.getMessage()); ge.initCause(e); throw ge; } }
Helper routine to decrypt from an InputStream and write the application data straight to an output array with minimal buffer copies. The confounder and the padding are stored separately and not copied into this output array.
Params:
  • key – the DES key to use
  • is – the InputStream from which the cipher text should be read
  • len – the length of the ciphertext data
  • dataOutBuf – the output buffer where the application data should be writte
  • dataOffset – the offser where the application data should be written.
Throws:
  • GSSException – is an error occurs while decrypting the data
/** * Helper routine to decrypt from an InputStream and write the * application data straight to an output array with minimal * buffer copies. The confounder and the padding are stored * separately and not copied into this output array. * @param key the DES key to use * @param is the InputStream from which the cipher text should be * read * @param len the length of the ciphertext data * @param dataOutBuf the output buffer where the application data * should be writte * @param dataOffset the offser where the application data should * be written. * @throws GSSException is an error occurs while decrypting the * data */
private void desCbcDecrypt(WrapToken token, byte[] key, InputStream is, int len, byte[] dataOutBuf, int dataOffset) throws GSSException, IOException { int temp = 0; Cipher des = getInitializedDes(false, key, ZERO_IV); WrapTokenInputStream truncatedInputStream = new WrapTokenInputStream(is, len); CipherInputStream cis = new CipherInputStream(truncatedInputStream, des); /* * Remove the counfounder first. * CONFOUNDER_SIZE is one DES block ie 8 bytes. */ temp = cis.read(token.confounder); len -= temp; // temp should be CONFOUNDER_SIZE // debug("Got " + temp + " bytes; CONFOUNDER_SIZE is " // + CONFOUNDER_SIZE + "\n"); // debug("Confounder is " + getHexBytes(confounder) + "\n"); /* * len is a multiple of 8 due to padding. * Decrypt all blocks directly into the output buffer except for * the very last block. Remove the trailing padding bytes from the * very last block and copy that into the output buffer. */ int blockSize = des.getBlockSize(); int numBlocks = len / blockSize - 1; // Iterate over all but the last block for (int i = 0; i < numBlocks; i++) { // debug("dataOffset is " + dataOffset + "\n"); temp = cis.read(dataOutBuf, dataOffset, blockSize); // temp should be blockSize // debug("Got " + temp + " bytes and blockSize is " // + blockSize + "\n"); // debug("Bytes are: " // + getHexBytes(dataOutBuf, dataOffset, temp) + "\n"); dataOffset += blockSize; } // Now process the last block byte[] finalBlock = new byte[blockSize]; // debug("Will call read on finalBlock" + "\n"); temp = cis.read(finalBlock); // temp should be blockSize /* debug("Got " + temp + " bytes and blockSize is " + blockSize + "\n"); debug("Bytes are: " + getHexBytes(finalBlock, 0, temp) + "\n"); debug("Will call doFinal" + "\n"); */ try { des.doFinal(); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use DES cipher - " + e.getMessage()); ge.initCause(e); throw ge; } /* * There is always at least one padding byte. The padding bytes * are all the value of the number of padding bytes. */ int padSize = finalBlock[blockSize - 1]; if (padSize < 1 || padSize > 8) throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, "Invalid padding on Wrap Token"); token.padding = WrapToken.pads[padSize]; blockSize -= padSize; // Copy this last block into the output buffer System.arraycopy(finalBlock, 0, dataOutBuf, dataOffset, blockSize); } private static byte[] getDesEncryptionKey(byte[] key) throws GSSException { /* * To meet export control requirements, double check that the * key being used is no longer than 64 bits. * * Note that from a protocol point of view, an * algorithm that is not DES will be rejected before this * point. Also, a DES key that is not 64 bits will be * rejected by a good JCE provider. */ if (key.length > 8) throw new GSSException(GSSException.FAILURE, -100, "Invalid DES Key!"); byte[] retVal = new byte[key.length]; for (int i = 0; i < key.length; i++) retVal[i] = (byte)(key[i] ^ 0xf0); // RFC 1964, Section 1.2.2 return retVal; } // ---- DES3-KD methods private void des3KdDecrypt(WrapToken token, byte[] ciphertext, int cStart, int cLen, byte[] plaintext, int pStart) throws GSSException { byte[] ptext; try { ptext = Des3.decryptRaw(keybytes, KG_USAGE_SEAL, ZERO_IV, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use DES3-KD Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } /* Krb5Token.debug("\ndes3KdDecrypt in: " + Krb5Token.getHexBytes(ciphertext, cStart, cLen)); Krb5Token.debug("\ndes3KdDecrypt plain: " + Krb5Token.getHexBytes(ptext)); */ // Strip out confounder and padding /* * There is always at least one padding byte. The padding bytes * are all the value of the number of padding bytes. */ int padSize = ptext[ptext.length - 1]; if (padSize < 1 || padSize > 8) throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, "Invalid padding on Wrap Token"); token.padding = WrapToken.pads[padSize]; int len = ptext.length - WrapToken.CONFOUNDER_SIZE - padSize; System.arraycopy(ptext, WrapToken.CONFOUNDER_SIZE, plaintext, pStart, len); // Needed to calculate checksum System.arraycopy(ptext, 0, token.confounder, 0, WrapToken.CONFOUNDER_SIZE); } private byte[] des3KdEncrypt(byte[] confounder, byte[] plaintext, int start, int len, byte[] padding) throws GSSException { // [confounder | plaintext | padding] byte[] all = new byte[confounder.length + len + padding.length]; System.arraycopy(confounder, 0, all, 0, confounder.length); System.arraycopy(plaintext, start, all, confounder.length, len); System.arraycopy(padding, 0, all, confounder.length + len, padding.length); // Krb5Token.debug("\ndes3KdEncrypt:" + Krb5Token.getHexBytes(all)); // Encrypt try { byte[] answer = Des3.encryptRaw(keybytes, KG_USAGE_SEAL, ZERO_IV, all, 0, all.length); // Krb5Token.debug("\ndes3KdEncrypt encrypted:" + // Krb5Token.getHexBytes(answer)); return answer; } catch (Exception e) { // GeneralSecurityException, KrbCryptoException GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use DES3-KD Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } } // ---- RC4-HMAC methods private void arcFourDecrypt(WrapToken token, byte[] ciphertext, int cStart, int cLen, byte[] plaintext, int pStart) throws GSSException { // obtain Sequence number needed for decryption // first decrypt the Sequence Number using checksum byte[] seqNum = decryptSeq(token.getChecksum(), token.getEncSeqNumber(), 0, 8); byte[] ptext; try { ptext = ArcFourHmac.decryptRaw(keybytes, KG_USAGE_SEAL, ZERO_IV, ciphertext, cStart, cLen, seqNum); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use ArcFour Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } /* Krb5Token.debug("\narcFourDecrypt in: " + Krb5Token.getHexBytes(ciphertext, cStart, cLen)); Krb5Token.debug("\narcFourDecrypt plain: " + Krb5Token.getHexBytes(ptext)); */ // Strip out confounder and padding /* * There is always at least one padding byte. The padding bytes * are all the value of the number of padding bytes. */ int padSize = ptext[ptext.length - 1]; if (padSize < 1) throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1, "Invalid padding on Wrap Token"); token.padding = WrapToken.pads[padSize]; int len = ptext.length - WrapToken.CONFOUNDER_SIZE - padSize; System.arraycopy(ptext, WrapToken.CONFOUNDER_SIZE, plaintext, pStart, len); // Krb5Token.debug("\narcFourDecrypt plaintext: " + // Krb5Token.getHexBytes(plaintext)); // Needed to calculate checksum System.arraycopy(ptext, 0, token.confounder, 0, WrapToken.CONFOUNDER_SIZE); } private byte[] arcFourEncrypt(WrapToken token, byte[] confounder, byte[] plaintext, int start, int len, byte[] padding) throws GSSException { // [confounder | plaintext | padding] byte[] all = new byte[confounder.length + len + padding.length]; System.arraycopy(confounder, 0, all, 0, confounder.length); System.arraycopy(plaintext, start, all, confounder.length, len); System.arraycopy(padding, 0, all, confounder.length + len, padding.length); // get the token Sequence Number required for encryption // Note: When using this RC4 based encryption type, the sequence number // is always sent in big-endian rather than little-endian order. byte[] seqNum = new byte[4]; WrapToken.writeBigEndian(token.getSequenceNumber(), seqNum); // Krb5Token.debug("\narcFourEncrypt:" + Krb5Token.getHexBytes(all)); // Encrypt try { byte[] answer = ArcFourHmac.encryptRaw(keybytes, KG_USAGE_SEAL, seqNum, all, 0, all.length); // Krb5Token.debug("\narcFourEncrypt encrypted:" + // Krb5Token.getHexBytes(answer)); return answer; } catch (Exception e) { // GeneralSecurityException, KrbCryptoException GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use ArcFour Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } } // ---- AES methods private byte[] aes128Encrypt(byte[] confounder, byte[] tokenHeader, byte[] plaintext, int start, int len, int key_usage) throws GSSException { // encrypt { AES-plaintext-data | filler | header } // AES-plaintext-data { confounder | plaintext } // WrapToken = { tokenHeader | // Encrypt (confounder | plaintext | tokenHeader ) | HMAC } byte[] all = new byte[confounder.length + len + tokenHeader.length]; System.arraycopy(confounder, 0, all, 0, confounder.length); System.arraycopy(plaintext, start, all, confounder.length, len); System.arraycopy(tokenHeader, 0, all, confounder.length+len, tokenHeader.length); // Krb5Token.debug("\naes128Encrypt:" + Krb5Token.getHexBytes(all)); try { byte[] answer = Aes128.encryptRaw(keybytes, key_usage, ZERO_IV_AES, all, 0, all.length); // Krb5Token.debug("\naes128Encrypt encrypted:" + // Krb5Token.getHexBytes(answer)); return answer; } catch (Exception e) { // GeneralSecurityException, KrbCryptoException GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use AES128 Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } } private void aes128Decrypt(WrapToken_v2 token, byte[] ciphertext, int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) throws GSSException { byte[] ptext = null; try { ptext = Aes128.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use AES128 Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } /* Krb5Token.debug("\naes128Decrypt in: " + Krb5Token.getHexBytes(ciphertext, cStart, cLen)); Krb5Token.debug("\naes128Decrypt plain: " + Krb5Token.getHexBytes(ptext)); Krb5Token.debug("\naes128Decrypt ptext: " + Krb5Token.getHexBytes(ptext)); */ // Strip out confounder and token header int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - WrapToken_v2.TOKEN_HEADER_SIZE; System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, plaintext, pStart, len); /* Krb5Token.debug("\naes128Decrypt plaintext: " + Krb5Token.getHexBytes(plaintext, pStart, len)); */ } private byte[] aes256Encrypt(byte[] confounder, byte[] tokenHeader, byte[] plaintext, int start, int len, int key_usage) throws GSSException { // encrypt { AES-plaintext-data | filler | header } // AES-plaintext-data { confounder | plaintext } // WrapToken = { tokenHeader | // Encrypt (confounder | plaintext | tokenHeader ) | HMAC } byte[] all = new byte[confounder.length + len + tokenHeader.length]; System.arraycopy(confounder, 0, all, 0, confounder.length); System.arraycopy(plaintext, start, all, confounder.length, len); System.arraycopy(tokenHeader, 0, all, confounder.length+len, tokenHeader.length); // Krb5Token.debug("\naes256Encrypt:" + Krb5Token.getHexBytes(all)); try { byte[] answer = Aes256.encryptRaw(keybytes, key_usage, ZERO_IV_AES, all, 0, all.length); // Krb5Token.debug("\naes256Encrypt encrypted:" + // Krb5Token.getHexBytes(answer)); return answer; } catch (Exception e) { // GeneralSecurityException, KrbCryptoException GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use AES256 Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } } private void aes256Decrypt(WrapToken_v2 token, byte[] ciphertext, int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) throws GSSException { byte[] ptext; try { ptext = Aes256.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, "Could not use AES128 Cipher - " + e.getMessage()); ge.initCause(e); throw ge; } /* Krb5Token.debug("\naes256Decrypt in: " + Krb5Token.getHexBytes(ciphertext, cStart, cLen)); Krb5Token.debug("\naes256Decrypt plain: " + Krb5Token.getHexBytes(ptext)); Krb5Token.debug("\naes256Decrypt ptext: " + Krb5Token.getHexBytes(ptext)); */ // Strip out confounder and token header int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - WrapToken_v2.TOKEN_HEADER_SIZE; System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, plaintext, pStart, len); /* Krb5Token.debug("\naes128Decrypt plaintext: " + Krb5Token.getHexBytes(plaintext, pStart, len)); */ }
This class provides a truncated inputstream needed by WrapToken. The truncated inputstream is passed to CipherInputStream. It prevents the CipherInputStream from treating the bytes of the following token as part fo the ciphertext for this token.
/** * This class provides a truncated inputstream needed by WrapToken. The * truncated inputstream is passed to CipherInputStream. It prevents * the CipherInputStream from treating the bytes of the following token * as part fo the ciphertext for this token. */
class WrapTokenInputStream extends InputStream { private InputStream is; private int length; private int remaining; private int temp; public WrapTokenInputStream(InputStream is, int length) { this.is = is; this.length = length; remaining = length; } public final int read() throws IOException { if (remaining == 0) return -1; else { temp = is.read(); if (temp != -1) remaining -= temp; return temp; } } public final int read(byte[] b) throws IOException { if (remaining == 0) return -1; else { temp = Math.min(remaining, b.length); temp = is.read(b, 0, temp); if (temp != -1) remaining -= temp; return temp; } } public final int read(byte[] b, int off, int len) throws IOException { if (remaining == 0) return -1; else { temp = Math.min(remaining, len); temp = is.read(b, off, temp); if (temp != -1) remaining -= temp; return temp; } } public final long skip(long n) throws IOException { if (remaining == 0) return 0; else { temp = (int) Math.min(remaining, n); temp = (int) is.skip(temp); remaining -= temp; return temp; } } public final int available() throws IOException { return Math.min(remaining, is.available()); } public final void close() throws IOException { remaining = 0; } } }