9
9
package org .elasticsearch .common .ssl ;
10
10
11
11
import org .elasticsearch .core .CharArrays ;
12
+ import org .elasticsearch .jdk .JavaVersion ;
12
13
13
14
import javax .crypto .Cipher ;
14
15
import javax .crypto .EncryptedPrivateKeyInfo ;
26
27
import java .nio .file .Files ;
27
28
import java .nio .file .NoSuchFileException ;
28
29
import java .nio .file .Path ;
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,81 @@ 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
+ if (JavaVersion .current ().compareTo (JavaVersion .parse ("11.0.0" )) < 0 ) {
389
+ // PBES2 appears to be supported on Oracle 8, but not OpenJDK8
390
+ // We don't bother clarifying the distinction here, because it's complicated and the best advice we can give is to
391
+ // use the bundled JDK.
392
+ throw new GeneralSecurityException (
393
+ "PKCS#8 Private Key is encrypted with PBES2 which is not supported on this JDK ["
394
+ + JavaVersion .current ()
395
+ + "], this problem can be resolved by using the Elasticsearch bundled JDK" ,
396
+ e
397
+ );
398
+ }
399
+ }
400
+ throw e ;
401
+ }
402
+ }
403
+
404
+ /**
405
+ * This is horrible, but it's the only option other than to parse the encoded ASN.1 value ourselves
406
+ * @see AlgorithmParameters#toString() and com.sun.crypto.provider.PBES2Parameters#toString()
407
+ */
408
+ private static String getPBES2Algorithm (EncryptedPrivateKeyInfo encryptedPrivateKeyInfo ) {
409
+ final AlgorithmParameters algParameters = encryptedPrivateKeyInfo .getAlgParameters ();
410
+ if (algParameters != null ) {
411
+ return algParameters .toString ();
412
+ } else {
413
+ // AlgorithmParameters can be null when running on BCFIPS.
414
+ // However, since BCFIPS doesn't support any PBE specs, nothing we do here would work, so we just do enough to avoid an NPE
415
+ return encryptedPrivateKeyInfo .getAlgName ();
416
+ }
417
+ }
418
+
350
419
/**
351
420
* Decrypts the password protected contents using the algorithm and IV that is specified in the PEM Headers of the file
352
421
*
@@ -575,7 +644,7 @@ private static String getKeyAlgorithmIdentifier(byte[] keyBytes) throws IOExcept
575
644
return "EC" ;
576
645
}
577
646
throw new GeneralSecurityException ("Error parsing key algorithm identifier. Algorithm with OID [" + oidString +
578
- "] is not żsupported " );
647
+ "] is not supported " );
579
648
}
580
649
581
650
public static List <Certificate > readCertificates (Collection <Path > certPaths ) throws CertificateException , IOException {
@@ -593,6 +662,56 @@ public static List<Certificate> readCertificates(Collection<Path> certPaths) thr
593
662
return certificates ;
594
663
}
595
664
665
+ private static String getAlgorithmNameFromOid (String oidString ) throws GeneralSecurityException {
666
+ switch (oidString ) {
667
+ case "1.2.840.10040.4.1" :
668
+ return "DSA" ;
669
+ case "1.2.840.113549.1.1.1" :
670
+ return "RSA" ;
671
+ case "1.2.840.10045.2.1" :
672
+ return "EC" ;
673
+ case "1.3.14.3.2.7" :
674
+ return "DES-CBC" ;
675
+ case "2.16.840.1.101.3.4.1.1" :
676
+ return "AES-128_ECB" ;
677
+ case "2.16.840.1.101.3.4.1.2" :
678
+ return "AES-128_CBC" ;
679
+ case "2.16.840.1.101.3.4.1.3" :
680
+ return "AES-128_OFB" ;
681
+ case "2.16.840.1.101.3.4.1.4" :
682
+ return "AES-128_CFB" ;
683
+ case "2.16.840.1.101.3.4.1.6" :
684
+ return "AES-128_GCM" ;
685
+ case "2.16.840.1.101.3.4.1.21" :
686
+ return "AES-192_ECB" ;
687
+ case "2.16.840.1.101.3.4.1.22" :
688
+ return "AES-192_CBC" ;
689
+ case "2.16.840.1.101.3.4.1.23" :
690
+ return "AES-192_OFB" ;
691
+ case "2.16.840.1.101.3.4.1.24" :
692
+ return "AES-192_CFB" ;
693
+ case "2.16.840.1.101.3.4.1.26" :
694
+ return "AES-192_GCM" ;
695
+ case "2.16.840.1.101.3.4.1.41" :
696
+ return "AES-256_ECB" ;
697
+ case "2.16.840.1.101.3.4.1.42" :
698
+ return "AES-256_CBC" ;
699
+ case "2.16.840.1.101.3.4.1.43" :
700
+ return "AES-256_OFB" ;
701
+ case "2.16.840.1.101.3.4.1.44" :
702
+ return "AES-256_CFB" ;
703
+ case "2.16.840.1.101.3.4.1.46" :
704
+ return "AES-256_GCM" ;
705
+ case "2.16.840.1.101.3.4.1.5" :
706
+ return "AESWrap-128" ;
707
+ case "2.16.840.1.101.3.4.1.25" :
708
+ return "AESWrap-192" ;
709
+ case "2.16.840.1.101.3.4.1.45" :
710
+ return "AESWrap-256" ;
711
+ }
712
+ return null ;
713
+ }
714
+
596
715
private static String getEcCurveNameFromOid (String oidString ) throws GeneralSecurityException {
597
716
switch (oidString ) {
598
717
// see https://tools.ietf.org/html/rfc5480#section-2.1.1.1
0 commit comments