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