diff --git a/src/main/java/io/vertx/core/net/impl/KeyStoreHelper.java b/src/main/java/io/vertx/core/net/impl/KeyStoreHelper.java index 77c7d411ea9..018968e4b65 100644 --- a/src/main/java/io/vertx/core/net/impl/KeyStoreHelper.java +++ b/src/main/java/io/vertx/core/net/impl/KeyStoreHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -11,33 +11,54 @@ package io.vertx.core.net.impl; -import io.netty.util.internal.PlatformDependent; -import io.vertx.core.VertxException; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.impl.VertxInternal; -import io.vertx.core.net.impl.pkcs1.PrivateKeyParser; - -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; -import javax.net.ssl.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket; -import java.security.*; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.Security; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509KeyManager; + +import io.netty.util.internal.PlatformDependent; +import io.vertx.core.VertxException; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.net.impl.pkcs1.PrivateKeyParser; + /** * @author Julien Viet */ @@ -257,6 +278,14 @@ private static PrivateKey loadPrivateKey(Buffer keyValue) throws Exception { List pems = loadPems(keyValue, (delimiter, content) -> { try { switch (delimiter) { + case "EC PRIVATE KEY": + if (ecKeyFactory == null) { + // ECC is not supported by JVM + return Collections.emptyList(); + } else { + // read PEM file as described in https://datatracker.ietf.org/doc/html/rfc5915#section-4 + return Collections.singletonList(ecKeyFactory.generatePrivate(PrivateKeyParser.getECKeySpec(content))); + } case "RSA PRIVATE KEY": return Collections.singletonList(rsaKeyFactory.generatePrivate(PrivateKeyParser.getRSAKeySpec(content))); case "PRIVATE KEY": @@ -265,10 +294,10 @@ private static PrivateKey loadPrivateKey(Buffer keyValue) throws Exception { String algorithm = PrivateKeyParser.getPKCS8EncodedKeyAlgorithm(content); if (rsaKeyFactory.getAlgorithm().equals(algorithm)) { return Collections.singletonList(rsaKeyFactory.generatePrivate(new PKCS8EncodedKeySpec(content))); - } else if (ecKeyFactory != null && - ecKeyFactory.getAlgorithm().equals(algorithm)) { + } else if (ecKeyFactory != null && ecKeyFactory.getAlgorithm().equals(algorithm)) { return Collections.singletonList(ecKeyFactory.generatePrivate(new PKCS8EncodedKeySpec(content))); } + // fall through if ECC is not supported by JVM default: return Collections.emptyList(); } @@ -277,7 +306,7 @@ private static PrivateKey loadPrivateKey(Buffer keyValue) throws Exception { } }); if (pems.isEmpty()) { - throw new RuntimeException("Missing -----BEGIN PRIVATE KEY----- or -----BEGIN RSA PRIVATE KEY----- delimiter"); + throw new RuntimeException("Missing -----BEGIN PRIVATE KEY----- or -----BEGIN RSA PRIVATE KEY----- or -----BEGIN EC PRIVATE KEY----- delimiter"); } return pems.get(0); } diff --git a/src/main/java/io/vertx/core/net/impl/pkcs1/PrivateKeyParser.java b/src/main/java/io/vertx/core/net/impl/pkcs1/PrivateKeyParser.java index d53cac354dc..069bf3604f4 100644 --- a/src/main/java/io/vertx/core/net/impl/pkcs1/PrivateKeyParser.java +++ b/src/main/java/io/vertx/core/net/impl/pkcs1/PrivateKeyParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -11,15 +11,20 @@ package io.vertx.core.net.impl.pkcs1; -import io.vertx.core.VertxException; -import io.vertx.core.buffer.Buffer; - import java.io.UnsupportedEncodingException; import java.math.BigInteger; -import java.security.spec.PKCS8EncodedKeySpec; +import java.security.GeneralSecurityException; +import java.security.KeyPairGenerator; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.util.Arrays; +import io.vertx.core.VertxException; +import io.vertx.core.buffer.Buffer; + /** * This code is copies and modifies over from net.oauth.java.jmeter:ApacheJMeter_oauth *

@@ -40,6 +45,39 @@ public class PrivateKeyParser { */ private static final byte[] OID_EC_PUBLIC_KEY = { 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 }; + private static String oidToString(byte[] oid) { + StringBuilder result = new StringBuilder(); + int value = oid[0] & 0xff; + result.append(value / 40).append(".").append(value % 40); + for (int index = 1; index < oid.length; ++index) { + byte bValue = oid[index]; + if (bValue < 0) { + value = (bValue & 0b01111111); + ++index; + if (index == oid.length) { + throw new IllegalArgumentException("Invalid OID"); + } + value <<= 7; + value |= (oid[index] & 0b01111111); + result.append(".").append(value); + } else { + result.append(".").append(bValue); + } + } + return result.toString(); + } + + private static ECParameterSpec getECParameterSpec(String curveName) throws VertxException { + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC"); + keyPairGenerator.initialize(new ECGenParameterSpec(curveName)); + ECPublicKey publicKey = (ECPublicKey) keyPairGenerator.generateKeyPair().getPublic(); + return publicKey.getParams(); + } catch (GeneralSecurityException e) { + throw new VertxException("Cannot determine EC parameter spec for curve name/OID", e); + } + } + /** * Gets the algorithm used by a PKCS#8 encoded private key. * @@ -79,6 +117,70 @@ public static String getPKCS8EncodedKeyAlgorithm(byte[] encodedKey) { } } + /** + * Converts a DER encoded ECPrivateKey into a Java ECPrivateKeySpec. + *

+ * + * RFC 5915 defines the following ASN.1 syntax for an EC private key: + *

+ *
+   * ECPrivateKey ::= SEQUENCE {
+   *   version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
+   *   privateKey     OCTET STRING,
+   *   parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
+   *   publicKey  [1] BIT STRING OPTIONAL
+   * }
+   * 
+ *

+ * A private key encoded like this will most often be found in a PEM file which will + * contain the Base64 encoded DER-encoding of an ECPrivateKey sandwiched between + *

+   * -----BEGIN EC PRIVATE KEY-----
+   * -----END EC PRIVATE KEY-----
+   * 
+ * as described in + * RFC 5915, Section 4 + * + * @see "https://datatracker.ietf.org/doc/html/rfc5915" + * @param keyBytes The encoded key. + * @return The spec that can be used to instantiate the private key. + * @throws VertxException if the byte array does not represent an ASN.1 ECPrivateKey structure. + */ + public static ECPrivateKeySpec getECKeySpec(byte[] keyBytes) throws VertxException { + DerParser parser = new DerParser(keyBytes); + + Asn1Object sequence = parser.read(); + if (sequence.getType() != DerParser.SEQUENCE) { + throw new VertxException("Invalid DER: not a sequence"); + } + + // Parse inside the sequence + parser = sequence.getParser(); + + Asn1Object version = parser.read(); + if (version.getType() != DerParser.INTEGER) { + throw new VertxException(String.format( + "Invalid DER: 'version' field must be of type INTEGER (2) but found type `%d`", + version.getType())); + } else if (version.getInteger().intValue() != 1) { + throw new VertxException(String.format( + "Invalid DER: expected 'version' field to have value '1' but found '%d'", + version.getInteger().intValue())); + } + byte[] privateValue = parser.read().getValue(); + parser = parser.read().getParser(); + Asn1Object params = parser.read(); + // ECParameters are mandatory according to RFC 5915, Section 3 + if (params.getType() != DerParser.OBJECT_IDENTIFIER) { + throw new VertxException(String.format( + "Invalid DER: expected to find an OBJECT_IDENTIFIER (6) in 'parameters' but found type '%d'", + params.getType())); + } + byte[] namedCurveOid = params.getValue(); + ECParameterSpec spec = getECParameterSpec(oidToString(namedCurveOid)); + return new ECPrivateKeySpec(new BigInteger(1, privateValue), spec); + } + /** * Convert PKCS#1 encoded private key into RSAPrivateCrtKeySpec. *

diff --git a/src/test/java/io/vertx/core/http/HttpTLSTest.java b/src/test/java/io/vertx/core/http/HttpTLSTest.java index ee98d9a9cda..c524477ae2f 100755 --- a/src/test/java/io/vertx/core/http/HttpTLSTest.java +++ b/src/test/java/io/vertx/core/http/HttpTLSTest.java @@ -11,24 +11,8 @@ package io.vertx.core.http; -import io.netty.util.internal.PlatformDependent; -import io.vertx.core.Future; -import io.vertx.core.Vertx; -import io.vertx.core.VertxException; -import io.vertx.core.VertxOptions; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.net.*; -import io.vertx.core.net.impl.TrustAllTrustManager; -import io.vertx.test.core.TestUtils; -import io.vertx.test.proxy.HAProxy; -import io.vertx.test.tls.Cert; -import io.vertx.test.tls.Trust; -import org.junit.Assume; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import static org.hamcrest.core.StringEndsWith.endsWith; -import javax.net.ssl.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -46,7 +30,39 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; -import static org.hamcrest.core.StringEndsWith.endsWith; +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManagerFactorySpi; + +import org.junit.Assume; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import io.netty.util.internal.PlatformDependent; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.VertxException; +import io.vertx.core.VertxOptions; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.JdkSSLEngineOptions; +import io.vertx.core.net.JksOptions; +import io.vertx.core.net.KeyCertOptions; +import io.vertx.core.net.KeyStoreOptions; +import io.vertx.core.net.OpenSSLEngineOptions; +import io.vertx.core.net.PemTrustOptions; +import io.vertx.core.net.ProxyOptions; +import io.vertx.core.net.ProxyType; +import io.vertx.core.net.SelfSignedCertificate; +import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.TrustOptions; +import io.vertx.core.net.impl.TrustAllTrustManager; +import io.vertx.test.core.TestUtils; +import io.vertx.test.proxy.HAProxy; +import io.vertx.test.tls.Cert; +import io.vertx.test.tls.Trust; /** * @author Julien Viet @@ -1384,17 +1400,23 @@ public void testKeyCertInvalidPem() throws IOException { "", "-----BEGIN PRIVATE KEY-----", "-----BEGIN RSA PRIVATE KEY-----", + "-----BEGIN EC PRIVATE KEY-----", "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----", "-----BEGIN RSA PRIVATE KEY-----\n-----END RSA PRIVATE KEY-----", + "-----BEGIN EC PRIVATE KEY-----\n-----END EC PRIVATE KEY-----", "-----BEGIN PRIVATE KEY-----\n*\n-----END PRIVATE KEY-----", - "-----BEGIN RSA PRIVATE KEY-----\n*\n-----END RSA PRIVATE KEY-----" + "-----BEGIN RSA PRIVATE KEY-----\n*\n-----END RSA PRIVATE KEY-----", + "-----BEGIN EC PRIVATE KEY-----\n*\n-----END EC PRIVATE KEY-----" }; String[] messages = { - "Missing -----BEGIN PRIVATE KEY----- or -----BEGIN RSA PRIVATE KEY----- delimiter", + "Missing -----BEGIN PRIVATE KEY----- or -----BEGIN RSA PRIVATE KEY----- or -----BEGIN EC PRIVATE KEY----- delimiter", "Missing -----END PRIVATE KEY----- delimiter", "Missing -----END RSA PRIVATE KEY----- delimiter", + "Missing -----END EC PRIVATE KEY----- delimiter", "Empty pem file", "Empty pem file", + "Empty pem file", + "Input byte[] should at least have 2 bytes for base64 bytes", "Input byte[] should at least have 2 bytes for base64 bytes", "Input byte[] should at least have 2 bytes for base64 bytes" }; diff --git a/src/test/java/io/vertx/core/net/KeyStoreHelperTest.java b/src/test/java/io/vertx/core/net/KeyStoreHelperTest.java index c7ac4612b83..ed32fea549c 100644 --- a/src/test/java/io/vertx/core/net/KeyStoreHelperTest.java +++ b/src/test/java/io/vertx/core/net/KeyStoreHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -15,7 +15,6 @@ import static org.hamcrest.CoreMatchers.instanceOf; import java.security.GeneralSecurityException; -import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.cert.X509Certificate; @@ -23,13 +22,12 @@ import java.security.interfaces.RSAPrivateKey; import java.util.Enumeration; -import io.vertx.core.net.impl.KeyStoreHelper; -import io.vertx.test.core.VertxTestBase; import org.junit.Assume; import org.junit.Test; -import io.vertx.core.impl.VertxInternal; -import io.vertx.core.net.PemKeyCertOptions; +import io.vertx.core.net.impl.KeyStoreHelper; +import io.vertx.test.core.TestUtils; +import io.vertx.test.core.VertxTestBase; /** @@ -47,8 +45,8 @@ public class KeyStoreHelperTest extends VertxTestBase { @Test public void testKeyStoreHelperSupportsRSAPrivateKeys() throws Exception { PemKeyCertOptions options = new PemKeyCertOptions() - .addKeyPath("target/test-classes/tls/server-key.pem") - .addCertPath("target/test-classes/tls/server-cert.pem"); + .addKeyPath("tls/server-key.pem") + .addCertPath("tls/server-cert.pem"); KeyStoreHelper helper = options.getHelper(vertx); assertKeyType(helper.store(), RSAPrivateKey.class); } @@ -60,12 +58,29 @@ public void testKeyStoreHelperSupportsRSAPrivateKeys() throws Exception { * @throws Exception if the key cannot be read. */ @Test - public void testKeyStoreHelperSupportsECPrivateKeys() throws Exception { + public void testKeyStoreHelperSupportsPKCS8ECPrivateKey() throws Exception { - Assume.assumeTrue("ECC is not supported by VM's security providers", isECCSupportedByVM()); + Assume.assumeTrue("ECC is not supported by VM's security providers", TestUtils.isECCSupportedByVM()); PemKeyCertOptions options = new PemKeyCertOptions() - .addKeyPath("target/test-classes/tls/server-key-ec.pem") - .addCertPath("target/test-classes/tls/server-cert-ec.pem"); + .addKeyPath("tls/server-key-ec.pem") + .addCertPath("tls/server-cert-ec.pem"); + KeyStoreHelper helper = options.getHelper(vertx); + assertKeyType(helper.store(), ECPrivateKey.class); + } + + /** + * Verifies that the key store helper can read a DER encoded EC private key + * from a PEM file. + * + * @throws Exception if the key cannot be read. + */ + @Test + public void testKeyStoreHelperSupportsReadingECPrivateKeyFromPEMFile() throws Exception { + + Assume.assumeTrue("ECC is not supported by VM's security providers", TestUtils.isECCSupportedByVM()); + PemKeyCertOptions options = new PemKeyCertOptions() + .addKeyPath("tls/server-key-ec-pkcs1.pem") + .addCertPath("tls/server-cert-ec.pem"); KeyStoreHelper helper = options.getHelper(vertx); assertKeyType(helper.store(), ECPrivateKey.class); } @@ -80,13 +95,4 @@ private void assertKeyType(KeyStore store, Class expectedKeyType) throws KeyS assertThat(store.getCertificate(alias), instanceOf(X509Certificate.class)); } } - - private boolean isECCSupportedByVM() { - try { - KeyFactory.getInstance("EC"); - return true; - } catch (GeneralSecurityException e) { - return false; - } - } } diff --git a/src/test/java/io/vertx/core/net/impl/pkcs1/PrivateKeyParserTest.java b/src/test/java/io/vertx/core/net/impl/pkcs1/PrivateKeyParserTest.java index 5a9d6d7d365..cc65ace546d 100644 --- a/src/test/java/io/vertx/core/net/impl/pkcs1/PrivateKeyParserTest.java +++ b/src/test/java/io/vertx/core/net/impl/pkcs1/PrivateKeyParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -13,10 +13,22 @@ package io.vertx.core.net.impl.pkcs1; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.ECPrivateKeySpec; +import java.util.Base64; + +import org.junit.Assume; import org.junit.Test; +import io.vertx.core.Vertx; +import io.vertx.test.core.TestUtils; + /** * Verifies behavior of {@link PrivateKeyParser}. @@ -66,7 +78,30 @@ public void testGetPKCS8EncodedKeySpecSupportsEC() { } private void assertKeySpecType(byte[] encodedKey, String expectedAlgorithm) { - String keyAlgorithm = PrivateKeyParser.getPKCS8EncodedKeyAlgorithm(encodedKey); - assertThat(keyAlgorithm, is(expectedAlgorithm)); + String keyAlgorithm = PrivateKeyParser.getPKCS8EncodedKeyAlgorithm(encodedKey); + assertThat(keyAlgorithm, is(expectedAlgorithm)); + } + + /** + * Verifies that the parser can read a DER encoded ECPrivateKey. + * + * @throws GeneralSecurityException if the JVM does not support + */ + @Test + public void testGetECKeySpecSucceedsForDEREncodedECPrivateKey() throws GeneralSecurityException { + + Assume.assumeTrue("ECC is not supported by VM's security providers", TestUtils.isECCSupportedByVM()); + Vertx vertx = Vertx.vertx(); + String b = vertx.fileSystem().readFileBlocking("tls/server-key-ec-pkcs1.pem") + .toString(StandardCharsets.US_ASCII) + .replaceAll("-----BEGIN EC PRIVATE KEY-----", "") + .replaceAll("-----END EC PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + byte[] derEncoding = Base64.getDecoder().decode(b); + ECPrivateKeySpec spec = PrivateKeyParser.getECKeySpec(derEncoding); + KeyFactory factory = KeyFactory.getInstance("EC"); + ECPrivateKey key = (ECPrivateKey) factory.generatePrivate(spec); + assertThat(key, notNullValue()); + assertThat(key.getAlgorithm(), is("EC")); } } diff --git a/src/test/java/io/vertx/test/core/TestUtils.java b/src/test/java/io/vertx/test/core/TestUtils.java index bc14606be54..eecb14079b7 100644 --- a/src/test/java/io/vertx/test/core/TestUtils.java +++ b/src/test/java/io/vertx/test/core/TestUtils.java @@ -11,20 +11,9 @@ package io.vertx.test.core; -import io.netty.buffer.Unpooled; -import io.netty.handler.codec.http2.Http2CodecUtil; -import io.netty.util.NetUtil; -import io.netty.util.internal.logging.InternalLoggerFactory; -import io.vertx.core.Future; -import io.vertx.core.MultiMap; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.Http2Settings; -import io.vertx.core.net.*; -import io.vertx.core.net.impl.KeyStoreHelper; -import io.vertx.test.netty.TestLoggerFactory; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; -import javax.security.cert.X509Certificate; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.RandomAccessFile; @@ -32,20 +21,36 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; import java.util.EnumSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.function.Supplier; -import java.util.jar.JarEntry; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import java.util.zip.GZIPOutputStream; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import javax.security.cert.X509Certificate; + +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http2.Http2CodecUtil; +import io.netty.util.NetUtil; +import io.netty.util.internal.logging.InternalLoggerFactory; +import io.vertx.core.Future; +import io.vertx.core.MultiMap; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.Http2Settings; +import io.vertx.core.net.JksOptions; +import io.vertx.core.net.KeyCertOptions; +import io.vertx.core.net.PemKeyCertOptions; +import io.vertx.core.net.PemTrustOptions; +import io.vertx.core.net.PfxOptions; +import io.vertx.core.net.TrustOptions; +import io.vertx.core.net.impl.KeyStoreHelper; +import io.vertx.test.netty.TestLoggerFactory; /** * @author Tim Fox @@ -512,4 +517,18 @@ public static TestLoggerFactory testLogging(Runnable runnable) { } return factory; } + + /** + * Checks if the JVM supports ECC algorithms. + * + * @return {@code true} if the JVM supports ECC. + */ + public static boolean isECCSupportedByVM() { + try { + KeyFactory.getInstance("EC"); + return true; + } catch (GeneralSecurityException e) { + return false; + } + } }