Fix use of password protected PKCS#8 keys for SSL (#55567)

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
This commit is contained in:
Tim Vernum 2020-04-22 16:38:51 +10:00 committed by GitHub
parent 32e46bf552
commit 8b566aea47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 5 deletions

View File

@ -350,7 +350,6 @@ final class PemUtils {
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes); EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword)); SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword));
Arrays.fill(keyPassword, '\u0000');
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters()); cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters());
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher); PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
@ -639,7 +638,7 @@ final class PemUtils {
case "1.3.132.0.39": case "1.3.132.0.39":
return "sect571r1"; return "sect571r1";
} }
throw new GeneralSecurityException("Error parsing EC named curve identifier. Named curve with OID: " + oidString throw new GeneralSecurityException("Error parsing EC named curve identifier. Named curve with OID: " + oidString
+ " is not supported"); + " is not supported");
} }

View File

@ -45,7 +45,7 @@ public class PemKeyConfigTests extends ESTestCase {
private static final int IP_NAME = 7; private static final int IP_NAME = 7;
private static final int DNS_NAME = 2; private static final int DNS_NAME = 2;
public void testBuildKeyConfigFromPemFilesWithoutPassword() throws Exception { public void testBuildKeyConfigFromPkcs1PemFilesWithoutPassword() throws Exception {
final Path cert = getDataPath("/certs/cert1/cert1.crt"); final Path cert = getDataPath("/certs/cert1/cert1.crt");
final Path key = getDataPath("/certs/cert1/cert1.key"); final Path key = getDataPath("/certs/cert1/cert1.key");
final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]); final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]);
@ -53,7 +53,7 @@ public class PemKeyConfigTests extends ESTestCase {
assertCertificateAndKey(keyConfig, "CN=cert1"); assertCertificateAndKey(keyConfig, "CN=cert1");
} }
public void testBuildKeyConfigFromPemFilesWithPassword() throws Exception { public void testBuildKeyConfigFromPkcs1PemFilesWithPassword() throws Exception {
final Path cert = getDataPath("/certs/cert2/cert2.crt"); final Path cert = getDataPath("/certs/cert2/cert2.crt");
final Path key = getDataPath("/certs/cert2/cert2.key"); final Path key = getDataPath("/certs/cert2/cert2.key");
final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray()); final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray());
@ -61,6 +61,22 @@ public class PemKeyConfigTests extends ESTestCase {
assertCertificateAndKey(keyConfig, "CN=cert2"); assertCertificateAndKey(keyConfig, "CN=cert2");
} }
public void testBuildKeyConfigFromPkcs8PemFilesWithoutPassword() throws Exception {
final Path cert = getDataPath("/certs/cert1/cert1.crt");
final Path key = getDataPath("/certs/cert1/cert1-pkcs8.key");
final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, new char[0]);
assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key));
assertCertificateAndKey(keyConfig, "CN=cert1");
}
public void testBuildKeyConfigFromPkcs8PemFilesWithPassword() throws Exception {
final Path cert = getDataPath("/certs/cert2/cert2.crt");
final Path key = getDataPath("/certs/cert2/cert2-pkcs8.key");
final PemKeyConfig keyConfig = new PemKeyConfig(cert, key, "c2-pass".toCharArray());
assertThat(keyConfig.getDependentFiles(), Matchers.containsInAnyOrder(cert, key));
assertCertificateAndKey(keyConfig, "CN=cert2");
}
public void testKeyManagerFailsWithIncorrectPassword() throws Exception { public void testKeyManagerFailsWithIncorrectPassword() throws Exception {
final Path cert = getDataPath("/certs/cert2/cert2.crt"); final Path cert = getDataPath("/certs/cert2/cert2.crt");
final Path key = getDataPath("/certs/cert2/cert2.key"); final Path key = getDataPath("/certs/cert2/cert2.key");

View File

@ -79,3 +79,7 @@ elasticsearch-certutil ca --pem --out ${PWD}/ca1-b.zip --days 9999 --ca-dn "CN=T
unzip ca1-b.zip unzip ca1-b.zip
mv ca ca1-b mv ca ca1-b
# 12. Convert certifcate keys to pkcs8
openssl pkcs8 -topk8 -inform PEM -in cert1/cert1.key -outform PEM -out cert1/cert1-pkcs8.key -nocrypt
openssl pkcs8 -topk8 -inform PEM -in cert2/cert2.key -outform PEM -out cert2/cert2-pkcs8.key -passin pass:"c2-pass" -passout pass:"c2-pass"

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWz6ITDTlkTLue
B30Jx0+7sWHdlM5ObZjWhMQ1eyJD0gYU/gkH2C88IN/PtSv04tzFS6PA4KPDLIya
AhczPlGElSansiui//CpieCI4tt5c2BgVo3XdJaylYoW3CRILUrlSBOMUmJCQEok
verxMrz8DeppNxRfj99pQkoxUkmFMZj/C7XNVYrTttdF1li5FUtWJxw234OUfum3
PQIzz6YTmoPtLrJ2fB8I4CH8R5hwGcryhBSAqq8pgy61aTPCgEBZ1c4Dvl65X8dG
2QEVPjwMZnnbGjvlZefOgkmAWJ1VjihA3GVgO2mx4tB4D2x5K/OAxh2foZkDVhqJ
fBkOblLnAgMBAAECggEAEKKUkR9rTjn8lADldPesPtrhHaz1WMdUDY2ViwSrEeoP
y6791gStqSdDKMkmMRv5GDYwuOzOg4/dbnt+jaN5IHPHUMYhdBhhNoJD5zWG2g20
+stxV+u/V7GRCtZ7lg6Q7VuW9Gp99irbQtREHxjmqbLrQXHW6HeZQCYUwv39qBhU
mjjILGc0OsyD1SMXhuf/f245/oLuYRUlFOXsRPnBxt2XXQQ5ZbabrSEk5AtI5j03
V5p8UQ75XZdvzQ8La3cyq3n5PufQIoLH+n3gXtADP6Gx+SxjehNRRfAvQqglMt0m
uMKgbiZYn9F7eoQomqYx9PhekkqsXsd+BwpELIfTqQKBgQD/lNVArgbUNzgudIKY
Jv4QJC+YZEG0roCWHdPFvgjAYcMYx8Lxbg6C3TcDxkT3amMhMd3X5wvKQoGyZpWY
S2LIPtgvkbea6ZHVh0wUQFJl3E5N4P3n5otjFOh8+wMhQg9IV5xFbCuLtPJhy9Qb
INyEvS8Nzoan3n9kUWC3bq7ECwKBgQCXDt6DYxwfCz1xIB+i5Qtspt/qNfPLbW3o
J6okYgP+SN1TCzremtxC+YW0Udau/eso7T2GBvcp3eBTPy320APcXvXxtJnDMsBL
A7m6M8dlBVFOi262AIZ4o+BerPJVZ6jjmOLgN9CXneMbRoDUPOhlIDf+jBy5foJU
Y8vNCeF6FQKBgAd71S66qcqG/2ck1DoeUiwo0xf0P5RJ08wRfYT5xonTkwHjv4qQ
PW6JibXblWNlQxfSvPs4cbjvb5rItDKsam0QogXqj2TC2BlXh9vD8mW3KLfREb47
mvNAxnn6Y6ISrB3jKtlBjJjfqIVCkahlsu9UFs+hr4G02ygV1e4pGIb3AoGAGMmF
1cVzndx4TpHY3x/6ie+wGnyT7rOcL1Yi4yl6QkWum6viExkSP6M2P2qWccyUw/h5
+f42nJYd80sQvclQeN7UOL9L4+32A9kuptFMTNVcjCjxF8hqSG2Lqb1zXnROEFrM
D8LY5agw1g7xoOIFuGJbDdfr9rw9op9ll9WhPCkCgYBIJPHnm5OGX2s35cMoVc3n
TQriv5D04E4SR8O4NQRGkGiFR+jLx07l6JOYaKrHk/SGjEQLvS+fDl8nPhmlInst
ykJ+rZN7OSck/cxkrBN0Ez+k4NI8xTlgJbhP2hwRC6Lk8o5MuVWjVAqsbzDp/9gx
9m4Zr3M/8uZMtkRp2j2e6Q==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,29 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6TAbBgkqhkiG9w0BBQMwDgQI3LMAczRe1pICAggABIIEyIfF+k/Z7mEPN+gu
rrV4slxmFjnIzyjoOsWbFBN9j5W8kZGewzWvjnYAzVMVeMUXXW27EMOC818aF8Ry
20BUVkmGNAUFVEslayeF0/S2sYR9xBPqYGLF0DI2VDTBIn8zrTdvQ0DYuuLf4g3W
nY69paCGSVjP24xrnEck+X6PjwIQ89MsJlVLn2chPEetvgu2jB+OdRneSA+lqBSW
2sDuKeXAmYdm3nCVz9do6T22OC9PUkzICGUvMyhYO4CD9dI3FFHa4ncKsr4+htfp
p3eppDczSneDKTPmGJg41UrEVdfENvqYLbYrX2ZZRnWUeuLsGbZUsWyfMT5TT2yz
sZXSS9+WRF8IDDhzKjsoSpsuNwMj8KMmc4oIrBNPysI+Bv0sQzPBlMJkHJ+Swb+W
I2wWC7NS7cwGFEEzwXKYAYI/34e/fAa+oOHd/aNdpGDhhnRyG+Eut3GbojlODiXN
ntLhDIZ+lQclJEkmxP2QfhIvjqkNBkPv+8Xt2Ami3ueF2iNj9PxQHUdx3aht1VMg
uVNLc0qLl98fDYx7+U2q5Y4L1mViZrCwGW+lcaY6a8hscPuasqW9aolFTE95YLVU
yFeUOZlUh1g5FYepISESR5jm5k9wf/WV2cp/KyAzCftdx2iRQtgvyQtSITFtthDa
kR7jI/T/HE2aPqiEcd0Sx+66aSH2JspseJ4VsLGkpg2lU0FPtPaAsl8lIhlfdoDA
43kOPKe5q2YT4QaUNZJROAuyJAlRbE8+LNlfAlZCm0UTQhgiXPzkEuVQFM4DNplq
FkBZnW4R/hD2rP81oy/SISxIANTyAB0FfCRxrvSRP6xe0XMYTIqsUVt9gpszI6AY
B+KTKlJR68cI8Gs1Jq6otn1ExAlCX45mwfaFO6Be89U0YNCZXdJ0X66SFNPmsMrg
eNbRLGziUhyoVUuPEhLXrxtuaGUO0LdHDSCQQGVNKCwNKbcWOE1clYWcJ1Lj0a7T
BTb4y1Nqt7LOHm5En5RtSaourgEKvi6PS2EcemBSUG4xSSP1pEoV/nQnvl2PFsG7
182571HDc7FRZplzE4fhRKetS5WUuKax2FIqljQXS0CJZwV65boJwfJ2FosxdHj4
eZK/wYHxi8R8LQB478bLqp3xZt28/EhsMjtLy/nO9SZfSy7Ajgq2mk2w4HZke23K
qv6JY8/ofu5nBp0QSJIPQj35arkxXnEYjzgqJD1IIDNckt3jRSpeX3OMfmVWDXus
a/wEwNaBACo5lgUYAwzXiPFyLEa6RkbD04z55GqsIjwZhIKrC1glyb0gmtxyY/N8
QfPaSc4aXgUQK2mrgsR22eKf735MhM0pHOykErJzrIV92lRfSuQvfIoqTeFV0fWY
Yxns7HEIBln7/9udgZpf+EWnzM3kpPjRX1iYNtAsMAKEMrK/lnt3jZTkKY2Drem5
NGjtEWNmzKOLH/S0TLcpHldoDJGdno7CytNGasrkFTaDLEhF68QI85zbMmyotKiZ
FKooG78oyxWCYqqPJ9vVBGwsMNfDMJP56T9UNhL72FwNqzSCaEcAUlDm2zCiQWKd
NpOfYzRx8uLp7UyzXbl959GjHWfTA74Yidug13eSHRgKwLXLuro05awG8yNXeDeL
wDswgDyKmlV7JDJjQw==
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -329,7 +329,6 @@ public class PemUtils {
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes); EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(keyBytes);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword)); SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(keyPassword));
Arrays.fill(keyPassword, '\u0000');
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters()); cipher.init(Cipher.DECRYPT_MODE, secretKey, encryptedPrivateKeyInfo.getAlgParameters());
PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher); PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);

