26
26
import java .nio .file .Files ;
27
27
import java .nio .file .NoSuchFileException ;
28
28
import java .nio .file .Path ;
29
+ import java .security .AccessControlException ;
30
+ import java .security .AlgorithmParameters ;
29
31
import java .security .GeneralSecurityException ;
30
32
import java .security .KeyFactory ;
31
33
import java .security .KeyPairGenerator ;
@@ -69,6 +71,9 @@ public final class PemUtils {
69
71
private static final String OPENSSL_EC_PARAMS_FOOTER = "-----END EC PARAMETERS-----" ;
70
72
private static final String HEADER = "-----BEGIN" ;
71
73
74
+ private static final String PBES2_OID = "1.2.840.113549.1.5.13" ;
75
+ private static final String AES_OID = "2.16.840.1.101.3.4.1" ;
76
+
72
77
private PemUtils () {
73
78
throw new IllegalStateException ("Utility class should not be instantiated" );
74
79
}
@@ -336,17 +341,70 @@ private static PrivateKey parsePKCS8Encrypted(BufferedReader bReader, char[] key
336
341
}
337
342
byte [] keyBytes = Base64 .getDecoder ().decode (sb .toString ());
338
343
339
- EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo (keyBytes );
340
- SecretKeyFactory secretKeyFactory = SecretKeyFactory .getInstance (encryptedPrivateKeyInfo .getAlgName ());
344
+ final EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = getEncryptedPrivateKeyInfo (keyBytes );
345
+ String algorithm = encryptedPrivateKeyInfo .getAlgName ();
346
+ if (algorithm .equals ("PBES2" ) || algorithm .equals ("1.2.840.113549.1.5.13" )) {
347
+ algorithm = getPBES2Algorithm (encryptedPrivateKeyInfo );
348
+ }
349
+ SecretKeyFactory secretKeyFactory = SecretKeyFactory .getInstance (algorithm );
341
350
SecretKey secretKey = secretKeyFactory .generateSecret (new PBEKeySpec (keyPassword ));
342
- Cipher cipher = Cipher .getInstance (encryptedPrivateKeyInfo . getAlgName () );
351
+ Cipher cipher = Cipher .getInstance (algorithm );
343
352
cipher .init (Cipher .DECRYPT_MODE , secretKey , encryptedPrivateKeyInfo .getAlgParameters ());
344
353
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo .getKeySpec (cipher );
345
354
String keyAlgo = getKeyAlgorithmIdentifier (keySpec .getEncoded ());
346
355
KeyFactory keyFactory = KeyFactory .getInstance (keyAlgo );
347
356
return keyFactory .generatePrivate (keySpec );
348
357
}
349
358
359
+ private static EncryptedPrivateKeyInfo getEncryptedPrivateKeyInfo (byte [] keyBytes ) throws IOException , GeneralSecurityException {
360
+ try {
361
+ return new EncryptedPrivateKeyInfo (keyBytes );
362
+ } catch (IOException e ) {
363
+ // The Sun JCE provider can't handle non-AES PBES2 data (but it can handle PBES1 DES data - go figure)
364
+ // It's not worth our effort to try and decrypt it ourselves, but we can detect it and give a good error message
365
+ DerParser parser = new DerParser (keyBytes );
366
+ final DerParser .Asn1Object rootSeq = parser .readAsn1Object (DerParser .Type .SEQUENCE );
367
+ parser = rootSeq .getParser ();
368
+ final DerParser .Asn1Object algSeq = parser .readAsn1Object (DerParser .Type .SEQUENCE );
369
+ parser = algSeq .getParser ();
370
+ final String algId = parser .readAsn1Object (DerParser .Type .OBJECT_OID ).getOid ();
371
+ if (PBES2_OID .equals (algId )) {
372
+ final DerParser .Asn1Object algData = parser .readAsn1Object (DerParser .Type .SEQUENCE );
373
+ parser = algData .getParser ();
374
+ final DerParser .Asn1Object ignoreKdf = parser .readAsn1Object (DerParser .Type .SEQUENCE );
375
+ final DerParser .Asn1Object cryptSeq = parser .readAsn1Object (DerParser .Type .SEQUENCE );
376
+ parser = cryptSeq .getParser ();
377
+ final String encryptionId = parser .readAsn1Object (DerParser .Type .OBJECT_OID ).getOid ();
378
+ if (encryptionId .startsWith (AES_OID ) == false ) {
379
+ final String name = getAlgorithmNameFromOid (encryptionId );
380
+ throw new GeneralSecurityException (
381
+ "PKCS#8 Private Key is encrypted with unsupported PBES2 algorithm ["
382
+ + encryptionId
383
+ + "]"
384
+ + (name == null ? "" : " (" + name + ")" ),
385
+ e
386
+ );
387
+ }
388
+ }
389
+ throw e ;
390
+ }
391
+ }
392
+
393
+ /**
394
+ * This is horrible, but it's the only option other than to parse the encoded ASN.1 value ourselves
395
+ * @see AlgorithmParameters#toString() and com.sun.crypto.provider.PBES2Parameters#toString()
396
+ */
397
+ private static String getPBES2Algorithm (EncryptedPrivateKeyInfo encryptedPrivateKeyInfo ) {
398
+ final AlgorithmParameters algParameters = encryptedPrivateKeyInfo .getAlgParameters ();
399
+ if (algParameters != null ) {
400
+ return algParameters .toString ();
401
+ } else {
402
+ // AlgorithmParameters can be null when running on BCFIPS.
403
+ // However, since BCFIPS doesn't support any PBE specs, nothing we do here would work, so we just do enough to avoid an NPE
404
+ return encryptedPrivateKeyInfo .getAlgName ();
405
+ }
406
+ }
407
+
350
408
/**
351
409
* Decrypts the password protected contents using the algorithm and IV that is specified in the PEM Headers of the file
352
410
*
@@ -575,7 +633,7 @@ private static String getKeyAlgorithmIdentifier(byte[] keyBytes) throws IOExcept
575
633
return "EC" ;
576
634
}
577
635
throw new GeneralSecurityException ("Error parsing key algorithm identifier. Algorithm with OID [" + oidString +
578
- "] is not żsupported " );
636
+ "] is not supported " );
579
637
}
580
638
581
639
public static List <Certificate > readCertificates (Collection <Path > certPaths ) throws CertificateException , IOException {
@@ -593,6 +651,56 @@ public static List<Certificate> readCertificates(Collection<Path> certPaths) thr
593
651
return certificates ;
594
652
}
595
653
654
+ private static String getAlgorithmNameFromOid (String oidString ) throws GeneralSecurityException {
655
+ switch (oidString ) {
656
+ case "1.2.840.10040.4.1" :
657
+ return "DSA" ;
658
+ case "1.2.840.113549.1.1.1" :
659
+ return "RSA" ;
660
+ case "1.2.840.10045.2.1" :
661
+ return "EC" ;
662
+ case "1.3.14.3.2.7" :
663
+ return "DES-CBC" ;
664
+ case "2.16.840.1.101.3.4.1.1" :
665
+ return "AES-128_ECB" ;
666
+ case "2.16.840.1.101.3.4.1.2" :
667
+ return "AES-128_CBC" ;
668
+ case "2.16.840.1.101.3.4.1.3" :
669
+ return "AES-128_OFB" ;
670
+ case "2.16.840.1.101.3.4.1.4" :
671
+ return "AES-128_CFB" ;
672
+ case "2.16.840.1.101.3.4.1.6" :
673
+ return "AES-128_GCM" ;
674
+ case "2.16.840.1.101.3.4.1.21" :
675
+ return "AES-192_ECB" ;
676
+ case "2.16.840.1.101.3.4.1.22" :
677
+ return "AES-192_CBC" ;
678
+ case "2.16.840.1.101.3.4.1.23" :
679
+ return "AES-192_OFB" ;
680
+ case "2.16.840.1.101.3.4.1.24" :
681
+ return "AES-192_CFB" ;
682
+ case "2.16.840.1.101.3.4.1.26" :
683
+ return "AES-192_GCM" ;
684
+ case "2.16.840.1.101.3.4.1.41" :
685
+ return "AES-256_ECB" ;
686
+ case "2.16.840.1.101.3.4.1.42" :
687
+ return "AES-256_CBC" ;
688
+ case "2.16.840.1.101.3.4.1.43" :
689
+ return "AES-256_OFB" ;
690
+ case "2.16.840.1.101.3.4.1.44" :
691
+ return "AES-256_CFB" ;
692
+ case "2.16.840.1.101.3.4.1.46" :
693
+ return "AES-256_GCM" ;
694
+ case "2.16.840.1.101.3.4.1.5" :
695
+ return "AESWrap-128" ;
696
+ case "2.16.840.1.101.3.4.1.25" :
697
+ return "AESWrap-192" ;
698
+ case "2.16.840.1.101.3.4.1.45" :
699
+ return "AESWrap-256" ;
700
+ }
701
+ return null ;
702
+ }
703
+
596
704
private static String getEcCurveNameFromOid (String oidString ) throws GeneralSecurityException {
597
705
switch (oidString ) {
598
706
// see https://tools.ietf.org/html/rfc5480#section-2.1.1.1
0 commit comments