25
25
import java .nio .file .Files ;
26
26
import java .nio .file .Path ;
27
27
import java .security .AccessControlException ;
28
+ import java .security .AlgorithmParameters ;
28
29
import java .security .GeneralSecurityException ;
29
30
import java .security .KeyFactory ;
30
31
import java .security .KeyPairGenerator ;
@@ -68,6 +69,9 @@ public final class PemUtils {
68
69
private static final String OPENSSL_EC_PARAMS_FOOTER = "-----END EC PARAMETERS-----" ;
69
70
private static final String HEADER = "-----BEGIN" ;
70
71
72
+ private static final String PBES2_OID = "1.2.840.113549.1.5.13" ;
73
+ private static final String AES_OID = "2.16.840.1.101.3.4.1" ;
74
+
71
75
private PemUtils () {
72
76
throw new IllegalStateException ("Utility class should not be instantiated" );
73
77
}
@@ -365,17 +369,70 @@ private static PrivateKey parsePKCS8Encrypted(BufferedReader bReader, char[] key
365
369
}
366
370
byte [] keyBytes = Base64 .getDecoder ().decode (sb .toString ());
367
371
368
- EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo (keyBytes );
369
- SecretKeyFactory secretKeyFactory = SecretKeyFactory .getInstance (encryptedPrivateKeyInfo .getAlgName ());
372
+ final EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = getEncryptedPrivateKeyInfo (keyBytes );
373
+ String algorithm = encryptedPrivateKeyInfo .getAlgName ();
374
+ if (algorithm .equals ("PBES2" ) || algorithm .equals ("1.2.840.113549.1.5.13" )) {
375
+ algorithm = getPBES2Algorithm (encryptedPrivateKeyInfo );
376
+ }
377
+ SecretKeyFactory secretKeyFactory = SecretKeyFactory .getInstance (algorithm );
370
378
SecretKey secretKey = secretKeyFactory .generateSecret (new PBEKeySpec (keyPassword ));
371
- Cipher cipher = Cipher .getInstance (encryptedPrivateKeyInfo . getAlgName () );
379
+ Cipher cipher = Cipher .getInstance (algorithm );
372
380
cipher .init (Cipher .DECRYPT_MODE , secretKey , encryptedPrivateKeyInfo .getAlgParameters ());
373
381
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo .getKeySpec (cipher );
374
382
String keyAlgo = getKeyAlgorithmIdentifier (keySpec .getEncoded ());
375
383
KeyFactory keyFactory = KeyFactory .getInstance (keyAlgo );
376
384
return keyFactory .generatePrivate (keySpec );
377
385
}
378
386
387
+ private static EncryptedPrivateKeyInfo getEncryptedPrivateKeyInfo (byte [] keyBytes ) throws IOException , GeneralSecurityException {
388
+ try {
389
+ return new EncryptedPrivateKeyInfo (keyBytes );
390
+ } catch (IOException e ) {
391
+ // The Sun JCE provider can't handle non-AES PBES2 data (but it can handle PBES1 DES data - go figure)
392
+ // It's not worth our effort to try and decrypt it ourselves, but we can detect it and give a good error message
393
+ DerParser parser = new DerParser (keyBytes );
394
+ final DerParser .Asn1Object rootSeq = parser .readAsn1Object (DerParser .Type .SEQUENCE );
395
+ parser = rootSeq .getParser ();
396
+ final DerParser .Asn1Object algSeq = parser .readAsn1Object (DerParser .Type .SEQUENCE );
397
+ parser = algSeq .getParser ();
398
+ final String algId = parser .readAsn1Object (DerParser .Type .OBJECT_OID ).getOid ();
399
+ if (PBES2_OID .equals (algId )) {
400
+ final DerParser .Asn1Object algData = parser .readAsn1Object (DerParser .Type .SEQUENCE );
401
+ parser = algData .getParser ();
402
+ final DerParser .Asn1Object ignoreKdf = parser .readAsn1Object (DerParser .Type .SEQUENCE );
403
+ final DerParser .Asn1Object cryptSeq = parser .readAsn1Object (DerParser .Type .SEQUENCE );
404
+ parser = cryptSeq .getParser ();
405
+ final String encryptionId = parser .readAsn1Object (DerParser .Type .OBJECT_OID ).getOid ();
406
+ if (encryptionId .startsWith (AES_OID ) == false ) {
407
+ final String name = getAlgorithmNameFromOid (encryptionId );
408
+ throw new GeneralSecurityException (
409
+ "PKCS#8 Private Key is encrypted with unsupported PBES2 algorithm ["
410
+ + encryptionId
411
+ + "]"
412
+ + (name == null ? "" : " (" + name + ")" ),
413
+ e
414
+ );
415
+ }
416
+ }
417
+ throw e ;
418
+ }
419
+ }
420
+
421
+ /**
422
+ * This is horrible, but it's the only option other than to parse the encoded ASN.1 value ourselves
423
+ * @see AlgorithmParameters#toString() and com.sun.crypto.provider.PBES2Parameters#toString()
424
+ */
425
+ private static String getPBES2Algorithm (EncryptedPrivateKeyInfo encryptedPrivateKeyInfo ) {
426
+ final AlgorithmParameters algParameters = encryptedPrivateKeyInfo .getAlgParameters ();
427
+ if (algParameters != null ) {
428
+ return algParameters .toString ();
429
+ } else {
430
+ // AlgorithmParameters can be null when running on BCFIPS.
431
+ // However, since BCFIPS doesn't support any PBE specs, nothing we do here would work, so we just do enough to avoid an NPE
432
+ return encryptedPrivateKeyInfo .getAlgName ();
433
+ }
434
+ }
435
+
379
436
/**
380
437
* Decrypts the password protected contents using the algorithm and IV that is specified in the PEM Headers of the file
381
438
*
@@ -604,7 +661,7 @@ private static String getKeyAlgorithmIdentifier(byte[] keyBytes) throws IOExcept
604
661
return "EC" ;
605
662
}
606
663
throw new GeneralSecurityException ("Error parsing key algorithm identifier. Algorithm with OID [" + oidString +
607
- "] is not żsupported " );
664
+ "] is not supported " );
608
665
}
609
666
610
667
public static List <Certificate > readCertificates (Collection <Path > certPaths ) throws CertificateException , IOException {
@@ -622,6 +679,56 @@ public static List<Certificate> readCertificates(Collection<Path> certPaths) thr
622
679
return certificates ;
623
680
}
624
681
682
+ private static String getAlgorithmNameFromOid (String oidString ) throws GeneralSecurityException {
683
+ switch (oidString ) {
684
+ case "1.2.840.10040.4.1" :
685
+ return "DSA" ;
686
+ case "1.2.840.113549.1.1.1" :
687
+ return "RSA" ;
688
+ case "1.2.840.10045.2.1" :
689
+ return "EC" ;
690
+ case "1.3.14.3.2.7" :
691
+ return "DES-CBC" ;
692
+ case "2.16.840.1.101.3.4.1.1" :
693
+ return "AES-128_ECB" ;
694
+ case "2.16.840.1.101.3.4.1.2" :
695
+ return "AES-128_CBC" ;
696
+ case "2.16.840.1.101.3.4.1.3" :
697
+ return "AES-128_OFB" ;
698
+ case "2.16.840.1.101.3.4.1.4" :
699
+ return "AES-128_CFB" ;
700
+ case "2.16.840.1.101.3.4.1.6" :
701
+ return "AES-128_GCM" ;
702
+ case "2.16.840.1.101.3.4.1.21" :
703
+ return "AES-192_ECB" ;
704
+ case "2.16.840.1.101.3.4.1.22" :
705
+ return "AES-192_CBC" ;
706
+ case "2.16.840.1.101.3.4.1.23" :
707
+ return "AES-192_OFB" ;
708
+ case "2.16.840.1.101.3.4.1.24" :
709
+ return "AES-192_CFB" ;
710
+ case "2.16.840.1.101.3.4.1.26" :
711
+ return "AES-192_GCM" ;
712
+ case "2.16.840.1.101.3.4.1.41" :
713
+ return "AES-256_ECB" ;
714
+ case "2.16.840.1.101.3.4.1.42" :
715
+ return "AES-256_CBC" ;
716
+ case "2.16.840.1.101.3.4.1.43" :
717
+ return "AES-256_OFB" ;
718
+ case "2.16.840.1.101.3.4.1.44" :
719
+ return "AES-256_CFB" ;
720
+ case "2.16.840.1.101.3.4.1.46" :
721
+ return "AES-256_GCM" ;
722
+ case "2.16.840.1.101.3.4.1.5" :
723
+ return "AESWrap-128" ;
724
+ case "2.16.840.1.101.3.4.1.25" :
725
+ return "AESWrap-192" ;
726
+ case "2.16.840.1.101.3.4.1.45" :
727
+ return "AESWrap-256" ;
728
+ }
729
+ return null ;
730
+ }
731
+
625
732
private static String getEcCurveNameFromOid (String oidString ) throws GeneralSecurityException {
626
733
switch (oidString ) {
627
734
// see https://tools.ietf.org/html/rfc5480#section-2.1.1.1
0 commit comments