|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.pkcs12; |
|
|
|
import java.io.*; |
|
import java.security.AccessController; |
|
import java.security.MessageDigest; |
|
import java.security.NoSuchAlgorithmException; |
|
import java.security.Key; |
|
import java.security.KeyFactory; |
|
import java.security.KeyStore; |
|
import java.security.KeyStoreSpi; |
|
import java.security.KeyStoreException; |
|
import java.security.PKCS12Attribute; |
|
import java.security.PrivateKey; |
|
import java.security.PrivilegedAction; |
|
import java.security.UnrecoverableEntryException; |
|
import java.security.UnrecoverableKeyException; |
|
import java.security.SecureRandom; |
|
import java.security.Security; |
|
import java.security.cert.Certificate; |
|
import java.security.cert.CertificateFactory; |
|
import java.security.cert.X509Certificate; |
|
import java.security.cert.CertificateException; |
|
import java.security.spec.AlgorithmParameterSpec; |
|
import java.security.spec.InvalidParameterSpecException; |
|
import java.security.spec.KeySpec; |
|
import java.security.spec.PKCS8EncodedKeySpec; |
|
import java.util.*; |
|
|
|
import static java.nio.charset.StandardCharsets.UTF_8; |
|
|
|
import java.security.AlgorithmParameters; |
|
import java.security.InvalidAlgorithmParameterException; |
|
import javax.crypto.spec.PBEParameterSpec; |
|
import javax.crypto.spec.PBEKeySpec; |
|
import javax.crypto.spec.SecretKeySpec; |
|
import javax.crypto.SecretKeyFactory; |
|
import javax.crypto.SecretKey; |
|
import javax.crypto.Cipher; |
|
import javax.crypto.Mac; |
|
import javax.security.auth.DestroyFailedException; |
|
import javax.security.auth.x500.X500Principal; |
|
|
|
import jdk.internal.access.SharedSecrets; |
|
import sun.security.action.GetPropertyAction; |
|
import sun.security.tools.KeyStoreUtil; |
|
import sun.security.util.*; |
|
import sun.security.pkcs.ContentInfo; |
|
import sun.security.x509.AlgorithmId; |
|
import sun.security.pkcs.EncryptedPrivateKeyInfo; |
|
import sun.security.provider.JavaKeyStore.JKS; |
|
import sun.security.x509.AuthorityKeyIdentifierExtension; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final class PKCS12KeyStore extends KeyStoreSpi { |
|
|
|
// Hardcoded defaults. They should be the same with commented out |
|
|
|
private static final String DEFAULT_CERT_PBE_ALGORITHM |
|
= "PBEWithHmacSHA256AndAES_256"; |
|
private static final String DEFAULT_KEY_PBE_ALGORITHM |
|
= "PBEWithHmacSHA256AndAES_256"; |
|
private static final String DEFAULT_MAC_ALGORITHM = "HmacPBESHA256"; |
|
private static final int DEFAULT_CERT_PBE_ITERATION_COUNT = 10000; |
|
private static final int DEFAULT_KEY_PBE_ITERATION_COUNT = 10000; |
|
private static final int DEFAULT_MAC_ITERATION_COUNT = 10000; |
|
|
|
|
|
private static final String LEGACY_CERT_PBE_ALGORITHM |
|
= "PBEWithSHA1AndRC2_40"; |
|
private static final String LEGACY_KEY_PBE_ALGORITHM |
|
= "PBEWithSHA1AndDESede"; |
|
private static final String LEGACY_MAC_ALGORITHM = "HmacPBESHA1"; |
|
private static final int LEGACY_PBE_ITERATION_COUNT = 50000; |
|
private static final int LEGACY_MAC_ITERATION_COUNT = 100000; |
|
|
|
// Big switch. When this system property is set. Legacy settings |
|
// are used no matter what other keystore.pkcs12.* properties are set. |
|
// Note: This is only a system property, there's no same-name |
|
|
|
private static final String USE_LEGACY_PROP = "keystore.pkcs12.legacy"; |
|
|
|
|
|
public static final class DualFormatPKCS12 extends KeyStoreDelegator { |
|
public DualFormatPKCS12() { |
|
super("PKCS12", PKCS12KeyStore.class, "JKS", JKS.class); |
|
} |
|
} |
|
|
|
public static final int VERSION_3 = 3; |
|
|
|
private static final int MAX_ITERATION_COUNT = 5000000; |
|
private static final int SALT_LEN = 20; |
|
|
|
private static final KnownOIDs[] CORE_ATTRIBUTES = { |
|
KnownOIDs.FriendlyName, |
|
KnownOIDs.LocalKeyID, |
|
KnownOIDs.ORACLE_TrustedKeyUsage |
|
}; |
|
|
|
private static final Debug debug = Debug.getInstance("pkcs12"); |
|
|
|
private static final ObjectIdentifier PKCS8ShroudedKeyBag_OID = |
|
ObjectIdentifier.of(KnownOIDs.PKCS8ShroudedKeyBag); |
|
private static final ObjectIdentifier CertBag_OID = |
|
ObjectIdentifier.of(KnownOIDs.CertBag); |
|
private static final ObjectIdentifier SecretBag_OID = |
|
ObjectIdentifier.of(KnownOIDs.SecretBag); |
|
|
|
private static final ObjectIdentifier PKCS9FriendlyName_OID = |
|
ObjectIdentifier.of(KnownOIDs.FriendlyName); |
|
private static final ObjectIdentifier PKCS9LocalKeyId_OID = |
|
ObjectIdentifier.of(KnownOIDs.LocalKeyID); |
|
private static final ObjectIdentifier PKCS9CertType_OID = |
|
ObjectIdentifier.of(KnownOIDs.CertTypeX509); |
|
private static final ObjectIdentifier pbes2_OID = |
|
ObjectIdentifier.of(KnownOIDs.PBES2); |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static final ObjectIdentifier TrustedKeyUsage_OID = |
|
ObjectIdentifier.of(KnownOIDs.ORACLE_TrustedKeyUsage); |
|
|
|
private static final ObjectIdentifier[] AnyUsage = new ObjectIdentifier[] { |
|
ObjectIdentifier.of(KnownOIDs.anyExtendedKeyUsage) |
|
}; |
|
|
|
private int counter = 0; |
|
|
|
// private key count |
|
// Note: This is a workaround to allow null localKeyID attribute |
|
|
|
private int privateKeyCount = 0; |
|
|
|
|
|
private int secretKeyCount = 0; |
|
|
|
|
|
private int certificateCount = 0; |
|
|
|
// Alg/params used for *this* keystore. Initialized as -1 for ic and |
|
// null for algorithm names. When an existing file is read, they will be |
|
// assigned inside engineLoad() so storing an existing keystore uses the |
|
// old alg/params. This makes sure if a keystore is created password-less |
|
// it will be password-less forever. Otherwise, engineStore() will read |
|
|
|
private String certProtectionAlgorithm = null; |
|
private int certPbeIterationCount = -1; |
|
private String macAlgorithm = null; |
|
private int macIterationCount = -1; |
|
|
|
|
|
private SecureRandom random; |
|
|
|
|
|
private static class Entry { |
|
Date date; |
|
String alias; |
|
byte[] keyId; |
|
Set<KeyStore.Entry.Attribute> attributes; |
|
} |
|
|
|
|
|
private static class KeyEntry extends Entry { |
|
} |
|
|
|
|
|
private static class PrivateKeyEntry extends KeyEntry { |
|
byte[] protectedPrivKey; |
|
Certificate[] chain; |
|
}; |
|
|
|
|
|
private static class SecretKeyEntry extends KeyEntry { |
|
byte[] protectedSecretKey; |
|
}; |
|
|
|
|
|
private static class CertEntry extends Entry { |
|
final X509Certificate cert; |
|
ObjectIdentifier[] trustedKeyUsage; |
|
|
|
CertEntry(X509Certificate cert, byte[] keyId, String alias) { |
|
this(cert, keyId, alias, null, null); |
|
} |
|
|
|
CertEntry(X509Certificate cert, byte[] keyId, String alias, |
|
ObjectIdentifier[] trustedKeyUsage, |
|
Set<? extends KeyStore.Entry.Attribute> attributes) { |
|
this.date = new Date(); |
|
this.cert = cert; |
|
this.keyId = keyId; |
|
this.alias = alias; |
|
this.trustedKeyUsage = trustedKeyUsage; |
|
this.attributes = new HashSet<>(); |
|
if (attributes != null) { |
|
this.attributes.addAll(attributes); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@FunctionalInterface |
|
private interface RetryWithZero<T> { |
|
|
|
T tryOnce(char[] password) throws Exception; |
|
|
|
static <S> S run(RetryWithZero<S> f, char[] password) throws Exception { |
|
try { |
|
return f.tryOnce(password); |
|
} catch (Exception e) { |
|
if (password.length == 0) { |
|
|
|
if (debug != null) { |
|
debug.println("Retry with a NUL password"); |
|
} |
|
return f.tryOnce(new char[1]); |
|
} |
|
throw e; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private Map<String, Entry> entries = |
|
Collections.synchronizedMap(new LinkedHashMap<String, Entry>()); |
|
|
|
private ArrayList<KeyEntry> keyList = new ArrayList<KeyEntry>(); |
|
private List<X509Certificate> allCerts = new ArrayList<>(); |
|
private ArrayList<CertEntry> certEntries = new ArrayList<CertEntry>(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Key engineGetKey(String alias, char[] password) |
|
throws NoSuchAlgorithmException, UnrecoverableKeyException |
|
{ |
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
Key key = null; |
|
|
|
if (entry == null || (!(entry instanceof KeyEntry))) { |
|
return null; |
|
} |
|
|
|
|
|
byte[] encrBytes = null; |
|
if (entry instanceof PrivateKeyEntry) { |
|
encrBytes = ((PrivateKeyEntry) entry).protectedPrivKey; |
|
} else if (entry instanceof SecretKeyEntry) { |
|
encrBytes = ((SecretKeyEntry) entry).protectedSecretKey; |
|
} else { |
|
throw new UnrecoverableKeyException("Error locating key"); |
|
} |
|
|
|
byte[] encryptedKey; |
|
AlgorithmParameters algParams; |
|
ObjectIdentifier algOid; |
|
|
|
try { |
|
|
|
EncryptedPrivateKeyInfo encrInfo = |
|
new EncryptedPrivateKeyInfo(encrBytes); |
|
encryptedKey = encrInfo.getEncryptedData(); |
|
|
|
|
|
DerValue val = new DerValue(encrInfo.getAlgorithm().encode()); |
|
DerInputStream in = val.toDerInputStream(); |
|
algOid = in.getOID(); |
|
algParams = parseAlgParameters(algOid, in); |
|
|
|
} catch (IOException ioe) { |
|
UnrecoverableKeyException uke = |
|
new UnrecoverableKeyException("Private key not stored as " |
|
+ "PKCS#8 EncryptedPrivateKeyInfo: " + ioe); |
|
uke.initCause(ioe); |
|
throw uke; |
|
} |
|
|
|
try { |
|
PBEParameterSpec pbeSpec; |
|
int ic; |
|
|
|
if (algParams != null) { |
|
try { |
|
pbeSpec = |
|
algParams.getParameterSpec(PBEParameterSpec.class); |
|
} catch (InvalidParameterSpecException ipse) { |
|
throw new IOException("Invalid PBE algorithm parameters"); |
|
} |
|
ic = pbeSpec.getIterationCount(); |
|
|
|
if (ic > MAX_ITERATION_COUNT) { |
|
throw new IOException("key PBE iteration count too large"); |
|
} |
|
} else { |
|
ic = 0; |
|
} |
|
|
|
key = RetryWithZero.run(pass -> { |
|
|
|
Cipher cipher = Cipher.getInstance( |
|
mapPBEParamsToAlgorithm(algOid, algParams)); |
|
SecretKey skey = getPBEKey(pass); |
|
try { |
|
cipher.init(Cipher.DECRYPT_MODE, skey, algParams); |
|
} finally { |
|
destroyPBEKey(skey); |
|
} |
|
byte[] keyInfo = cipher.doFinal(encryptedKey); |
|
|
|
|
|
|
|
*/ |
|
DerValue val = new DerValue(keyInfo); |
|
try { |
|
DerInputStream in = val.toDerInputStream(); |
|
int i = in.getInteger(); |
|
DerValue[] value = in.getSequence(2); |
|
if (value.length < 1 || value.length > 2) { |
|
throw new IOException("Invalid length for AlgorithmIdentifier"); |
|
} |
|
AlgorithmId algId = new AlgorithmId(value[0].getOID()); |
|
String keyAlgo = algId.getName(); |
|
|
|
|
|
if (entry instanceof PrivateKeyEntry) { |
|
KeyFactory kfac = KeyFactory.getInstance(keyAlgo); |
|
PKCS8EncodedKeySpec kspec = new PKCS8EncodedKeySpec(keyInfo); |
|
try { |
|
Key tmp = kfac.generatePrivate(kspec); |
|
|
|
if (debug != null) { |
|
debug.println("Retrieved a protected private key at alias" + |
|
" '" + alias + "' (" + |
|
mapPBEParamsToAlgorithm(algOid, algParams) + |
|
" iterations: " + ic + ")"); |
|
} |
|
return tmp; |
|
} finally { |
|
SharedSecrets.getJavaSecuritySpecAccess() |
|
.clearEncodedKeySpec(kspec); |
|
} |
|
// decode secret key |
|
} else { |
|
byte[] keyBytes = in.getOctetString(); |
|
SecretKeySpec secretKeySpec = |
|
new SecretKeySpec(keyBytes, keyAlgo); |
|
|
|
try { |
|
|
|
Key tmp; |
|
if (keyAlgo.startsWith("PBE")) { |
|
SecretKeyFactory sKeyFactory = |
|
SecretKeyFactory.getInstance(keyAlgo); |
|
KeySpec pbeKeySpec = |
|
sKeyFactory.getKeySpec(secretKeySpec, PBEKeySpec.class); |
|
try { |
|
tmp = sKeyFactory.generateSecret(pbeKeySpec); |
|
} finally { |
|
((PBEKeySpec)pbeKeySpec).clearPassword(); |
|
SharedSecrets.getJavaxCryptoSpecAccess() |
|
.clearSecretKeySpec(secretKeySpec); |
|
} |
|
} else { |
|
tmp = secretKeySpec; |
|
} |
|
|
|
if (debug != null) { |
|
debug.println("Retrieved a protected secret key at alias " + |
|
"'" + alias + "' (" + |
|
mapPBEParamsToAlgorithm(algOid, algParams) + |
|
" iterations: " + ic + ")"); |
|
} |
|
return tmp; |
|
} finally { |
|
Arrays.fill(keyBytes, (byte)0); |
|
} |
|
} |
|
} finally { |
|
val.clear(); |
|
Arrays.fill(keyInfo, (byte) 0); |
|
} |
|
}, password); |
|
|
|
} catch (Exception e) { |
|
UnrecoverableKeyException uke = |
|
new UnrecoverableKeyException("Get Key failed: " + |
|
e.getMessage()); |
|
uke.initCause(e); |
|
throw uke; |
|
} |
|
return key; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Certificate[] engineGetCertificateChain(String alias) { |
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (entry != null && entry instanceof PrivateKeyEntry) { |
|
if (((PrivateKeyEntry) entry).chain == null) { |
|
return null; |
|
} else { |
|
|
|
if (debug != null) { |
|
debug.println("Retrieved a " + |
|
((PrivateKeyEntry) entry).chain.length + |
|
"-certificate chain at alias '" + alias + "'"); |
|
} |
|
|
|
return ((PrivateKeyEntry) entry).chain.clone(); |
|
} |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Certificate engineGetCertificate(String alias) { |
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (entry == null) { |
|
return null; |
|
} |
|
if (entry instanceof CertEntry && |
|
((CertEntry) entry).trustedKeyUsage != null) { |
|
|
|
if (debug != null) { |
|
if (Arrays.equals(AnyUsage, |
|
((CertEntry) entry).trustedKeyUsage)) { |
|
debug.println("Retrieved a certificate at alias '" + alias + |
|
"' (trusted for any purpose)"); |
|
} else { |
|
debug.println("Retrieved a certificate at alias '" + alias + |
|
"' (trusted for limited purposes)"); |
|
} |
|
} |
|
|
|
return ((CertEntry) entry).cert; |
|
|
|
} else if (entry instanceof PrivateKeyEntry) { |
|
if (((PrivateKeyEntry) entry).chain == null) { |
|
return null; |
|
} else { |
|
|
|
if (debug != null) { |
|
debug.println("Retrieved a certificate at alias '" + alias + |
|
"'"); |
|
} |
|
|
|
return ((PrivateKeyEntry) entry).chain[0]; |
|
} |
|
|
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date engineGetCreationDate(String alias) { |
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (entry != null) { |
|
return new Date(entry.date.getTime()); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void engineSetKeyEntry(String alias, Key key, |
|
char[] password, Certificate[] chain) |
|
throws KeyStoreException |
|
{ |
|
KeyStore.PasswordProtection passwordProtection = |
|
new KeyStore.PasswordProtection(password); |
|
|
|
try { |
|
setKeyEntry(alias, key, passwordProtection, chain, null); |
|
|
|
} finally { |
|
try { |
|
passwordProtection.destroy(); |
|
} catch (DestroyFailedException dfe) { |
|
// ignore |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void setKeyEntry(String alias, Key key, |
|
KeyStore.PasswordProtection passwordProtection, Certificate[] chain, |
|
Set<KeyStore.Entry.Attribute> attributes) |
|
throws KeyStoreException |
|
{ |
|
try { |
|
Entry entry; |
|
|
|
if (key instanceof PrivateKey) { |
|
|
|
checkX509Certs(chain); |
|
|
|
PrivateKeyEntry keyEntry = new PrivateKeyEntry(); |
|
keyEntry.date = new Date(); |
|
|
|
if ((key.getFormat().equals("PKCS#8")) || |
|
(key.getFormat().equals("PKCS8"))) { |
|
|
|
if (debug != null) { |
|
debug.println( |
|
"Setting a protected private key at alias '" + |
|
alias + "'"); |
|
} |
|
|
|
|
|
byte[] encoded = key.getEncoded(); |
|
try { |
|
keyEntry.protectedPrivKey = |
|
encryptPrivateKey(encoded, passwordProtection); |
|
} finally { |
|
if (encoded != null) { |
|
Arrays.fill(encoded, (byte) 0); |
|
} |
|
} |
|
} else { |
|
throw new KeyStoreException("Private key is not encoded" + |
|
"as PKCS#8"); |
|
} |
|
|
|
|
|
if (chain != null) { |
|
|
|
if ((chain.length > 1) && (!validateChain(chain))) |
|
throw new KeyStoreException("Certificate chain is " + |
|
"not valid"); |
|
keyEntry.chain = chain.clone(); |
|
certificateCount += chain.length; |
|
|
|
if (debug != null) { |
|
debug.println("Setting a " + chain.length + |
|
"-certificate chain at alias '" + alias + "'"); |
|
} |
|
} |
|
privateKeyCount++; |
|
entry = keyEntry; |
|
|
|
} else if (key instanceof SecretKey) { |
|
SecretKeyEntry keyEntry = new SecretKeyEntry(); |
|
keyEntry.date = new Date(); |
|
|
|
|
|
DerOutputStream secretKeyInfo = new DerOutputStream(); |
|
secretKeyInfo.putInteger(0); |
|
AlgorithmId algId = AlgorithmId.get(key.getAlgorithm()); |
|
algId.encode(secretKeyInfo); |
|
|
|
byte[] encoded = key.getEncoded(); |
|
secretKeyInfo.putOctetString(encoded); |
|
Arrays.fill(encoded, (byte)0); |
|
|
|
DerValue pkcs8 = DerValue.wrap(DerValue.tag_Sequence, secretKeyInfo); |
|
byte[] p8Array = pkcs8.toByteArray(); |
|
pkcs8.clear(); |
|
try { |
|
|
|
keyEntry.protectedSecretKey = |
|
encryptPrivateKey(p8Array, passwordProtection); |
|
} finally { |
|
Arrays.fill(p8Array, (byte)0); |
|
} |
|
|
|
if (debug != null) { |
|
debug.println("Setting a protected secret key at alias '" + |
|
alias + "'"); |
|
} |
|
secretKeyCount++; |
|
entry = keyEntry; |
|
|
|
} else { |
|
throw new KeyStoreException("Unsupported Key type"); |
|
} |
|
|
|
entry.attributes = new HashSet<>(); |
|
if (attributes != null) { |
|
entry.attributes.addAll(attributes); |
|
} |
|
|
|
entry.keyId = ("Time " + (entry.date).getTime()).getBytes(UTF_8); |
|
|
|
entry.alias = alias.toLowerCase(Locale.ENGLISH); |
|
|
|
entries.put(alias.toLowerCase(Locale.ENGLISH), entry); |
|
|
|
} catch (KeyStoreException kse) { |
|
throw kse; |
|
} catch (Exception nsae) { |
|
throw new KeyStoreException("Key protection" + |
|
" algorithm not found: " + nsae, nsae); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void engineSetKeyEntry(String alias, byte[] key, |
|
Certificate[] chain) |
|
throws KeyStoreException |
|
{ |
|
|
|
checkX509Certs(chain); |
|
|
|
// Private key must be encoded as EncryptedPrivateKeyInfo |
|
|
|
try { |
|
new EncryptedPrivateKeyInfo(key); |
|
} catch (IOException ioe) { |
|
throw new KeyStoreException("Private key is not stored" |
|
+ " as PKCS#8 EncryptedPrivateKeyInfo: " + ioe, ioe); |
|
} |
|
|
|
PrivateKeyEntry entry = new PrivateKeyEntry(); |
|
entry.date = new Date(); |
|
|
|
if (debug != null) { |
|
debug.println("Setting a protected private key at alias '" + |
|
alias + "'"); |
|
} |
|
|
|
|
|
entry.keyId = ("Time " + (entry.date).getTime()).getBytes(UTF_8); |
|
|
|
entry.alias = alias.toLowerCase(Locale.ENGLISH); |
|
|
|
entry.protectedPrivKey = key.clone(); |
|
if (chain != null) { |
|
|
|
if ((chain.length > 1) && (!validateChain(chain))) { |
|
throw new KeyStoreException("Certificate chain is " |
|
+ "not valid"); |
|
} |
|
entry.chain = chain.clone(); |
|
certificateCount += chain.length; |
|
|
|
if (debug != null) { |
|
debug.println("Setting a " + entry.chain.length + |
|
"-certificate chain at alias '" + alias + "'"); |
|
} |
|
} |
|
|
|
|
|
privateKeyCount++; |
|
entries.put(alias.toLowerCase(Locale.ENGLISH), entry); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] getSalt() |
|
{ |
|
|
|
byte[] salt = new byte[SALT_LEN]; |
|
if (random == null) { |
|
random = new SecureRandom(); |
|
} |
|
random.nextBytes(salt); |
|
return salt; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private AlgorithmParameters getPBEAlgorithmParameters( |
|
String algorithm, int iterationCount) throws IOException { |
|
AlgorithmParameters algParams; |
|
|
|
byte[] salt = getSalt(); |
|
if (KnownOIDs.findMatch(algorithm) == KnownOIDs.PBEWithMD5AndDES) { |
|
|
|
salt = Arrays.copyOf(salt, 8); |
|
} |
|
|
|
|
|
PBEParameterSpec paramSpec = |
|
new PBEParameterSpec(salt, iterationCount); |
|
try { |
|
algParams = AlgorithmParameters.getInstance(algorithm); |
|
algParams.init(paramSpec); |
|
} catch (Exception e) { |
|
throw new IOException("getPBEAlgorithmParameters failed: " + |
|
e.getMessage(), e); |
|
} |
|
return algParams; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private AlgorithmParameters parseAlgParameters(ObjectIdentifier algorithm, |
|
DerInputStream in) throws IOException |
|
{ |
|
AlgorithmParameters algParams = null; |
|
try { |
|
DerValue params; |
|
if (in.available() == 0) { |
|
params = null; |
|
} else { |
|
params = in.getDerValue(); |
|
if (params.tag == DerValue.tag_Null) { |
|
params = null; |
|
} |
|
} |
|
if (params != null) { |
|
if (algorithm.equals(pbes2_OID)) { |
|
algParams = AlgorithmParameters.getInstance("PBES2"); |
|
} else { |
|
algParams = AlgorithmParameters.getInstance("PBE"); |
|
} |
|
algParams.init(params.toByteArray()); |
|
} |
|
} catch (Exception e) { |
|
throw new IOException("parseAlgParameters failed: " + |
|
e.getMessage(), e); |
|
} |
|
return algParams; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private SecretKey getPBEKey(char[] password) throws IOException |
|
{ |
|
SecretKey skey = null; |
|
|
|
try { |
|
PBEKeySpec keySpec = new PBEKeySpec(password); |
|
SecretKeyFactory skFac = SecretKeyFactory.getInstance("PBE"); |
|
skey = skFac.generateSecret(keySpec); |
|
keySpec.clearPassword(); |
|
} catch (Exception e) { |
|
throw new IOException("getSecretKey failed: " + |
|
e.getMessage(), e); |
|
} |
|
return skey; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void destroyPBEKey(SecretKey key) { |
|
try { |
|
key.destroy(); |
|
} catch (DestroyFailedException e) { |
|
// Accept this |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] encryptPrivateKey(byte[] data, |
|
KeyStore.PasswordProtection passwordProtection) |
|
throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException |
|
{ |
|
byte[] key = null; |
|
|
|
try { |
|
String algorithm; |
|
AlgorithmParameters algParams; |
|
AlgorithmId algid; |
|
|
|
|
|
algorithm = passwordProtection.getProtectionAlgorithm(); |
|
if (algorithm != null) { |
|
AlgorithmParameterSpec algParamSpec = |
|
passwordProtection.getProtectionParameters(); |
|
if (algParamSpec != null) { |
|
algParams = AlgorithmParameters.getInstance(algorithm); |
|
algParams.init(algParamSpec); |
|
} else { |
|
algParams = getPBEAlgorithmParameters(algorithm, |
|
defaultKeyPbeIterationCount()); |
|
} |
|
} else { |
|
|
|
algorithm = defaultKeyProtectionAlgorithm(); |
|
algParams = getPBEAlgorithmParameters(algorithm, |
|
defaultKeyPbeIterationCount()); |
|
} |
|
|
|
ObjectIdentifier pbeOID = mapPBEAlgorithmToOID(algorithm); |
|
if (pbeOID == null) { |
|
throw new IOException("PBE algorithm '" + algorithm + |
|
" 'is not supported for key entry protection"); |
|
} |
|
|
|
|
|
Cipher cipher = Cipher.getInstance(algorithm); |
|
SecretKey skey = getPBEKey(passwordProtection.getPassword()); |
|
try { |
|
cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); |
|
} finally { |
|
destroyPBEKey(skey); |
|
} |
|
byte[] encryptedKey = cipher.doFinal(data); |
|
algid = new AlgorithmId(pbeOID, cipher.getParameters()); |
|
|
|
if (debug != null) { |
|
debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + |
|
")"); |
|
} |
|
|
|
// wrap encrypted private key in EncryptedPrivateKeyInfo |
|
|
|
EncryptedPrivateKeyInfo encrInfo = |
|
new EncryptedPrivateKeyInfo(algid, encryptedKey); |
|
key = encrInfo.getEncoded(); |
|
} catch (Exception e) { |
|
UnrecoverableKeyException uke = |
|
new UnrecoverableKeyException("Encrypt Private Key failed: " |
|
+ e.getMessage()); |
|
uke.initCause(e); |
|
throw uke; |
|
} |
|
|
|
return key; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static ObjectIdentifier mapPBEAlgorithmToOID(String algorithm) |
|
throws NoSuchAlgorithmException { |
|
|
|
if (algorithm.toLowerCase(Locale.ENGLISH).startsWith("pbewithhmacsha")) { |
|
return pbes2_OID; |
|
} |
|
return AlgorithmId.get(algorithm).getOID(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static String mapPBEParamsToAlgorithm(ObjectIdentifier algorithm, |
|
AlgorithmParameters algParams) throws NoSuchAlgorithmException { |
|
|
|
if (algorithm.equals(pbes2_OID) && algParams != null) { |
|
return algParams.toString(); |
|
} |
|
return new AlgorithmId(algorithm).getName(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void engineSetCertificateEntry(String alias, |
|
Certificate cert) throws KeyStoreException |
|
{ |
|
setCertEntry(alias, cert, null); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private void setCertEntry(String alias, Certificate cert, |
|
Set<KeyStore.Entry.Attribute> attributes) throws KeyStoreException { |
|
|
|
|
|
if (cert != null && (!(cert instanceof X509Certificate))) { |
|
throw new KeyStoreException( |
|
"Only X.509 certificates are supported - rejecting class: " + |
|
cert.getClass().getName()); |
|
} |
|
|
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (entry != null && entry instanceof KeyEntry) { |
|
throw new KeyStoreException("Cannot overwrite own certificate"); |
|
} |
|
|
|
CertEntry certEntry = |
|
new CertEntry((X509Certificate) cert, null, alias, AnyUsage, |
|
attributes); |
|
certificateCount++; |
|
entries.put(alias.toLowerCase(Locale.ENGLISH), certEntry); |
|
|
|
if (debug != null) { |
|
debug.println("Setting a trusted certificate at alias '" + alias + |
|
"'"); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void engineDeleteEntry(String alias) |
|
throws KeyStoreException |
|
{ |
|
if (debug != null) { |
|
debug.println("Removing entry at alias '" + alias + "'"); |
|
} |
|
|
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (entry instanceof PrivateKeyEntry) { |
|
PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; |
|
if (keyEntry.chain != null) { |
|
certificateCount -= keyEntry.chain.length; |
|
} |
|
privateKeyCount--; |
|
} else if (entry instanceof CertEntry) { |
|
certificateCount--; |
|
} else if (entry instanceof SecretKeyEntry) { |
|
secretKeyCount--; |
|
} |
|
entries.remove(alias.toLowerCase(Locale.ENGLISH)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Enumeration<String> engineAliases() { |
|
return Collections.enumeration(entries.keySet()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean engineContainsAlias(String alias) { |
|
return entries.containsKey(alias.toLowerCase(Locale.ENGLISH)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int engineSize() { |
|
return entries.size(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean engineIsKeyEntry(String alias) { |
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (entry != null && entry instanceof KeyEntry) { |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean engineIsCertificateEntry(String alias) { |
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (entry != null && entry instanceof CertEntry && |
|
((CertEntry) entry).trustedKeyUsage != null) { |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean |
|
engineEntryInstanceOf(String alias, |
|
Class<? extends KeyStore.Entry> entryClass) |
|
{ |
|
if (entryClass == KeyStore.TrustedCertificateEntry.class) { |
|
return engineIsCertificateEntry(alias); |
|
} |
|
|
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (entryClass == KeyStore.PrivateKeyEntry.class) { |
|
return (entry != null && entry instanceof PrivateKeyEntry); |
|
} |
|
if (entryClass == KeyStore.SecretKeyEntry.class) { |
|
return (entry != null && entry instanceof SecretKeyEntry); |
|
} |
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String engineGetCertificateAlias(Certificate cert) { |
|
Certificate certElem = null; |
|
|
|
for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) { |
|
String alias = e.nextElement(); |
|
Entry entry = entries.get(alias); |
|
if (entry instanceof PrivateKeyEntry) { |
|
if (((PrivateKeyEntry) entry).chain != null) { |
|
certElem = ((PrivateKeyEntry) entry).chain[0]; |
|
} |
|
} else if (entry instanceof CertEntry && |
|
((CertEntry) entry).trustedKeyUsage != null) { |
|
certElem = ((CertEntry) entry).cert; |
|
} else { |
|
continue; |
|
} |
|
if (certElem != null && certElem.equals(cert)) { |
|
return alias; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void engineStore(OutputStream stream, char[] password) |
|
throws IOException, NoSuchAlgorithmException, CertificateException |
|
{ |
|
|
|
|
|
DerOutputStream pfx = new DerOutputStream(); |
|
|
|
|
|
DerOutputStream version = new DerOutputStream(); |
|
version.putInteger(VERSION_3); |
|
byte[] pfxVersion = version.toByteArray(); |
|
pfx.write(pfxVersion); |
|
|
|
|
|
DerOutputStream authSafe = new DerOutputStream(); |
|
|
|
|
|
DerOutputStream authSafeContentInfo = new DerOutputStream(); |
|
|
|
|
|
if (privateKeyCount > 0 || secretKeyCount > 0) { |
|
|
|
if (debug != null) { |
|
debug.println("Storing " + (privateKeyCount + secretKeyCount) + |
|
" protected key(s) in a PKCS#7 data"); |
|
} |
|
|
|
byte[] safeContentData = createSafeContent(); |
|
ContentInfo dataContentInfo = new ContentInfo(safeContentData); |
|
dataContentInfo.encode(authSafeContentInfo); |
|
} |
|
|
|
|
|
if (certificateCount > 0) { |
|
|
|
if (certProtectionAlgorithm == null) { |
|
certProtectionAlgorithm = defaultCertProtectionAlgorithm(); |
|
} |
|
if (certPbeIterationCount < 0) { |
|
certPbeIterationCount = defaultCertPbeIterationCount(); |
|
} |
|
|
|
if (debug != null) { |
|
debug.println("Storing " + certificateCount + |
|
" certificate(s) in a PKCS#7 encryptedData"); |
|
} |
|
|
|
byte[] encrData = createEncryptedData(password); |
|
if (!certProtectionAlgorithm.equalsIgnoreCase("NONE")) { |
|
ContentInfo encrContentInfo = |
|
new ContentInfo(ContentInfo.ENCRYPTED_DATA_OID, |
|
new DerValue(encrData)); |
|
encrContentInfo.encode(authSafeContentInfo); |
|
} else { |
|
ContentInfo dataContentInfo = new ContentInfo(encrData); |
|
dataContentInfo.encode(authSafeContentInfo); |
|
} |
|
} |
|
|
|
|
|
DerOutputStream cInfo = new DerOutputStream(); |
|
cInfo.write(DerValue.tag_SequenceOf, authSafeContentInfo); |
|
byte[] authenticatedSafe = cInfo.toByteArray(); |
|
|
|
|
|
ContentInfo contentInfo = new ContentInfo(authenticatedSafe); |
|
contentInfo.encode(authSafe); |
|
byte[] authSafeData = authSafe.toByteArray(); |
|
pfx.write(authSafeData); |
|
|
|
|
|
if (macAlgorithm == null) { |
|
macAlgorithm = defaultMacAlgorithm(); |
|
} |
|
if (macIterationCount < 0) { |
|
macIterationCount = defaultMacIterationCount(); |
|
} |
|
if (!macAlgorithm.equalsIgnoreCase("NONE")) { |
|
byte[] macData = calculateMac(password, authenticatedSafe); |
|
pfx.write(macData); |
|
} |
|
|
|
DerOutputStream pfxout = new DerOutputStream(); |
|
pfxout.write(DerValue.tag_Sequence, pfx); |
|
byte[] pfxData = pfxout.toByteArray(); |
|
stream.write(pfxData); |
|
stream.flush(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public KeyStore.Entry engineGetEntry(String alias, |
|
KeyStore.ProtectionParameter protParam) |
|
throws KeyStoreException, NoSuchAlgorithmException, |
|
UnrecoverableEntryException { |
|
|
|
if (!engineContainsAlias(alias)) { |
|
return null; |
|
} |
|
|
|
Entry entry = entries.get(alias.toLowerCase(Locale.ENGLISH)); |
|
if (protParam == null) { |
|
if (engineIsCertificateEntry(alias)) { |
|
if (entry instanceof CertEntry && |
|
((CertEntry) entry).trustedKeyUsage != null) { |
|
|
|
if (debug != null) { |
|
debug.println("Retrieved a trusted certificate at " + |
|
"alias '" + alias + "'"); |
|
} |
|
|
|
return new KeyStore.TrustedCertificateEntry( |
|
((CertEntry)entry).cert, getAttributes(entry)); |
|
} |
|
} else { |
|
throw new UnrecoverableKeyException |
|
("requested entry requires a password"); |
|
} |
|
} |
|
|
|
if (protParam instanceof KeyStore.PasswordProtection) { |
|
if (engineIsCertificateEntry(alias)) { |
|
throw new UnsupportedOperationException |
|
("trusted certificate entries are not password-protected"); |
|
} else if (engineIsKeyEntry(alias)) { |
|
KeyStore.PasswordProtection pp = |
|
(KeyStore.PasswordProtection)protParam; |
|
char[] password = pp.getPassword(); |
|
|
|
Key key = engineGetKey(alias, password); |
|
if (key instanceof PrivateKey) { |
|
Certificate[] chain = engineGetCertificateChain(alias); |
|
|
|
return new KeyStore.PrivateKeyEntry((PrivateKey)key, chain, |
|
getAttributes(entry)); |
|
|
|
} else if (key instanceof SecretKey) { |
|
|
|
return new KeyStore.SecretKeyEntry((SecretKey)key, |
|
getAttributes(entry)); |
|
} |
|
} else if (!engineIsKeyEntry(alias)) { |
|
throw new UnsupportedOperationException |
|
("untrusted certificate entries are not " + |
|
"password-protected"); |
|
} |
|
} |
|
|
|
throw new UnsupportedOperationException(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public synchronized void engineSetEntry(String alias, KeyStore.Entry entry, |
|
KeyStore.ProtectionParameter protParam) throws KeyStoreException { |
|
|
|
|
|
if (protParam != null && |
|
!(protParam instanceof KeyStore.PasswordProtection)) { |
|
throw new KeyStoreException("unsupported protection parameter"); |
|
} |
|
KeyStore.PasswordProtection pProtect = null; |
|
if (protParam != null) { |
|
pProtect = (KeyStore.PasswordProtection)protParam; |
|
} |
|
|
|
|
|
if (entry instanceof KeyStore.TrustedCertificateEntry) { |
|
if (protParam != null && pProtect.getPassword() != null) { |
|
|
|
throw new KeyStoreException |
|
("trusted certificate entries are not password-protected"); |
|
} else { |
|
KeyStore.TrustedCertificateEntry tce = |
|
(KeyStore.TrustedCertificateEntry)entry; |
|
setCertEntry(alias, tce.getTrustedCertificate(), |
|
tce.getAttributes()); |
|
|
|
return; |
|
} |
|
} else if (entry instanceof KeyStore.PrivateKeyEntry) { |
|
if (pProtect == null || pProtect.getPassword() == null) { |
|
|
|
throw new KeyStoreException |
|
("non-null password required to create PrivateKeyEntry"); |
|
} else { |
|
KeyStore.PrivateKeyEntry pke = (KeyStore.PrivateKeyEntry)entry; |
|
setKeyEntry(alias, pke.getPrivateKey(), pProtect, |
|
pke.getCertificateChain(), pke.getAttributes()); |
|
|
|
return; |
|
} |
|
} else if (entry instanceof KeyStore.SecretKeyEntry) { |
|
if (pProtect == null || pProtect.getPassword() == null) { |
|
|
|
throw new KeyStoreException |
|
("non-null password required to create SecretKeyEntry"); |
|
} else { |
|
KeyStore.SecretKeyEntry ske = (KeyStore.SecretKeyEntry)entry; |
|
setKeyEntry(alias, ske.getSecretKey(), pProtect, |
|
(Certificate[])null, ske.getAttributes()); |
|
|
|
return; |
|
} |
|
} |
|
|
|
throw new KeyStoreException |
|
("unsupported entry type: " + entry.getClass().getName()); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private Set<KeyStore.Entry.Attribute> getAttributes(Entry entry) { |
|
|
|
if (entry.attributes == null) { |
|
entry.attributes = new HashSet<>(); |
|
} |
|
|
|
|
|
entry.attributes.add(new PKCS12Attribute( |
|
PKCS9FriendlyName_OID.toString(), entry.alias)); |
|
|
|
|
|
byte[] keyIdValue = entry.keyId; |
|
if (keyIdValue != null) { |
|
entry.attributes.add(new PKCS12Attribute( |
|
PKCS9LocalKeyId_OID.toString(), Debug.toString(keyIdValue))); |
|
} |
|
|
|
|
|
if (entry instanceof CertEntry) { |
|
ObjectIdentifier[] trustedKeyUsageValue = |
|
((CertEntry) entry).trustedKeyUsage; |
|
if (trustedKeyUsageValue != null) { |
|
if (trustedKeyUsageValue.length == 1) { |
|
entry.attributes.add(new PKCS12Attribute( |
|
TrustedKeyUsage_OID.toString(), |
|
trustedKeyUsageValue[0].toString())); |
|
} else { |
|
entry.attributes.add(new PKCS12Attribute( |
|
TrustedKeyUsage_OID.toString(), |
|
Arrays.toString(trustedKeyUsageValue))); |
|
} |
|
} |
|
} |
|
|
|
return entry.attributes; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] calculateMac(char[] passwd, byte[] data) |
|
throws IOException |
|
{ |
|
byte[] mData = null; |
|
String algName = macAlgorithm.substring(7); |
|
|
|
try { |
|
|
|
byte[] salt = getSalt(); |
|
|
|
|
|
Mac m = Mac.getInstance(macAlgorithm); |
|
PBEParameterSpec params = |
|
new PBEParameterSpec(salt, macIterationCount); |
|
SecretKey key = getPBEKey(passwd); |
|
try { |
|
m.init(key, params); |
|
} finally { |
|
destroyPBEKey(key); |
|
} |
|
m.update(data); |
|
byte[] macResult = m.doFinal(); |
|
|
|
|
|
MacData macData = new MacData(algName, macResult, salt, |
|
macIterationCount); |
|
DerOutputStream bytes = new DerOutputStream(); |
|
bytes.write(macData.getEncoded()); |
|
mData = bytes.toByteArray(); |
|
} catch (Exception e) { |
|
throw new IOException("calculateMac failed: " + e, e); |
|
} |
|
return mData; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean validateChain(Certificate[] certChain) |
|
{ |
|
for (int i = 0; i < certChain.length-1; i++) { |
|
X500Principal issuerDN = |
|
((X509Certificate)certChain[i]).getIssuerX500Principal(); |
|
X500Principal subjectDN = |
|
((X509Certificate)certChain[i+1]).getSubjectX500Principal(); |
|
if (!(issuerDN.equals(subjectDN))) |
|
return false; |
|
} |
|
|
|
// Check for loops in the chain. If there are repeated certs, |
|
// the Set of certs in the chain will contain fewer certs than |
|
|
|
Set<Certificate> set = new HashSet<>(Arrays.asList(certChain)); |
|
return set.size() == certChain.length; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static void checkX509Certs(Certificate[] certs) |
|
throws KeyStoreException { |
|
if (certs != null) { |
|
for (Certificate cert : certs) { |
|
if (!(cert instanceof X509Certificate)) { |
|
throw new KeyStoreException( |
|
"Only X.509 certificates are supported - " + |
|
"rejecting class: " + cert.getClass().getName()); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] getBagAttributes(String alias, byte[] keyId, |
|
Set<KeyStore.Entry.Attribute> attributes) throws IOException { |
|
return getBagAttributes(alias, keyId, null, attributes); |
|
} |
|
|
|
private byte[] getBagAttributes(String alias, byte[] keyId, |
|
ObjectIdentifier[] trustedUsage, |
|
Set<KeyStore.Entry.Attribute> attributes) throws IOException { |
|
|
|
byte[] localKeyID = null; |
|
byte[] friendlyName = null; |
|
byte[] trustedKeyUsage = null; |
|
|
|
|
|
if ((alias == null) && (keyId == null) && (trustedKeyUsage == null)) { |
|
return null; |
|
} |
|
|
|
|
|
DerOutputStream bagAttrs = new DerOutputStream(); |
|
|
|
|
|
if (alias != null) { |
|
DerOutputStream bagAttr1 = new DerOutputStream(); |
|
bagAttr1.putOID(PKCS9FriendlyName_OID); |
|
DerOutputStream bagAttrContent1 = new DerOutputStream(); |
|
DerOutputStream bagAttrValue1 = new DerOutputStream(); |
|
bagAttrContent1.putBMPString(alias); |
|
bagAttr1.write(DerValue.tag_Set, bagAttrContent1); |
|
bagAttrValue1.write(DerValue.tag_Sequence, bagAttr1); |
|
friendlyName = bagAttrValue1.toByteArray(); |
|
} |
|
|
|
|
|
if (keyId != null) { |
|
DerOutputStream bagAttr2 = new DerOutputStream(); |
|
bagAttr2.putOID(PKCS9LocalKeyId_OID); |
|
DerOutputStream bagAttrContent2 = new DerOutputStream(); |
|
DerOutputStream bagAttrValue2 = new DerOutputStream(); |
|
bagAttrContent2.putOctetString(keyId); |
|
bagAttr2.write(DerValue.tag_Set, bagAttrContent2); |
|
bagAttrValue2.write(DerValue.tag_Sequence, bagAttr2); |
|
localKeyID = bagAttrValue2.toByteArray(); |
|
} |
|
|
|
|
|
if (trustedUsage != null) { |
|
DerOutputStream bagAttr3 = new DerOutputStream(); |
|
bagAttr3.putOID(TrustedKeyUsage_OID); |
|
DerOutputStream bagAttrContent3 = new DerOutputStream(); |
|
DerOutputStream bagAttrValue3 = new DerOutputStream(); |
|
for (ObjectIdentifier usage : trustedUsage) { |
|
bagAttrContent3.putOID(usage); |
|
} |
|
bagAttr3.write(DerValue.tag_Set, bagAttrContent3); |
|
bagAttrValue3.write(DerValue.tag_Sequence, bagAttr3); |
|
trustedKeyUsage = bagAttrValue3.toByteArray(); |
|
} |
|
|
|
DerOutputStream attrs = new DerOutputStream(); |
|
if (friendlyName != null) { |
|
attrs.write(friendlyName); |
|
} |
|
if (localKeyID != null) { |
|
attrs.write(localKeyID); |
|
} |
|
if (trustedKeyUsage != null) { |
|
attrs.write(trustedKeyUsage); |
|
} |
|
|
|
if (attributes != null) { |
|
for (KeyStore.Entry.Attribute attribute : attributes) { |
|
String attributeName = attribute.getName(); |
|
|
|
if (CORE_ATTRIBUTES[0].value().equals(attributeName) || |
|
CORE_ATTRIBUTES[1].value().equals(attributeName) || |
|
CORE_ATTRIBUTES[2].value().equals(attributeName)) { |
|
continue; |
|
} |
|
attrs.write(((PKCS12Attribute) attribute).getEncoded()); |
|
} |
|
} |
|
|
|
bagAttrs.write(DerValue.tag_Set, attrs); |
|
return bagAttrs.toByteArray(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] createEncryptedData(char[] password) |
|
throws CertificateException, IOException |
|
{ |
|
DerOutputStream out = new DerOutputStream(); |
|
for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) { |
|
|
|
String alias = e.nextElement(); |
|
Entry entry = entries.get(alias); |
|
|
|
|
|
Certificate[] certs; |
|
|
|
if (entry instanceof PrivateKeyEntry) { |
|
PrivateKeyEntry keyEntry = (PrivateKeyEntry) entry; |
|
if (keyEntry.chain != null) { |
|
certs = keyEntry.chain; |
|
} else { |
|
certs = new Certificate[0]; |
|
} |
|
} else if (entry instanceof CertEntry) { |
|
certs = new Certificate[]{((CertEntry) entry).cert}; |
|
} else { |
|
certs = new Certificate[0]; |
|
} |
|
|
|
for (int i = 0; i < certs.length; i++) { |
|
|
|
DerOutputStream safeBag = new DerOutputStream(); |
|
safeBag.putOID(CertBag_OID); |
|
|
|
|
|
DerOutputStream certBag = new DerOutputStream(); |
|
certBag.putOID(PKCS9CertType_OID); |
|
|
|
|
|
DerOutputStream certValue = new DerOutputStream(); |
|
X509Certificate cert = (X509Certificate) certs[i]; |
|
certValue.putOctetString(cert.getEncoded()); |
|
certBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, |
|
true, (byte) 0), certValue); |
|
|
|
|
|
DerOutputStream certout = new DerOutputStream(); |
|
certout.write(DerValue.tag_Sequence, certBag); |
|
byte[] certBagValue = certout.toByteArray(); |
|
|
|
|
|
DerOutputStream bagValue = new DerOutputStream(); |
|
bagValue.write(certBagValue); |
|
|
|
safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, |
|
true, (byte) 0), bagValue); |
|
|
|
// write SafeBag Attributes |
|
// All Certs should have a unique friendlyName. |
|
|
|
byte[] bagAttrs = null; |
|
if (i == 0) { |
|
|
|
if (entry instanceof KeyEntry) { |
|
KeyEntry keyEntry = (KeyEntry) entry; |
|
bagAttrs = |
|
getBagAttributes(keyEntry.alias, keyEntry.keyId, |
|
keyEntry.attributes); |
|
} else { |
|
CertEntry certEntry = (CertEntry) entry; |
|
bagAttrs = |
|
getBagAttributes(certEntry.alias, certEntry.keyId, |
|
certEntry.trustedKeyUsage, |
|
certEntry.attributes); |
|
} |
|
} else { |
|
// Trusted root CA certs and Intermediate CA certs do not |
|
// need to have a localKeyId, and hence localKeyId is null |
|
// This change is made to meet NSS/Netscape requirements. |
|
// NSS pkcs12 library requires trusted CA certs in the |
|
// certificate chain to have unique or null localKeyID. |
|
|
|
bagAttrs = getBagAttributes( |
|
cert.getSubjectX500Principal().getName(), null, |
|
entry.attributes); |
|
} |
|
if (bagAttrs != null) { |
|
safeBag.write(bagAttrs); |
|
} |
|
|
|
|
|
out.write(DerValue.tag_Sequence, safeBag); |
|
} // for cert-chain |
|
} |
|
|
|
|
|
DerOutputStream safeBagValue = new DerOutputStream(); |
|
safeBagValue.write(DerValue.tag_SequenceOf, out); |
|
byte[] safeBagData = safeBagValue.toByteArray(); |
|
|
|
|
|
if (!certProtectionAlgorithm.equalsIgnoreCase("NONE")) { |
|
byte[] encrContentInfo = encryptContent(safeBagData, password); |
|
|
|
|
|
DerOutputStream encrData = new DerOutputStream(); |
|
DerOutputStream encrDataContent = new DerOutputStream(); |
|
encrData.putInteger(0); |
|
encrData.write(encrContentInfo); |
|
encrDataContent.write(DerValue.tag_Sequence, encrData); |
|
return encrDataContent.toByteArray(); |
|
} else { |
|
return safeBagData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] createSafeContent() |
|
throws CertificateException, IOException { |
|
|
|
DerOutputStream out = new DerOutputStream(); |
|
for (Enumeration<String> e = engineAliases(); e.hasMoreElements(); ) { |
|
|
|
String alias = e.nextElement(); |
|
Entry entry = entries.get(alias); |
|
if (entry == null || (!(entry instanceof KeyEntry))) { |
|
continue; |
|
} |
|
DerOutputStream safeBag = new DerOutputStream(); |
|
KeyEntry keyEntry = (KeyEntry) entry; |
|
|
|
|
|
if (keyEntry instanceof PrivateKeyEntry) { |
|
|
|
safeBag.putOID(PKCS8ShroudedKeyBag_OID); |
|
|
|
|
|
byte[] encrBytes = ((PrivateKeyEntry)keyEntry).protectedPrivKey; |
|
EncryptedPrivateKeyInfo encrInfo = null; |
|
try { |
|
encrInfo = new EncryptedPrivateKeyInfo(encrBytes); |
|
|
|
} catch (IOException ioe) { |
|
throw new IOException("Private key not stored as " |
|
+ "PKCS#8 EncryptedPrivateKeyInfo" |
|
+ ioe.getMessage()); |
|
} |
|
|
|
|
|
DerOutputStream bagValue = new DerOutputStream(); |
|
bagValue.write(encrInfo.getEncoded()); |
|
safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, |
|
true, (byte) 0), bagValue); |
|
|
|
// DER encode the secret key |
|
} else if (keyEntry instanceof SecretKeyEntry) { |
|
|
|
safeBag.putOID(SecretBag_OID); |
|
|
|
|
|
DerOutputStream secretBag = new DerOutputStream(); |
|
secretBag.putOID(PKCS8ShroudedKeyBag_OID); |
|
|
|
|
|
DerOutputStream secretKeyValue = new DerOutputStream(); |
|
secretKeyValue.putOctetString( |
|
((SecretKeyEntry) keyEntry).protectedSecretKey); |
|
secretBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, |
|
true, (byte) 0), secretKeyValue); |
|
|
|
|
|
DerOutputStream secretBagSeq = new DerOutputStream(); |
|
secretBagSeq.write(DerValue.tag_Sequence, secretBag); |
|
byte[] secretBagValue = secretBagSeq.toByteArray(); |
|
|
|
|
|
DerOutputStream bagValue = new DerOutputStream(); |
|
bagValue.write(secretBagValue); |
|
|
|
|
|
safeBag.write(DerValue.createTag(DerValue.TAG_CONTEXT, |
|
true, (byte) 0), bagValue); |
|
} else { |
|
continue; |
|
} |
|
|
|
|
|
byte[] bagAttrs = |
|
getBagAttributes(alias, entry.keyId, entry.attributes); |
|
safeBag.write(bagAttrs); |
|
|
|
|
|
out.write(DerValue.tag_Sequence, safeBag); |
|
} |
|
|
|
|
|
DerOutputStream safeBagValue = new DerOutputStream(); |
|
safeBagValue.write(DerValue.tag_Sequence, out); |
|
return safeBagValue.toByteArray(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] encryptContent(byte[] data, char[] password) |
|
throws IOException { |
|
|
|
byte[] encryptedData = null; |
|
|
|
|
|
try { |
|
|
|
AlgorithmParameters algParams = getPBEAlgorithmParameters( |
|
certProtectionAlgorithm, certPbeIterationCount); |
|
DerOutputStream bytes = new DerOutputStream(); |
|
|
|
|
|
Cipher cipher = Cipher.getInstance(certProtectionAlgorithm); |
|
SecretKey skey = getPBEKey(password); |
|
try { |
|
cipher.init(Cipher.ENCRYPT_MODE, skey, algParams); |
|
} finally { |
|
destroyPBEKey(skey); |
|
} |
|
encryptedData = cipher.doFinal(data); |
|
|
|
AlgorithmId algId = new AlgorithmId( |
|
mapPBEAlgorithmToOID(certProtectionAlgorithm), |
|
cipher.getParameters()); |
|
|
|
algId.encode(bytes); |
|
byte[] encodedAlgId = bytes.toByteArray(); |
|
|
|
if (debug != null) { |
|
debug.println(" (Cipher algorithm: " + cipher.getAlgorithm() + |
|
")"); |
|
} |
|
|
|
|
|
DerOutputStream bytes2 = new DerOutputStream(); |
|
bytes2.putOID(ContentInfo.DATA_OID); |
|
bytes2.write(encodedAlgId); |
|
|
|
|
|
DerOutputStream tmpout2 = new DerOutputStream(); |
|
tmpout2.putOctetString(encryptedData); |
|
bytes2.writeImplicit(DerValue.createTag(DerValue.TAG_CONTEXT, |
|
false, (byte) 0), tmpout2); |
|
|
|
|
|
DerOutputStream out = new DerOutputStream(); |
|
out.write(DerValue.tag_Sequence, bytes2); |
|
return out.toByteArray(); |
|
} catch (IOException ioe) { |
|
throw ioe; |
|
} catch (Exception e) { |
|
throw new IOException("Failed to encrypt" + |
|
" safe contents entry: " + e, e); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized void engineLoad(InputStream stream, char[] password) |
|
throws IOException, NoSuchAlgorithmException, CertificateException |
|
{ |
|
|
|
|
|
certProtectionAlgorithm = null; |
|
certPbeIterationCount = -1; |
|
macAlgorithm = null; |
|
macIterationCount = -1; |
|
|
|
if (stream == null) |
|
return; |
|
|
|
|
|
counter = 0; |
|
|
|
DerValue val = new DerValue(stream); |
|
DerInputStream s = val.toDerInputStream(); |
|
int version = s.getInteger(); |
|
|
|
if (version != VERSION_3) { |
|
throw new IOException("PKCS12 keystore not in version 3 format"); |
|
} |
|
|
|
entries.clear(); |
|
|
|
|
|
|
|
*/ |
|
byte[] authSafeData; |
|
ContentInfo authSafe = new ContentInfo(s); |
|
ObjectIdentifier contentType = authSafe.getContentType(); |
|
|
|
if (contentType.equals(ContentInfo.DATA_OID)) { |
|
authSafeData = authSafe.getData(); |
|
} else { |
|
throw new IOException("public key protected PKCS12 not supported"); |
|
} |
|
|
|
DerInputStream as = new DerInputStream(authSafeData); |
|
DerValue[] safeContentsArray = as.getSequence(2); |
|
int count = safeContentsArray.length; |
|
|
|
|
|
privateKeyCount = 0; |
|
secretKeyCount = 0; |
|
certificateCount = 0; |
|
|
|
boolean seeEncBag = false; |
|
|
|
|
|
|
|
*/ |
|
for (int i = 0; i < count; i++) { |
|
ContentInfo safeContents; |
|
DerInputStream sci; |
|
byte[] eAlgId = null; |
|
|
|
sci = new DerInputStream(safeContentsArray[i].toByteArray()); |
|
safeContents = new ContentInfo(sci); |
|
contentType = safeContents.getContentType(); |
|
if (contentType.equals(ContentInfo.DATA_OID)) { |
|
|
|
if (debug != null) { |
|
debug.println("Loading PKCS#7 data"); |
|
} |
|
|
|
loadSafeContents(new DerInputStream(safeContents.getData())); |
|
} else if (contentType.equals(ContentInfo.ENCRYPTED_DATA_OID)) { |
|
if (password == null) { |
|
|
|
if (debug != null) { |
|
debug.println("Warning: skipping PKCS#7 encryptedData" + |
|
" - no password was supplied"); |
|
} |
|
// No password to decrypt ENCRYPTED_DATA_OID. *Skip it*. |
|
// This means user will see a PrivateKeyEntry without |
|
// certificates and a whole TrustedCertificateEntry will |
|
// be lost. This is not a perfect solution but alternative |
|
// solutions are more disruptive: |
|
// |
|
// We cannot just fail, since KeyStore.load(is, null) |
|
// has been known to never fail because of a null password. |
|
// |
|
// We cannot just throw away the whole PrivateKeyEntry, |
|
// this is too silent and no one will notice anything. |
|
// |
|
// We also cannot fail when getCertificate() on such a |
|
// PrivateKeyEntry is called, since the method has not |
|
|
|
continue; |
|
} |
|
|
|
DerInputStream edi = |
|
safeContents.getContent().toDerInputStream(); |
|
int edVersion = edi.getInteger(); |
|
DerValue[] seq = edi.getSequence(3); |
|
if (seq.length != 3) { |
|
// We require the encryptedContent field, even though |
|
|
|
throw new IOException("Invalid length for EncryptedContentInfo"); |
|
} |
|
ObjectIdentifier edContentType = seq[0].getOID(); |
|
eAlgId = seq[1].toByteArray(); |
|
if (!seq[2].isContextSpecific((byte)0)) { |
|
throw new IOException("unsupported encrypted content type " |
|
+ seq[2].tag); |
|
} |
|
byte newTag = DerValue.tag_OctetString; |
|
if (seq[2].isConstructed()) |
|
newTag |= 0x20; |
|
seq[2].resetTag(newTag); |
|
byte[] rawData = seq[2].getOctetString(); |
|
|
|
|
|
DerInputStream in = seq[1].toDerInputStream(); |
|
ObjectIdentifier algOid = in.getOID(); |
|
AlgorithmParameters algParams = parseAlgParameters(algOid, in); |
|
|
|
PBEParameterSpec pbeSpec; |
|
int ic = 0; |
|
|
|
if (algParams != null) { |
|
try { |
|
pbeSpec = |
|
algParams.getParameterSpec(PBEParameterSpec.class); |
|
} catch (InvalidParameterSpecException ipse) { |
|
throw new IOException( |
|
"Invalid PBE algorithm parameters"); |
|
} |
|
ic = pbeSpec.getIterationCount(); |
|
|
|
if (ic > MAX_ITERATION_COUNT) { |
|
throw new IOException("cert PBE iteration count too large"); |
|
} |
|
|
|
certProtectionAlgorithm |
|
= mapPBEParamsToAlgorithm(algOid, algParams); |
|
certPbeIterationCount = ic; |
|
seeEncBag = true; |
|
} |
|
|
|
if (debug != null) { |
|
debug.println("Loading PKCS#7 encryptedData " + |
|
"(" + mapPBEParamsToAlgorithm(algOid, algParams) + |
|
" iterations: " + ic + ")"); |
|
} |
|
|
|
try { |
|
RetryWithZero.run(pass -> { |
|
|
|
Cipher cipher = Cipher.getInstance( |
|
mapPBEParamsToAlgorithm(algOid, algParams)); |
|
SecretKey skey = getPBEKey(pass); |
|
try { |
|
cipher.init(Cipher.DECRYPT_MODE, skey, algParams); |
|
} finally { |
|
destroyPBEKey(skey); |
|
} |
|
loadSafeContents(new DerInputStream(cipher.doFinal(rawData))); |
|
return null; |
|
}, password); |
|
} catch (Exception e) { |
|
throw new IOException("keystore password was incorrect", |
|
new UnrecoverableKeyException( |
|
"failed to decrypt safe contents entry: " + e)); |
|
} |
|
} else { |
|
throw new IOException("public key protected PKCS12" + |
|
" not supported"); |
|
} |
|
} |
|
|
|
|
|
if (!seeEncBag && certificateCount > 0) { |
|
certProtectionAlgorithm = "NONE"; |
|
} |
|
|
|
|
|
if (s.available() > 0) { |
|
// If there is no password, we cannot fail. KeyStore.load(is, null) |
|
|
|
if (password != null) { |
|
MacData macData = new MacData(s); |
|
int ic = macData.getIterations(); |
|
|
|
try { |
|
if (ic > MAX_ITERATION_COUNT) { |
|
throw new InvalidAlgorithmParameterException( |
|
"MAC iteration count too large: " + ic); |
|
} |
|
|
|
String algName = |
|
macData.getDigestAlgName().toUpperCase(Locale.ENGLISH); |
|
|
|
|
|
algName = algName.replace("-", ""); |
|
|
|
macAlgorithm = "HmacPBE" + algName; |
|
macIterationCount = ic; |
|
|
|
|
|
Mac m = Mac.getInstance(macAlgorithm); |
|
PBEParameterSpec params = |
|
new PBEParameterSpec(macData.getSalt(), ic); |
|
|
|
RetryWithZero.run(pass -> { |
|
SecretKey key = getPBEKey(pass); |
|
try { |
|
m.init(key, params); |
|
} finally { |
|
destroyPBEKey(key); |
|
} |
|
m.update(authSafeData); |
|
byte[] macResult = m.doFinal(); |
|
|
|
if (debug != null) { |
|
debug.println("Checking keystore integrity " + |
|
"(" + m.getAlgorithm() + " iterations: " + ic + ")"); |
|
} |
|
|
|
if (!MessageDigest.isEqual(macData.getDigest(), macResult)) { |
|
throw new UnrecoverableKeyException("Failed PKCS12" + |
|
" integrity checking"); |
|
} |
|
return (Void) null; |
|
}, password); |
|
} catch (Exception e) { |
|
throw new IOException("Integrity check failed: " + e, e); |
|
} |
|
} |
|
} else { |
|
macAlgorithm = "NONE"; |
|
} |
|
|
|
|
|
|
|
*/ |
|
PrivateKeyEntry[] list = |
|
keyList.toArray(new PrivateKeyEntry[keyList.size()]); |
|
for (int m = 0; m < list.length; m++) { |
|
PrivateKeyEntry entry = list[m]; |
|
if (entry.keyId != null) { |
|
ArrayList<X509Certificate> chain = |
|
new ArrayList<X509Certificate>(); |
|
X509Certificate cert = findMatchedCertificate(entry); |
|
|
|
mainloop: |
|
while (cert != null) { |
|
|
|
if (!chain.isEmpty()) { |
|
for (X509Certificate chainCert : chain) { |
|
if (cert.equals(chainCert)) { |
|
if (debug != null) { |
|
debug.println("Loop detected in " + |
|
"certificate chain. Skip adding " + |
|
"repeated cert to chain. Subject: " + |
|
cert.getSubjectX500Principal() |
|
.toString()); |
|
} |
|
break mainloop; |
|
} |
|
} |
|
} |
|
chain.add(cert); |
|
if (KeyStoreUtil.isSelfSigned(cert)) { |
|
break; |
|
} |
|
cert = findIssuer(cert); |
|
} |
|
|
|
if (chain.size() > 0) { |
|
entry.chain = chain.toArray(new Certificate[chain.size()]); |
|
} else { |
|
// Remove private key entries where there is no associated |
|
// certs. Most likely the keystore is loaded with a null |
|
|
|
entries.remove(entry); |
|
} |
|
} |
|
} |
|
|
|
if (debug != null) { |
|
debug.println("PKCS12KeyStore load: private key count: " + |
|
privateKeyCount + ". secret key count: " + secretKeyCount + |
|
". certificate count: " + certificateCount); |
|
} |
|
|
|
certEntries.clear(); |
|
allCerts.clear(); |
|
keyList.clear(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private X509Certificate findIssuer(X509Certificate input) { |
|
|
|
X509Certificate fallback = null; |
|
X500Principal issuerPrinc = input.getIssuerX500Principal(); |
|
|
|
|
|
byte[] issuerIdExtension = input.getExtensionValue( |
|
KnownOIDs.AuthorityKeyID.value()); |
|
byte[] issuerId = null; |
|
|
|
if (issuerIdExtension != null) { |
|
try { |
|
issuerId = new AuthorityKeyIdentifierExtension( |
|
false, |
|
new DerValue(issuerIdExtension).getOctetString()) |
|
.getEncodedKeyIdentifier(); |
|
} catch (IOException e) { |
|
// ignored. issuerId is still null |
|
} |
|
} |
|
|
|
for (X509Certificate cert : allCerts) { |
|
if (cert.getSubjectX500Principal().equals(issuerPrinc)) { |
|
if (issuerId != null) { |
|
|
|
byte[] subjectIdExtension = cert.getExtensionValue( |
|
KnownOIDs.SubjectKeyID.value()); |
|
byte[] subjectId = null; |
|
if (subjectIdExtension != null) { |
|
try { |
|
subjectId = new DerValue(subjectIdExtension) |
|
.getOctetString(); |
|
} catch (IOException e) { |
|
// ignored. issuerId is still null |
|
} |
|
} |
|
if (subjectId != null) { |
|
if (Arrays.equals(issuerId, subjectId)) { |
|
|
|
return cert; |
|
} else { |
|
|
|
continue; |
|
} |
|
} else { |
|
|
|
fallback = cert; |
|
} |
|
} else { |
|
return cert; |
|
} |
|
} |
|
} |
|
return fallback; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static boolean isPasswordless(File f) throws IOException { |
|
|
|
try (FileInputStream stream = new FileInputStream(f)) { |
|
DerValue val = new DerValue(stream); |
|
DerInputStream s = val.toDerInputStream(); |
|
|
|
s.getInteger(); |
|
|
|
ContentInfo authSafe = new ContentInfo(s); |
|
DerInputStream as = new DerInputStream(authSafe.getData()); |
|
for (DerValue seq : as.getSequence(2)) { |
|
DerInputStream sci = new DerInputStream(seq.toByteArray()); |
|
ContentInfo safeContents = new ContentInfo(sci); |
|
if (safeContents.getContentType() |
|
.equals(ContentInfo.ENCRYPTED_DATA_OID)) { |
|
|
|
return false; |
|
} |
|
} |
|
|
|
if (s.available() > 0) { |
|
|
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private X509Certificate findMatchedCertificate(PrivateKeyEntry entry) { |
|
CertEntry keyIdMatch = null; |
|
CertEntry aliasMatch = null; |
|
for (CertEntry ce: certEntries) { |
|
if (Arrays.equals(entry.keyId, ce.keyId)) { |
|
keyIdMatch = ce; |
|
if (entry.alias.equalsIgnoreCase(ce.alias)) { |
|
|
|
return ce.cert; |
|
} |
|
} else if (entry.alias.equalsIgnoreCase(ce.alias)) { |
|
aliasMatch = ce; |
|
} |
|
} |
|
|
|
if (keyIdMatch != null) return keyIdMatch.cert; |
|
else if (aliasMatch != null) return aliasMatch.cert; |
|
else return null; |
|
} |
|
|
|
private void loadSafeContents(DerInputStream stream) |
|
throws IOException, NoSuchAlgorithmException, CertificateException |
|
{ |
|
DerValue[] safeBags = stream.getSequence(2); |
|
int count = safeBags.length; |
|
|
|
|
|
|
|
*/ |
|
for (int i = 0; i < count; i++) { |
|
ObjectIdentifier bagId; |
|
DerInputStream sbi; |
|
DerValue bagValue; |
|
Object bagItem = null; |
|
|
|
sbi = safeBags[i].toDerInputStream(); |
|
bagId = sbi.getOID(); |
|
bagValue = sbi.getDerValue(); |
|
if (!bagValue.isContextSpecific((byte)0)) { |
|
throw new IOException("unsupported PKCS12 bag value type " |
|
+ bagValue.tag); |
|
} |
|
bagValue = bagValue.data.getDerValue(); |
|
if (bagId.equals(PKCS8ShroudedKeyBag_OID)) { |
|
PrivateKeyEntry kEntry = new PrivateKeyEntry(); |
|
kEntry.protectedPrivKey = bagValue.toByteArray(); |
|
bagItem = kEntry; |
|
privateKeyCount++; |
|
} else if (bagId.equals(CertBag_OID)) { |
|
DerInputStream cs = new DerInputStream(bagValue.toByteArray()); |
|
DerValue[] certValues = cs.getSequence(2); |
|
if (certValues.length != 2) { |
|
throw new IOException("Invalid length for CertBag"); |
|
} |
|
ObjectIdentifier certId = certValues[0].getOID(); |
|
if (!certValues[1].isContextSpecific((byte)0)) { |
|
throw new IOException("unsupported PKCS12 cert value type " |
|
+ certValues[1].tag); |
|
} |
|
DerValue certValue = certValues[1].data.getDerValue(); |
|
CertificateFactory cf = CertificateFactory.getInstance("X509"); |
|
X509Certificate cert; |
|
cert = (X509Certificate)cf.generateCertificate |
|
(new ByteArrayInputStream(certValue.getOctetString())); |
|
bagItem = cert; |
|
certificateCount++; |
|
} else if (bagId.equals(SecretBag_OID)) { |
|
DerInputStream ss = new DerInputStream(bagValue.toByteArray()); |
|
DerValue[] secretValues = ss.getSequence(2); |
|
if (secretValues.length != 2) { |
|
throw new IOException("Invalid length for SecretBag"); |
|
} |
|
ObjectIdentifier secretId = secretValues[0].getOID(); |
|
if (!secretValues[1].isContextSpecific((byte)0)) { |
|
throw new IOException( |
|
"unsupported PKCS12 secret value type " |
|
+ secretValues[1].tag); |
|
} |
|
DerValue secretValue = secretValues[1].data.getDerValue(); |
|
SecretKeyEntry kEntry = new SecretKeyEntry(); |
|
kEntry.protectedSecretKey = secretValue.getOctetString(); |
|
bagItem = kEntry; |
|
secretKeyCount++; |
|
} else { |
|
|
|
if (debug != null) { |
|
debug.println("Unsupported PKCS12 bag type: " + bagId); |
|
} |
|
} |
|
|
|
DerValue[] attrSet; |
|
try { |
|
attrSet = sbi.getSet(3); |
|
} catch (IOException e) { |
|
// entry does not have attributes |
|
// Note: CA certs can have no attributes |
|
|
|
attrSet = null; |
|
} |
|
|
|
String alias = null; |
|
byte[] keyId = null; |
|
ObjectIdentifier[] trustedKeyUsage = null; |
|
Set<PKCS12Attribute> attributes = new HashSet<>(); |
|
|
|
if (attrSet != null) { |
|
for (int j = 0; j < attrSet.length; j++) { |
|
byte[] encoded = attrSet[j].toByteArray(); |
|
DerInputStream as = new DerInputStream(encoded); |
|
DerValue[] attrSeq = as.getSequence(2); |
|
if (attrSeq.length != 2) { |
|
throw new IOException("Invalid length for Attribute"); |
|
} |
|
ObjectIdentifier attrId = attrSeq[0].getOID(); |
|
DerInputStream vs = |
|
new DerInputStream(attrSeq[1].toByteArray()); |
|
DerValue[] valSet; |
|
try { |
|
valSet = vs.getSet(1); |
|
} catch (IOException e) { |
|
throw new IOException("Attribute " + attrId + |
|
" should have a value " + e.getMessage()); |
|
} |
|
if (attrId.equals(PKCS9FriendlyName_OID)) { |
|
alias = valSet[0].getBMPString(); |
|
} else if (attrId.equals(PKCS9LocalKeyId_OID)) { |
|
keyId = valSet[0].getOctetString(); |
|
} else if |
|
(attrId.equals(TrustedKeyUsage_OID)) { |
|
trustedKeyUsage = new ObjectIdentifier[valSet.length]; |
|
for (int k = 0; k < valSet.length; k++) { |
|
trustedKeyUsage[k] = valSet[k].getOID(); |
|
} |
|
} else { |
|
attributes.add(new PKCS12Attribute(encoded)); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (bagItem instanceof KeyEntry) { |
|
KeyEntry entry = (KeyEntry)bagItem; |
|
|
|
if (keyId == null) { |
|
if (bagItem instanceof PrivateKeyEntry) { |
|
// Insert a localKeyID for the privateKey |
|
// Note: This is a workaround to allow null localKeyID |
|
// attribute in pkcs12 with one private key entry and |
|
|
|
if (privateKeyCount == 1) { |
|
keyId = "01".getBytes(UTF_8); |
|
} else { |
|
continue; |
|
} |
|
} else { |
|
|
|
keyId = "00".getBytes(UTF_8); |
|
} |
|
} |
|
entry.keyId = keyId; |
|
|
|
String keyIdStr = new String(keyId, UTF_8); |
|
Date date = null; |
|
if (keyIdStr.startsWith("Time ")) { |
|
try { |
|
date = new Date( |
|
Long.parseLong(keyIdStr.substring(5))); |
|
} catch (Exception e) { |
|
date = null; |
|
} |
|
} |
|
if (date == null) { |
|
date = new Date(); |
|
} |
|
entry.date = date; |
|
|
|
if (bagItem instanceof PrivateKeyEntry) { |
|
keyList.add((PrivateKeyEntry) entry); |
|
} |
|
if (entry.attributes == null) { |
|
entry.attributes = new HashSet<>(); |
|
} |
|
entry.attributes.addAll(attributes); |
|
if (alias == null) { |
|
alias = getUnfriendlyName(); |
|
} |
|
entry.alias = alias; |
|
entries.put(alias.toLowerCase(Locale.ENGLISH), entry); |
|
|
|
} else if (bagItem instanceof X509Certificate) { |
|
X509Certificate cert = (X509Certificate)bagItem; |
|
// Insert a localKeyID for the corresponding cert |
|
// Note: This is a workaround to allow null localKeyID |
|
// attribute in pkcs12 with one private key entry and |
|
|
|
if ((keyId == null) && (privateKeyCount == 1)) { |
|
|
|
if (i == 0) { |
|
keyId = "01".getBytes(UTF_8); |
|
} |
|
} |
|
|
|
if (trustedKeyUsage != null) { |
|
if (alias == null) { |
|
alias = getUnfriendlyName(); |
|
} |
|
CertEntry certEntry = |
|
new CertEntry(cert, keyId, alias, trustedKeyUsage, |
|
attributes); |
|
entries.put(alias.toLowerCase(Locale.ENGLISH), certEntry); |
|
} else { |
|
certEntries.add(new CertEntry(cert, keyId, alias)); |
|
} |
|
allCerts.add(cert); |
|
} |
|
} |
|
} |
|
|
|
private String getUnfriendlyName() { |
|
counter++; |
|
return (String.valueOf(counter)); |
|
} |
|
|
|
/* |
|
* PKCS12 permitted first 24 bytes: |
|
* |
|
* 30 80 02 01 03 30 80 06 09 2A 86 48 86 F7 0D 01 07 01 A0 80 24 80 04 -- |
|
* 30 82 -- -- 02 01 03 30 82 -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 8- |
|
* 30 -- 02 01 03 30 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 -- 04 -- -- -- |
|
* 30 81 -- 02 01 03 30 81 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 81 -- 04 |
|
* 30 82 -- -- 02 01 03 30 81 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 81 -- |
|
* 30 83 -- -- -- 02 01 03 30 82 -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 |
|
* 30 83 -- -- -- 02 01 03 30 83 -- -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 |
|
* 30 84 -- -- -- -- 02 01 03 30 83 -- -- -- 06 09 2A 86 48 86 F7 0D 01 07 |
|
* 30 84 -- -- -- -- 02 01 03 30 84 -- -- -- -- 06 09 2A 86 48 86 F7 0D 01 |
|
*/ |
|
|
|
private static final long[][] PKCS12_HEADER_PATTERNS = { |
|
{ 0x3080020103308006L, 0x092A864886F70D01L, 0x0701A08024800400L }, |
|
{ 0x3082000002010330L, 0x82000006092A8648L, 0x86F70D010701A080L }, |
|
{ 0x3000020103300006L, 0x092A864886F70D01L, 0x0701A00004000000L }, |
|
{ 0x3081000201033081L, 0x0006092A864886F7L, 0x0D010701A0810004L }, |
|
{ 0x3082000002010330L, 0x810006092A864886L, 0xF70D010701A08100L }, |
|
{ 0x3083000000020103L, 0x3082000006092A86L, 0x4886F70D010701A0L }, |
|
{ 0x3083000000020103L, 0x308300000006092AL, 0x864886F70D010701L }, |
|
{ 0x3084000000000201L, 0x0330830000000609L, 0x2A864886F70D0107L }, |
|
{ 0x3084000000000201L, 0x0330840000000006L, 0x092A864886F70D01L } |
|
}; |
|
|
|
private static final long[][] PKCS12_HEADER_MASKS = { |
|
{ 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFF00L }, |
|
{ 0xFFFF0000FFFFFFFFL, 0xFF0000FFFFFFFFFFL, 0xFFFFFFFFFFFFFFF0L }, |
|
{ 0xFF00FFFFFFFF00FFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFF00FF000000L }, |
|
{ 0xFFFF00FFFFFFFFFFL, 0x00FFFFFFFFFFFFFFL, 0xFFFFFFFFFFFF00FFL }, |
|
{ 0xFFFF0000FFFFFFFFL, 0xFF00FFFFFFFFFFFFL, 0xFFFFFFFFFFFFFF00L }, |
|
{ 0xFFFF000000FFFFFFL, 0xFFFF0000FFFFFFFFL, 0xFFFFFFFFFFFFFFFFL }, |
|
{ 0xFFFF000000FFFFFFL, 0xFFFF000000FFFFFFL, 0xFFFFFFFFFFFFFFFFL }, |
|
{ 0xFFFF00000000FFFFL, 0xFFFFFF000000FFFFL, 0xFFFFFFFFFFFFFFFFL }, |
|
{ 0xFFFF00000000FFFFL, 0xFFFFFF00000000FFL, 0xFFFFFFFFFFFFFFFFL } |
|
}; |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean engineProbe(InputStream stream) throws IOException { |
|
|
|
DataInputStream dataStream; |
|
if (stream instanceof DataInputStream) { |
|
dataStream = (DataInputStream)stream; |
|
} else { |
|
dataStream = new DataInputStream(stream); |
|
} |
|
|
|
long firstPeek = dataStream.readLong(); |
|
long nextPeek = dataStream.readLong(); |
|
long finalPeek = dataStream.readLong(); |
|
boolean result = false; |
|
|
|
for (int i = 0; i < PKCS12_HEADER_PATTERNS.length; i++) { |
|
if (PKCS12_HEADER_PATTERNS[i][0] == |
|
(firstPeek & PKCS12_HEADER_MASKS[i][0]) && |
|
(PKCS12_HEADER_PATTERNS[i][1] == |
|
(nextPeek & PKCS12_HEADER_MASKS[i][1])) && |
|
(PKCS12_HEADER_PATTERNS[i][2] == |
|
(finalPeek & PKCS12_HEADER_MASKS[i][2]))) { |
|
result = true; |
|
break; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
// The following methods are related to customizing |
|
// the generation of a PKCS12 keystore or private/secret |
|
// key entries. |
|
|
|
private static boolean useLegacy() { |
|
return GetPropertyAction.privilegedGetProperty( |
|
USE_LEGACY_PROP) != null; |
|
} |
|
|
|
private static String defaultCertProtectionAlgorithm() { |
|
if (useLegacy()) { |
|
return LEGACY_CERT_PBE_ALGORITHM; |
|
} |
|
String result = SecurityProperties.privilegedGetOverridable( |
|
"keystore.pkcs12.certProtectionAlgorithm"); |
|
return (result != null && !result.isEmpty()) |
|
? result : DEFAULT_CERT_PBE_ALGORITHM; |
|
} |
|
|
|
private static int defaultCertPbeIterationCount() { |
|
if (useLegacy()) { |
|
return LEGACY_PBE_ITERATION_COUNT; |
|
} |
|
String result = SecurityProperties.privilegedGetOverridable( |
|
"keystore.pkcs12.certPbeIterationCount"); |
|
return (result != null && !result.isEmpty()) |
|
? string2IC("certPbeIterationCount", result) |
|
: DEFAULT_CERT_PBE_ITERATION_COUNT; |
|
} |
|
|
|
// Read both "keystore.pkcs12.keyProtectionAlgorithm" and |
|
|
|
private static String defaultKeyProtectionAlgorithm() { |
|
if (useLegacy()) { |
|
return LEGACY_KEY_PBE_ALGORITHM; |
|
} |
|
@SuppressWarnings("removal") |
|
String result = AccessController.doPrivileged(new PrivilegedAction<String>() { |
|
public String run() { |
|
String result; |
|
String name1 = "keystore.pkcs12.keyProtectionAlgorithm"; |
|
String name2 = "keystore.PKCS12.keyProtectionAlgorithm"; |
|
result = System.getProperty(name1); |
|
if (result != null) { |
|
return result; |
|
} |
|
result = System.getProperty(name2); |
|
if (result != null) { |
|
return result; |
|
} |
|
result = Security.getProperty(name1); |
|
if (result != null) { |
|
return result; |
|
} |
|
return Security.getProperty(name2); |
|
} |
|
}); |
|
return (result != null && !result.isEmpty()) |
|
? result : DEFAULT_KEY_PBE_ALGORITHM; |
|
} |
|
|
|
private static int defaultKeyPbeIterationCount() { |
|
if (useLegacy()) { |
|
return LEGACY_PBE_ITERATION_COUNT; |
|
} |
|
String result = SecurityProperties.privilegedGetOverridable( |
|
"keystore.pkcs12.keyPbeIterationCount"); |
|
return (result != null && !result.isEmpty()) |
|
? string2IC("keyPbeIterationCount", result) |
|
: DEFAULT_KEY_PBE_ITERATION_COUNT; |
|
} |
|
|
|
private static String defaultMacAlgorithm() { |
|
if (useLegacy()) { |
|
return LEGACY_MAC_ALGORITHM; |
|
} |
|
String result = SecurityProperties.privilegedGetOverridable( |
|
"keystore.pkcs12.macAlgorithm"); |
|
return (result != null && !result.isEmpty()) |
|
? result : DEFAULT_MAC_ALGORITHM; |
|
} |
|
|
|
private static int defaultMacIterationCount() { |
|
if (useLegacy()) { |
|
return LEGACY_MAC_ITERATION_COUNT; |
|
} |
|
String result = SecurityProperties.privilegedGetOverridable( |
|
"keystore.pkcs12.macIterationCount"); |
|
return (result != null && !result.isEmpty()) |
|
? string2IC("macIterationCount", result) |
|
: DEFAULT_MAC_ITERATION_COUNT; |
|
} |
|
|
|
private static int string2IC(String type, String value) { |
|
int number; |
|
try { |
|
number = Integer.parseInt(value); |
|
} catch (NumberFormatException e) { |
|
throw new IllegalArgumentException("keystore.pkcs12." + type |
|
+ " is not a number: " + value); |
|
} |
|
if (number <= 0 || number > MAX_ITERATION_COUNT) { |
|
throw new IllegalArgumentException("Invalid keystore.pkcs12." |
|
+ type + ": " + value); |
|
} |
|
return number; |
|
} |
|
} |