1503 lines
55 KiB
Java
1503 lines
55 KiB
Java
/*
|
|
* Copyright (c) 2004, 2017, 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;
|
|
import sun.security.krb5.internal.crypto.EType;
|
|
|
|
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;
|
|
|
|
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;
|
|
break;
|
|
|
|
default:
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"Unsupported encryption type: " + etype);
|
|
}
|
|
}
|
|
|
|
int getSgnAlg() {
|
|
return sgnAlg;
|
|
}
|
|
|
|
int getSealAlg() {
|
|
return sealAlg;
|
|
}
|
|
|
|
// new token format from draft-ietf-krb-wg-gssapi-cfx-07
|
|
// proto is used to determine new GSS token format for "newer" etypes
|
|
int getProto() {
|
|
return EType.isNewer(etype) ? 1 : 0;
|
|
}
|
|
|
|
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.
|
|
*
|
|
* @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];
|
|
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.
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
}
|