Skip to content

Commit 566770e

Browse files
committed
fixup! Validate exipry trust anchor
1 parent aec1249 commit 566770e

File tree

6 files changed

+143
-1
lines changed

6 files changed

+143
-1
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/CrossClusterApiKeySignatureManager.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,17 +189,60 @@ public boolean verify(X509CertificateSignature signature, String... headers) thr
189189
leaf.getPublicKey().getAlgorithm()
190190
);
191191
}
192+
193+
authTrustManager.checkClientTrusted(signature.certificates(), leaf.getPublicKey().getAlgorithm());
194+
192195
for (var certificate : signature.certificates()) {
193196
certificate.checkValidity();
194197
}
195198

196-
authTrustManager.checkClientTrusted(signature.certificates(), leaf.getPublicKey().getAlgorithm());
199+
var trustAnchor = findTrustAnchor(signature.topCertificate(), trustManager.get().getAcceptedIssuers());
200+
assert trustAnchor != null : Strings.format("Failed to find trust anchor for [%s]", signature.topCertificate());
201+
202+
trustAnchor.checkValidity();
197203

198204
final Signature signer = Signature.getInstance(signature.algorithm());
199205
signer.initVerify(leaf);
200206
signer.update(getSignableBytes(headers));
201207
return signer.verify(signature.signature().array());
202208
}
209+
210+
/**
211+
* Find the certificate that issued the certificate at the top of the current chain. A certificate chain is a list of
212+
* certificates followed by one or more CA certificates (usually the last one being a self-signed certificate), with the
213+
* following properties:
214+
* <br>
215+
* 1. The Issuer of each certificate (except the last one) matches the Subject of the next certificate in the list.
216+
* 2. Each certificate (except the last one) is signed by the secret key corresponding to the next certificate in the chain (i.e.
217+
* the signature of one certificate can be verified using the public key contained in the following certificate).
218+
* 3. The last certificate in the list is a trust anchor.
219+
*/
220+
private X509Certificate findTrustAnchor(X509Certificate topCert, X509Certificate[] trustAnchors) {
221+
X500Principal issuer = topCert.getIssuerX500Principal();
222+
223+
for (X509Certificate anchor : trustAnchors) {
224+
// Check if the top cert itself is the trust anchor (handles directly trusted certs and full chains)
225+
// Per X.509 spec, a certificate is uniquely identified by issuer DN + serial number
226+
if (anchor.getIssuerX500Principal().equals(topCert.getIssuerX500Principal())
227+
&& anchor.getSerialNumber().equals(topCert.getSerialNumber())) {
228+
return anchor;
229+
}
230+
231+
if (anchor.getSubjectX500Principal().equals(issuer)) {
232+
try {
233+
// Verify this anchor actually signed the top cert
234+
topCert.verify(anchor.getPublicKey());
235+
return anchor;
236+
} catch (GeneralSecurityException e) {
237+
logger.trace(
238+
"Trust anchor [{}] matches issuer name but did not sign the certificate",
239+
anchor.getSubjectX500Principal()
240+
);
241+
}
242+
}
243+
}
244+
return null;
245+
}
203246
}
204247

