Skip to content

Commit 1d93753

Browse files
authored
Fix use of password protected PKCS#8 keys for SSL (#55907)
PEMUtils would incorrectly fill the encryption password with zeros (the '\0' character) after decrypting a PKCS#8 key. Since PEMUtils did not take ownership of this password it should not zero it out because it does not know whether the caller will use that password array again. This is actually what PEMKeyConfig does - it uses the key encryption password as the password for the ephemeral keystore that it creates in order to build a KeyManager. Backport of: #55457, #55567
1 parent 8e24bcf commit 1d93753

File tree

7 files changed

+146
-5
lines changed

7 files changed

+146
-5
lines changed

libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,6 @@ private static PrivateKey parsePKCS8Encrypted(BufferedReader bReader, char[] key
351351
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes);
352352
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
353353
SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword));
354-
Arrays.fill(keyPassword, '\u0000');
355354
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
356355
cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters());
357356
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
@@ -648,7 +647,7 @@ private static String getEcCurveNameFromOid(String oidString) throws GeneralSecu
648647
case "1.3.132.0.39":
649648
return "sect571r1";
650649
}
651-
throw new GeneralSecurityException("Error parsing EC named curve identifier. Named curve with OID: " + oidString
650+
throw new GeneralSecurityException("Error parsing EC named curve identifier. Named curve with OID: " + oidString
652651
+ " is not supported");
653652
}
654653

libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/PemKeyConfigTests.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,38 @@ public class PemKeyConfigTests extends ESTestCase {
4545
private static final int IP_NAME = 7;
4646
private static final int DNS_NAME = 2;
4747

48-
public void testBuildKeyConfigFromPemFilesWithoutPassword() throws Exception {
48+
public void testBuildKeyConfigFromPkcs1PemFilesWithoutPassword() throws Exception {
4949
final Path cert = getDataPath("/certs/cert1/cert1.crt");
5050
final Path key = getDataPath("/certs/cert1/cert1.key");
5151
final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]);
5252
assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key));
5353
assertCertificateAndKey(keyConfig, "CN=cert1");
5454
}
5555

56-
public void testBuildKeyConfigFromPemFilesWithPassword() throws Exception {
56+
public void testBuildKeyConfigFromPkcs1PemFilesWithPassword() throws Exception {
5757
final Path cert = getDataPath("/certs/cert2/cert2.crt");
5858
final Path key = getDataPath("/certs/cert2/cert2.key");
5959
final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray());
6060
assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key));
6161
assertCertificateAndKey(keyConfig, "CN=cert2");
6262
}
6363

