Commit 593f63818eab3461427882fe262081ba63ed6768

Authored by Illia Barkov
Committed by GitHub
1 parent 1d2918f0

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

* Added support for RSA encrypted keys in PEM client credentials

* Refactoring

* Refactoring
@@ -27,21 +27,28 @@ import org.bouncycastle.openssl.PEMEncryptedKeyPair; @@ -27,21 +27,28 @@ import org.bouncycastle.openssl.PEMEncryptedKeyPair;
27 import org.bouncycastle.openssl.PEMKeyPair; 27 import org.bouncycastle.openssl.PEMKeyPair;
28 import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; 28 import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
29 import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; 29 import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
  30 +import org.bouncycastle.util.encoders.Hex;
30 import org.springframework.util.StringUtils; 31 import org.springframework.util.StringUtils;
31 32
32 import javax.crypto.Cipher; 33 import javax.crypto.Cipher;
33 import javax.crypto.EncryptedPrivateKeyInfo; 34 import javax.crypto.EncryptedPrivateKeyInfo;
  35 +import javax.crypto.SecretKey;
34 import javax.crypto.SecretKeyFactory; 36 import javax.crypto.SecretKeyFactory;
  37 +import javax.crypto.spec.IvParameterSpec;
35 import javax.crypto.spec.PBEKeySpec; 38 import javax.crypto.spec.PBEKeySpec;
  39 +import javax.crypto.spec.SecretKeySpec;
36 import javax.net.ssl.KeyManagerFactory; 40 import javax.net.ssl.KeyManagerFactory;
37 import javax.net.ssl.TrustManagerFactory; 41 import javax.net.ssl.TrustManagerFactory;
38 import java.io.ByteArrayInputStream; 42 import java.io.ByteArrayInputStream;
39 import java.io.InputStream; 43 import java.io.InputStream;
  44 +import java.math.BigInteger;
  45 +import java.nio.ByteBuffer;
40 import java.security.AlgorithmParameters; 46 import java.security.AlgorithmParameters;
41 import java.security.Key; 47 import java.security.Key;
42 import java.security.KeyFactory; 48 import java.security.KeyFactory;
43 import java.security.KeyPair; 49 import java.security.KeyPair;
44 import java.security.KeyStore; 50 import java.security.KeyStore;
  51 +import java.security.MessageDigest;
45 import java.security.PrivateKey; 52 import java.security.PrivateKey;
46 import java.security.Security; 53 import java.security.Security;
47 import java.security.cert.Certificate; 54 import java.security.cert.Certificate;
@@ -49,6 +56,9 @@ import java.security.cert.CertificateFactory; @@ -49,6 +56,9 @@ import java.security.cert.CertificateFactory;
49 import java.security.cert.X509Certificate; 56 import java.security.cert.X509Certificate;
50 import java.security.spec.KeySpec; 57 import java.security.spec.KeySpec;
51 import java.security.spec.PKCS8EncodedKeySpec; 58 import java.security.spec.PKCS8EncodedKeySpec;
  59 +import java.security.spec.RSAPrivateCrtKeySpec;
  60 +import java.util.regex.Matcher;
  61 +import java.util.regex.Pattern;
