|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
/* |
|
* |
|
* (C) Copyright IBM Corp. 1999 All Rights Reserved. |
|
* Copyright 1997 The Open Group Research Institute. All rights reserved. |
|
*/ |
|
|
|
package sun.security.krb5; |
|
|
|
import sun.security.util.*; |
|
import sun.security.krb5.internal.*; |
|
import sun.security.krb5.internal.crypto.*; |
|
import java.io.IOException; |
|
import java.security.GeneralSecurityException; |
|
import java.util.Arrays; |
|
import sun.security.krb5.internal.ktab.KeyTab; |
|
import sun.security.krb5.internal.ccache.CCacheOutputStream; |
|
import javax.crypto.spec.DESKeySpec; |
|
import javax.crypto.spec.DESedeKeySpec; |
|
|
|
/** |
|
* This class encapsulates the concept of an EncryptionKey. An encryption |
|
* key is defined in RFC 4120 as: |
|
* |
|
* EncryptionKey ::= SEQUENCE { |
|
* keytype [0] Int32 -- actually encryption type --, |
|
* keyvalue [1] OCTET STRING |
|
* } |
|
* |
|
* keytype |
|
* This field specifies the encryption type of the encryption key |
|
* that follows in the keyvalue field. Although its name is |
|
* "keytype", it actually specifies an encryption type. Previously, |
|
* multiple cryptosystems that performed encryption differently but |
|
* were capable of using keys with the same characteristics were |
|
* permitted to share an assigned number to designate the type of |
|
* key; this usage is now deprecated. |
|
* |
|
* keyvalue |
|
* This field contains the key itself, encoded as an octet string. |
|
*/ |
|
|
|
public class EncryptionKey |
|
implements Cloneable { |
|
|
|
public static final EncryptionKey NULL_KEY = |
|
new EncryptionKey(new byte[] {}, EncryptedData.ETYPE_NULL, null); |
|
|
|
private int keyType; |
|
private byte[] keyValue; |
|
private Integer kvno; |
|
|
|
private static final boolean DEBUG = Krb5.DEBUG; |
|
|
|
public synchronized int getEType() { |
|
return keyType; |
|
} |
|
|
|
public final Integer getKeyVersionNumber() { |
|
return kvno; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public final byte[] getBytes() { |
|
// This method cannot be called outside sun.security, hence no |
|
|
|
return keyValue; |
|
} |
|
|
|
public synchronized Object clone() { |
|
return new EncryptionKey(keyValue, keyType, kvno); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static EncryptionKey[] acquireSecretKeys(PrincipalName princ, |
|
String keytab) { |
|
|
|
if (princ == null) |
|
throw new IllegalArgumentException( |
|
"Cannot have null pricipal name to look in keytab."); |
|
|
|
// KeyTab getInstance(keytab) will call KeyTab.getInstance() |
|
|
|
KeyTab ktab = KeyTab.getInstance(keytab); |
|
return ktab.readServiceKeys(princ); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static EncryptionKey acquireSecretKey(PrincipalName cname, |
|
char[] password, int etype, PAData.SaltAndParams snp) |
|
throws KrbException { |
|
String salt; |
|
byte[] s2kparams; |
|
if (snp != null) { |
|
salt = snp.salt != null ? snp.salt : cname.getSalt(); |
|
s2kparams = snp.params; |
|
} else { |
|
salt = cname.getSalt(); |
|
s2kparams = null; |
|
} |
|
return acquireSecretKey(password, salt, etype, s2kparams); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static EncryptionKey acquireSecretKey(char[] password, |
|
String salt, int etype, byte[] s2kparams) |
|
throws KrbException { |
|
|
|
return new EncryptionKey( |
|
stringToKey(password, salt, s2kparams, etype), |
|
etype, null); |
|
} |
|
|
|
/** |
|
* Generate a list of keys using the given principal and password. |
|
* Construct a key for each configured etype. |
|
* Caller is responsible for clearing password. |
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static EncryptionKey[] acquireSecretKeys(char[] password, |
|
String salt) throws KrbException { |
|
|
|
int[] etypes = EType.getDefaults("default_tkt_enctypes"); |
|
|
|
EncryptionKey[] encKeys = new EncryptionKey[etypes.length]; |
|
for (int i = 0; i < etypes.length; i++) { |
|
if (EType.isSupported(etypes[i])) { |
|
encKeys[i] = new EncryptionKey( |
|
stringToKey(password, salt, null, etypes[i]), |
|
etypes[i], null); |
|
} else { |
|
if (DEBUG) { |
|
System.out.println("Encryption Type " + |
|
EType.toString(etypes[i]) + |
|
" is not supported/enabled"); |
|
} |
|
} |
|
} |
|
return encKeys; |
|
} |
|
|
|
|
|
public EncryptionKey(byte[] keyValue, |
|
int keyType, |
|
Integer kvno) { |
|
|
|
if (keyValue != null) { |
|
this.keyValue = new byte[keyValue.length]; |
|
System.arraycopy(keyValue, 0, this.keyValue, 0, keyValue.length); |
|
} else { |
|
throw new IllegalArgumentException("EncryptionKey: " + |
|
"Key bytes cannot be null!"); |
|
} |
|
this.keyType = keyType; |
|
this.kvno = kvno; |
|
} |
|
|
|
/** |
|
* Constructs an EncryptionKey by using the specified key type and key |
|
* value. It is used to recover the key when retrieving data from |
|
* credential cache file. |
|
* |
|
*/ |
|
// Used in JSSE (KerberosWrapper), Credentials, |
|
|
|
public EncryptionKey(int keyType, |
|
byte[] keyValue) { |
|
this(keyValue, keyType, null); |
|
} |
|
|
|
private static byte[] stringToKey(char[] password, String salt, |
|
byte[] s2kparams, int keyType) throws KrbCryptoException { |
|
|
|
char[] slt = salt.toCharArray(); |
|
char[] pwsalt = new char[password.length + slt.length]; |
|
System.arraycopy(password, 0, pwsalt, 0, password.length); |
|
System.arraycopy(slt, 0, pwsalt, password.length, slt.length); |
|
Arrays.fill(slt, '0'); |
|
|
|
try { |
|
switch (keyType) { |
|
case EncryptedData.ETYPE_DES_CBC_CRC: |
|
case EncryptedData.ETYPE_DES_CBC_MD5: |
|
return Des.string_to_key_bytes(pwsalt); |
|
|
|
case EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD: |
|
return Des3.stringToKey(pwsalt); |
|
|
|
case EncryptedData.ETYPE_ARCFOUR_HMAC: |
|
return ArcFourHmac.stringToKey(password); |
|
|
|
case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: |
|
return Aes128.stringToKey(password, salt, s2kparams); |
|
|
|
case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: |
|
return Aes256.stringToKey(password, salt, s2kparams); |
|
|
|
default: |
|
throw new IllegalArgumentException("encryption type " + |
|
EType.toString(keyType) + " not supported"); |
|
} |
|
|
|
} catch (GeneralSecurityException e) { |
|
KrbCryptoException ke = new KrbCryptoException(e.getMessage()); |
|
ke.initCause(e); |
|
throw ke; |
|
} finally { |
|
Arrays.fill(pwsalt, '0'); |
|
} |
|
} |
|
|
|
|
|
public EncryptionKey(char[] password, |
|
String salt, |
|
String algorithm) throws KrbCryptoException { |
|
|
|
if (algorithm == null || algorithm.equalsIgnoreCase("DES")) { |
|
keyType = EncryptedData.ETYPE_DES_CBC_MD5; |
|
} else if (algorithm.equalsIgnoreCase("DESede")) { |
|
keyType = EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD; |
|
} else if (algorithm.equalsIgnoreCase("AES128")) { |
|
keyType = EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96; |
|
} else if (algorithm.equalsIgnoreCase("ArcFourHmac")) { |
|
keyType = EncryptedData.ETYPE_ARCFOUR_HMAC; |
|
} else if (algorithm.equalsIgnoreCase("AES256")) { |
|
keyType = EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96; |
|
|
|
if (!EType.isSupported(keyType)) { |
|
throw new IllegalArgumentException("Algorithm " + algorithm + |
|
" not enabled"); |
|
} |
|
} else { |
|
throw new IllegalArgumentException("Algorithm " + algorithm + |
|
" not supported"); |
|
} |
|
|
|
keyValue = stringToKey(password, salt, null, keyType); |
|
kvno = null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public EncryptionKey(EncryptionKey key) throws KrbCryptoException { |
|
|
|
keyValue = Confounder.bytes(key.keyValue.length); |
|
for (int i = 0; i < keyValue.length; i++) { |
|
keyValue[i] ^= key.keyValue[i]; |
|
} |
|
keyType = key.keyType; |
|
|
|
|
|
try { |
|
|
|
if ((keyType == EncryptedData.ETYPE_DES_CBC_MD5) || |
|
(keyType == EncryptedData.ETYPE_DES_CBC_CRC)) { |
|
|
|
if (!DESKeySpec.isParityAdjusted(keyValue, 0)) { |
|
keyValue = Des.set_parity(keyValue); |
|
} |
|
|
|
if (DESKeySpec.isWeak(keyValue, 0)) { |
|
keyValue[7] = (byte)(keyValue[7] ^ 0xF0); |
|
} |
|
} |
|
|
|
if (keyType == EncryptedData.ETYPE_DES3_CBC_HMAC_SHA1_KD) { |
|
|
|
if (!DESedeKeySpec.isParityAdjusted(keyValue, 0)) { |
|
keyValue = Des3.parityFix(keyValue); |
|
} |
|
|
|
byte[] oneKey = new byte[8]; |
|
for (int i=0; i<keyValue.length; i+=8) { |
|
System.arraycopy(keyValue, i, oneKey, 0, 8); |
|
if (DESKeySpec.isWeak(oneKey, 0)) { |
|
keyValue[i+7] = (byte)(keyValue[i+7] ^ 0xF0); |
|
} |
|
} |
|
} |
|
} catch (GeneralSecurityException e) { |
|
KrbCryptoException ke = new KrbCryptoException(e.getMessage()); |
|
ke.initCause(e); |
|
throw ke; |
|
} |
|
} |
|
|
|
/** |
|
* Constructs an instance of EncryptionKey type. |
|
* @param encoding a single DER-encoded value. |
|
* @exception Asn1Exception if an error occurs while decoding an ASN1 |
|
* encoded data. |
|
* @exception IOException if an I/O error occurs while reading encoded |
|
* data. |
|
* |
|
* |
|
*/ |
|
|
|
public EncryptionKey(DerValue encoding) throws Asn1Exception, IOException { |
|
DerValue der; |
|
if (encoding.getTag() != DerValue.tag_Sequence) { |
|
throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
|
} |
|
der = encoding.getData().getDerValue(); |
|
if ((der.getTag() & (byte)0x1F) == (byte)0x00) { |
|
keyType = der.getData().getBigInteger().intValue(); |
|
} |
|
else |
|
throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
|
der = encoding.getData().getDerValue(); |
|
if ((der.getTag() & (byte)0x1F) == (byte)0x01) { |
|
keyValue = der.getData().getOctetString(); |
|
} |
|
else |
|
throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
|
if (der.getData().available() > 0) { |
|
throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized byte[] asn1Encode() throws Asn1Exception, IOException { |
|
DerOutputStream bytes = new DerOutputStream(); |
|
DerOutputStream temp = new DerOutputStream(); |
|
temp.putInteger(keyType); |
|
bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, |
|
(byte)0x00), temp); |
|
temp = new DerOutputStream(); |
|
temp.putOctetString(keyValue); |
|
bytes.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, |
|
(byte)0x01), temp); |
|
temp = new DerOutputStream(); |
|
temp.write(DerValue.tag_Sequence, bytes); |
|
return temp.toByteArray(); |
|
} |
|
|
|
public synchronized void destroy() { |
|
if (keyValue != null) |
|
for (int i = 0; i < keyValue.length; i++) |
|
keyValue[i] = 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static EncryptionKey parse(DerInputStream data, byte |
|
explicitTag, boolean optional) throws |
|
Asn1Exception, IOException { |
|
if ((optional) && (((byte)data.peekByte() & (byte)0x1F) != |
|
explicitTag)) { |
|
return null; |
|
} |
|
DerValue der = data.getDerValue(); |
|
if (explicitTag != (der.getTag() & (byte)0x1F)) { |
|
throw new Asn1Exception(Krb5.ASN1_BAD_ID); |
|
} else { |
|
DerValue subDer = der.getData().getDerValue(); |
|
return new EncryptionKey(subDer); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void writeKey(CCacheOutputStream cos) |
|
throws IOException { |
|
|
|
cos.write16(keyType); |
|
// we use KRB5_FCC_FVNO_3 |
|
cos.write16(keyType); |
|
cos.write32(keyValue.length); |
|
for (int i = 0; i < keyValue.length; i++) { |
|
cos.write8(keyValue[i]); |
|
} |
|
} |
|
|
|
public String toString() { |
|
return new String("EncryptionKey: keyType=" + keyType |
|
+ " kvno=" + kvno |
|
+ " keyValue (hex dump)=" |
|
+ (keyValue == null || keyValue.length == 0 ? |
|
" Empty Key" : '\n' |
|
+ Krb5.hexDumper.encodeBuffer(keyValue) |
|
+ '\n')); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public static EncryptionKey findKey(int etype, EncryptionKey[] keys) |
|
throws KrbException { |
|
return findKey(etype, null, keys); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean versionMatches(Integer v1, Integer v2) { |
|
if (v1 == null || v1 == 0 || v2 == null || v2 == 0) { |
|
return true; |
|
} |
|
return v1.equals(v2); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static EncryptionKey findKey(int etype, Integer kvno, EncryptionKey[] keys) |
|
throws KrbException { |
|
|
|
|
|
if (!EType.isSupported(etype)) { |
|
throw new KrbException("Encryption type " + |
|
EType.toString(etype) + " is not supported/enabled"); |
|
} |
|
|
|
int ktype; |
|
boolean etypeFound = false; |
|
|
|
// When no matched kvno is found, returns tke key of the same |
|
|
|
int kvno_found = 0; |
|
EncryptionKey key_found = null; |
|
|
|
for (int i = 0; i < keys.length; i++) { |
|
ktype = keys[i].getEType(); |
|
if (EType.isSupported(ktype)) { |
|
Integer kv = keys[i].getKeyVersionNumber(); |
|
if (etype == ktype) { |
|
etypeFound = true; |
|
if (versionMatches(kvno, kv)) { |
|
return keys[i]; |
|
} else if (kv > kvno_found) { |
|
|
|
key_found = keys[i]; |
|
kvno_found = kv; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Key not found. |
|
|
|
if ((etype == EncryptedData.ETYPE_DES_CBC_CRC || |
|
etype == EncryptedData.ETYPE_DES_CBC_MD5)) { |
|
for (int i = 0; i < keys.length; i++) { |
|
ktype = keys[i].getEType(); |
|
if (ktype == EncryptedData.ETYPE_DES_CBC_CRC || |
|
ktype == EncryptedData.ETYPE_DES_CBC_MD5) { |
|
Integer kv = keys[i].getKeyVersionNumber(); |
|
etypeFound = true; |
|
if (versionMatches(kvno, kv)) { |
|
return new EncryptionKey(etype, keys[i].getBytes()); |
|
} else if (kv > kvno_found) { |
|
key_found = new EncryptionKey(etype, keys[i].getBytes()); |
|
kvno_found = kv; |
|
} |
|
} |
|
} |
|
} |
|
if (etypeFound) { |
|
return key_found; |
|
// For compatibility, will not fail here. |
|
//throw new KrbException(Krb5.KRB_AP_ERR_BADKEYVER); |
|
} |
|
return null; |
|
} |
|
} |