1package org.bouncycastle.jcajce.provider.keystore.bc;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.DataInputStream;
6import java.io.DataOutputStream;
7import java.io.IOException;
8import java.io.InputStream;
9import java.io.OutputStream;
10import java.security.Key;
11import java.security.KeyFactory;
12import java.security.KeyStoreException;
13import java.security.KeyStoreSpi;
14import java.security.NoSuchAlgorithmException;
15import java.security.NoSuchProviderException;
16import java.security.PrivateKey;
17import java.security.PublicKey;
18import java.security.SecureRandom;
19import java.security.UnrecoverableKeyException;
20import java.security.cert.Certificate;
21import java.security.cert.CertificateEncodingException;
22import java.security.cert.CertificateException;
23import java.security.cert.CertificateFactory;
24import java.security.spec.KeySpec;
25import java.security.spec.PKCS8EncodedKeySpec;
26import java.security.spec.X509EncodedKeySpec;
27import java.util.Date;
28import java.util.Enumeration;
29import java.util.Hashtable;
30
31import javax.crypto.Cipher;
32import javax.crypto.CipherInputStream;
33import javax.crypto.CipherOutputStream;
34import javax.crypto.SecretKeyFactory;
35import javax.crypto.spec.PBEKeySpec;
36import javax.crypto.spec.PBEParameterSpec;
37import javax.crypto.spec.SecretKeySpec;
38
39import org.bouncycastle.crypto.CipherParameters;
40import org.bouncycastle.crypto.Digest;
41import org.bouncycastle.crypto.PBEParametersGenerator;
42import org.bouncycastle.crypto.digests.SHA1Digest;
43import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
44import org.bouncycastle.crypto.io.DigestInputStream;
45import org.bouncycastle.crypto.io.DigestOutputStream;
46import org.bouncycastle.crypto.io.MacInputStream;
47import org.bouncycastle.crypto.io.MacOutputStream;
48import org.bouncycastle.crypto.macs.HMac;
49import org.bouncycastle.jce.interfaces.BCKeyStore;
50import org.bouncycastle.jce.provider.BouncyCastleProvider;
51import org.bouncycastle.util.Arrays;
52import org.bouncycastle.util.io.Streams;
53import org.bouncycastle.util.io.TeeOutputStream;
54
55public class BcKeyStoreSpi
56    extends KeyStoreSpi
57    implements BCKeyStore
58{
59    private static final int    STORE_VERSION = 2;
60
61    private static final int    STORE_SALT_SIZE = 20;
62    private static final String STORE_CIPHER = "PBEWithSHAAndTwofish-CBC";
63
64    private static final int    KEY_SALT_SIZE = 20;
65    private static final int    MIN_ITERATIONS = 1024;
66
67    private static final String KEY_CIPHER = "PBEWithSHAAnd3-KeyTripleDES-CBC";
68
69    //
70    // generic object types
71    //
72    static final int NULL           = 0;
73    static final int CERTIFICATE    = 1;
74    static final int KEY            = 2;
75    static final int SECRET         = 3;
76    static final int SEALED         = 4;
77
78    //
79    // key types
80    //
81    static final int    KEY_PRIVATE = 0;
82    static final int    KEY_PUBLIC  = 1;
83    static final int    KEY_SECRET  = 2;
84
85    protected Hashtable       table = new Hashtable();
86
87    protected SecureRandom    random = new SecureRandom();
88
89    protected int              version;
90
91    public BcKeyStoreSpi(int version)
92    {
93        this.version = version;
94    }
95
96    private class StoreEntry
97    {
98        int             type;
99        String          alias;
100        Object          obj;
101        Certificate[]   certChain;
102        Date            date = new Date();
103
104        StoreEntry(
105            String       alias,
106            Certificate  obj)
107        {
108            this.type = CERTIFICATE;
109            this.alias = alias;
110            this.obj = obj;
111            this.certChain = null;
112        }
113
114        StoreEntry(
115            String          alias,
116            byte[]          obj,
117            Certificate[]   certChain)
118        {
119            this.type = SECRET;
120            this.alias = alias;
121            this.obj = obj;
122            this.certChain = certChain;
123        }
124
125        StoreEntry(
126            String          alias,
127            Key             key,
128            char[]          password,
129            Certificate[]   certChain)
130            throws Exception
131        {
132            this.type = SEALED;
133            this.alias = alias;
134            this.certChain = certChain;
135
136            byte[] salt = new byte[KEY_SALT_SIZE];
137
138            random.setSeed(System.currentTimeMillis());
139            random.nextBytes(salt);
140
141            int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
142
143
144            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
145            DataOutputStream        dOut = new DataOutputStream(bOut);
146
147            dOut.writeInt(salt.length);
148            dOut.write(salt);
149            dOut.writeInt(iterationCount);
150
151            Cipher              cipher = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
152            CipherOutputStream  cOut = new CipherOutputStream(dOut, cipher);
153
154            dOut = new DataOutputStream(cOut);
155
156            encodeKey(key, dOut);
157
158            dOut.close();
159
160            obj = bOut.toByteArray();
161        }
162
163        StoreEntry(
164            String          alias,
165            Date            date,
166            int             type,
167            Object          obj)
168        {
169            this.alias = alias;
170            this.date = date;
171            this.type = type;
172            this.obj = obj;
173        }
174
175        StoreEntry(
176            String          alias,
177            Date            date,
178            int             type,
179            Object          obj,
180            Certificate[]   certChain)
181        {
182            this.alias = alias;
183            this.date = date;
184            this.type = type;
185            this.obj = obj;
186            this.certChain = certChain;
187        }
188
189        int getType()
190        {
191            return type;
192        }
193
194        String getAlias()
195        {
196            return alias;
197        }
198
199        Object getObject()
200        {
201            return obj;
202        }
203
204        Object getObject(
205            char[]  password)
206            throws NoSuchAlgorithmException, UnrecoverableKeyException
207        {
208            if (password == null || password.length == 0)
209            {
210                if (obj instanceof Key)
211                {
212                    return obj;
213                }
214            }
215
216            if (type == SEALED)
217            {
218                ByteArrayInputStream    bIn = new ByteArrayInputStream((byte[])obj);
219                DataInputStream         dIn = new DataInputStream(bIn);
220
221                try
222                {
223                    byte[]      salt = new byte[dIn.readInt()];
224
225                    dIn.readFully(salt);
226
227                    int     iterationCount = dIn.readInt();
228
229                    Cipher      cipher = makePBECipher(KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
230
231                    CipherInputStream cIn = new CipherInputStream(dIn, cipher);
232
233                    try
234                    {
235                        return decodeKey(new DataInputStream(cIn));
236                    }
237                    catch (Exception x)
238                    {
239                        bIn = new ByteArrayInputStream((byte[])obj);
240                        dIn = new DataInputStream(bIn);
241
242                        salt = new byte[dIn.readInt()];
243
244                        dIn.readFully(salt);
245
246                        iterationCount = dIn.readInt();
247
248                        cipher = makePBECipher("Broken" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
249
250                        cIn = new CipherInputStream(dIn, cipher);
251
252                        Key k = null;
253
254                        try
255                        {
256                            k = decodeKey(new DataInputStream(cIn));
257                        }
258                        catch (Exception y)
259                        {
260                            bIn = new ByteArrayInputStream((byte[])obj);
261                            dIn = new DataInputStream(bIn);
262
263                            salt = new byte[dIn.readInt()];
264
265                            dIn.readFully(salt);
266
267                            iterationCount = dIn.readInt();
268
269                            cipher = makePBECipher("Old" + KEY_CIPHER, Cipher.DECRYPT_MODE, password, salt, iterationCount);
270
271                            cIn = new CipherInputStream(dIn, cipher);
272
273                            k = decodeKey(new DataInputStream(cIn));
274                        }
275
276                        //
277                        // reencrypt key with correct cipher.
278                        //
279                        if (k != null)
280                        {
281                            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
282                            DataOutputStream        dOut = new DataOutputStream(bOut);
283
284                            dOut.writeInt(salt.length);
285                            dOut.write(salt);
286                            dOut.writeInt(iterationCount);
287
288                            Cipher              out = makePBECipher(KEY_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
289                            CipherOutputStream  cOut = new CipherOutputStream(dOut, out);
290
291                            dOut = new DataOutputStream(cOut);
292
293                            encodeKey(k, dOut);
294
295                            dOut.close();
296
297                            obj = bOut.toByteArray();
298
299                            return k;
300                        }
301                        else
302                        {
303                            throw new UnrecoverableKeyException("no match");
304                        }
305                    }
306                }
307                catch (Exception e)
308                {
309                    throw new UnrecoverableKeyException("no match");
310                }
311            }
312            else
313            {
314                throw new RuntimeException("forget something!");
315                // TODO
316                // if we get to here key was saved as byte data, which
317                // according to the docs means it must be a private key
318                // in EncryptedPrivateKeyInfo (PKCS8 format), later...
319                //
320            }
321        }
322
323        Certificate[] getCertificateChain()
324        {
325            return certChain;
326        }
327
328        Date getDate()
329        {
330            return date;
331        }
332    }
333
334    private void encodeCertificate(
335        Certificate         cert,
336        DataOutputStream    dOut)
337        throws IOException
338    {
339        try
340        {
341            byte[]      cEnc = cert.getEncoded();
342
343            dOut.writeUTF(cert.getType());
344            dOut.writeInt(cEnc.length);
345            dOut.write(cEnc);
346        }
347        catch (CertificateEncodingException ex)
348        {
349            throw new IOException(ex.toString());
350        }
351    }
352
353    private Certificate decodeCertificate(
354        DataInputStream   dIn)
355        throws IOException
356    {
357        String      type = dIn.readUTF();
358        byte[]      cEnc = new byte[dIn.readInt()];
359
360        dIn.readFully(cEnc);
361
362        try
363        {
364            CertificateFactory cFact = CertificateFactory.getInstance(type, BouncyCastleProvider.PROVIDER_NAME);
365            ByteArrayInputStream bIn = new ByteArrayInputStream(cEnc);
366
367            return cFact.generateCertificate(bIn);
368        }
369        catch (NoSuchProviderException ex)
370        {
371            throw new IOException(ex.toString());
372        }
373        catch (CertificateException ex)
374        {
375            throw new IOException(ex.toString());
376        }
377    }
378
379    private void encodeKey(
380        Key                 key,
381        DataOutputStream    dOut)
382        throws IOException
383    {
384        byte[]      enc = key.getEncoded();
385
386        if (key instanceof PrivateKey)
387        {
388            dOut.write(KEY_PRIVATE);
389        }
390        else if (key instanceof PublicKey)
391        {
392            dOut.write(KEY_PUBLIC);
393        }
394        else
395        {
396            dOut.write(KEY_SECRET);
397        }
398
399        dOut.writeUTF(key.getFormat());
400        dOut.writeUTF(key.getAlgorithm());
401        dOut.writeInt(enc.length);
402        dOut.write(enc);
403    }
404
405    private Key decodeKey(
406        DataInputStream dIn)
407        throws IOException
408    {
409        int         keyType = dIn.read();
410        String      format = dIn.readUTF();
411        String      algorithm = dIn.readUTF();
412        byte[]      enc = new byte[dIn.readInt()];
413        KeySpec     spec;
414
415        dIn.readFully(enc);
416
417        if (format.equals("PKCS#8") || format.equals("PKCS8"))
418        {
419            spec = new PKCS8EncodedKeySpec(enc);
420        }
421        else if (format.equals("X.509") || format.equals("X509"))
422        {
423            spec = new X509EncodedKeySpec(enc);
424        }
425        else if (format.equals("RAW"))
426        {
427            return new SecretKeySpec(enc, algorithm);
428        }
429        else
430        {
431            throw new IOException("Key format " + format + " not recognised!");
432        }
433
434        try
435        {
436            switch (keyType)
437            {
438            case KEY_PRIVATE:
439                return KeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generatePrivate(spec);
440            case KEY_PUBLIC:
441                return KeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generatePublic(spec);
442            case KEY_SECRET:
443                return SecretKeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME).generateSecret(spec);
444            default:
445                throw new IOException("Key type " + keyType + " not recognised!");
446            }
447        }
448        catch (Exception e)
449        {
450            throw new IOException("Exception creating key: " + e.toString());
451        }
452    }
453
454    protected Cipher makePBECipher(
455        String  algorithm,
456        int     mode,
457        char[]  password,
458        byte[]  salt,
459        int     iterationCount)
460        throws IOException
461    {
462        try
463        {
464            PBEKeySpec          pbeSpec = new PBEKeySpec(password);
465            SecretKeyFactory    keyFact = SecretKeyFactory.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
466            PBEParameterSpec    defParams = new PBEParameterSpec(salt, iterationCount);
467
468            Cipher cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
469
470            cipher.init(mode, keyFact.generateSecret(pbeSpec), defParams);
471
472            return cipher;
473        }
474        catch (Exception e)
475        {
476            throw new IOException("Error initialising store of key store: " + e);
477        }
478    }
479
480    public void setRandom(
481            SecureRandom    rand)
482    {
483        this.random = rand;
484    }
485
486    public Enumeration engineAliases()
487    {
488        return table.keys();
489    }
490
491    public boolean engineContainsAlias(
492        String  alias)
493    {
494        return (table.get(alias) != null);
495    }
496
497    public void engineDeleteEntry(
498        String  alias)
499        throws KeyStoreException
500    {
501        Object  entry = table.get(alias);
502
503        if (entry == null)
504        {
505            return;
506        }
507
508        table.remove(alias);
509    }
510
511    public Certificate engineGetCertificate(
512        String alias)
513    {
514        StoreEntry  entry = (StoreEntry)table.get(alias);
515
516        if (entry != null)
517        {
518            if (entry.getType() == CERTIFICATE)
519            {
520                return (Certificate)entry.getObject();
521            }
522            else
523            {
524                Certificate[]   chain = entry.getCertificateChain();
525
526                if (chain != null)
527                {
528                    return chain[0];
529                }
530            }
531        }
532
533        return null;
534    }
535
536    public String engineGetCertificateAlias(
537        Certificate cert)
538    {
539        Enumeration e = table.elements();
540        while (e.hasMoreElements())
541        {
542            StoreEntry  entry = (StoreEntry)e.nextElement();
543
544            if (entry.getObject() instanceof Certificate)
545            {
546                Certificate c = (Certificate)entry.getObject();
547
548                if (c.equals(cert))
549                {
550                    return entry.getAlias();
551                }
552            }
553            else
554            {
555                Certificate[]   chain = entry.getCertificateChain();
556
557                if (chain != null && chain[0].equals(cert))
558                {
559                    return entry.getAlias();
560                }
561            }
562        }
563
564        return null;
565    }
566
567    public Certificate[] engineGetCertificateChain(
568        String alias)
569    {
570        StoreEntry  entry = (StoreEntry)table.get(alias);
571
572        if (entry != null)
573        {
574            return entry.getCertificateChain();
575        }
576
577        return null;
578    }
579
580    public Date engineGetCreationDate(String alias)
581    {
582        StoreEntry  entry = (StoreEntry)table.get(alias);
583
584        if (entry != null)
585        {
586            return entry.getDate();
587        }
588
589        return null;
590    }
591
592    public Key engineGetKey(
593        String alias,
594        char[] password)
595        throws NoSuchAlgorithmException, UnrecoverableKeyException
596    {
597        StoreEntry  entry = (StoreEntry)table.get(alias);
598
599        if (entry == null || entry.getType() == CERTIFICATE)
600        {
601            return null;
602        }
603
604        return (Key)entry.getObject(password);
605    }
606
607    public boolean engineIsCertificateEntry(
608        String alias)
609    {
610        StoreEntry  entry = (StoreEntry)table.get(alias);
611
612        if (entry != null && entry.getType() == CERTIFICATE)
613        {
614            return true;
615        }
616
617        return false;
618    }
619
620    public boolean engineIsKeyEntry(
621        String alias)
622    {
623        StoreEntry  entry = (StoreEntry)table.get(alias);
624
625        if (entry != null && entry.getType() != CERTIFICATE)
626        {
627            return true;
628        }
629
630        return false;
631    }
632
633    public void engineSetCertificateEntry(
634        String      alias,
635        Certificate cert)
636        throws KeyStoreException
637    {
638        StoreEntry  entry = (StoreEntry)table.get(alias);
639
640        if (entry != null && entry.getType() != CERTIFICATE)
641        {
642            throw new KeyStoreException("key store already has a key entry with alias " + alias);
643        }
644
645        table.put(alias, new StoreEntry(alias, cert));
646    }
647
648    public void engineSetKeyEntry(
649        String alias,
650        byte[] key,
651        Certificate[] chain)
652        throws KeyStoreException
653    {
654        table.put(alias, new StoreEntry(alias, key, chain));
655    }
656
657    public void engineSetKeyEntry(
658        String          alias,
659        Key             key,
660        char[]          password,
661        Certificate[]   chain)
662        throws KeyStoreException
663    {
664        if ((key instanceof PrivateKey) && (chain == null))
665        {
666            throw new KeyStoreException("no certificate chain for private key");
667        }
668
669        try
670        {
671            table.put(alias, new StoreEntry(alias, key, password, chain));
672        }
673        catch (Exception e)
674        {
675            throw new KeyStoreException(e.toString());
676        }
677    }
678
679    public int engineSize()
680    {
681        return table.size();
682    }
683
684    protected void loadStore(
685        InputStream in)
686        throws IOException
687    {
688        DataInputStream     dIn = new DataInputStream(in);
689        int                 type = dIn.read();
690
691        while (type > NULL)
692        {
693            String          alias = dIn.readUTF();
694            Date            date = new Date(dIn.readLong());
695            int             chainLength = dIn.readInt();
696            Certificate[]   chain = null;
697
698            if (chainLength != 0)
699            {
700                chain = new Certificate[chainLength];
701
702                for (int i = 0; i != chainLength; i++)
703                {
704                    chain[i] = decodeCertificate(dIn);
705                }
706            }
707
708            switch (type)
709            {
710            case CERTIFICATE:
711                    Certificate     cert = decodeCertificate(dIn);
712
713                    table.put(alias, new StoreEntry(alias, date, CERTIFICATE, cert));
714                    break;
715            case KEY:
716                    Key     key = decodeKey(dIn);
717                    table.put(alias, new StoreEntry(alias, date, KEY, key, chain));
718                    break;
719            case SECRET:
720            case SEALED:
721                    byte[]      b = new byte[dIn.readInt()];
722
723                    dIn.readFully(b);
724                    table.put(alias, new StoreEntry(alias, date, type, b, chain));
725                    break;
726            default:
727                    throw new RuntimeException("Unknown object type in store.");
728            }
729
730            type = dIn.read();
731        }
732    }
733
734    protected void saveStore(
735        OutputStream    out)
736        throws IOException
737    {
738        Enumeration         e = table.elements();
739        DataOutputStream    dOut = new DataOutputStream(out);
740
741        while (e.hasMoreElements())
742        {
743            StoreEntry  entry = (StoreEntry)e.nextElement();
744
745            dOut.write(entry.getType());
746            dOut.writeUTF(entry.getAlias());
747            dOut.writeLong(entry.getDate().getTime());
748
749            Certificate[]   chain = entry.getCertificateChain();
750            if (chain == null)
751            {
752                dOut.writeInt(0);
753            }
754            else
755            {
756                dOut.writeInt(chain.length);
757                for (int i = 0; i != chain.length; i++)
758                {
759                    encodeCertificate(chain[i], dOut);
760                }
761            }
762
763            switch (entry.getType())
764            {
765            case CERTIFICATE:
766                    encodeCertificate((Certificate)entry.getObject(), dOut);
767                    break;
768            case KEY:
769                    encodeKey((Key)entry.getObject(), dOut);
770                    break;
771            case SEALED:
772            case SECRET:
773                    byte[]  b = (byte[])entry.getObject();
774
775                    dOut.writeInt(b.length);
776                    dOut.write(b);
777                    break;
778            default:
779                    throw new RuntimeException("Unknown object type in store.");
780            }
781        }
782
783        dOut.write(NULL);
784    }
785
786    public void engineLoad(
787        InputStream stream,
788        char[]      password)
789        throws IOException
790    {
791        table.clear();
792
793        if (stream == null)     // just initialising
794        {
795            return;
796        }
797
798        DataInputStream     dIn = new DataInputStream(stream);
799        int                 version = dIn.readInt();
800
801        if (version != STORE_VERSION)
802        {
803            if (version != 0 && version != 1)
804            {
805                throw new IOException("Wrong version of key store.");
806            }
807        }
808
809        int saltLength = dIn.readInt();
810        if (saltLength <= 0)
811        {
812            throw new IOException("Invalid salt detected");
813        }
814
815        byte[]      salt = new byte[saltLength];
816
817        dIn.readFully(salt);
818
819        int         iterationCount = dIn.readInt();
820
821        //
822        // we only do an integrity check if the password is provided.
823        //
824        HMac hMac = new HMac(new SHA1Digest());
825        if (password != null && password.length != 0)
826        {
827            byte[] passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password);
828
829            PBEParametersGenerator pbeGen = new PKCS12ParametersGenerator(new SHA1Digest());
830            pbeGen.init(passKey, salt, iterationCount);
831
832            CipherParameters macParams;
833
834            if (version != 2)
835            {
836                macParams = pbeGen.generateDerivedMacParameters(hMac.getMacSize());
837            }
838            else
839            {
840                macParams = pbeGen.generateDerivedMacParameters(hMac.getMacSize() * 8);
841            }
842
843            Arrays.fill(passKey, (byte)0);
844
845            hMac.init(macParams);
846            MacInputStream mIn = new MacInputStream(dIn, hMac);
847
848            loadStore(mIn);
849
850            // Finalise our mac calculation
851            byte[] mac = new byte[hMac.getMacSize()];
852            hMac.doFinal(mac, 0);
853
854            // TODO Should this actually be reading the remainder of the stream?
855            // Read the original mac from the stream
856            byte[] oldMac = new byte[hMac.getMacSize()];
857            dIn.readFully(oldMac);
858
859            if (!Arrays.constantTimeAreEqual(mac, oldMac))
860            {
861                table.clear();
862                throw new IOException("KeyStore integrity check failed.");
863            }
864        }
865        else
866        {
867            loadStore(dIn);
868
869            // TODO Should this actually be reading the remainder of the stream?
870            // Parse the original mac from the stream too
871            byte[] oldMac = new byte[hMac.getMacSize()];
872            dIn.readFully(oldMac);
873        }
874    }
875
876
877    public void engineStore(OutputStream stream, char[] password)
878        throws IOException
879    {
880        DataOutputStream    dOut = new DataOutputStream(stream);
881        byte[]              salt = new byte[STORE_SALT_SIZE];
882        int                 iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
883
884        random.nextBytes(salt);
885
886        dOut.writeInt(version);
887        dOut.writeInt(salt.length);
888        dOut.write(salt);
889        dOut.writeInt(iterationCount);
890
891        HMac                    hMac = new HMac(new SHA1Digest());
892        MacOutputStream         mOut = new MacOutputStream(hMac);
893        PBEParametersGenerator  pbeGen = new PKCS12ParametersGenerator(new SHA1Digest());
894        byte[]                  passKey = PBEParametersGenerator.PKCS12PasswordToBytes(password);
895
896        pbeGen.init(passKey, salt, iterationCount);
897
898        if (version < 2)
899        {
900            hMac.init(pbeGen.generateDerivedMacParameters(hMac.getMacSize()));
901        }
902        else
903        {
904            hMac.init(pbeGen.generateDerivedMacParameters(hMac.getMacSize() * 8));
905        }
906
907        for (int i = 0; i != passKey.length; i++)
908        {
909            passKey[i] = 0;
910        }
911
912        saveStore(new TeeOutputStream(dOut, mOut));
913
914        byte[]  mac = new byte[hMac.getMacSize()];
915
916        hMac.doFinal(mac, 0);
917
918        dOut.write(mac);
919
920        dOut.close();
921    }
922
923    /**
924     * the BouncyCastle store. This wont work with the key tool as the
925     * store is stored encrypted on disk, so the password is mandatory,
926     * however if you hard drive is in a bad part of town and you absolutely,
927     * positively, don't want nobody peeking at your things, this is the
928     * one to use, no problem! After all in a Bouncy Castle nothing can
929     * touch you.
930     *
931     * Also referred to by the alias UBER.
932     */
933    public static class BouncyCastleStore
934        extends BcKeyStoreSpi
935    {
936        public BouncyCastleStore()
937        {
938            super(1);
939        }
940
941        public void engineLoad(
942            InputStream stream,
943            char[]      password)
944            throws IOException
945        {
946            table.clear();
947
948            if (stream == null)     // just initialising
949            {
950                return;
951            }
952
953            DataInputStream     dIn = new DataInputStream(stream);
954            int                 version = dIn.readInt();
955
956            if (version != STORE_VERSION)
957            {
958                if (version != 0 && version != 1)
959                {
960                    throw new IOException("Wrong version of key store.");
961                }
962            }
963
964            byte[]      salt = new byte[dIn.readInt()];
965
966            if (salt.length != STORE_SALT_SIZE)
967            {
968                throw new IOException("Key store corrupted.");
969            }
970
971            dIn.readFully(salt);
972
973            int         iterationCount = dIn.readInt();
974
975            if ((iterationCount < 0) || (iterationCount > 4 *  MIN_ITERATIONS))
976            {
977                throw new IOException("Key store corrupted.");
978            }
979
980            String cipherAlg;
981            if (version == 0)
982            {
983                cipherAlg = "Old" + STORE_CIPHER;
984            }
985            else
986            {
987                cipherAlg = STORE_CIPHER;
988            }
989
990            Cipher cipher = this.makePBECipher(cipherAlg, Cipher.DECRYPT_MODE, password, salt, iterationCount);
991            CipherInputStream cIn = new CipherInputStream(dIn, cipher);
992
993            Digest dig = new SHA1Digest();
994            DigestInputStream  dgIn = new DigestInputStream(cIn, dig);
995
996            this.loadStore(dgIn);
997
998            // Finalise our digest calculation
999            byte[] hash = new byte[dig.getDigestSize()];
1000            dig.doFinal(hash, 0);
1001
1002            // TODO Should this actually be reading the remainder of the stream?
1003            // Read the original digest from the stream
1004            byte[] oldHash = new byte[dig.getDigestSize()];
1005            Streams.readFully(cIn, oldHash);
1006
1007            if (!Arrays.constantTimeAreEqual(hash, oldHash))
1008            {
1009                table.clear();
1010                throw new IOException("KeyStore integrity check failed.");
1011            }
1012        }
1013
1014        public void engineStore(OutputStream stream, char[] password)
1015            throws IOException
1016        {
1017            Cipher              cipher;
1018            DataOutputStream    dOut = new DataOutputStream(stream);
1019            byte[]              salt = new byte[STORE_SALT_SIZE];
1020            int                 iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
1021
1022            random.nextBytes(salt);
1023
1024            dOut.writeInt(version);
1025            dOut.writeInt(salt.length);
1026            dOut.write(salt);
1027            dOut.writeInt(iterationCount);
1028
1029            cipher = this.makePBECipher(STORE_CIPHER, Cipher.ENCRYPT_MODE, password, salt, iterationCount);
1030
1031            CipherOutputStream  cOut = new CipherOutputStream(dOut, cipher);
1032            DigestOutputStream  dgOut = new DigestOutputStream(new SHA1Digest());
1033
1034            this.saveStore(new TeeOutputStream(cOut, dgOut));
1035
1036            byte[]  dig = dgOut.getDigest();
1037
1038            cOut.write(dig);
1039
1040            cOut.close();
1041        }
1042    }
1043
1044    public static class Std
1045       extends BcKeyStoreSpi
1046    {
1047        public Std()
1048        {
1049            super(STORE_VERSION);
1050        }
1051    }
1052
1053    public static class Version1
1054        extends BcKeyStoreSpi
1055    {
1056        public Version1()
1057        {
1058            super(1);
1059        }
1060    }
1061}
1062