205248
public class Signer {

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/X509CertificateSignature.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public X509Certificate leafCertificate() {
7979
return certificateChain[0];
8080
}
8181

82+
public X509Certificate topCertificate() {
83+
return certificateChain[certificateChain.length - 1];
84+
}
85+
8286
public String algorithm() {
8387
return algorithm;
8488
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/CrossClusterApiKeySignatureManagerTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,31 @@ public void testSignAndVerifyExpiredCertFails() {
229229
assertThat(exception.getMessage(), containsString(inFipsJvm() ? "certificate expired on" : "NotAfter"));
230230
}
231231

232+
public void testSignAndVerifyExpiredTrustAnchorCertFails() {
233+
var builder = Settings.builder()
234+
.put("path.home", createTempDir())
235+
.put(Node.NODE_NAME_SETTING.getKey(), randomAlphaOfLengthBetween(3, 8));
236+
237+
builder.put(
238+
"cluster.remote.signing.certificate_authorities",
239+
getDataPath("/org/elasticsearch/xpack/security/signature/expired_ca_cert.crt")
240+
)
241+
.put(
242+
"cluster.remote.my_remote.signing.certificate",
243+
getDataPath("/org/elasticsearch/xpack/security/signature/valid_cert_with_expired_ca.crt")
244+
)
245+
.put(
246+
"cluster.remote.my_remote.signing.key",
247+
getDataPath("/org/elasticsearch/xpack/security/signature/valid_key_with_expired_ca.key")
248+
);
249+
250+
var manager = new CrossClusterApiKeySignatureManager(TestEnvironment.newEnvironment(builder.build()));
251+
var signature = manager.signerForClusterAlias("my_remote").sign("a_header");
252+
var verifier = manager.verifier();
253+
var exception = assertThrows(CertificateException.class, () -> verifier.verify(signature, "test"));
254+
assertThat(exception.getMessage(), containsString(inFipsJvm() ? "certificate expired on" : "NotAfter"));
255+
}
256+
232257
private void addStorePathToBuilder(String storeName, String password, String passwordFips, Settings.Builder builder) {
233258
String storeType = inFipsJvm() ? "BCFKS" : "PKCS12";
234259
String extension = inFipsJvm() ? ".bcfks" : ".jks";
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDSjCCAjKgAwIBAgIUFZpIskODPIOEFLzG1VL/Oqes+g0wDQYJKoZIhvcNAQEL
3+
BQAwPTEYMBYGA1UEAwwPRXhwaXJlZCBUZXN0IENBMRQwEgYDVQQKDAtUZXN0IENB
4+
IE9yZzELMAkGA1UEBhMCVVMwHhcNMDAwMTAxMDAwMDAwWhcNMTAwMTAxMDAwMDAw
5+
WjA9MRgwFgYDVQQDDA9FeHBpcmVkIFRlc3QgQ0ExFDASBgNVBAoMC1Rlc3QgQ0Eg
6+
T3JnMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
7+
AMDDDRT20V+krmukCsLk2uGRA49qWPaXaMLnchJYSGvy9FAh7ziqcjlG2d5MiBzf
8+
GGB4GBK6zsysYxoaUeJxDSRtFEsSXy4iMyDRU0FmUM3ORhymLdQYZXRC/wWnzruc
9+
PCwsfqUbnBvOqOLHHIboWxkvp3HZokqioiBG4VZuTMlxMvpIklHlw8JmQnL+35+a
10+
TFlJYSozh2Kld3g0g1UEwESMoO4t2xDwFO2O73wk/zryeBmPpKkt9CS2Nc759fEV
11+
VFbXZmcbJO6+WwxZEQS4rrX8vI7eKs/TPGQyeMevJZi0PYnZqP601Lx0y+4y/PiN
12+
JqtrdGSRdNklSlhHET3R0c8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
13+
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFIIJuktlwdkN4v6sgRZSql7X4iliMA0GCSqG
14+
SIb3DQEBCwUAA4IBAQBwvmoOdFyjHUx8V99zcgd8HOQ/XB7Uctt9te/NfLlhVCN0
15+
ek0feLWmMJr9XqYM1CJ9jVG78ZS56FitwHkXWRmTqr69+WaZRTrW5csNroyICBS4
16+
BHhbOM7iEVRenVyGVYGadhBYVAVn/9mkRVARQiIUheNK/3v9pyMuSLf4Sd5QYs6p
17+
svXin434/PxFj3Teoplqv+iXsPwy7QU5jq+KXl0PWDkxc3Ku7tgc/1Nsk5Zlf3ck
18+
pQNsdZK979Zx+aTLIbkZnYojgqJSO+2nwTTQj5xZziK9ckeAHvD7VIH5hMDmwAzf
19+
bjXnaZG0Bfb6vE67G0STGWqNto7M6sXMgL6UgLgm
20+
-----END CERTIFICATE-----
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDxTCCAq2gAwIBAgIUOLClxkGLN1IzdCtlAzyv7ubfnGswDQYJKoZIhvcNAQEL
3+
BQAwPTEYMBYGA1UEAwwPRXhwaXJlZCBUZXN0IENBMRQwEgYDVQQKDAtUZXN0IENB
4+
IE9yZzELMAkGA1UEBhMCVVMwIBcNMjUxMDIyMDkyNDIwWhgPMjEyNTA5MjgwOTI5
5+
MjBaMEIxIDAeBgNVBAMMF3ZhbGlkLWxlYWYuZXhhbXBsZS50ZXN0MREwDwYDVQQK
6+
DAhMZWFmIE9yZzELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
7+
ggEKAoIBAQCesxBHTVrGTnbZ7afvsHEE1xpVDdA8eZgOakFa6JJW4zwdRGIfUbw/
8+
yWTBiZpq2I1Xi7dbY6ReH09UtbWUPCXrhZ71Vq4MO2FSd1knZqXIoqjX9jRAGT5J
9+
+WU3tPNL2aN7fGSQRHKR9mO/TYhTf9yQGCRHzzpLNHJ1RXOKEkdmhsZZ36wz0LKW
10+
Ps01gzFsTnp8T4SpttJryW2f74q0ZJvT2oSynqyuJS5MbSwqHl2nyeao8Z8BNVe3
11+
EOKQAWHUUzpf1xy0/p6Eb4LQObxl9N8sJ3zHX/CcwMpcgh/NRsbQn+TiKJwVkGa3
12+
SU+UxKlCD1+rTkIr//J91njgictfoXnbAgMBAAGjgbUwgbIwDAYDVR0TAQH/BAIw
13+
ADAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC
14+
MDMGA1UdEQQsMCqCF3ZhbGlkLWxlYWYuZXhhbXBsZS50ZXN0gglsb2NhbGhvc3SH
15+
BH8AAAEwHwYDVR0jBBgwFoAUggm6S2XB2Q3i/qyBFlKqXtfiKWIwHQYDVR0OBBYE
16+
FEqSu+i1sHYHANJN5DzvUCZYo6hhMA0GCSqGSIb3DQEBCwUAA4IBAQCDYdk9rdz9
17+
/q4WvH3FwCA5BwFuiaglL8w0k0DhRCuJBd94YdnmMqNbf8C4elJJF6z1nH6Fv2+9
18+
2KFQ26VsTchGSDrTy+GGT9F7xMQl/pXAvH0aQs/Km1egtWV6GKb0z1HPqyni0Xet
19+
swJZ6JKlArKjUKGzKCq7fKBm6BR9xvPj5K2wsfRAozUUFfaOou/jBZ0PDpAZoyZb
20+
MDMh0EfcvKsOh2VesMsfK3bwXbCScjmkSsIebTC+jWHKpoEm2SlVHAMJRSRWIVV1
21+
AgKMYcwLQRNcslkS7I6HtEjF79vhR54Yu5kxSuU9QiGgYmccfUrzxikg5M+s7nQm
22+
zbQhWJ68ME07
23+
-----END CERTIFICATE-----
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEowIBAAKCAQEAnrMQR01axk522e2n77BxBNcaVQ3QPHmYDmpBWuiSVuM8HURi
3+
H1G8P8lkwYmaatiNV4u3W2OkXh9PVLW1lDwl64We9VauDDthUndZJ2alyKKo1/Y0
4+
QBk+SfllN7TzS9mje3xkkERykfZjv02IU3/ckBgkR886SzRydUVzihJHZobGWd+s
5+
M9Cylj7NNYMxbE56fE+EqbbSa8ltn++KtGSb09qEsp6sriUuTG0sKh5dp8nmqPGf
6+
ATVXtxDikAFh1FM6X9cctP6ehG+C0Dm8ZfTfLCd8x1/wnMDKXIIfzUbG0J/k4iic
7+
FZBmt0lPlMSpQg9fq05CK//yfdZ44InLX6F52wIDAQABAoIBACuB7dGObHd3ZXAD
8+
jonQtntcOVTeD5u0vjIdgUNaBVyauY1QhRIPB2v5W40Pm2z1Z3J71E3SuGoxbT9M
9+
/bXg84hpPpYGKHskAF9qZt+9bW/e4Cksz1BPW1tOayhljFncFcyx4qQGj95iTSBS
10+
MjCqGh7K749cSZ/6hfKOkslkj3yUwdlA8I27MO4iDMhPj6z+eubHV2G3ypfefGlD
11+
QQ7ljuFodobtR7udUVWYnPhuScJyUvHG//uCMaLLaW2rWXD3jv0MUbIfTdYUetig
12+
p0swMeZGUmGh1zlvlJ+l2LBJMXeEi6gzWsHaRO6JJHw/nq64nVb8mq7dCiljRVGv
13+
G8THEFkCgYEAzoS7zVl1A4M/DbJLJ7i/ZWlT48McAizYx9oq1eAgObrdmDe2Jbvm
14+
TIwgS1RUfjfViRwgWbuR0X7xt9OIT2QnDGqkOnDJzu764XtLT/D4TNJA1QG3H4L2
15+
Jy0JksMLhoUXIormGhAV0onSSfNV5SqHt//OrDAQKwfCIUXh9+62LbcCgYEAxLk+
16+
sEtHp9EzUGYayUbXghWk1u3/RiuEtlx2sS3v+qtbOGAfyXzPYAHJVFiHFo2/JG1Q
17+
rsYVx5rj/YoQwiFHjR0lkLeEpRqhXv1s1goaUiuBVxzmHX8V97QPLfzEuQLabZwM
18+
9pWLH8zGTKrSIooRb4I1HU7ooElVYDX02JYcFP0CgYEAiJc953ntbN9XyuVL0//b
19+
h2V8uL4JPl8PGk/v2PmeFtDDU7Q1Ywu+LI7ZpTknkTu4njDeLLtknJ1LnnvoQipJ
20+
sWqvKIAE2jsx8ASuMTd94sGFY9z4k3z49bxSAqHCc7x/KreXrVFKPbAuR/8LpsDU
21+
dxxYQ4aeivdcrMkdxfA6yk0CgYAEhgn1/dUo+7uFVsO46yMbf6nps1FSaL/FfbzQ
22+
+DBzgCs50aQJexA9sezSPrLkht/lU4ouaqmnjF0/wEQAYsmFai0p9b5cGY+qYoN1
23+
LIhMaWmw+h4kgX6c0owiz5QqePFS4eq+ZNPtKEVLEAaC+s/J06GrCdx5ixYmfzch
24+
H9qHdQKBgAvOPI7f8bLNJManzHfcnnNnxrKYg1J3YNvRJa+QkGR3sXQULT2NRiT/
25+
35NrOnKo0pwCgVMgO2zkSG3jQMCG9Yd9hE5fRHCwjhMUK5i7R4xS5FwrvowO+IMW
26+
g0yyx7tg4keTdlKo9KuJSVlv+tnRIX8ZmYxBMcdtIbLEoMTbA4Yh
27+
-----END RSA PRIVATE KEY-----

0 commit comments

Comments
 (0)