View File

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.ssl;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Matchers;
import javax.net.ssl.X509ExtendedKeyManager;
import static org.hamcrest.Matchers.notNullValue;
public class PEMKeyConfigTests extends ESTestCase {
public static final SecureString NO_PASSWORD = new SecureString("".toCharArray());
public static final SecureString TESTNODE_PASSWORD = new SecureString("testnode".toCharArray());
public void testEncryptedPkcs8RsaKey() throws Exception {
verifyKeyConfig("testnode.crt", "key_pkcs8_encrypted.pem", TESTNODE_PASSWORD);
}
public void testUnencryptedPkcs8RsaKey() throws Exception {
verifyKeyConfig("testnode.crt", "rsa_key_pkcs8_plain.pem", NO_PASSWORD);
}
public void testUnencryptedPkcs8DsaKey() throws Exception {
verifyKeyConfig("testnode.crt", "dsa_key_pkcs8_plain.pem", NO_PASSWORD);
}
public void testUnencryptedPkcs8EcKey() throws Exception {
verifyKeyConfig("testnode.crt", "ec_key_pkcs8_plain.pem", NO_PASSWORD);
}
public void testEncryptedPkcs1RsaKey() throws Exception {
verifyKeyConfig("testnode.crt", "testnode-aes" + randomFrom(128, 192, 256) + ".pem", TESTNODE_PASSWORD);
}
public void testUnencryptedPkcs1RsaKey() throws Exception {
verifyKeyConfig("testnode.crt", "testnode-unprotected.pem", NO_PASSWORD);
}
private void verifyKeyConfig(String certName, String keyName, SecureString keyPassword) throws Exception {
final Environment env = TestEnvironment.newEnvironment(buildEnvSettings(Settings.EMPTY));
PEMKeyConfig config = new PEMKeyConfig(getPath(keyName), keyPassword, getPath(certName));
assertThat(config.certificates(env), Matchers.iterableWithSize(1));
X509ExtendedKeyManager keyManager = config.createKeyManager(env);
assertThat(keyManager, notNullValue());
assertThat(keyManager.getPrivateKey("key"), notNullValue());
}
private String getPath(String fileName) {
return getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/" + fileName)
.toAbsolutePath().toString();
}
}