blob: fdf886ba751f1197f0643acf4f0d266955dfcad0 [file] [log] [blame]
package org.bouncycastle.jcajce.provider.keystore.bcfks;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.ParseException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.bc.EncryptedObjectStoreData;
import org.bouncycastle.asn1.bc.EncryptedPrivateKeyData;
import org.bouncycastle.asn1.bc.EncryptedSecretKeyData;
import org.bouncycastle.asn1.bc.ObjectData;
import org.bouncycastle.asn1.bc.ObjectDataSequence;
import org.bouncycastle.asn1.bc.ObjectStore;
import org.bouncycastle.asn1.bc.ObjectStoreData;
import org.bouncycastle.asn1.bc.ObjectStoreIntegrityCheck;
import org.bouncycastle.asn1.bc.PbkdMacIntegrityCheck;
import org.bouncycastle.asn1.bc.SecretKeyData;
import org.bouncycastle.asn1.cms.CCMParameters;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.EncryptionScheme;
import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
import org.bouncycastle.asn1.pkcs.PBES2Parameters;
import org.bouncycastle.asn1.pkcs.PBKDF2Params;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Strings;
class BcFKSKeyStoreSpi
extends KeyStoreSpi
{
private static final Map<String, ASN1ObjectIdentifier> oidMap = new HashMap<String, ASN1ObjectIdentifier>();
private static final Map<ASN1ObjectIdentifier, String> publicAlgMap = new HashMap<ASN1ObjectIdentifier, String>();
static
{
// Note: AES handled inline
oidMap.put("DESEDE", OIWObjectIdentifiers.desEDE);
oidMap.put("TRIPLEDES", OIWObjectIdentifiers.desEDE);
oidMap.put("TDEA", OIWObjectIdentifiers.desEDE);
oidMap.put("HMACSHA1", PKCSObjectIdentifiers.id_hmacWithSHA1);
oidMap.put("HMACSHA224", PKCSObjectIdentifiers.id_hmacWithSHA224);
oidMap.put("HMACSHA256", PKCSObjectIdentifiers.id_hmacWithSHA256);
oidMap.put("HMACSHA384", PKCSObjectIdentifiers.id_hmacWithSHA384);
oidMap.put("HMACSHA512", PKCSObjectIdentifiers.id_hmacWithSHA512);
publicAlgMap.put(PKCSObjectIdentifiers.rsaEncryption, "RSA");
publicAlgMap.put(X9ObjectIdentifiers.id_ecPublicKey, "EC");
publicAlgMap.put(OIWObjectIdentifiers.elGamalAlgorithm, "DH");
publicAlgMap.put(PKCSObjectIdentifiers.dhKeyAgreement, "DH");
publicAlgMap.put(X9ObjectIdentifiers.id_dsa, "DSA");
}
private static String getPublicKeyAlg(ASN1ObjectIdentifier oid)
{
String algName = (String)publicAlgMap.get(oid);
if (algName != null)
{
return algName;
}
return oid.getId();
}
private final static BigInteger CERTIFICATE = BigInteger.valueOf(0);
private final static BigInteger PRIVATE_KEY = BigInteger.valueOf(1);
private final static BigInteger SECRET_KEY = BigInteger.valueOf(2);
private final static BigInteger PROTECTED_PRIVATE_KEY = BigInteger.valueOf(3);
private final static BigInteger PROTECTED_SECRET_KEY = BigInteger.valueOf(4);
private final BouncyCastleProvider provider;
private final Map<String, ObjectData> entries = new HashMap<String, ObjectData>();
private final Map<String, PrivateKey> privateKeyCache = new HashMap<String, PrivateKey>();
private AlgorithmIdentifier hmacAlgorithm;
private KeyDerivationFunc hmacPkbdAlgorithm;
private Date creationDate;
private Date lastModifiedDate;
BcFKSKeyStoreSpi(BouncyCastleProvider provider)
{
this.provider = provider;
}
public Key engineGetKey(String alias, char[] password)
throws NoSuchAlgorithmException, UnrecoverableKeyException
{
ObjectData ent = (ObjectData)entries.get(alias);
if (ent != null)
{
if (ent.getType().equals(PRIVATE_KEY) || ent.getType().equals(PROTECTED_PRIVATE_KEY))
{
PrivateKey cachedKey = (PrivateKey)privateKeyCache.get(alias);
if (cachedKey != null)
{
return cachedKey;
}
EncryptedPrivateKeyData encPrivData = EncryptedPrivateKeyData.getInstance(ent.getData());
EncryptedPrivateKeyInfo encInfo = EncryptedPrivateKeyInfo.getInstance(encPrivData.getEncryptedPrivateKeyInfo());
try
{
PrivateKeyInfo pInfo = PrivateKeyInfo.getInstance(decryptData("PRIVATE_KEY_ENCRYPTION", encInfo.getEncryptionAlgorithm(), password, encInfo.getEncryptedData()));
KeyFactory kFact;
if (provider != null)
{
kFact = KeyFactory.getInstance(pInfo.getPrivateKeyAlgorithm().getAlgorithm().getId(), provider);
}
else
{
kFact = KeyFactory.getInstance(getPublicKeyAlg(pInfo.getPrivateKeyAlgorithm().getAlgorithm()));
}
PrivateKey privateKey = kFact.generatePrivate(new PKCS8EncodedKeySpec(pInfo.getEncoded()));
// check that the key pair and the certificate public key are consistent
// TODO: new ConsistentKeyPair(engineGetCertificate(alias).getPublicKey(), privateKey);
privateKeyCache.put(alias, privateKey);
return privateKey;
}
catch (Exception e)
{
throw new UnrecoverableKeyException("BCFKS KeyStore unable to recover private key (" + alias + "): " + e.getMessage());
}
}
else if (ent.getType().equals(SECRET_KEY) || ent.getType().equals(PROTECTED_SECRET_KEY))
{
EncryptedSecretKeyData encKeyData = EncryptedSecretKeyData.getInstance(ent.getData());
try
{
SecretKeyData keyData = SecretKeyData.getInstance(decryptData("SECRET_KEY_ENCRYPTION", encKeyData.getKeyEncryptionAlgorithm(), password, encKeyData.getEncryptedKeyData()));
SecretKeyFactory kFact;
if (provider != null)
{
kFact = SecretKeyFactory.getInstance(keyData.getKeyAlgorithm().getId(), provider);
}
else
{
kFact = SecretKeyFactory.getInstance(keyData.getKeyAlgorithm().getId());
}
return kFact.generateSecret(new SecretKeySpec(keyData.getKeyBytes(), keyData.getKeyAlgorithm().getId()));
}
catch (Exception e)
{
throw new UnrecoverableKeyException("BCFKS KeyStore unable to recover secret key (" + alias + "): " + e.getMessage());
}
}
else
{
throw new UnrecoverableKeyException("BCFKS KeyStore unable to recover secret key (" + alias + "): type not recognized");
}
}
return null;
}
public Certificate[] engineGetCertificateChain(String alias)
{
ObjectData ent = (ObjectData)entries.get(alias);
if (ent != null)
{
if (ent.getType().equals(PRIVATE_KEY) || ent.getType().equals(PROTECTED_PRIVATE_KEY))
{
EncryptedPrivateKeyData encPrivData = EncryptedPrivateKeyData.getInstance(ent.getData());
org.bouncycastle.asn1.x509.Certificate[] certificates = encPrivData.getCertificateChain();
Certificate[] chain = new X509Certificate[certificates.length];
for (int i = 0; i != chain.length; i++)
{
chain[i] = decodeCertificate(certificates[i]);
}
return chain;
}
}
return null;
}
public Certificate engineGetCertificate(String s)
{
ObjectData ent = (ObjectData)entries.get(s);
if (ent != null)
{
if (ent.getType().equals(PRIVATE_KEY) || ent.getType().equals(PROTECTED_PRIVATE_KEY))
{
EncryptedPrivateKeyData encPrivData = EncryptedPrivateKeyData.getInstance(ent.getData());
org.bouncycastle.asn1.x509.Certificate[] certificates = encPrivData.getCertificateChain();
return decodeCertificate(certificates[0]);
}
else if (ent.getType().equals(CERTIFICATE))
{
return decodeCertificate(ent.getData());
}
}
return null;
}
private Certificate decodeCertificate(Object cert)
{
if (provider != null)
{
try
{
CertificateFactory certFact = CertificateFactory.getInstance("X.509", provider);
return certFact.generateCertificate(new ByteArrayInputStream(org.bouncycastle.asn1.x509.Certificate.getInstance(cert).getEncoded()));
}
catch (Exception e)
{
return null;
}
}
else
{
try
{
CertificateFactory certFact = CertificateFactory.getInstance("X.509");
return certFact.generateCertificate(new ByteArrayInputStream(org.bouncycastle.asn1.x509.Certificate.getInstance(cert).getEncoded()));
}
catch (Exception e)
{
return null;
}
}
}
public Date engineGetCreationDate(String s)
{
ObjectData ent = (ObjectData)entries.get(s);
if (ent != null)
{
try
{
// we return last modified as it represents date current state of entry was created
return ent.getLastModifiedDate().getDate();
}
catch (ParseException e)
{
return new Date(); // it's here, but...
}
}
return null;
}
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
throws KeyStoreException
{
Date creationDate = new Date();
Date lastEditDate = creationDate;
ObjectData entry = (ObjectData)entries.get(alias);
if (entry != null)
{
creationDate = extractCreationDate(entry, creationDate);
}
privateKeyCache.remove(alias);
if (key instanceof PrivateKey)
{
if (chain == null)
{
throw new KeyStoreException("BCFKS KeyStore requires a certificate chain for private key storage.");
}
try
{
// check that the key pair and the certificate public are consistent
// TODO: new ConsistentKeyPair(chain[0].getPublicKey(), (PrivateKey)key);
byte[] encodedKey = key.getEncoded();
KeyDerivationFunc pbkdAlgId = generatePkbdAlgorithmIdentifier(256 / 8);
byte[] keyBytes = generateKey(pbkdAlgId, "PRIVATE_KEY_ENCRYPTION", ((password != null) ? password : new char[0]));
Cipher c;
if (provider == null)
{
c = Cipher.getInstance("AES/CCM/NoPadding");
}
else
{
c = Cipher.getInstance("AES/CCM/NoPadding", provider);
}
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));
byte[] encryptedKey = c.doFinal(encodedKey);
AlgorithmParameters algParams = c.getParameters();
PBES2Parameters pbeParams = new PBES2Parameters(pbkdAlgId, new EncryptionScheme(NISTObjectIdentifiers.id_aes256_CCM, CCMParameters.getInstance(algParams.getEncoded())));
EncryptedPrivateKeyInfo keyInfo = new EncryptedPrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, pbeParams), encryptedKey);
EncryptedPrivateKeyData keySeq = createPrivateKeySequence(keyInfo, chain);
entries.put(alias, new ObjectData(PRIVATE_KEY, alias, creationDate, lastEditDate, keySeq.getEncoded(), null));
}
catch (Exception e)
{
throw new ExtKeyStoreException("BCFKS KeyStore exception storing private key: " + e.toString(), e);
}
}
else if (key instanceof SecretKey)
{
if (chain != null)
{
throw new KeyStoreException("BCFKS KeyStore cannot store certificate chain with secret key.");
}
try
{
byte[] encodedKey = key.getEncoded();
KeyDerivationFunc pbkdAlgId = generatePkbdAlgorithmIdentifier(256 / 8);
byte[] keyBytes = generateKey(pbkdAlgId, "SECRET_KEY_ENCRYPTION", ((password != null) ? password : new char[0]));
Cipher c;
if (provider == null)
{
c = Cipher.getInstance("AES/CCM/NoPadding");
}
else
{
c = Cipher.getInstance("AES/CCM/NoPadding", provider);
}
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));
String keyAlg = Strings.toUpperCase(key.getAlgorithm());
byte[] encryptedKey;
if (keyAlg.indexOf("AES") > -1)
{
encryptedKey = c.doFinal(new SecretKeyData(NISTObjectIdentifiers.aes, encodedKey).getEncoded());
}
else
{
ASN1ObjectIdentifier algOid = (ASN1ObjectIdentifier)oidMap.get(keyAlg);
if (algOid != null)
{
encryptedKey = c.doFinal(new SecretKeyData(algOid, encodedKey).getEncoded());
}
else
{
throw new KeyStoreException("BCFKS KeyStore cannot recognize secret key (" + keyAlg + ") for storage.");
}
}
AlgorithmParameters algParams = c.getParameters();
PBES2Parameters pbeParams = new PBES2Parameters(pbkdAlgId, new EncryptionScheme(NISTObjectIdentifiers.id_aes256_CCM, CCMParameters.getInstance(algParams.getEncoded())));
EncryptedSecretKeyData keyData = new EncryptedSecretKeyData(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, pbeParams), encryptedKey);
entries.put(alias, new ObjectData(SECRET_KEY, alias, creationDate, lastEditDate, keyData.getEncoded(), null));
}
catch (Exception e)
{
throw new ExtKeyStoreException("BCFKS KeyStore exception storing private key: " + e.toString(), e);
}
}
else
{
throw new KeyStoreException("BCFKS KeyStore unable to recognize key.");
}
lastModifiedDate = lastEditDate;
}
private SecureRandom getDefaultSecureRandom()
{
return new SecureRandom();
}
private EncryptedPrivateKeyData createPrivateKeySequence(EncryptedPrivateKeyInfo encryptedPrivateKeyInfo, Certificate[] chain)
throws CertificateEncodingException
{
org.bouncycastle.asn1.x509.Certificate[] certChain = new org.bouncycastle.asn1.x509.Certificate[chain.length];
for (int i = 0; i != chain.length; i++)
{
certChain[i] = org.bouncycastle.asn1.x509.Certificate.getInstance(chain[i].getEncoded());
}
return new EncryptedPrivateKeyData(encryptedPrivateKeyInfo, certChain);
}
public void engineSetKeyEntry(String alias, byte[] keyBytes, Certificate[] chain)
throws KeyStoreException
{
Date creationDate = new Date();
Date lastEditDate = creationDate;
ObjectData entry = (ObjectData)entries.get(alias);
if (entry != null)
{
creationDate = extractCreationDate(entry, creationDate);
}
if (chain != null)
{
EncryptedPrivateKeyInfo encInfo;
try
{
encInfo = EncryptedPrivateKeyInfo.getInstance(keyBytes);
}
catch (Exception e)
{
throw new ExtKeyStoreException("BCFKS KeyStore private key encoding must be an EncryptedPrivateKeyInfo.", e);
}
try
{
privateKeyCache.remove(alias);
entries.put(alias, new ObjectData(PROTECTED_PRIVATE_KEY, alias, creationDate, lastEditDate, createPrivateKeySequence(encInfo, chain).getEncoded(), null));
}
catch (Exception e)
{
throw new ExtKeyStoreException("BCFKS KeyStore exception storing protected private key: " + e.toString(), e);
}
}
else
{
try
{
entries.put(alias, new ObjectData(PROTECTED_SECRET_KEY, alias, creationDate, lastEditDate, keyBytes, null));
}
catch (Exception e)
{
throw new ExtKeyStoreException("BCFKS KeyStore exception storing protected private key: " + e.toString(), e);
}
}
lastModifiedDate = lastEditDate;
}
public void engineSetCertificateEntry(String alias, Certificate certificate)
throws KeyStoreException
{
ObjectData entry = (ObjectData)entries.get(alias);
Date creationDate = new Date();
Date lastEditDate = creationDate;
if (entry != null)
{
if (!entry.getType().equals(CERTIFICATE))
{
throw new KeyStoreException("BCFKS KeyStore already has a key entry with alias " + alias);
}
creationDate = extractCreationDate(entry, creationDate);
}
try
{
entries.put(alias, new ObjectData(CERTIFICATE, alias, creationDate, lastEditDate, certificate.getEncoded(), null));
}
catch (CertificateEncodingException e)
{
throw new ExtKeyStoreException("BCFKS KeyStore unable to handle certificate: " + e.getMessage(), e);
}
lastModifiedDate = lastEditDate;
}
private Date extractCreationDate(ObjectData entry, Date creationDate)
{
try
{
creationDate = entry.getCreationDate().getDate();
}
catch (ParseException e)
{
// this should never happen, if it does we'll leave creation date unmodified and hope for the best.
}
return creationDate;
}
public void engineDeleteEntry(String alias)
throws KeyStoreException
{
ObjectData entry = (ObjectData)entries.get(alias);
if (entry == null)
{
return;
}
privateKeyCache.remove(alias);
entries.remove(alias);
lastModifiedDate = new Date();
}
public Enumeration<String> engineAliases()
{
final Iterator<String> it = new HashSet(entries.keySet()).iterator();
return new Enumeration()
{
public boolean hasMoreElements()
{
return it.hasNext();
}
public Object nextElement()
{
return it.next();
}
};
}
public boolean engineContainsAlias(String alias)
{
if (alias == null)
{
throw new NullPointerException("alias value is null");
}
return entries.containsKey(alias);
}
public int engineSize()
{
return entries.size();
}
public boolean engineIsKeyEntry(String alias)
{
ObjectData ent = (ObjectData)entries.get(alias);
if (ent != null)
{
BigInteger entryType = ent.getType();
return entryType.equals(PRIVATE_KEY) || entryType.equals(SECRET_KEY)
|| entryType.equals(PROTECTED_PRIVATE_KEY) || entryType.equals(PROTECTED_SECRET_KEY);
}
return false;
}
public boolean engineIsCertificateEntry(String alias)
{
ObjectData ent = (ObjectData)entries.get(alias);
if (ent != null)
{
return ent.getType().equals(CERTIFICATE);
}
return false;
}
public String engineGetCertificateAlias(Certificate certificate)
{
if (certificate == null)
{
return null;
}
byte[] encodedCert;
try
{
encodedCert = certificate.getEncoded();
}
catch (CertificateEncodingException e)
{
return null;
}
for (Iterator<String> it = entries.keySet().iterator(); it.hasNext(); )
{
String alias = (String)it.next();
ObjectData ent = (ObjectData)entries.get(alias);
if (ent.getType().equals(CERTIFICATE))
{
if (Arrays.areEqual(ent.getData(), encodedCert))
{
return alias;
}
}
else if (ent.getType().equals(PRIVATE_KEY) || ent.getType().equals(PROTECTED_PRIVATE_KEY))
{
try
{
EncryptedPrivateKeyData encPrivData = EncryptedPrivateKeyData.getInstance(ent.getData());
if (Arrays.areEqual(encPrivData.getCertificateChain()[0].toASN1Primitive().getEncoded(), encodedCert))
{
return alias;
}
}
catch (IOException e)
{
// ignore - this should never happen
}
}
}
return null;
}
private byte[] generateKey(KeyDerivationFunc pbkdAlgorithm, String purpose, char[] password)
throws IOException
{
byte[] encPassword = PBEParametersGenerator.PKCS12PasswordToBytes(password);
byte[] differentiator = PBEParametersGenerator.PKCS12PasswordToBytes(purpose.toCharArray());
int keySizeInBytes;
PKCS5S2ParametersGenerator pGen = new PKCS5S2ParametersGenerator(new SHA512Digest());
if (pbkdAlgorithm.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBKDF2))
{
PBKDF2Params pbkdf2Params = PBKDF2Params.getInstance(pbkdAlgorithm.getParameters());
if (pbkdf2Params.getPrf().getAlgorithm().equals(PKCSObjectIdentifiers.id_hmacWithSHA512))
{
pGen.init(Arrays.concatenate(encPassword, differentiator), pbkdf2Params.getSalt(), pbkdf2Params.getIterationCount().intValue());
keySizeInBytes = pbkdf2Params.getKeyLength().intValue();
}
else
{
throw new IOException("BCFKS KeyStore: unrecognized MAC PBKD PRF.");
}
}
else
{
throw new IOException("BCFKS KeyStore: unrecognized MAC PBKD.");
}
return ((KeyParameter)pGen.generateDerivedParameters(keySizeInBytes * 8)).getKey();
}
private void verifyMac(byte[] content, PbkdMacIntegrityCheck integrityCheck, char[] password)
throws NoSuchAlgorithmException, IOException
{
byte[] check = calculateMac(content, integrityCheck.getMacAlgorithm(), integrityCheck.getPbkdAlgorithm(), password);
if (!Arrays.constantTimeAreEqual(check, integrityCheck.getMac()))
{
throw new IOException("BCFKS KeyStore corrupted: MAC calculation failed.");
}
}
private byte[] calculateMac(byte[] content, AlgorithmIdentifier algorithm, KeyDerivationFunc pbkdAlgorithm, char[] password)
throws NoSuchAlgorithmException, IOException
{
String algorithmId = algorithm.getAlgorithm().getId();
Mac mac;
if (provider != null)
{
mac = Mac.getInstance(algorithmId, provider);
}
else
{
mac = Mac.getInstance(algorithmId);
}
try
{
mac.init(new SecretKeySpec(generateKey(pbkdAlgorithm, "INTEGRITY_CHECK", ((password != null) ? password : new char[0])), algorithmId));
}
catch (InvalidKeyException e)
{
throw new IOException("Cannot set up MAC calculation: " + e.getMessage());
}
return mac.doFinal(content);
}
public void engineStore(OutputStream outputStream, char[] password)
throws IOException, NoSuchAlgorithmException, CertificateException
{
ObjectData[] dataArray = (ObjectData[])entries.values().toArray(new ObjectData[entries.size()]);
KeyDerivationFunc pbkdAlgId = generatePkbdAlgorithmIdentifier(256 / 8);
byte[] keyBytes = generateKey(pbkdAlgId, "STORE_ENCRYPTION", ((password != null) ? password : new char[0]));
ObjectStoreData storeData = new ObjectStoreData(hmacAlgorithm, creationDate, lastModifiedDate, new ObjectDataSequence(dataArray), null);
EncryptedObjectStoreData encStoreData;
try
{
Cipher c;
if (provider == null)
{
c = Cipher.getInstance("AES/CCM/NoPadding");
}
else
{
c = Cipher.getInstance("AES/CCM/NoPadding", provider);
}
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));
byte[] encOut = c.doFinal(storeData.getEncoded());
AlgorithmParameters algorithmParameters = c.getParameters();
PBES2Parameters pbeParams = new PBES2Parameters(pbkdAlgId, new EncryptionScheme(NISTObjectIdentifiers.id_aes256_CCM, CCMParameters.getInstance(algorithmParameters.getEncoded())));
encStoreData = new EncryptedObjectStoreData(new AlgorithmIdentifier(PKCSObjectIdentifiers.id_PBES2, pbeParams), encOut);
}
catch (NoSuchPaddingException e)
{
throw new NoSuchAlgorithmException(e.toString());
}
catch (BadPaddingException e)
{
throw new IOException(e.toString());
}
catch (IllegalBlockSizeException e)
{
throw new IOException(e.toString());
}
catch (InvalidKeyException e)
{
throw new IOException(e.toString());
}
// update the salt
PBKDF2Params pbkdf2Params = PBKDF2Params.getInstance(hmacPkbdAlgorithm.getParameters());
byte[] pbkdSalt = new byte[pbkdf2Params.getSalt().length];
getDefaultSecureRandom().nextBytes(pbkdSalt);
hmacPkbdAlgorithm = new KeyDerivationFunc(hmacPkbdAlgorithm.getAlgorithm(), new PBKDF2Params(pbkdSalt, pbkdf2Params.getIterationCount().intValue(), pbkdf2Params.getKeyLength().intValue(), pbkdf2Params.getPrf()));
byte[] mac = calculateMac(encStoreData.getEncoded(), hmacAlgorithm, hmacPkbdAlgorithm, password);
ObjectStore store = new ObjectStore(encStoreData, new ObjectStoreIntegrityCheck(new PbkdMacIntegrityCheck(hmacAlgorithm, hmacPkbdAlgorithm, mac)));
outputStream.write(store.getEncoded());
outputStream.flush();
}
public void engineLoad(InputStream inputStream, char[] password)
throws IOException, NoSuchAlgorithmException, CertificateException
{
// reset any current values
entries.clear();
privateKeyCache.clear();
lastModifiedDate = creationDate = null;
hmacAlgorithm = null;
if (inputStream == null)
{
// initialise defaults
lastModifiedDate = creationDate = new Date();
hmacAlgorithm = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA512, DERNull.INSTANCE);
hmacPkbdAlgorithm = generatePkbdAlgorithmIdentifier(512 / 8);
return;
}
ASN1InputStream aIn = new ASN1InputStream(inputStream);
ObjectStore store = ObjectStore.getInstance(aIn.readObject());
ObjectStoreIntegrityCheck integrityCheck = store.getIntegrityCheck();
if (integrityCheck.getType() == ObjectStoreIntegrityCheck.PBKD_MAC_CHECK)
{
PbkdMacIntegrityCheck pbkdMacIntegrityCheck = PbkdMacIntegrityCheck.getInstance(integrityCheck.getIntegrityCheck());
hmacAlgorithm = pbkdMacIntegrityCheck.getMacAlgorithm();
hmacPkbdAlgorithm = pbkdMacIntegrityCheck.getPbkdAlgorithm();
verifyMac(store.getStoreData().toASN1Primitive().getEncoded(), pbkdMacIntegrityCheck, password);
}
else
{
throw new IOException("BCFKS KeyStore unable to recognize integrity check.");
}
ASN1Encodable sData = store.getStoreData();
ObjectStoreData storeData;
if (sData instanceof EncryptedObjectStoreData)
{
EncryptedObjectStoreData encryptedStoreData = (EncryptedObjectStoreData)sData;
AlgorithmIdentifier protectAlgId = encryptedStoreData.getEncryptionAlgorithm();
storeData = ObjectStoreData.getInstance(decryptData("STORE_ENCRYPTION", protectAlgId, password, encryptedStoreData.getEncryptedContent().getOctets()));
}
else
{
storeData = ObjectStoreData.getInstance(sData);
}
try
{
creationDate = storeData.getCreationDate().getDate();
lastModifiedDate = storeData.getLastModifiedDate().getDate();
}
catch (ParseException e)
{
throw new IOException("BCFKS KeyStore unable to parse store data information.");
}
if (!storeData.getIntegrityAlgorithm().equals(hmacAlgorithm))
{
throw new IOException("BCFKS KeyStore storeData integrity algorithm does not match store integrity algorithm.");
}
for (Iterator it = storeData.getObjectDataSequence().iterator(); it.hasNext(); )
{
ObjectData objData = ObjectData.getInstance(it.next());
entries.put(objData.getIdentifier(), objData);
}
}
private byte[] decryptData(String purpose, AlgorithmIdentifier protectAlgId, char[] password, byte[] encryptedData)
throws IOException
{
if (!protectAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBES2))
{
throw new IOException("BCFKS KeyStore cannot recognize protection algorithm.");
}
PBES2Parameters pbes2Parameters = PBES2Parameters.getInstance(protectAlgId.getParameters());
EncryptionScheme algId = pbes2Parameters.getEncryptionScheme();
if (!algId.getAlgorithm().equals(NISTObjectIdentifiers.id_aes256_CCM))
{
throw new IOException("BCFKS KeyStore cannot recognize protection encryption algorithm.");
}
try
{
CCMParameters ccmParameters = CCMParameters.getInstance(algId.getParameters());
Cipher c;
AlgorithmParameters algParams;
if (provider == null)
{
c = Cipher.getInstance("AES/CCM/NoPadding");
algParams = AlgorithmParameters.getInstance("CCM");
}
else
{
c = Cipher.getInstance("AES/CCM/NoPadding", provider);
algParams = AlgorithmParameters.getInstance("CCM", provider);
}
algParams.init(ccmParameters.getEncoded());
byte[] keyBytes = generateKey(pbes2Parameters.getKeyDerivationFunc(), purpose, ((password != null) ? password : new char[0]));
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), algParams);
byte[] rv = c.doFinal(encryptedData);
return rv;
}
catch (Exception e)
{
throw new IOException(e.toString());
}
}
private KeyDerivationFunc generatePkbdAlgorithmIdentifier(int keySizeInBytes)
{
byte[] pbkdSalt = new byte[512 / 8];
getDefaultSecureRandom().nextBytes(pbkdSalt);
return new KeyDerivationFunc(PKCSObjectIdentifiers.id_PBKDF2, new PBKDF2Params(pbkdSalt, 1024, keySizeInBytes, new AlgorithmIdentifier(PKCSObjectIdentifiers.id_hmacWithSHA512, DERNull.INSTANCE)));
}
public static class Std
extends BcFKSKeyStoreSpi
{
public Std()
{
super(new BouncyCastleProvider());
}
}
public static class Def
extends BcFKSKeyStoreSpi
{
public Def()
{
super(null);
}
}
private static class ExtKeyStoreException
extends KeyStoreException
{
private final Throwable cause;
ExtKeyStoreException(String msg, Throwable cause)
{
super(msg);
this.cause = cause;
}
public Throwable getCause()
{
return cause;
}
}
}