99package org .elasticsearch .common .ssl ;
1010
1111import org .elasticsearch .core .CharArrays ;
12+ import org .elasticsearch .jdk .JavaVersion ;
1213
1314import javax .crypto .Cipher ;
1415import javax .crypto .EncryptedPrivateKeyInfo ;
2627import java .nio .file .Files ;
2728import java .nio .file .NoSuchFileException ;
2829import java .nio .file .Path ;
30+ import java .security .AlgorithmParameters ;
2931import java .security .GeneralSecurityException ;
3032import java .security .KeyFactory ;
3133import java .security .KeyPairGenerator ;
@@ -69,6 +71,9 @@ public final class PemUtils {
6971 private static final String OPENSSL_EC_PARAMS_FOOTER = "-----END EC PARAMETERS-----" ;
7072 private static final String HEADER = "-----BEGIN" ;
7173
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+
7277 private PemUtils () {
7378 throw new IllegalStateException ("Utility class should not be instantiated" );
7479 }
@@ -336,17 +341,81 @@ private static PrivateKey parsePKCS8Encrypted(BufferedReader bReader, char[] key
336341 }
337342 byte [] keyBytes = Base64 .getDecoder ().decode (sb .toString ());
338343
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 );
341350 SecretKey secretKey = secretKeyFactory .generateSecret (new PBEKeySpec (keyPassword ));
342- Cipher cipher = Cipher .getInstance (encryptedPrivateKeyInfo . getAlgName () );
351+ Cipher cipher = Cipher .getInstance (algorithm );
343352 cipher .init (Cipher .DECRYPT_MODE , secretKey , encryptedPrivateKeyInfo .getAlgParameters ());
344353 PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo .getKeySpec (cipher );
345354 String keyAlgo = getKeyAlgorithmIdentifier (keySpec .getEncoded ());
346355 KeyFactory keyFactory = KeyFactory .getInstance (keyAlgo );
347356 return keyFactory .generatePrivate (keySpec );
348357 }
349358
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+
350419 /**
351420 * Decrypts the password protected contents using the algorithm and IV that is specified in the PEM Headers of the file
352421 *
@@ -575,7 +644,7 @@ private static String getKeyAlgorithmIdentifier(byte[] keyBytes) throws IOExcept
575644 return "EC" ;
576645 }
577646 throw new GeneralSecurityException ("Error parsing key algorithm identifier. Algorithm with OID [" + oidString +
578- "] is not żsupported " );
647+ "] is not supported " );
579648 }
580649
581650 public static List <Certificate > readCertificates (Collection <Path > certPaths ) throws CertificateException , IOException {
@@ -593,6 +662,56 @@ public static List<Certificate> readCertificates(Collection<Path> certPaths) thr
593662 return certificates ;
594663 }
595664
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+
596715 private static String getEcCurveNameFromOid (String oidString ) throws GeneralSecurityException {
597716 switch (oidString ) {
598717 // see https://tools.ietf.org/html/rfc5480#section-2.1.1.1
0 commit comments