From b756f0a75648b151122855ea63f943281afc99ec Mon Sep 17 00:00:00 2001 From: jack-berg <34418638+jack-berg@users.noreply.github.com> Date: Tue, 22 Nov 2022 08:40:21 -0600 Subject: [PATCH] Add support for EC mTLS keys (#4920) * Add support for EC mTlS keys * Revert change to integration test --- .../exporter/internal/TlsUtil.java | 41 ++++++++++-- .../exporter/internal/TlsUtilTest.java | 63 +++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 exporters/common/src/test/java/io/opentelemetry/exporter/internal/TlsUtilTest.java diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsUtil.java index cd529eba68f..83d9d89f2f1 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsUtil.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsUtil.java @@ -6,6 +6,7 @@ package io.opentelemetry.exporter.internal; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -48,6 +49,21 @@ public final class TlsUtil { private static final String PEM_KEY_HEADER = "-----BEGIN PRIVATE KEY-----"; private static final String PEM_KEY_FOOTER = "-----END PRIVATE KEY-----"; + private static final List SUPPORTED_KEY_FACTORIES; + + static { + SUPPORTED_KEY_FACTORIES = new ArrayList<>(); + try { + SUPPORTED_KEY_FACTORIES.add(KeyFactory.getInstance("RSA")); + } catch (NoSuchAlgorithmException e) { + // Ignore and continue + } + try { + SUPPORTED_KEY_FACTORIES.add(KeyFactory.getInstance("EC")); + } catch (NoSuchAlgorithmException e) { + // Ignore and continue + } + } private TlsUtil() {} @@ -83,9 +99,8 @@ public static X509KeyManager keyManager(byte[] privateKeyPem, byte[] certificate try { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null); - KeyFactory factory = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodePem(privateKeyPem)); - PrivateKey key = factory.generatePrivate(keySpec); + PrivateKey key = generatePrivateKey(keySpec, SUPPORTED_KEY_FACTORIES); CertificateFactory cf = CertificateFactory.getInstance("X.509"); @@ -106,12 +121,27 @@ public static X509KeyManager keyManager(byte[] privateKeyPem, byte[] certificate | KeyStoreException | IOException | NoSuchAlgorithmException - | UnrecoverableKeyException - | InvalidKeySpecException e) { + | UnrecoverableKeyException e) { throw new SSLException("Could not build KeyManagerFactory from clientKeysPem.", e); } } + // Visible for testing + static PrivateKey generatePrivateKey(PKCS8EncodedKeySpec keySpec, List keyFactories) + throws SSLException { + // Try to generate key using supported key factories + for (KeyFactory factory : keyFactories) { + try { + return factory.generatePrivate(keySpec); + } catch (InvalidKeySpecException e) { + // Ignore + } + } + throw new SSLException( + "Unable to generate key from supported algorithms: " + + keyFactories.stream().map(KeyFactory::getAlgorithm).collect(joining(",", "[", "]"))); + } + /** Returns a {@link TrustManager} for the given trusted certificates. */ public static X509TrustManager trustManager(byte[] trustedCertificatesPem) throws SSLException { requireNonNull(trustedCertificatesPem, "trustedCertificatesPem"); @@ -140,7 +170,8 @@ public static X509TrustManager trustManager(byte[] trustedCertificatesPem) throw // We catch linkage error to provide a better exception message on Android. // https://github.com/open-telemetry/opentelemetry-java/issues/4533 @IgnoreJRERequirement - private static byte[] decodePem(byte[] pem) { + // Visible for testing + static byte[] decodePem(byte[] pem) { String pemStr = new String(pem, StandardCharsets.UTF_8).trim(); if (!pemStr.startsWith(PEM_KEY_HEADER) || !pemStr.endsWith(PEM_KEY_FOOTER)) { // pem may already be a decoded binary key, try to use it. diff --git a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/TlsUtilTest.java b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/TlsUtilTest.java new file mode 100644 index 00000000000..caca1dee1ab --- /dev/null +++ b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/TlsUtilTest.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.exporter.internal; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import com.linecorp.armeria.internal.common.util.SelfSignedCertificate; +import java.security.KeyFactory; +import java.security.cert.CertificateException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.time.Instant; +import java.util.Collections; +import java.util.Date; +import javax.net.ssl.SSLException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class TlsUtilTest { + + private SelfSignedCertificate rsaCertificate; + private SelfSignedCertificate ecCertificate; + + @BeforeEach + void setup() throws CertificateException { + rsaCertificate = + new SelfSignedCertificate(Date.from(Instant.now()), Date.from(Instant.now()), "RSA", 2048); + ecCertificate = + new SelfSignedCertificate(Date.from(Instant.now()), Date.from(Instant.now()), "EC", 256); + } + + @Test + void keyManager_Rsa() { + assertThatCode( + () -> + TlsUtil.keyManager( + rsaCertificate.key().getEncoded(), rsaCertificate.cert().getEncoded())) + .doesNotThrowAnyException(); + } + + @Test + void keyManager_Ec() { + assertThatCode( + () -> + TlsUtil.keyManager( + ecCertificate.key().getEncoded(), ecCertificate.cert().getEncoded())) + .doesNotThrowAnyException(); + } + + @Test + void generatePrivateKey_Invalid() { + PKCS8EncodedKeySpec keySpec = + new PKCS8EncodedKeySpec(TlsUtil.decodePem(rsaCertificate.key().getEncoded())); + assertThatCode( + () -> + TlsUtil.generatePrivateKey( + keySpec, Collections.singletonList(KeyFactory.getInstance("EC")))) + .isInstanceOf(SSLException.class) + .hasMessage("Unable to generate key from supported algorithms: [EC]"); + } +}