1 package org.bouncycastle.jcajce.provider.keystore.bc;
2 
3 import java.io.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.DataInputStream;
6 import java.io.DataOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.OutputStream;
10 import java.security.Key;
11 import java.security.KeyFactory;
12 import java.security.KeyStoreException;
13 import java.security.KeyStoreSpi;
14 import java.security.NoSuchAlgorithmException;
15 import java.security.NoSuchProviderException;
16 import java.security.PrivateKey;
17 import java.security.Provider;
18 import java.security.PublicKey;
19 import java.security.SecureRandom;
20 import java.security.Security;
21 import java.security.UnrecoverableKeyException;
22 import java.security.cert.Certificate;
23 import java.security.cert.CertificateEncodingException;
24 import java.security.cert.CertificateException;
25 import java.security.cert.CertificateFactory;
26 import java.security.spec.KeySpec;
27 import java.security.spec.PKCS8EncodedKeySpec;
28 import java.security.spec.X509EncodedKeySpec;
29 import java.util.Date;
30 import java.util.Enumeration;
31 import java.util.Hashtable;
32 
33 import javax.crypto.Cipher;
34 import javax.crypto.CipherInputStream;
35 import javax.crypto.CipherOutputStream;
36 import javax.crypto.SecretKeyFactory;
37 import javax.crypto.spec.PBEKeySpec;
38 import javax.crypto.spec.PBEParameterSpec;
39 import javax.crypto.spec.SecretKeySpec;
40 
41 import org.bouncycastle.crypto.CipherParameters;
42 import org.bouncycastle.crypto.Digest;
43 import org.bouncycastle.crypto.PBEParametersGenerator;
44 import org.bouncycastle.crypto.digests.SHA1Digest;
45 import org.bouncycastle.crypto.generators.PKCS12ParametersGenerator;
46 import org.bouncycastle.crypto.io.DigestInputStream;
47 import org.bouncycastle.crypto.io.DigestOutputStream;
48 import org.bouncycastle.crypto.io.MacInputStream;
49 import org.bouncycastle.crypto.io.MacOutputStream;
50 import org.bouncycastle.crypto.macs.HMac;
51 import org.bouncycastle.jcajce.util.BCJcaJceHelper;
52 import org.bouncycastle.jcajce.util.JcaJceHelper;
53 import org.bouncycastle.jce.interfaces.BCKeyStore;
54 import org.bouncycastle.jce.provider.BouncyCastleProvider;
55 import org.bouncycastle.util.Arrays;
56 import org.bouncycastle.util.io.Streams;
57 import org.bouncycastle.util.io.TeeOutputStream;
58 
59 public 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 
BcKeyStoreSpi(int version)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 
StoreEntry( String alias, Certificate obj)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 
StoreEntry( String alias, byte[] obj, Certificate[] certChain)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 
StoreEntry( String alias, Key key, char[] password, Certificate[] certChain)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 
StoreEntry( String alias, Date date, int type, Object obj)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 
StoreEntry( String alias, Date date, int type, Object obj, Certificate[] certChain)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 
getType()195         int getType()
196         {
197             return type;
198         }
199 
getAlias()200         String getAlias()
201         {
202             return alias;
203         }
204 
getObject()205         Object getObject()
206         {
207             return obj;
208         }
209 
getObject( char[] password)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 
getCertificateChain()329         Certificate[] getCertificateChain()
330         {
331             return certChain;
332         }
333 
getDate()334         Date getDate()
335         {
336             return date;
337         }
338     }
339 
encodeCertificate( Certificate cert, DataOutputStream dOut)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 
decodeCertificate( DataInputStream dIn)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 
encodeKey( Key key, DataOutputStream dOut)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 
decodeKey( DataInputStream dIn)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 
makePBECipher( String algorithm, int mode, char[] password, byte[] salt, int iterationCount)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 
setRandom( SecureRandom rand)486     public void setRandom(
487             SecureRandom    rand)
488     {
489         this.random = rand;
490     }
491 
engineAliases()492     public Enumeration engineAliases()
493     {
494         return table.keys();
495     }
496 
engineContainsAlias( String alias)497     public boolean engineContainsAlias(
498         String  alias)
499     {
500         return (table.get(alias) != null);
501     }
502 
engineDeleteEntry( String alias)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 
engineGetCertificate( String alias)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 
engineGetCertificateAlias( Certificate cert)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 
engineGetCertificateChain( String alias)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 
engineGetCreationDate(String alias)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 
engineGetKey( String alias, char[] password)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 
engineIsCertificateEntry( String alias)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 
engineIsKeyEntry( String alias)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 
engineSetCertificateEntry( String alias, Certificate cert)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 
engineSetKeyEntry( String alias, byte[] key, Certificate[] chain)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 
engineSetKeyEntry( String alias, Key key, char[] password, Certificate[] chain)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 
engineSize()685     public int engineSize()
686     {
687         return table.size();
688     }
689 
loadStore( InputStream in)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 
saveStore( OutputStream out)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 
engineLoad( InputStream stream, char[] password)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 
engineStore(OutputStream stream, char[] password)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     {
BouncyCastleStore()942         public BouncyCastleStore()
943         {
944             super(1);
945         }
946 
engineLoad( InputStream stream, char[] password)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 
engineStore(OutputStream stream, char[] password)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 
getBouncyCastleProvider()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     {
Std()1065         public Std()
1066         {
1067             super(STORE_VERSION);
1068         }
1069     }
1070 
1071     public static class Version1
1072         extends BcKeyStoreSpi
1073     {
Version1()1074         public Version1()
1075         {
1076             super(1);
1077         }
1078     }
1079 }
1080