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