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