52 62
53 @Data 63 @Data
54 @Slf4j 64 @Slf4j
@@ -61,6 +71,15 @@ public class CertPemCredentials implements ClientCredentials { @@ -61,6 +71,15 @@ public class CertPemCredentials implements ClientCredentials {
61 private String privateKey; 71 private String privateKey;
62 private String password; 72 private String password;
63 73
  74 + static final String OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX = "\\s*"
  75 + + "-----BEGIN RSA PRIVATE KEY-----" + "\\s*"
  76 + + "Proc-Type: 4,ENCRYPTED" + "\\s*"
  77 + + "DEK-Info:" + "\\s*([^\\s]+)" + "\\s+"
  78 + + "([\\s\\S]*)"
  79 + + "-----END RSA PRIVATE KEY-----" + "\\s*";
  80 +
  81 + static final Pattern OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN = Pattern.compile(OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX);
  82 +
64 @Override 83 @Override
65 public CredentialsType getType() { 84 public CredentialsType getType() {
66 return CredentialsType.CERT_PEM; 85 return CredentialsType.CERT_PEM;
@@ -103,7 +122,7 @@ public class CertPemCredentials implements ClientCredentials { @@ -103,7 +122,7 @@ public class CertPemCredentials implements ClientCredentials {
103 KeyPair key = keyConverter.getKeyPair((PEMKeyPair) keyObject); 122 KeyPair key = keyConverter.getKeyPair((PEMKeyPair) keyObject);
104 privateKey = key.getPrivate(); 123 privateKey = key.getPrivate();
105 } else if (keyObject instanceof PrivateKey) { 124 } else if (keyObject instanceof PrivateKey) {
106 - privateKey = (PrivateKey)keyObject; 125 + privateKey = (PrivateKey) keyObject;
107 } else { 126 } else {
108 throw new RuntimeException("Unable to get private key from object: " + keyObject.getClass()); 127 throw new RuntimeException("Unable to get private key from object: " + keyObject.getClass());
109 } 128 }
@@ -152,34 +171,144 @@ public class CertPemCredentials implements ClientCredentials { @@ -152,34 +171,144 @@ public class CertPemCredentials implements ClientCredentials {
152 private PrivateKey readPrivateKeyFile(String fileContent) throws Exception { 171 private PrivateKey readPrivateKeyFile(String fileContent) throws Exception {
153 PrivateKey privateKey = null; 172 PrivateKey privateKey = null;
154 if (fileContent != null && !fileContent.isEmpty()) { 173 if (fileContent != null && !fileContent.isEmpty()) {
155 - fileContent = fileContent.replaceAll(".*BEGIN.*PRIVATE KEY.*", "")  
156 - .replaceAll(".*END.*PRIVATE KEY.*", "")  
157 - .replaceAll("\\s", "");  
158 - byte[] decoded = Base64.decodeBase64(fileContent);  
159 KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 174 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
160 - KeySpec keySpec = getKeySpec(decoded); 175 + KeySpec keySpec = getKeySpec(fileContent);
161 privateKey = keyFactory.generatePrivate(keySpec); 176 privateKey = keyFactory.generatePrivate(keySpec);
162 } 177 }
163 return privateKey; 178 return privateKey;
164 } 179 }
165 180
166 - private KeySpec getKeySpec(byte[] encodedKey) throws Exception {  
167 - KeySpec keySpec;  
168 - if (password == null || password.isEmpty()) {  
169 - keySpec = new PKCS8EncodedKeySpec(encodedKey); 181 + private KeySpec getKeySpec(String encodedKey) throws Exception {
  182 + KeySpec keySpec = null;
  183 + Matcher matcher = OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN.matcher(encodedKey);
  184 + if (matcher.matches()) {
  185 + String encryptionDetails = matcher.group(1).trim();
  186 + String encryptedKey = matcher.group(2).replaceAll("\\s", "");
  187 + byte[] encryptedBinaryKey = java.util.Base64.getDecoder().decode(encryptedKey);
  188 + String[] encryptionDetailsParts = encryptionDetails.split(",");
  189 + if (encryptionDetailsParts.length == 2) {
  190 + String encryptionAlgorithm = encryptionDetailsParts[0];
  191 + String encryptedAlgorithmParams = encryptionDetailsParts[1];
  192 + byte[] pw = password.getBytes();
  193 + byte[] iv = Hex.decode(encryptedAlgorithmParams);
  194 +
  195 + MessageDigest digest = MessageDigest.getInstance("MD5");
  196 + digest.update(pw);
  197 + digest.update(iv, 0, 8);
  198 +
  199 + byte[] round1Digest = digest.digest();
  200 + digest.update(round1Digest);
  201 + digest.update(pw);
  202 + digest.update(iv, 0, 8);
  203 +
  204 + byte[] round2Digest = digest.digest();
  205 + Cipher cipher = null;
  206 + SecretKey secretKey = null;
  207 + byte[] key = null;
  208 +
  209 + switch(encryptionAlgorithm) {
  210 + case "AES-256-CBC":
  211 + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  212 + key = new byte[32];
  213 + System.arraycopy(round1Digest, 0, key, 0, 16);
  214 + System.arraycopy(round2Digest, 0, key, 16, 16);
  215 + secretKey = new SecretKeySpec(key, "AES");
  216 + break;
  217 + case "AES-192-CBC":
  218 + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  219 + key = new byte[24];
  220 + System.arraycopy(round1Digest, 0, key, 0, 16);
  221 + System.arraycopy(round2Digest, 0, key, 16, 8);
  222 + secretKey = new SecretKeySpec(key, "AES");
  223 + break;
  224 + case "AES-128-CBC":
  225 + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  226 + key = new byte[16];
  227 + System.arraycopy(round1Digest, 0, key, 0, 16);
  228 + secretKey = new SecretKeySpec(key, "AES");
  229 + break;
  230 + case "DES-EDE3-CBC":
  231 + cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
  232 + key = new byte[24];
  233 + System.arraycopy(round1Digest, 0, key, 0, 16);
  234 + System.arraycopy(round2Digest, 0, key, 16, 8);
  235 + secretKey = new SecretKeySpec(key, "DESede");
  236 + break;
  237 + case "DES-CBC":
  238 + cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
  239 + key = new byte[8];
  240 + System.arraycopy(round1Digest, 0, key, 0, 8);
  241 + secretKey = new SecretKeySpec(key, "DES");
  242 + break;
  243 + }
  244 + if (cipher != null) {
  245 + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
  246 + byte[] pkcs1 = cipher.doFinal(encryptedBinaryKey);
  247 + keySpec = decodeRSAPrivatePKCS1(pkcs1);
  248 + } else {
  249 + throw new RuntimeException("Unknown Encryption algorithm!");
  250 + }
  251 + } else {
  252 + throw new RuntimeException("Wrong encryption details!");
  253 + }
170 } else { 254 } else {
171 - PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray()); 255 + encodedKey = encodedKey.replaceAll(".*BEGIN.*PRIVATE KEY.*", "")
  256 + .replaceAll(".*END.*PRIVATE KEY.*", "")
  257 + .replaceAll("\\s", "");
  258 + byte[] decoded = Base64.decodeBase64(encodedKey);
  259 + if (password == null || password.isEmpty()) {
  260 + keySpec = new PKCS8EncodedKeySpec(decoded);
  261 + } else {
  262 + PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
172 263
173 - EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(encodedKey);  
174 - String algorithmName = privateKeyInfo.getAlgName();  
175 - Cipher cipher = Cipher.getInstance(algorithmName);  
176 - SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithmName); 264 + EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(decoded);
  265 + String algorithmName = privateKeyInfo.getAlgName();
  266 + Cipher cipher = Cipher.getInstance(algorithmName);
  267 + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithmName);
177 268
178 - Key pbeKey = secretKeyFactory.generateSecret(pbeKeySpec);  
179 - AlgorithmParameters algParams = privateKeyInfo.getAlgParameters();  
180 - cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);  
181 - keySpec = privateKeyInfo.getKeySpec(cipher); 269 + Key pbeKey = secretKeyFactory.generateSecret(pbeKeySpec);
  270 + AlgorithmParameters algParams = privateKeyInfo.getAlgParameters();
  271 + cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
  272 + keySpec = privateKeyInfo.getKeySpec(cipher);
  273 + }
182 } 274 }
183 return keySpec; 275 return keySpec;
184 } 276 }
  277 +
  278 + private static BigInteger derint(ByteBuffer input) {
  279 + int len = der(input, 0x02);
  280 + byte[] value = new byte[len];
  281 + input.get(value);
  282 + return new BigInteger(+1, value);
  283 + }
  284 +
  285 + private static int der(ByteBuffer input, int exp) {
  286 + int tag = input.get() & 0xFF;
  287 + if (tag != exp) throw new IllegalArgumentException("Unexpected tag");
  288 + int n = input.get() & 0xFF;
  289 + if (n < 128) return n;
  290 + n &= 0x7F;
  291 + if ((n < 1) || (n > 2)) throw new IllegalArgumentException("Invalid length");
  292 + int len = 0;
  293 + while (n-- > 0) {
  294 + len <<= 8;
  295 + len |= input.get() & 0xFF;
  296 + }
  297 + return len;
  298 + }
  299 +
  300 + static RSAPrivateCrtKeySpec decodeRSAPrivatePKCS1(byte[] encoded) {
  301 + ByteBuffer input = ByteBuffer.wrap(encoded);
  302 + if (der(input, 0x30) != input.remaining()) throw new IllegalArgumentException("Excess data");
  303 + if (!BigInteger.ZERO.equals(derint(input))) throw new IllegalArgumentException("Unsupported version");
  304 + BigInteger n = derint(input);
  305 + BigInteger e = derint(input);
  306 + BigInteger d = derint(input);
  307 + BigInteger p = derint(input);
  308 + BigInteger q = derint(input);
  309 + BigInteger ep = derint(input);
  310 + BigInteger eq = derint(input);
  311 + BigInteger c = derint(input);
  312 + return new RSAPrivateCrtKeySpec(n, e, d, p, q, ep, eq, c);
  313 + }
185 } 314 }