From c45a0f49e66bc11cab94e753055bb84616da4ad3 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Mon, 7 Nov 2022 11:26:12 -0600 Subject: [PATCH 1/2] Add support for EC mTlS keys --- .../exporter/internal/TlsUtil.java | 41 ++++++++++-- .../exporter/internal/TlsUtilTest.java | 63 +++++++++++++++++++ .../OtlpExporterIntegrationTest.java | 54 ++++++++-------- 3 files changed, 128 insertions(+), 30 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]"); + } +} diff --git a/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java b/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java index e0f0dd5a789..53a8716ed4c 100644 --- a/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java +++ b/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java @@ -11,10 +11,10 @@ import static org.testcontainers.Testcontainers.exposeHostPorts; import com.google.protobuf.InvalidProtocolBufferException; +import com.linecorp.armeria.internal.common.util.SelfSignedCertificate; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.grpc.protocol.AbstractUnaryGrpcService; -import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.Attributes; @@ -68,10 +68,12 @@ import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import java.io.UncheckedIOException; +import java.security.cert.CertificateException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.concurrent.CompletionStage; import org.junit.jupiter.api.AfterAll; @@ -79,7 +81,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.LoggerFactory; @@ -113,17 +114,22 @@ abstract class OtlpExporterIntegrationTest { .put(ResourceAttributes.SERVICE_NAME, "integration test") .build(); - @RegisterExtension - static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension(); - - @RegisterExtension - static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension(); + static SelfSignedCertificate serverTls; + static SelfSignedCertificate clientTls; private static OtlpGrpcServer grpcServer; private static GenericContainer collector; @BeforeAll - static void beforeAll() { + static void beforeAll() throws CertificateException { + serverTls = new SelfSignedCertificate(); + clientTls = + new SelfSignedCertificate( + Date.from(Instant.now().minusSeconds(365 * 24 * 3600)), + Date.from(Instant.ofEpochMilli(253402300799000L)), + "EC", + 256); + grpcServer = new OtlpGrpcServer(); grpcServer.start(); @@ -136,13 +142,11 @@ static void beforeAll() { .withImagePullPolicy(PullPolicy.alwaysPull()) .withEnv("LOGGING_EXPORTER_LOG_LEVEL", "INFO") .withCopyFileToContainer( - MountableFile.forHostPath(serverTls.certificateFile().toPath(), 0555), - "/server.cert") + MountableFile.forHostPath(serverTls.certificate().toPath(), 0555), "/server.cert") .withCopyFileToContainer( - MountableFile.forHostPath(serverTls.privateKeyFile().toPath(), 0555), "/server.key") + MountableFile.forHostPath(serverTls.privateKey().toPath(), 0555), "/server.key") .withCopyFileToContainer( - MountableFile.forHostPath(clientTls.certificateFile().toPath(), 0555), - "/client.cert") + MountableFile.forHostPath(clientTls.certificate().toPath(), 0555), "/client.cert") .withEnv( "OTLP_EXPORTER_ENDPOINT", "host.testcontainers.internal:" + grpcServer.httpPort()) .withEnv("MTLS_CLIENT_CERTIFICATE", "/client.cert") @@ -202,8 +206,8 @@ void testOtlpGrpcTraceExport_mtls() throws Exception { + collector.getHost() + ":" + collector.getMappedPort(COLLECTOR_OTLP_GRPC_MTLS_PORT)) - .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) - .setTrustedCertificates(serverTls.certificate().getEncoded()) + .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) + .setTrustedCertificates(serverTls.cert().getEncoded()) .build(); testTraceExport(exporter); @@ -236,8 +240,8 @@ void testOtlpHttpTraceExport_mtls() throws Exception { + ":" + collector.getMappedPort(COLLECTOR_OTLP_HTTP_MTLS_PORT) + "/v1/traces") - .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) - .setTrustedCertificates(serverTls.certificate().getEncoded()) + .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) + .setTrustedCertificates(serverTls.cert().getEncoded()) .build(); testTraceExport(exporter); @@ -335,8 +339,8 @@ void testOtlpGrpcMetricExport_mtls() throws Exception { + collector.getHost() + ":" + collector.getMappedPort(COLLECTOR_OTLP_GRPC_MTLS_PORT)) - .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) - .setTrustedCertificates(serverTls.certificate().getEncoded()) + .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) + .setTrustedCertificates(serverTls.cert().getEncoded()) .build(); testMetricExport(exporter); @@ -369,8 +373,8 @@ void testOtlpHttpMetricExport_mtls() throws Exception { + ":" + collector.getMappedPort(COLLECTOR_OTLP_HTTP_MTLS_PORT) + "/v1/metrics") - .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) - .setTrustedCertificates(serverTls.certificate().getEncoded()) + .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) + .setTrustedCertificates(serverTls.cert().getEncoded()) .build(); testMetricExport(exporter); @@ -460,8 +464,8 @@ void testOtlpGrpcLogExport_mtls() throws Exception { + collector.getHost() + ":" + collector.getMappedPort(COLLECTOR_OTLP_GRPC_MTLS_PORT)) - .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) - .setTrustedCertificates(serverTls.certificate().getEncoded()) + .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) + .setTrustedCertificates(serverTls.cert().getEncoded()) .build(); testLogRecordExporter(exporter); @@ -494,8 +498,8 @@ void testOtlpHttpLogExport_mtls() throws Exception { + ":" + collector.getMappedPort(COLLECTOR_OTLP_HTTP_MTLS_PORT) + "/v1/logs") - .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) - .setTrustedCertificates(serverTls.certificate().getEncoded()) + .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) + .setTrustedCertificates(serverTls.cert().getEncoded()) .build(); testLogRecordExporter(exporter); From bc24f8bedc5c2c56f77ff5414af43a685450bf7a Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Mon, 21 Nov 2022 16:02:39 -0600 Subject: [PATCH 2/2] Revert change to integration test --- .../OtlpExporterIntegrationTest.java | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java b/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java index 53a8716ed4c..e0f0dd5a789 100644 --- a/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java +++ b/integration-tests/otlp/src/main/java/io/opentelemetry/integrationtest/OtlpExporterIntegrationTest.java @@ -11,10 +11,10 @@ import static org.testcontainers.Testcontainers.exposeHostPorts; import com.google.protobuf.InvalidProtocolBufferException; -import com.linecorp.armeria.internal.common.util.SelfSignedCertificate; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.grpc.protocol.AbstractUnaryGrpcService; +import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.common.Attributes; @@ -68,12 +68,10 @@ import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import java.io.UncheckedIOException; -import java.security.cert.CertificateException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.concurrent.CompletionStage; import org.junit.jupiter.api.AfterAll; @@ -81,6 +79,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.LoggerFactory; @@ -114,22 +113,17 @@ abstract class OtlpExporterIntegrationTest { .put(ResourceAttributes.SERVICE_NAME, "integration test") .build(); - static SelfSignedCertificate serverTls; - static SelfSignedCertificate clientTls; + @RegisterExtension + static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension(); + + @RegisterExtension + static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension(); private static OtlpGrpcServer grpcServer; private static GenericContainer collector; @BeforeAll - static void beforeAll() throws CertificateException { - serverTls = new SelfSignedCertificate(); - clientTls = - new SelfSignedCertificate( - Date.from(Instant.now().minusSeconds(365 * 24 * 3600)), - Date.from(Instant.ofEpochMilli(253402300799000L)), - "EC", - 256); - + static void beforeAll() { grpcServer = new OtlpGrpcServer(); grpcServer.start(); @@ -142,11 +136,13 @@ static void beforeAll() throws CertificateException { .withImagePullPolicy(PullPolicy.alwaysPull()) .withEnv("LOGGING_EXPORTER_LOG_LEVEL", "INFO") .withCopyFileToContainer( - MountableFile.forHostPath(serverTls.certificate().toPath(), 0555), "/server.cert") + MountableFile.forHostPath(serverTls.certificateFile().toPath(), 0555), + "/server.cert") .withCopyFileToContainer( - MountableFile.forHostPath(serverTls.privateKey().toPath(), 0555), "/server.key") + MountableFile.forHostPath(serverTls.privateKeyFile().toPath(), 0555), "/server.key") .withCopyFileToContainer( - MountableFile.forHostPath(clientTls.certificate().toPath(), 0555), "/client.cert") + MountableFile.forHostPath(clientTls.certificateFile().toPath(), 0555), + "/client.cert") .withEnv( "OTLP_EXPORTER_ENDPOINT", "host.testcontainers.internal:" + grpcServer.httpPort()) .withEnv("MTLS_CLIENT_CERTIFICATE", "/client.cert") @@ -206,8 +202,8 @@ void testOtlpGrpcTraceExport_mtls() throws Exception { + collector.getHost() + ":" + collector.getMappedPort(COLLECTOR_OTLP_GRPC_MTLS_PORT)) - .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) - .setTrustedCertificates(serverTls.cert().getEncoded()) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) + .setTrustedCertificates(serverTls.certificate().getEncoded()) .build(); testTraceExport(exporter); @@ -240,8 +236,8 @@ void testOtlpHttpTraceExport_mtls() throws Exception { + ":" + collector.getMappedPort(COLLECTOR_OTLP_HTTP_MTLS_PORT) + "/v1/traces") - .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) - .setTrustedCertificates(serverTls.cert().getEncoded()) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) + .setTrustedCertificates(serverTls.certificate().getEncoded()) .build(); testTraceExport(exporter); @@ -339,8 +335,8 @@ void testOtlpGrpcMetricExport_mtls() throws Exception { + collector.getHost() + ":" + collector.getMappedPort(COLLECTOR_OTLP_GRPC_MTLS_PORT)) - .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) - .setTrustedCertificates(serverTls.cert().getEncoded()) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) + .setTrustedCertificates(serverTls.certificate().getEncoded()) .build(); testMetricExport(exporter); @@ -373,8 +369,8 @@ void testOtlpHttpMetricExport_mtls() throws Exception { + ":" + collector.getMappedPort(COLLECTOR_OTLP_HTTP_MTLS_PORT) + "/v1/metrics") - .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) - .setTrustedCertificates(serverTls.cert().getEncoded()) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) + .setTrustedCertificates(serverTls.certificate().getEncoded()) .build(); testMetricExport(exporter); @@ -464,8 +460,8 @@ void testOtlpGrpcLogExport_mtls() throws Exception { + collector.getHost() + ":" + collector.getMappedPort(COLLECTOR_OTLP_GRPC_MTLS_PORT)) - .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) - .setTrustedCertificates(serverTls.cert().getEncoded()) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) + .setTrustedCertificates(serverTls.certificate().getEncoded()) .build(); testLogRecordExporter(exporter); @@ -498,8 +494,8 @@ void testOtlpHttpLogExport_mtls() throws Exception { + ":" + collector.getMappedPort(COLLECTOR_OTLP_HTTP_MTLS_PORT) + "/v1/logs") - .setClientTls(clientTls.key().getEncoded(), clientTls.cert().getEncoded()) - .setTrustedCertificates(serverTls.cert().getEncoded()) + .setClientTls(clientTls.privateKey().getEncoded(), clientTls.certificate().getEncoded()) + .setTrustedCertificates(serverTls.certificate().getEncoded()) .build(); testLogRecordExporter(exporter);