diff options
Diffstat (limited to 'prov/src/main/java/org/spongycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java')
-rw-r--r-- | prov/src/main/java/org/spongycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java | 1061 |
1 files changed, 1061 insertions, 0 deletions
diff --git a/prov/src/main/java/org/spongycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java b/prov/src/main/java/org/spongycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java new file mode 100644 index 00000000..a06ae391 --- /dev/null +++ b/prov/src/main/java/org/spongycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java @@ -0,0 +1,1061 @@ +package org.spongycastle.jcajce.provider.keystore.bc; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyStoreException; +import java.security.KeyStoreSpi; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +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.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.spongycastle.crypto.CipherParameters; +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.PBEParametersGenerator; +import org.spongycastle.crypto.digests.SHA1Digest; +import org.spongycastle.crypto.generators.PKCS12ParametersGenerator; +import org.spongycastle.crypto.io.DigestInputStream; +import org.spongycastle.crypto.io.DigestOutputStream; +import org.spongycastle.crypto.io.MacInputStream; +import org.spongycastle.crypto.io.MacOutputStream; +import org.spongycastle.crypto.macs.HMac; +import org.spongycastle.jce.interfaces.BCKeyStore; +import org.spongycastle.jce.provider.BouncyCastleProvider; +import org.spongycastle.util.Arrays; +import org.spongycastle.util.io.Streams; +import org.spongycastle.util.io.TeeOutputStream; + +public class BcKeyStoreSpi + extends KeyStoreSpi + implements BCKeyStore +{ + private static final int STORE_VERSION = 2; + + private static final int STORE_SALT_SIZE = 20; + private static final String STORE_CIPHER = "PBEWithSHAAndTwofish-CBC"; + + private static final int KEY_SALT_SIZE = 20; + private static final int MIN_ITERATIONS = 1024; + + private static final String KEY_CIPHER = "PBEWithSHAAnd3-KeyTripleDES-CBC"; + + // + // generic object types + // + static final int NULL = 0; + static final int CERTIFICATE = 1; + static final int KEY = 2; + static final int SECRET = 3; + static final int SEALED = 4; + + // + // key types + // + static final int KEY_PRIVATE = 0; + static final int KEY_PUBLIC = 1; + static final int KEY_SECRET = 2; + + protected Hashtable table = new Hashtable(); + + protected SecureRandom random = new SecureRandom(); + + protected int version; + + public BcKeyStoreSpi(int version) + { + this.version = version; + } + + private class StoreEntry + { + int type; + String alias; + Object obj; + Certificate[] certChain; + Date date = new Date(); + + StoreEntry( + String alias, + Certificate obj) + { + this.type = CERTIFICATE; + this.alias = alias; + this.obj = obj; + this.certChain = null; + } + + StoreEntry( + String alias, + byte[] obj, + Certificate[] certChain) + { + this.type = SECRET; + this.alias = alias; + this.obj = obj; + this.certChain = certChain; + } + + StoreEntry( + String alias, + Key key, + char[] password, + Certificate[] certChain) + throws Exception + { + this.type = SEALED; + this.alias = alias; + this.certChain = certChain; + + byte[] salt = new byte[KEY_SALT_SIZE]; + + random.setSeed(System.currentTimeMillis()); + random.nextBytes(salt); + + int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff); + + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DataOutputStream dOut = new DataOutputStream(bOut); + + dOut.writeInt(salt.length); + dOut.write(salt); + dOut.writeInt(iterationCount); + + Cipher cipher = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount); + CipherOutputStream cOut = new CipherOutputStream(dOut, cipher); + + dOut = new DataOutputStream(cOut); + + encodeKey(key, dOut); + + dOut.close(); + + obj = bOut.toByteArray(); + } + + StoreEntry( + String alias, + Date date, + int type, + Object obj) + { + this.alias = alias; + this.date = date; + this.type = type; + this.obj = obj; + } + + StoreEntry( + String alias, + Date date, + int type, + Object obj, + Certificate[] certChain) + { + this.alias = alias; + this.date = date; + this.type = type; + this.obj = obj; + this.certChain = certChain; + } + + int getType() + { + return type; + } + + String getAlias() + { + return alias; + } + + Object getObject() + { + return obj; + } + + Object getObject( + char[] password) + throws NoSuchAlgorithmException, UnrecoverableKeyException + { + if (password == null || password.length == 0) + { + if (obj instanceof Key) + { + return obj; + } + } + + if (type == SEALED) + { + ByteArrayInputStream bIn = new ByteArrayInputStream((byte[])obj); + DataInputStream dIn = new DataInputStream(bIn); + + try + { + byte[] salt = new byte[dIn.readInt()]; + + dIn.readFully(salt); + + int iterationCount = dIn.readInt(); + + Cipher cipher = makePBECipher(KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount); + + CipherInputStream cIn = new CipherInputStream(dIn, cipher); + + try + { + return decodeKey(new DataInputStream(cIn)); + } + catch (Exception x) + { + bIn = new ByteArrayInputStream((byte[])obj); + dIn = new DataInputStream(bIn); + + salt = new byte[dIn.readInt()]; + + dIn.readFully(salt); + + iterationCount = dIn.readInt(); + + cipher = makePBECipher("Broken" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount); + + cIn = new CipherInputStream(dIn, cipher); + + Key k = null; + + try + { + k = decodeKey(new DataInputStream(cIn)); + } + catch (Exception y) + { + bIn = new ByteArrayInputStream((byte[])obj); + dIn = new DataInputStream(bIn); + + salt = new byte[dIn.readInt()]; + + dIn.readFully(salt); + + iterationCount = dIn.readInt(); + + cipher = makePBECipher("Old" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount); + + cIn = new CipherInputStream(dIn, cipher); + + k = decodeKey(new DataInputStream(cIn)); + } + + // + // reencrypt key with correct cipher. + // + if (k != null) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DataOutputStream dOut = new DataOutputStream(bOut); + + dOut.writeInt(salt.length); + dOut.write(salt); + dOut.writeInt(iterationCount); + + Cipher out = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount); + CipherOutputStream cOut = new CipherOutputStream(dOut, out); + + dOut = new DataOutputStream(cOut); + + encodeKey(k, dOut); + + dOut.close(); + + obj = bOut.toByteArray(); + + return k; + } + else + { + throw new UnrecoverableKeyException("no match"); + } + } + } + catch (Exception e) + { + throw new UnrecoverableKeyException("no match"); + } + } + else + { + throw new RuntimeException("forget something!"); + // TODO + // if we get to here key was saved as byte data, which + // according to the docs means it must be a private key + // in EncryptedPrivateKeyInfo (PKCS8 format), later... + // + } + } + + Certificate[] getCertificateChain() + { + return certChain; + } + + Date getDate() + { + return date; + } + } + + private void encodeCertificate( + Certificate cert, + DataOutputStream dOut) + throws IOException + { + try + { + byte[] cEnc = cert.getEncoded(); + + dOut.writeUTF(cert.getType()); + dOut.writeInt(cEnc.length); + dOut.write(cEnc); + } + catch (CertificateEncodingException ex) + { + throw new IOException(ex.toString()); + } + } + + private Certificate decodeCertificate( + DataInputStream dIn) + throws IOException + { + String type = dIn.readUTF(); + byte[] cEnc = new byte[dIn.readInt()]; + + dIn.readFully(cEnc); + + try + { + CertificateFactory cFact = CertificateFactory.getInstance(type, BouncyCastleProvider.PROVIDER_NAME); + ByteArrayInputStream bIn = new ByteArrayInputStream(cEnc); + + return cFact.generateCertificate(bIn); + } + catch (NoSuchProviderException ex) + { + throw new IOException(ex.toString()); + } + catch (CertificateException ex) + { + throw new IOException(ex.toString()); + } + } + + private void encodeKey( + Key key, + DataOutputStream dOut) + throws IOException + { + byte[] enc = key.getEncoded(); + + if (key instanceof PrivateKey) + { + dOut.write(KEY_PRIVATE); + } + else if (key instanceof PublicKey) + { + dOut.write(KEY_PUBLIC); + } + else + { + dOut.write(KEY_SECRET); + } + + dOut.writeUTF(key.getFormat()); + dOut.writeUTF(key.getAlgorithm()); + dOut.writeInt(enc.length); + dOut.write(enc); + } + + private Key decodeKey( + DataInputStream dIn) + throws IOException + { + int keyType = dIn.read(); + String format = dIn.readUTF(); + String algorithm = dIn.readUTF(); + byte[] enc = new byte[dIn.readInt()]; + KeySpec spec; + + dIn.readFully(enc); + + if (format.equals("PKCS#8") || format.equals("PKCS8")) + { + spec = new PKCS8EncodedKeySpec(enc); + } + else if (format.equals("X.509") || format.equals("X509")) + { + spec = new X509EncodedKeySpec(enc); + } + else if (format.equals("RAW")) + { + return new SecretKeySpec(enc, algorithm); + } + else + { + throw new IOException("Key format " + format + " not recognised!"); + } + + try + { + switch (keyType) + { + case KEY_PRIVATE: + return KeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generatePrivate(spec); + case KEY_PUBLIC: + return KeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generatePublic(spec); + case KEY_SECRET: + return SecretKeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generateSecret(spec); + default: + throw new IOException("Key type " + keyType + " not recognised!"); + } + } + catch (Exception e) + { + throw new IOException("Exception creating key: " + e.toString()); + } + } + + protected Cipher makePBECipher( + String algorithm, + int mode, + char[] password, + byte[] salt, + int iterationCount) + throws IOException + { + try + { + PBEKeySpec pbeSpec = new PBEKeySpec(password); + SecretKeyFactory keyFact = SecretKeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME); + PBEParameterSpec defParams = new PBEParameterSpec(salt, iterationCount); + + Cipher cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME); + + cipher.init(mode, keyFact.generateSecret(pbeSpec), defParams); + + return cipher; + } + catch (Exception e) + { + throw new IOException("Error initialising store of key store: " + e); + } + } + + public void setRandom( + SecureRandom rand) + { + this.random = rand; + } + + public Enumeration engineAliases() + { + return table.keys(); + } + + public boolean engineContainsAlias( + String alias) + { + return (table.get(alias) != null); + } + + public void engineDeleteEntry( + String alias) + throws KeyStoreException + { + Object entry = table.get(alias); + + if (entry == null) + { + return; + } + + table.remove(alias); + } + + public Certificate engineGetCertificate( + String alias) + { + StoreEntry entry = (StoreEntry)table.get(alias); + + if (entry != null) + { + if (entry.getType() == CERTIFICATE) + { + return (Certificate)entry.getObject(); + } + else + { + Certificate[] chain = entry.getCertificateChain(); + + if (chain != null) + { + return chain[0]; + } + } + } + + return null; + } + + public String engineGetCertificateAlias( + Certificate cert) + { + Enumeration e = table.elements(); + while (e.hasMoreElements()) + { + StoreEntry entry = (StoreEntry)e.nextElement(); + + if (entry.getObject() instanceof Certificate) + { + Certificate c = (Certificate)entry.getObject(); + + if (c.equals(cert)) + { + return entry.getAlias(); + } + } + else + { + Certificate[] chain = entry.getCertificateChain(); + + if (chain != null && chain[0].equals(cert)) + { + return entry.getAlias(); + } + } + } + + return null; + } + + public Certificate[] engineGetCertificateChain( + String alias) + { + StoreEntry entry = (StoreEntry)table.get(alias); + + if (entry != null) + { + return entry.getCertificateChain(); + } + + return null; + } + + public Date engineGetCreationDate(String alias) + { + StoreEntry entry = (StoreEntry)table.get(alias); + + if (entry != null) + { + return entry.getDate(); + } + + return null; + } + + public Key engineGetKey( + String alias, + char[] password) + throws NoSuchAlgorithmException, UnrecoverableKeyException + { + StoreEntry entry = (StoreEntry)table.get(alias); + + if (entry == null || entry.getType() == CERTIFICATE) + { + return null; + } + + return (Key)entry.getObject(password); + } + + public boolean engineIsCertificateEntry( + String alias) + { + StoreEntry entry = (StoreEntry)table.get(alias); + + if (entry != null && entry.getType() == CERTIFICATE) + { + return true; + } + + return false; + } + + public boolean engineIsKeyEntry( + String alias) + { + StoreEntry entry = (StoreEntry)table.get(alias); + + if (entry != null && entry.getType() != CERTIFICATE) + { + return true; + } + + return false; + } + + public void engineSetCertificateEntry( + String alias, + Certificate cert) + throws KeyStoreException + { + StoreEntry entry = (StoreEntry)table.get(alias); + + if (entry != null && entry.getType() != CERTIFICATE) + { + throw new KeyStoreException("key store already has a key entry with alias " + alias); + } + + table.put(alias, new StoreEntry(alias, cert)); + } + + public void engineSetKeyEntry( + String alias, + byte[] key, + Certificate[] chain) + throws KeyStoreException + { + table.put(alias, new StoreEntry(alias, key, chain)); + } + + public void engineSetKeyEntry( + String alias, + Key key, + char[] password, + Certificate[] chain) + throws KeyStoreException + { + if ((key instanceof PrivateKey) && (chain == null)) + { + throw new KeyStoreException("no certificate chain for private key"); + } + + try + { + table.put(alias, new StoreEntry(alias, key, password, chain)); + } + catch (Exception e) + { + throw new KeyStoreException(e.toString()); + } + } + + public int engineSize() + { + return table.size(); + } + + protected void loadStore( + InputStream in) + throws IOException + { + DataInputStream dIn = new DataInputStream(in); + int type = dIn.read(); + + while (type > NULL) + { + String alias = dIn.readUTF(); + Date date = new Date(dIn.readLong()); + int chainLength = dIn.readInt(); + Certificate[] chain = null; + + if (chainLength != 0) + { + chain = new Certificate[chainLength]; + + for (int i = 0; i != chainLength; i++) + { + chain[i] = decodeCertificate(dIn); + } + } + + switch (type) + { + case CERTIFICATE: + Certificate cert = decodeCertificate(dIn); + + table.put(alias, new StoreEntry(alias, date, CERTIFICATE, cert)); + break; + case KEY: + Key key = decodeKey(dIn); + table.put(alias, new StoreEntry(alias, date, KEY, key, chain)); + break; + case SECRET: + case SEALED: + byte[] b = new byte[dIn.readInt()]; + + dIn.readFully(b); + table.put(alias, new StoreEntry(alias, date, type, b, chain)); + break; + default: + throw new RuntimeException("Unknown object type in store."); + } + + type = dIn.read(); + } + } + + protected void saveStore( + OutputStream out) + throws IOException + { + Enumeration e = table.elements(); + DataOutputStream dOut = new DataOutputStream(out); + + while (e.hasMoreElements()) + { + StoreEntry entry = (StoreEntry)e.nextElement(); + + dOut.write(entry.getType()); + dOut.writeUTF(entry.getAlias()); + dOut.writeLong(entry.getDate().getTime()); + + Certificate[] chain = entry.getCertificateChain(); + if (chain == null) + { + dOut.writeInt(0); + } + else + { + dOut.writeInt(chain.length); + for (int i = 0; i != chain.length; i++) + { + encodeCertificate(chain[i], dOut); + } + } + + switch (entry.getType()) + { + case CERTIFICATE: + encodeCertificate((Certificate)entry.getObject(), dOut); + break; + case KEY: + encodeKey((Key)entry.getObject(), dOut); + break; + case SEALED: + case SECRET: + byte[] b = (byte[])entry.getObject(); + + dOut.writeInt(b.length); + dOut.write(b); + break; + default: + throw new RuntimeException("Unknown object type in store."); + } + } + + dOut.write(NULL); + } + + public void engineLoad( + InputStream stream, + char[] password) + throws IOException + { + table.clear(); + + if (stream == null) // just initialising + { + return; + } + + DataInputStream dIn = new DataInputStream(stream); + int version = dIn.readInt(); + + if (version != STORE_VERSION) + { + if (version != 0 && version != 1) + { + throw new IOException("Wrong version of key store."); + } + } + + int saltLength = dIn.readInt(); + if (saltLength <= 0) + { + throw new IOException("Invalid salt detected"); + } + + byte[] salt = new byte[saltLength]; + + dIn.readFully(salt); + + int iterationCount = dIn.readInt(); + + // + // we only do an integrity check if the password is provided. + // + HMac hMac = new HMac(new SHA1Digest()); + if (password != null && password.length != 0) + { + byte[] passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password); + + PBEParametersGenerator pbeGen = new PKCS12ParametersGenerator(new SHA1Digest()); + pbeGen.init(passKey, salt, iterationCount); + + CipherParameters macParams; + + if (version != 2) + { + macParams = pbeGen.generateDerivedMacParameters(hMac.getMacSize()); + } + else + { + macParams = pbeGen.generateDerivedMacParameters(hMac.getMacSize() * 8); + } + + Arrays.fill(passKey, (byte)0); + + hMac.init(macParams); + MacInputStream mIn = new MacInputStream(dIn, hMac); + + loadStore(mIn); + + // Finalise our mac calculation + byte[] mac = new byte[hMac.getMacSize()]; + hMac.doFinal(mac, 0); + + // TODO Should this actually be reading the remainder of the stream? + // Read the original mac from the stream + byte[] oldMac = new byte[hMac.getMacSize()]; + dIn.readFully(oldMac); + + if (!Arrays.constantTimeAreEqual(mac, oldMac)) + { + table.clear(); + throw new IOException("KeyStore integrity check failed."); + } + } + else + { + loadStore(dIn); + + // TODO Should this actually be reading the remainder of the stream? + // Parse the original mac from the stream too + byte[] oldMac = new byte[hMac.getMacSize()]; + dIn.readFully(oldMac); + } + } + + + public void engineStore(OutputStream stream, char[] password) + throws IOException + { + DataOutputStream dOut = new DataOutputStream(stream); + byte[] salt = new byte[STORE_SALT_SIZE]; + int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff); + + random.nextBytes(salt); + + dOut.writeInt(version); + dOut.writeInt(salt.length); + dOut.write(salt); + dOut.writeInt(iterationCount); + + HMac hMac = new HMac(new SHA1Digest()); + MacOutputStream mOut = new MacOutputStream(hMac); + PBEParametersGenerator pbeGen = new PKCS12ParametersGenerator(new SHA1Digest()); + byte[] passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password); + + pbeGen.init(passKey, salt, iterationCount); + + if (version < 2) + { + hMac.init(pbeGen.generateDerivedMacParameters(hMac.getMacSize())); + } + else + { + hMac.init(pbeGen.generateDerivedMacParameters(hMac.getMacSize() * 8)); + } + + for (int i = 0; i != passKey.length; i++) + { + passKey[i] = 0; + } + + saveStore(new TeeOutputStream(dOut, mOut)); + + byte[] mac = new byte[hMac.getMacSize()]; + + hMac.doFinal(mac, 0); + + dOut.write(mac); + + dOut.close(); + } + + /** + * the BouncyCastle store. This wont work with the key tool as the + * store is stored encrypted on disk, so the password is mandatory, + * however if you hard drive is in a bad part of town and you absolutely, + * positively, don't want nobody peeking at your things, this is the + * one to use, no problem! After all in a Bouncy Castle nothing can + * touch you. + * + * Also referred to by the alias UBER. + */ + public static class BouncyCastleStore + extends BcKeyStoreSpi + { + public BouncyCastleStore() + { + super(1); + } + + public void engineLoad( + InputStream stream, + char[] password) + throws IOException + { + table.clear(); + + if (stream == null) // just initialising + { + return; + } + + DataInputStream dIn = new DataInputStream(stream); + int version = dIn.readInt(); + + if (version != STORE_VERSION) + { + if (version != 0 && version != 1) + { + throw new IOException("Wrong version of key store."); + } + } + + byte[] salt = new byte[dIn.readInt()]; + + if (salt.length != STORE_SALT_SIZE) + { + throw new IOException("Key store corrupted."); + } + + dIn.readFully(salt); + + int iterationCount = dIn.readInt(); + + if ((iterationCount < 0) || (iterationCount > 4 * MIN_ITERATIONS)) + { + throw new IOException("Key store corrupted."); + } + + String cipherAlg; + if (version == 0) + { + cipherAlg = "Old" + STORE_CIPHER; + } + else + { + cipherAlg = STORE_CIPHER; + } + + Cipher cipher = this.makePBECipher(cipherAlg, Cipher.DECRYPT_MODE, password, salt, iterationCount); + CipherInputStream cIn = new CipherInputStream(dIn, cipher); + + Digest dig = new SHA1Digest(); + DigestInputStream dgIn = new DigestInputStream(cIn, dig); + + this.loadStore(dgIn); + + // Finalise our digest calculation + byte[] hash = new byte[dig.getDigestSize()]; + dig.doFinal(hash, 0); + + // TODO Should this actually be reading the remainder of the stream? + // Read the original digest from the stream + byte[] oldHash = new byte[dig.getDigestSize()]; + Streams.readFully(cIn, oldHash); + + if (!Arrays.constantTimeAreEqual(hash, oldHash)) + { + table.clear(); + throw new IOException("KeyStore integrity check failed."); + } + } + + public void engineStore(OutputStream stream, char[] password) + throws IOException + { + Cipher cipher; + DataOutputStream dOut = new DataOutputStream(stream); + byte[] salt = new byte[STORE_SALT_SIZE]; + int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff); + + random.nextBytes(salt); + + dOut.writeInt(version); + dOut.writeInt(salt.length); + dOut.write(salt); + dOut.writeInt(iterationCount); + + cipher = this.makePBECipher(STORE_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount); + + CipherOutputStream cOut = new CipherOutputStream(dOut, cipher); + DigestOutputStream dgOut = new DigestOutputStream(new SHA1Digest()); + + this.saveStore(new TeeOutputStream(cOut, dgOut)); + + byte[] dig = dgOut.getDigest(); + + cOut.write(dig); + + cOut.close(); + } + } + + public static class Std + extends BcKeyStoreSpi + { + public Std() + { + super(STORE_VERSION); + } + } + + public static class Version1 + extends BcKeyStoreSpi + { + public Version1() + { + super(1); + } + } +} |