1
package org.bouncycastle.jce.provider;
3
import java.io.ByteArrayInputStream;
4
import java.io.ByteArrayOutputStream;
5
import java.io.DataInputStream;
6
import java.io.DataOutputStream;
7
import java.io.IOException;
8
import java.io.InputStream;
9
import java.io.OutputStream;
10
import java.security.Key;
11
import java.security.KeyFactory;
12
import java.security.KeyStoreException;
13
import java.security.KeyStoreSpi;
14
import java.security.NoSuchAlgorithmException;
15
import java.security.NoSuchProviderException;
16
import java.security.PrivateKey;
17
import java.security.PublicKey;
18
import java.security.SecureRandom;
19
import java.security.UnrecoverableKeyException;
20
import java.security.cert.Certificate;
21
import java.security.cert.CertificateEncodingException;
22
import java.security.cert.CertificateException;
23
import java.security.cert.CertificateFactory;
24
import java.security.spec.KeySpec;
25
import java.security.spec.PKCS8EncodedKeySpec;
26
import java.security.spec.X509EncodedKeySpec;
27
import java.util.Date;
28
import java.util.Enumeration;
29
import java.util.Hashtable;
31
import javax.crypto.Cipher;
32
import javax.crypto.CipherInputStream;
33
import javax.crypto.CipherOutputStream;
34
import javax.crypto.SecretKeyFactory;
35
import javax.crypto.spec.PBEKeySpec;
36
import javax.crypto.spec.PBEParameterSpec;
37
import javax.crypto.spec.SecretKeySpec;
39
import org.bouncycastle.crypto.CipherParameters;
40
import org.bouncycastle.crypto.Digest;
41
import org.bouncycastle.crypto.PBEParametersGenerator;
42
import org.bouncycastle.crypto.digests.SHA1Digest;
43
import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
44
import org.bouncycastle.crypto.io.DigestInputStream;
45
import org.bouncycastle.crypto.io.DigestOutputStream;
46
import org.bouncycastle.crypto.io.MacInputStream;
47
import org.bouncycastle.crypto.io.MacOutputStream;
48
import org.bouncycastle.crypto.macs.HMac;
49
import org.bouncycastle.jce.interfaces.BCKeyStore;
50
import org.bouncycastle.util.Arrays;
51
import org.bouncycastle.util.io.Streams;
53
public class JDKKeyStore
57
private static final int STORE_VERSION = 1;
59
private static final int STORE_SALT_SIZE = 20;
60
private static final String STORE_CIPHER = "PBEWithSHAAndTwofish-CBC";
62
private static final int KEY_SALT_SIZE = 20;
63
private static final int MIN_ITERATIONS = 1024;
65
private static final String KEY_CIPHER = "PBEWithSHAAnd3-KeyTripleDES-CBC";
68
// generic object types
70
static final int NULL = 0;
71
static final int CERTIFICATE = 1;
72
static final int KEY = 2;
73
static final int SECRET = 3;
74
static final int SEALED = 4;
79
static final int KEY_PRIVATE = 0;
80
static final int KEY_PUBLIC = 1;
81
static final int KEY_SECRET = 2;
83
protected Hashtable table = new Hashtable();
85
protected SecureRandom random = new SecureRandom();
91
private class StoreEntry
96
Certificate[] certChain;
97
Date date = new Date();
103
this.type = CERTIFICATE;
106
this.certChain = null;
112
Certificate[] certChain)
117
this.certChain = certChain;
124
Certificate[] certChain)
129
this.certChain = certChain;
131
byte[] salt = new byte[KEY_SALT_SIZE];
133
random.setSeed(System.currentTimeMillis());
134
random.nextBytes(salt);
136
int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
139
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
140
DataOutputStream dOut = new DataOutputStream(bOut);
142
dOut.writeInt(salt.length);
144
dOut.writeInt(iterationCount);
146
Cipher cipher = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
147
CipherOutputStream cOut = new CipherOutputStream(dOut, cipher);
149
dOut = new DataOutputStream(cOut);
151
encodeKey(key, dOut);
155
obj = bOut.toByteArray();
175
Certificate[] certChain)
181
this.certChain = certChain;
201
throws NoSuchAlgorithmException, UnrecoverableKeyException
203
if (password == null || password.length == 0)
205
if (obj instanceof Key)
213
ByteArrayInputStream bIn = new ByteArrayInputStream((byte[])obj);
214
DataInputStream dIn = new DataInputStream(bIn);
218
byte[] salt = new byte[dIn.readInt()];
222
int iterationCount = dIn.readInt();
224
Cipher cipher = makePBECipher(KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
226
CipherInputStream cIn = new CipherInputStream(dIn, cipher);
230
return decodeKey(new DataInputStream(cIn));
234
bIn = new ByteArrayInputStream((byte[])obj);
235
dIn = new DataInputStream(bIn);
237
salt = new byte[dIn.readInt()];
241
iterationCount = dIn.readInt();
243
cipher = makePBECipher("Broken" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
245
cIn = new CipherInputStream(dIn, cipher);
251
k = decodeKey(new DataInputStream(cIn));
255
bIn = new ByteArrayInputStream((byte[])obj);
256
dIn = new DataInputStream(bIn);
258
salt = new byte[dIn.readInt()];
262
iterationCount = dIn.readInt();
264
cipher = makePBECipher("Old" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
266
cIn = new CipherInputStream(dIn, cipher);
268
k = decodeKey(new DataInputStream(cIn));
272
// reencrypt key with correct cipher.
276
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
277
DataOutputStream dOut = new DataOutputStream(bOut);
279
dOut.writeInt(salt.length);
281
dOut.writeInt(iterationCount);
283
Cipher out = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
284
CipherOutputStream cOut = new CipherOutputStream(dOut, out);
286
dOut = new DataOutputStream(cOut);
292
obj = bOut.toByteArray();
298
throw new UnrecoverableKeyException("no match");
304
throw new UnrecoverableKeyException("no match");
309
throw new RuntimeException("forget something!");
311
// if we get to here key was saved as byte data, which
312
// according to the docs means it must be a private key
313
// in EncryptedPrivateKeyInfo (PKCS8 format), later...
318
Certificate[] getCertificateChain()
329
private void encodeCertificate(
331
DataOutputStream dOut)
336
byte[] cEnc = cert.getEncoded();
338
dOut.writeUTF(cert.getType());
339
dOut.writeInt(cEnc.length);
342
catch (CertificateEncodingException ex)
344
throw new IOException(ex.toString());
348
private Certificate decodeCertificate(
352
String type = dIn.readUTF();
353
byte[] cEnc = new byte[dIn.readInt()];
359
CertificateFactory cFact = CertificateFactory.getInstance(type, BouncyCastleProvider.PROVIDER_NAME);
360
ByteArrayInputStream bIn = new ByteArrayInputStream(cEnc);
362
return cFact.generateCertificate(bIn);
364
catch (NoSuchProviderException ex)
366
throw new IOException(ex.toString());
368
catch (CertificateException ex)
370
throw new IOException(ex.toString());
374
private void encodeKey(
376
DataOutputStream dOut)
379
byte[] enc = key.getEncoded();
381
if (key instanceof PrivateKey)
383
dOut.write(KEY_PRIVATE);
385
else if (key instanceof PublicKey)
387
dOut.write(KEY_PUBLIC);
391
dOut.write(KEY_SECRET);
394
dOut.writeUTF(key.getFormat());
395
dOut.writeUTF(key.getAlgorithm());
396
dOut.writeInt(enc.length);
400
private Key decodeKey(
404
int keyType = dIn.read();
405
String format = dIn.readUTF();
406
String algorithm = dIn.readUTF();
407
byte[] enc = new byte[dIn.readInt()];
412
if (format.equals("PKCS#8") || format.equals("PKCS8"))
414
spec = new PKCS8EncodedKeySpec(enc);
416
else if (format.equals("X.509") || format.equals("X509"))
418
spec = new X509EncodedKeySpec(enc);
420
else if (format.equals("RAW"))
422
return new SecretKeySpec(enc, algorithm);
426
throw new IOException("Key format " + format + " not recognised!");
434
return KeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generatePrivate(spec);
436
return KeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generatePublic(spec);
438
return SecretKeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generateSecret(spec);
440
throw new IOException("Key type " + keyType + " not recognised!");
445
throw new IOException("Exception creating key: " + e.toString());
449
protected Cipher makePBECipher(
459
PBEKeySpec pbeSpec = new PBEKeySpec(password);
460
SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
461
PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount);
463
Cipher cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
465
cipher.init(mode, keyFact.generateSecret(pbeSpec), defParams);
471
throw new IOException("Error initialising store of key store: " + e);
475
public void setRandom(
481
public Enumeration engineAliases()
486
public boolean engineContainsAlias(
489
return (table.get(alias) != null);
492
public void engineDeleteEntry(
494
throws KeyStoreException
496
Object entry = table.get(alias);
500
throw new KeyStoreException("no such entry as " + alias);
506
public Certificate engineGetCertificate(
509
StoreEntry entry = (StoreEntry)table.get(alias);
513
if (entry.getType() == CERTIFICATE)
515
return (Certificate)entry.getObject();
519
Certificate[] chain = entry.getCertificateChain();
531
public String engineGetCertificateAlias(
534
Enumeration e = table.elements();
535
while (e.hasMoreElements())
537
StoreEntry entry = (StoreEntry)e.nextElement();
539
if (entry.getObject() instanceof Certificate)
541
Certificate c = (Certificate)entry.getObject();
545
return entry.getAlias();
550
Certificate[] chain = entry.getCertificateChain();
552
if (chain != null && chain[0].equals(cert))
554
return entry.getAlias();
562
public Certificate[] engineGetCertificateChain(
565
StoreEntry entry = (StoreEntry)table.get(alias);
569
return entry.getCertificateChain();
575
public Date engineGetCreationDate(String alias)
577
StoreEntry entry = (StoreEntry)table.get(alias);
581
return entry.getDate();
587
public Key engineGetKey(
590
throws NoSuchAlgorithmException, UnrecoverableKeyException
592
StoreEntry entry = (StoreEntry)table.get(alias);
594
if (entry == null || entry.getType() == CERTIFICATE)
599
return (Key)entry.getObject(password);
602
public boolean engineIsCertificateEntry(
605
StoreEntry entry = (StoreEntry)table.get(alias);
607
if (entry != null && entry.getType() == CERTIFICATE)
615
public boolean engineIsKeyEntry(
618
StoreEntry entry = (StoreEntry)table.get(alias);
620
if (entry != null && entry.getType() != CERTIFICATE)
628
public void engineSetCertificateEntry(
631
throws KeyStoreException
633
StoreEntry entry = (StoreEntry)table.get(alias);
635
if (entry != null && entry.getType() != CERTIFICATE)
637
throw new KeyStoreException("key store already has a key entry with alias " + alias);
640
table.put(alias, new StoreEntry(alias, cert));
643
public void engineSetKeyEntry(
647
throws KeyStoreException
649
table.put(alias, new StoreEntry(alias, key, chain));
652
public void engineSetKeyEntry(
657
throws KeyStoreException
659
if ((key instanceof PrivateKey) && (chain == null))
661
throw new KeyStoreException("no certificate chain for private key");
666
table.put(alias, new StoreEntry(alias, key, password, chain));
670
throw new KeyStoreException(e.toString());
674
public int engineSize()
679
protected void loadStore(
683
DataInputStream dIn = new DataInputStream(in);
684
int type = dIn.read();
688
String alias = dIn.readUTF();
689
Date date = new Date(dIn.readLong());
690
int chainLength = dIn.readInt();
691
Certificate[] chain = null;
693
if (chainLength != 0)
695
chain = new Certificate[chainLength];
697
for (int i = 0; i != chainLength; i++)
699
chain[i] = decodeCertificate(dIn);
706
Certificate cert = decodeCertificate(dIn);
708
table.put(alias, new StoreEntry(alias, date, CERTIFICATE, cert));
711
Key key = decodeKey(dIn);
712
table.put(alias, new StoreEntry(alias, date, KEY, key, chain));
716
byte[] b = new byte[dIn.readInt()];
719
table.put(alias, new StoreEntry(alias, date, type, b, chain));
722
throw new RuntimeException("Unknown object type in store.");
729
protected void saveStore(
733
Enumeration e = table.elements();
734
DataOutputStream dOut = new DataOutputStream(out);
736
while (e.hasMoreElements())
738
StoreEntry entry = (StoreEntry)e.nextElement();
740
dOut.write(entry.getType());
741
dOut.writeUTF(entry.getAlias());
742
dOut.writeLong(entry.getDate().getTime());
744
Certificate[] chain = entry.getCertificateChain();
751
dOut.writeInt(chain.length);
752
for (int i = 0; i != chain.length; i++)
754
encodeCertificate(chain[i], dOut);
758
switch (entry.getType())
761
encodeCertificate((Certificate)entry.getObject(), dOut);
764
encodeKey((Key)entry.getObject(), dOut);
768
byte[] b = (byte[])entry.getObject();
770
dOut.writeInt(b.length);
774
throw new RuntimeException("Unknown object type in store.");
781
public void engineLoad(
788
if (stream == null) // just initialising
793
DataInputStream dIn = new DataInputStream(stream);
794
int version = dIn.readInt();
796
if (version != STORE_VERSION)
800
throw new IOException("Wrong version of key store.");
804
byte[] salt = new byte[dIn.readInt()];
808
int iterationCount = dIn.readInt();
811
// we only do an integrity check if the password is provided.
813
HMac hMac = new HMac(new SHA1Digest());
814
if (password != null && password.length != 0)
816
byte[] passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password);
818
PBEParametersGenerator pbeGen = new PKCS12ParametersGenerator(new SHA1Digest());
819
pbeGen.init(passKey, salt, iterationCount);
820
CipherParameters macParams = pbeGen.generateDerivedMacParameters(hMac.getMacSize());
821
Arrays.fill(passKey, (byte)0);
823
hMac.init(macParams);
824
MacInputStream mIn = new MacInputStream(dIn, hMac);
828
// Finalise our mac calculation
829
byte[] mac = new byte[hMac.getMacSize()];
830
hMac.doFinal(mac, 0);
832
// TODO Should this actually be reading the remainder of the stream?
833
// Read the original mac from the stream
834
byte[] oldMac = new byte[hMac.getMacSize()];
835
dIn.readFully(oldMac);
837
if (!Arrays.constantTimeAreEqual(mac, oldMac))
840
throw new IOException("KeyStore integrity check failed.");
847
// TODO Should this actually be reading the remainder of the stream?
848
// Parse the original mac from the stream too
849
byte[] oldMac = new byte[hMac.getMacSize()];
850
dIn.readFully(oldMac);
855
public void engineStore(OutputStream stream, char[] password)
858
DataOutputStream dOut = new DataOutputStream(stream);
859
byte[] salt = new byte[STORE_SALT_SIZE];
860
int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
862
random.nextBytes(salt);
864
dOut.writeInt(STORE_VERSION);
865
dOut.writeInt(salt.length);
867
dOut.writeInt(iterationCount);
869
HMac hMac = new HMac(new SHA1Digest());
870
MacOutputStream mOut = new MacOutputStream(dOut, hMac);
871
PBEParametersGenerator pbeGen = new PKCS12ParametersGenerator(new SHA1Digest());
872
byte[] passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password);
874
pbeGen.init(passKey, salt, iterationCount);
876
hMac.init(pbeGen.generateDerivedMacParameters(hMac.getMacSize()));
878
for (int i = 0; i != passKey.length; i++)
885
byte[] mac = new byte[hMac.getMacSize()];
887
hMac.doFinal(mac, 0);
895
* the BouncyCastle store. This wont work with the key tool as the
896
* store is stored encrypteed on disk, so the password is mandatory,
897
* however if you hard drive is in a bad part of town and you absolutely,
898
* positively, don't want nobody peeking at your things, this is the
899
* one to use, no problem! After all in a Bouncy Castle nothing can
902
* Also referred to by the alias UBER.
904
public static class BouncyCastleStore
907
public void engineLoad(
914
if (stream == null) // just initialising
919
DataInputStream dIn = new DataInputStream(stream);
920
int version = dIn.readInt();
922
if (version != STORE_VERSION)
926
throw new IOException("Wrong version of key store.");
930
byte[] salt = new byte[dIn.readInt()];
932
if (salt.length != STORE_SALT_SIZE)
934
throw new IOException("Key store corrupted.");
939
int iterationCount = dIn.readInt();
941
if ((iterationCount < 0) || (iterationCount > 4 * MIN_ITERATIONS))
943
throw new IOException("Key store corrupted.");
949
cipherAlg = "Old" + STORE_CIPHER;
953
cipherAlg = STORE_CIPHER;
956
Cipher cipher = this.makePBECipher(cipherAlg, Cipher.DECRYPT_MODE, password, salt, iterationCount);
957
CipherInputStream cIn = new CipherInputStream(dIn, cipher);
959
Digest dig = new SHA1Digest();
960
DigestInputStream dgIn = new DigestInputStream(cIn, dig);
962
this.loadStore(dgIn);
964
// Finalise our digest calculation
965
byte[] hash = new byte[dig.getDigestSize()];
966
dig.doFinal(hash, 0);
968
// TODO Should this actually be reading the remainder of the stream?
969
// Read the original digest from the stream
970
byte[] oldHash = new byte[dig.getDigestSize()];
971
Streams.readFully(cIn, oldHash);
973
if (!Arrays.constantTimeAreEqual(hash, oldHash))
976
throw new IOException("KeyStore integrity check failed.");
981
public void engineStore(OutputStream stream, char[] password)
985
DataOutputStream dOut = new DataOutputStream(stream);
986
byte[] salt = new byte[STORE_SALT_SIZE];
987
int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
989
random.nextBytes(salt);
991
dOut.writeInt(STORE_VERSION);
992
dOut.writeInt(salt.length);
994
dOut.writeInt(iterationCount);
996
cipher = this.makePBECipher(STORE_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
998
CipherOutputStream cOut = new CipherOutputStream(dOut, cipher);
999
DigestOutputStream dgOut = new DigestOutputStream(cOut, new SHA1Digest());
1001
this.saveStore(dgOut);
1003
Digest dig = dgOut.getDigest();
1004
byte[] hash = new byte[dig.getDigestSize()];
1006
dig.doFinal(hash, 0);