Skip to content

Commit

Permalink
Add support for EC mTLS keys (#4920)
Browse files Browse the repository at this point in the history
* Add support for EC mTlS keys

* Revert change to integration test
  • Loading branch information
jack-berg committed Nov 22, 2022
1 parent e61d7c8 commit c4d0d86
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 5 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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<KeyFactory> 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() {}

Expand Down Expand Up @@ -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");

Expand All @@ -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<KeyFactory> 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");
Expand Down Expand Up @@ -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.
Expand Down
@@ -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]");
}
}

0 comments on commit c4d0d86

Please sign in to comment.