Added support for RSA encrypted keys in PEM client credentials (#4230)

* Added support for RSA encrypted keys in PEM client credentials

* Refactoring

* Refactoring
This commit is contained in:
Illia Barkov 2021-03-15 11:40:23 +02:00 committed by GitHub
parent 1d2918f0a1
commit 593f63818e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -27,21 +27,28 @@ import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory; import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters; import java.security.AlgorithmParameters;
import java.security.Key; import java.security.Key;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Security; import java.security.Security;
import java.security.cert.Certificate; import java.security.cert.Certificate;
@ -49,6 +56,9 @@ import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.spec.KeySpec; import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Data @Data
@Slf4j @Slf4j
@ -61,6 +71,15 @@ public class CertPemCredentials implements ClientCredentials {
private String privateKey; private String privateKey;
private String password; private String password;
static final String OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX = "\\s*"
+ "-----BEGIN RSA PRIVATE KEY-----" + "\\s*"
+ "Proc-Type: 4,ENCRYPTED" + "\\s*"
+ "DEK-Info:" + "\\s*([^\\s]+)" + "\\s+"
+ "([\\s\\S]*)"
+ "-----END RSA PRIVATE KEY-----" + "\\s*";
static final Pattern OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN = Pattern.compile(OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX);
@Override @Override
public CredentialsType getType() { public CredentialsType getType() {
return CredentialsType.CERT_PEM; return CredentialsType.CERT_PEM;
@ -152,25 +171,97 @@ public class CertPemCredentials implements ClientCredentials {
private PrivateKey readPrivateKeyFile(String fileContent) throws Exception { private PrivateKey readPrivateKeyFile(String fileContent) throws Exception {
PrivateKey privateKey = null; PrivateKey privateKey = null;
if (fileContent != null && !fileContent.isEmpty()) { if (fileContent != null && !fileContent.isEmpty()) {
fileContent = fileContent.replaceAll(".*BEGIN.*PRIVATE KEY.*", "")
.replaceAll(".*END.*PRIVATE KEY.*", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.decodeBase64(fileContent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); KeyFactory keyFactory = KeyFactory.getInstance("RSA");
KeySpec keySpec = getKeySpec(decoded); KeySpec keySpec = getKeySpec(fileContent);
privateKey = keyFactory.generatePrivate(keySpec); privateKey = keyFactory.generatePrivate(keySpec);
} }
return privateKey; return privateKey;
} }
private KeySpec getKeySpec(byte[] encodedKey) throws Exception { private KeySpec getKeySpec(String encodedKey) throws Exception {
KeySpec keySpec; KeySpec keySpec = null;
Matcher matcher = OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN.matcher(encodedKey);
if (matcher.matches()) {
String encryptionDetails = matcher.group(1).trim();
String encryptedKey = matcher.group(2).replaceAll("\\s", "");
byte[] encryptedBinaryKey = java.util.Base64.getDecoder().decode(encryptedKey);
String[] encryptionDetailsParts = encryptionDetails.split(",");
if (encryptionDetailsParts.length == 2) {
String encryptionAlgorithm = encryptionDetailsParts[0];
String encryptedAlgorithmParams = encryptionDetailsParts[1];
byte[] pw = password.getBytes();
byte[] iv = Hex.decode(encryptedAlgorithmParams);
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(pw);
digest.update(iv, 0, 8);
byte[] round1Digest = digest.digest();
digest.update(round1Digest);
digest.update(pw);
digest.update(iv, 0, 8);
byte[] round2Digest = digest.digest();
Cipher cipher = null;
SecretKey secretKey = null;
byte[] key = null;
switch(encryptionAlgorithm) {
case "AES-256-CBC":
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[32];
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 16);
secretKey = new SecretKeySpec(key, "AES");
break;
case "AES-192-CBC":
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[24];
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 8);
secretKey = new SecretKeySpec(key, "AES");
break;
case "AES-128-CBC":
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[16];
System.arraycopy(round1Digest, 0, key, 0, 16);
secretKey = new SecretKeySpec(key, "AES");
break;
case "DES-EDE3-CBC":
cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
key = new byte[24];
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 8);
secretKey = new SecretKeySpec(key, "DESede");
break;
case "DES-CBC":
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
key = new byte[8];
System.arraycopy(round1Digest, 0, key, 0, 8);
secretKey = new SecretKeySpec(key, "DES");
break;
}
if (cipher != null) {
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] pkcs1 = cipher.doFinal(encryptedBinaryKey);
keySpec = decodeRSAPrivatePKCS1(pkcs1);
} else {
throw new RuntimeException("Unknown Encryption algorithm!");
}
} else {
throw new RuntimeException("Wrong encryption details!");
}
} else {
encodedKey = encodedKey.replaceAll(".*BEGIN.*PRIVATE KEY.*", "")
.replaceAll(".*END.*PRIVATE KEY.*", "")
.replaceAll("\\s", "");
byte[] decoded = Base64.decodeBase64(encodedKey);
if (password == null || password.isEmpty()) { if (password == null || password.isEmpty()) {
keySpec = new PKCS8EncodedKeySpec(encodedKey); keySpec = new PKCS8EncodedKeySpec(decoded);
} else { } else {
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(encodedKey); EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(decoded);
String algorithmName = privateKeyInfo.getAlgName(); String algorithmName = privateKeyInfo.getAlgName();
Cipher cipher = Cipher.getInstance(algorithmName); Cipher cipher = Cipher.getInstance(algorithmName);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithmName); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithmName);
@ -180,6 +271,44 @@ public class CertPemCredentials implements ClientCredentials {
cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams); cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
keySpec = privateKeyInfo.getKeySpec(cipher); keySpec = privateKeyInfo.getKeySpec(cipher);
} }
}
return keySpec; return keySpec;
} }
private static BigInteger derint(ByteBuffer input) {
int len = der(input, 0x02);
byte[] value = new byte[len];
input.get(value);
return new BigInteger(+1, value);
}
private static int der(ByteBuffer input, int exp) {
int tag = input.get() & 0xFF;
if (tag != exp) throw new IllegalArgumentException("Unexpected tag");
int n = input.get() & 0xFF;
if (n < 128) return n;
n &= 0x7F;
if ((n < 1) || (n > 2)) throw new IllegalArgumentException("Invalid length");
int len = 0;
while (n-- > 0) {
len <<= 8;
len |= input.get() & 0xFF;
}
return len;
}
static RSAPrivateCrtKeySpec decodeRSAPrivatePKCS1(byte[] encoded) {
ByteBuffer input = ByteBuffer.wrap(encoded);
if (der(input, 0x30) != input.remaining()) throw new IllegalArgumentException("Excess data");
if (!BigInteger.ZERO.equals(derint(input))) throw new IllegalArgumentException("Unsupported version");
BigInteger n = derint(input);
BigInteger e = derint(input);
BigInteger d = derint(input);
BigInteger p = derint(input);
BigInteger q = derint(input);
BigInteger ep = derint(input);
BigInteger eq = derint(input);
BigInteger c = derint(input);
return new RSAPrivateCrtKeySpec(n, e, d, p, q, ep, eq, c);
}
} }