From 081e53f137148c57f18f3ea94b893ba17e5ed0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Mon, 26 Feb 2024 18:21:13 +0100 Subject: [PATCH] SSL.com: Add support for ECC and RSA mode --- .../provider/sslcom/SslComAcmeProvider.java | 18 +++++++---- .../sslcom/SslComAcmeProviderTest.java | 20 +++++++++--- .../org/shredzone/acme4j/it/ProviderIT.java | 32 +++++++++++++------ src/doc/docs/ca/sslcom.md | 14 ++++++-- 4 files changed, 61 insertions(+), 23 deletions(-) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/sslcom/SslComAcmeProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/sslcom/SslComAcmeProvider.java index aa5f12d9..191bd23a 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/sslcom/SslComAcmeProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/sslcom/SslComAcmeProvider.java @@ -34,8 +34,10 @@ import org.shredzone.acme4j.provider.AcmeProvider; */ public class SslComAcmeProvider extends AbstractAcmeProvider { - private static final String PRODUCTION_DIRECTORY_URL = "https://acme.ssl.com/sslcom-dv-ecc"; - private static final String STAGING_DIRECTORY_URL = "https://acme-try.ssl.com/sslcom-dv-ecc"; + private static final String PRODUCTION_ECC_DIRECTORY_URL = "https://acme.ssl.com/sslcom-dv-ecc"; + private static final String PRODUCTION_RSA_DIRECTORY_URL = "https://acme.ssl.com/sslcom-dv-rsa"; + private static final String STAGING_ECC_DIRECTORY_URL = "https://acme-try.ssl.com/sslcom-dv-ecc"; + private static final String STAGING_RSA_DIRECTORY_URL = "https://acme-try.ssl.com/sslcom-dv-rsa"; @Override public boolean accepts(URI serverUri) { @@ -47,10 +49,14 @@ public class SslComAcmeProvider extends AbstractAcmeProvider { public URL resolve(URI serverUri) { var path = serverUri.getPath(); String directoryUrl; - if (path == null || path.isEmpty() || "/".equals(path)) { - directoryUrl = PRODUCTION_DIRECTORY_URL; - } else if ("/staging".equals(path)) { - directoryUrl = STAGING_DIRECTORY_URL; + if (path == null || path.isEmpty() || "/".equals(path) || "/ecc".equals(path)) { + directoryUrl = PRODUCTION_ECC_DIRECTORY_URL; + } else if ("/rsa".equals(path)) { + directoryUrl = PRODUCTION_RSA_DIRECTORY_URL; + } else if ("/staging".equals(path) || "/staging/ecc".equals(path)) { + directoryUrl = STAGING_ECC_DIRECTORY_URL; + } else if ("/staging/rsa".equals(path)) { + directoryUrl = STAGING_RSA_DIRECTORY_URL; } else { throw new IllegalArgumentException("Unknown URI " + serverUri); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/sslcom/SslComAcmeProviderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/sslcom/SslComAcmeProviderTest.java index 531e1739..9e3f2805 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/sslcom/SslComAcmeProviderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/sslcom/SslComAcmeProviderTest.java @@ -28,8 +28,10 @@ import org.junit.jupiter.api.Test; */ public class SslComAcmeProviderTest { - private static final String PRODUCTION_DIRECTORY_URL = "https://acme.ssl.com/sslcom-dv-ecc"; - private static final String STAGING_DIRECTORY_URL = "https://acme-try.ssl.com/sslcom-dv-ecc"; + private static final String PRODUCTION_ECC_DIRECTORY_URL = "https://acme.ssl.com/sslcom-dv-ecc"; + private static final String PRODUCTION_RSA_DIRECTORY_URL = "https://acme.ssl.com/sslcom-dv-rsa"; + private static final String STAGING_ECC_DIRECTORY_URL = "https://acme-try.ssl.com/sslcom-dv-ecc"; + private static final String STAGING_RSA_DIRECTORY_URL = "https://acme-try.ssl.com/sslcom-dv-rsa"; /** * Tests if the provider accepts the correct URIs. @@ -41,7 +43,11 @@ public class SslComAcmeProviderTest { try (var softly = new AutoCloseableSoftAssertions()) { softly.assertThat(provider.accepts(new URI("acme://ssl.com"))).isTrue(); softly.assertThat(provider.accepts(new URI("acme://ssl.com/"))).isTrue(); + softly.assertThat(provider.accepts(new URI("acme://ssl.com/ecc"))).isTrue(); + softly.assertThat(provider.accepts(new URI("acme://ssl.com/rsa"))).isTrue(); softly.assertThat(provider.accepts(new URI("acme://ssl.com/staging"))).isTrue(); + softly.assertThat(provider.accepts(new URI("acme://ssl.com/staging/ecc"))).isTrue(); + softly.assertThat(provider.accepts(new URI("acme://ssl.com/staging/rsa"))).isTrue(); softly.assertThat(provider.accepts(new URI("acme://example.com"))).isFalse(); softly.assertThat(provider.accepts(new URI("http://example.com/acme"))).isFalse(); softly.assertThat(provider.accepts(new URI("https://example.com/acme"))).isFalse(); @@ -55,9 +61,13 @@ public class SslComAcmeProviderTest { public void testResolve() throws URISyntaxException { var provider = new SslComAcmeProvider(); - assertThat(provider.resolve(new URI("acme://ssl.com"))).isEqualTo(url(PRODUCTION_DIRECTORY_URL)); - assertThat(provider.resolve(new URI("acme://ssl.com/"))).isEqualTo(url(PRODUCTION_DIRECTORY_URL)); - assertThat(provider.resolve(new URI("acme://ssl.com/staging"))).isEqualTo(url(STAGING_DIRECTORY_URL)); + assertThat(provider.resolve(new URI("acme://ssl.com"))).isEqualTo(url(PRODUCTION_ECC_DIRECTORY_URL)); + assertThat(provider.resolve(new URI("acme://ssl.com/"))).isEqualTo(url(PRODUCTION_ECC_DIRECTORY_URL)); + assertThat(provider.resolve(new URI("acme://ssl.com/ecc"))).isEqualTo(url(PRODUCTION_ECC_DIRECTORY_URL)); + assertThat(provider.resolve(new URI("acme://ssl.com/rsa"))).isEqualTo(url(PRODUCTION_RSA_DIRECTORY_URL)); + assertThat(provider.resolve(new URI("acme://ssl.com/staging"))).isEqualTo(url(STAGING_ECC_DIRECTORY_URL)); + assertThat(provider.resolve(new URI("acme://ssl.com/staging/ecc"))).isEqualTo(url(STAGING_ECC_DIRECTORY_URL)); + assertThat(provider.resolve(new URI("acme://ssl.com/staging/rsa"))).isEqualTo(url(STAGING_RSA_DIRECTORY_URL)); assertThatIllegalArgumentException().isThrownBy(() -> provider.resolve(new URI("acme://ssl.com/v99"))); } diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/ProviderIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/ProviderIT.java index efda0eeb..952c95da 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/ProviderIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/ProviderIT.java @@ -71,17 +71,29 @@ public class ProviderIT { */ @Test public void testSslCom() throws AcmeException, MalformedURLException { - var session = new Session("acme://ssl.com"); - assertThat(session.getMetadata().getWebsite()).hasValue(new URL("https://www.ssl.com")); - assertThatNoException().isThrownBy(() -> session.resourceUrl(Resource.NEW_ACCOUNT)); - assertThat(session.getMetadata().isExternalAccountRequired()).isFalse(); - assertThat(session.getMetadata().isAutoRenewalEnabled()).isFalse(); + var sessionEcc = new Session("acme://ssl.com/ecc"); + assertThat(sessionEcc.getMetadata().getWebsite()).hasValue(new URL("https://www.ssl.com")); + assertThatNoException().isThrownBy(() -> sessionEcc.resourceUrl(Resource.NEW_ACCOUNT)); + assertThat(sessionEcc.getMetadata().isExternalAccountRequired()).isFalse(); + assertThat(sessionEcc.getMetadata().isAutoRenewalEnabled()).isFalse(); - var sessionStage = new Session("acme://ssl.com/staging"); - assertThat(sessionStage.getMetadata().getWebsite()).hasValue(new URL("https://www.ssl.com")); - assertThatNoException().isThrownBy(() -> sessionStage.resourceUrl(Resource.NEW_ACCOUNT)); - assertThat(sessionStage.getMetadata().isExternalAccountRequired()).isFalse(); - assertThat(sessionStage.getMetadata().isAutoRenewalEnabled()).isFalse(); + var sessionRsa = new Session("acme://ssl.com/rsa"); + assertThat(sessionRsa.getMetadata().getWebsite()).hasValue(new URL("https://www.ssl.com")); + assertThatNoException().isThrownBy(() -> sessionRsa.resourceUrl(Resource.NEW_ACCOUNT)); + assertThat(sessionRsa.getMetadata().isExternalAccountRequired()).isFalse(); + assertThat(sessionRsa.getMetadata().isAutoRenewalEnabled()).isFalse(); + + var sessionEccStage = new Session("acme://ssl.com/staging/ecc"); + assertThat(sessionEccStage.getMetadata().getWebsite()).hasValue(new URL("https://www.ssl.com")); + assertThatNoException().isThrownBy(() -> sessionEccStage.resourceUrl(Resource.NEW_ACCOUNT)); + assertThat(sessionEccStage.getMetadata().isExternalAccountRequired()).isFalse(); + assertThat(sessionEccStage.getMetadata().isAutoRenewalEnabled()).isFalse(); + + var sessionRsaStage = new Session("acme://ssl.com/staging/rsa"); + assertThat(sessionRsaStage.getMetadata().getWebsite()).hasValue(new URL("https://www.ssl.com")); + assertThatNoException().isThrownBy(() -> sessionRsaStage.resourceUrl(Resource.NEW_ACCOUNT)); + assertThat(sessionRsaStage.getMetadata().isExternalAccountRequired()).isFalse(); + assertThat(sessionRsaStage.getMetadata().isAutoRenewalEnabled()).isFalse(); } /** diff --git a/src/doc/docs/ca/sslcom.md b/src/doc/docs/ca/sslcom.md index 56a30ec9..0f49ad27 100644 --- a/src/doc/docs/ca/sslcom.md +++ b/src/doc/docs/ca/sslcom.md @@ -6,5 +6,15 @@ Available since acme4j 3.2.0 ## Connection URIs -* `acme://ssl.com` - Production server -* `acme://ssl.com/staging` - Testing server +* `acme://ssl.com`, `acme://ssl.com/ecc` - Production server, ECDSA certificate mode +* `acme://ssl.com/rsa` - Production server, RSA certificate mode +* `acme://ssl.com/staging`, `acme://ssl.com/staging/ecc` - Testing server, ECDSA certificate mode +* `acme://ssl.com/staging/rsa` - Testing server, RSA certificate mode + +## Note + +* This CA requires [External Account Binding (EAB)](../usage/account.md#external-account-binding) for account creation. However `Metadata.isExternalAccountRequired()` returns `false` due to an error in the CA's directory resource. + +## Disclaimer + +_acme4j_ is not officially supported or endorsed by SSL.com. If you have _acme4j_ related issues, please do not ask them for support, but [open an issue here](https://github.com/shred/acme4j/issues).