64+
public void testBuildKeyConfigFromPkcs8PemFilesWithoutPassword() throws Exception {
65+
final Path cert = getDataPath("/certs/cert1/cert1.crt");
66+
final Path key = getDataPath("/certs/cert1/cert1-pkcs8.key");
67+
final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]);
68+
assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key));
69+
assertCertificateAndKey(keyConfig, "CN=cert1");
70+
}
71+
72+
public void testBuildKeyConfigFromPkcs8PemFilesWithPassword() throws Exception {
73+
final Path cert = getDataPath("/certs/cert2/cert2.crt");
74+
final Path key = getDataPath("/certs/cert2/cert2-pkcs8.key");
75+
final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray());
76+
assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key));
77+
assertCertificateAndKey(keyConfig, "CN=cert2");
78+
}
79+
6480
public void testKeyManagerFailsWithIncorrectPassword() throws Exception {
6581
final Path cert = getDataPath("/certs/cert2/cert2.crt");
6682
final Path key = getDataPath("/certs/cert2/cert2.key");

libs/ssl-config/src/test/resources/certs/README.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,9 @@ do
7373
keytool -keypasswd -keystore cert-all/certs.jks -alias $Cert -keypass p12-pass -new key-pass -storepass jks-pass
7474
done
7575

76+
# 11. (Not relevant on 6.8 branch)
77+
78+
# 12. Convert certifcate keys to pkcs8
79+
80+
openssl pkcs8 -topk8 -inform PEM -in cert1/cert1.key -outform PEM -out cert1/cert1-pkcs8.key -nocrypt
81+
openssl pkcs8 -topk8 -inform PEM -in cert2/cert2.key -outform PEM -out cert2/cert2-pkcs8.key -passin pass:"c2-pass" -passout pass:"c2-pass"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWz6ITDTlkTLue
3+
B30Jx0+7sWHdlM5ObZjWhMQ1eyJD0gYU/gkH2C88IN/PtSv04tzFS6PA4KPDLIya
4+
AhczPlGElSansiui//CpieCI4tt5c2BgVo3XdJaylYoW3CRILUrlSBOMUmJCQEok
5+
verxMrz8DeppNxRfj99pQkoxUkmFMZj/C7XNVYrTttdF1li5FUtWJxw234OUfum3
6+
PQIzz6YTmoPtLrJ2fB8I4CH8R5hwGcryhBSAqq8pgy61aTPCgEBZ1c4Dvl65X8dG
7+
2QEVPjwMZnnbGjvlZefOgkmAWJ1VjihA3GVgO2mx4tB4D2x5K/OAxh2foZkDVhqJ
8+
fBkOblLnAgMBAAECggEAEKKUkR9rTjn8lADldPesPtrhHaz1WMdUDY2ViwSrEeoP
9+
y6791gStqSdDKMkmMRv5GDYwuOzOg4/dbnt+jaN5IHPHUMYhdBhhNoJD5zWG2g20
10+
+stxV+u/V7GRCtZ7lg6Q7VuW9Gp99irbQtREHxjmqbLrQXHW6HeZQCYUwv39qBhU
11+
mjjILGc0OsyD1SMXhuf/f245/oLuYRUlFOXsRPnBxt2XXQQ5ZbabrSEk5AtI5j03
12+
V5p8UQ75XZdvzQ8La3cyq3n5PufQIoLH+n3gXtADP6Gx+SxjehNRRfAvQqglMt0m
13+
uMKgbiZYn9F7eoQomqYx9PhekkqsXsd+BwpELIfTqQKBgQD/lNVArgbUNzgudIKY
14+
Jv4QJC+YZEG0roCWHdPFvgjAYcMYx8Lxbg6C3TcDxkT3amMhMd3X5wvKQoGyZpWY
15+
S2LIPtgvkbea6ZHVh0wUQFJl3E5N4P3n5otjFOh8+wMhQg9IV5xFbCuLtPJhy9Qb
16+
INyEvS8Nzoan3n9kUWC3bq7ECwKBgQCXDt6DYxwfCz1xIB+i5Qtspt/qNfPLbW3o
17+
J6okYgP+SN1TCzremtxC+YW0Udau/eso7T2GBvcp3eBTPy320APcXvXxtJnDMsBL
18+
A7m6M8dlBVFOi262AIZ4o+BerPJVZ6jjmOLgN9CXneMbRoDUPOhlIDf+jBy5foJU
19+
Y8vNCeF6FQKBgAd71S66qcqG/2ck1DoeUiwo0xf0P5RJ08wRfYT5xonTkwHjv4qQ
20+
PW6JibXblWNlQxfSvPs4cbjvb5rItDKsam0QogXqj2TC2BlXh9vD8mW3KLfREb47
21+
mvNAxnn6Y6ISrB3jKtlBjJjfqIVCkahlsu9UFs+hr4G02ygV1e4pGIb3AoGAGMmF
22+
1cVzndx4TpHY3x/6ie+wGnyT7rOcL1Yi4yl6QkWum6viExkSP6M2P2qWccyUw/h5
23+
+f42nJYd80sQvclQeN7UOL9L4+32A9kuptFMTNVcjCjxF8hqSG2Lqb1zXnROEFrM
24+
D8LY5agw1g7xoOIFuGJbDdfr9rw9op9ll9WhPCkCgYBIJPHnm5OGX2s35cMoVc3n
25+
TQriv5D04E4SR8O4NQRGkGiFR+jLx07l6JOYaKrHk/SGjEQLvS+fDl8nPhmlInst
26+
ykJ+rZN7OSck/cxkrBN0Ez+k4NI8xTlgJbhP2hwRC6Lk8o5MuVWjVAqsbzDp/9gx
27+
9m4Zr3M/8uZMtkRp2j2e6Q==
28+
-----END PRIVATE KEY-----
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
-----BEGIN ENCRYPTED PRIVATE KEY-----
2+
MIIE6TAbBgkqhkiG9w0BBQMwDgQI3LMAczRe1pICAggABIIEyIfF+k/Z7mEPN+gu
3+
rrV4slxmFjnIzyjoOsWbFBN9j5W8kZGewzWvjnYAzVMVeMUXXW27EMOC818aF8Ry
4+
20BUVkmGNAUFVEslayeF0/S2sYR9xBPqYGLF0DI2VDTBIn8zrTdvQ0DYuuLf4g3W
5+
nY69paCGSVjP24xrnEck+X6PjwIQ89MsJlVLn2chPEetvgu2jB+OdRneSA+lqBSW
6+
2sDuKeXAmYdm3nCVz9do6T22OC9PUkzICGUvMyhYO4CD9dI3FFHa4ncKsr4+htfp
7+
p3eppDczSneDKTPmGJg41UrEVdfENvqYLbYrX2ZZRnWUeuLsGbZUsWyfMT5TT2yz
8+
sZXSS9+WRF8IDDhzKjsoSpsuNwMj8KMmc4oIrBNPysI+Bv0sQzPBlMJkHJ+Swb+W
9+
I2wWC7NS7cwGFEEzwXKYAYI/34e/fAa+oOHd/aNdpGDhhnRyG+Eut3GbojlODiXN
10+
ntLhDIZ+lQclJEkmxP2QfhIvjqkNBkPv+8Xt2Ami3ueF2iNj9PxQHUdx3aht1VMg
11+
uVNLc0qLl98fDYx7+U2q5Y4L1mViZrCwGW+lcaY6a8hscPuasqW9aolFTE95YLVU
12+
yFeUOZlUh1g5FYepISESR5jm5k9wf/WV2cp/KyAzCftdx2iRQtgvyQtSITFtthDa
13+
kR7jI/T/HE2aPqiEcd0Sx+66aSH2JspseJ4VsLGkpg2lU0FPtPaAsl8lIhlfdoDA
14+
43kOPKe5q2YT4QaUNZJROAuyJAlRbE8+LNlfAlZCm0UTQhgiXPzkEuVQFM4DNplq
15+
FkBZnW4R/hD2rP81oy/SISxIANTyAB0FfCRxrvSRP6xe0XMYTIqsUVt9gpszI6AY
16+
B+KTKlJR68cI8Gs1Jq6otn1ExAlCX45mwfaFO6Be89U0YNCZXdJ0X66SFNPmsMrg
17+
eNbRLGziUhyoVUuPEhLXrxtuaGUO0LdHDSCQQGVNKCwNKbcWOE1clYWcJ1Lj0a7T
18+
BTb4y1Nqt7LOHm5En5RtSaourgEKvi6PS2EcemBSUG4xSSP1pEoV/nQnvl2PFsG7
19+
182571HDc7FRZplzE4fhRKetS5WUuKax2FIqljQXS0CJZwV65boJwfJ2FosxdHj4
20+
eZK/wYHxi8R8LQB478bLqp3xZt28/EhsMjtLy/nO9SZfSy7Ajgq2mk2w4HZke23K
21+
qv6JY8/ofu5nBp0QSJIPQj35arkxXnEYjzgqJD1IIDNckt3jRSpeX3OMfmVWDXus
22+
a/wEwNaBACo5lgUYAwzXiPFyLEa6RkbD04z55GqsIjwZhIKrC1glyb0gmtxyY/N8
23+
QfPaSc4aXgUQK2mrgsR22eKf735MhM0pHOykErJzrIV92lRfSuQvfIoqTeFV0fWY
24+
Yxns7HEIBln7/9udgZpf+EWnzM3kpPjRX1iYNtAsMAKEMrK/lnt3jZTkKY2Drem5
25+
NGjtEWNmzKOLH/S0TLcpHldoDJGdno7CytNGasrkFTaDLEhF68QI85zbMmyotKiZ
26+
FKooG78oyxWCYqqPJ9vVBGwsMNfDMJP56T9UNhL72FwNqzSCaEcAUlDm2zCiQWKd
27+
NpOfYzRx8uLp7UyzXbl959GjHWfTA74Yidug13eSHRgKwLXLuro05awG8yNXeDeL
28+
wDswgDyKmlV7JDJjQw==
29+
-----END ENCRYPTED PRIVATE KEY-----

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/PemUtils.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ private static PrivateKey parsePKCS8Encrypted(BufferedReader bReader, char[] key
328328
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes);
329329
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
330330
SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword));
331-
Arrays.fill(keyPassword, '\u0000');
332331
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
333332
cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters());
334333
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.core.ssl;
8+
9+
import org.elasticsearch.common.settings.SecureString;
10+
import org.elasticsearch.common.settings.Settings;
11+
import org.elasticsearch.env.Environment;
12+
import org.elasticsearch.env.TestEnvironment;
13+
import org.elasticsearch.test.ESTestCase;
14+
import org.hamcrest.Matchers;
15+
16+
import javax.net.ssl.X509ExtendedKeyManager;
17+
18+
import static org.hamcrest.Matchers.notNullValue;
19+
20+
public class PEMKeyConfigTests extends ESTestCase {
21+
22+
public static final SecureString NO_PASSWORD = new SecureString("".toCharArray());
23+
public static final SecureString TESTNODE_PASSWORD = new SecureString("testnode".toCharArray());
24+
25+
public void testEncryptedPkcs8RsaKey() throws Exception {
26+
verifyKeyConfig("testnode.crt", "key_pkcs8_encrypted.pem", TESTNODE_PASSWORD);
27+
}
28+
29+
public void testUnencryptedPkcs8RsaKey() throws Exception {
30+
verifyKeyConfig("testnode.crt", "rsa_key_pkcs8_plain.pem", NO_PASSWORD);
31+
}
32+
33+
public void testUnencryptedPkcs8DsaKey() throws Exception {
34+
verifyKeyConfig("testnode.crt", "dsa_key_pkcs8_plain.pem", NO_PASSWORD);
35+
}
36+
37+
public void testUnencryptedPkcs8EcKey() throws Exception {
38+
verifyKeyConfig("testnode.crt", "ec_key_pkcs8_plain.pem", NO_PASSWORD);
39+
}
40+
41+
public void testEncryptedPkcs1RsaKey() throws Exception {
42+
verifyKeyConfig("testnode.crt", "testnode-aes" + randomFrom(128, 192, 256) + ".pem", TESTNODE_PASSWORD);
43+
}
44+
45+
public void testUnencryptedPkcs1RsaKey() throws Exception {
46+
verifyKeyConfig("testnode.crt", "testnode-unprotected.pem", NO_PASSWORD);
47+
}
48+
49+
private void verifyKeyConfig(String certName, String keyName, SecureString keyPassword) throws Exception {
50+
Settings settings = Settings.builder().put("path.home", createTempDir()).build();
51+
final Environment env = TestEnvironment.newEnvironment(settings);
52+
PEMKeyConfig config = new PEMKeyConfig(getPath(keyName), keyPassword, getPath(certName));
53+
assertThat(config.certificates(env), Matchers.iterableWithSize(1));
54+
X509ExtendedKeyManager keyManager = config.createKeyManager(env);
55+
assertThat(keyManager, notNullValue());
56+
assertThat(keyManager.getPrivateKey("key"), notNullValue());
57+
}
58+
59+
private String getPath(String fileName) {
60+
return getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/" + fileName)
61+
.toAbsolutePath().toString();
62+
}
63+
64+
}

0 commit comments

Comments
 (0)