/* * Copyright (c) 2004, 2006, 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. */ 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; } 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. */ void encryptData(WrapToken_v2 token, byte[] confounder, byte[] tokenHeader, byte[] plaintext, int start, int len, int key_usage, OutputStream os) throws GSSException, IOException { byte[] ctext = null; switch (etype) { case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: ctext = aes128Encrypt(confounder, tokenHeader, plaintext, start, len, key_usage); break; case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: ctext = aes256Encrypt(confounder, tokenHeader, plaintext, start, len, key_usage); break; default: throw new GSSException(GSSException.FAILURE, -1, "Unsupported etype: " + etype); } // Krb5Token.debug("EncryptedData = " + // Krb5Token.getHexBytes(ctext) + "\n"); // Write to stream os.write(ctext); } 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. * * @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. * * @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. * @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. * @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]; token.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. */ 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; } } }