diff --git a/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java b/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java
index b4604e4c8c9..c85308d1526 100644
--- a/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java
+++ b/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java
@@ -138,6 +138,9 @@ public static TlsKeyPair ofSelfSigned(String hostname) {
/**
* Generates a self-signed certificate for the local hostname.
+ *
+ *
Note that if the local hostname exceeds 64 characters, it is truncated to satisfy the
+ * RFC 5280 common name length limit.
*/
public static TlsKeyPair ofSelfSigned() {
return ofSelfSigned(SystemInfo.hostname());
diff --git a/core/src/main/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificate.java b/core/src/main/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificate.java
index 469a0730e57..d3bfa9c052f 100644
--- a/core/src/main/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificate.java
+++ b/core/src/main/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificate.java
@@ -36,6 +36,9 @@
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.google.common.collect.ImmutableList;
/**
@@ -53,6 +56,10 @@
*/
public final class SelfSignedCertificate extends SignedCertificate {
+ private static final Logger logger = LoggerFactory.getLogger(SelfSignedCertificate.class);
+
+ private static final int MAX_COMMON_NAME_LENGTH = 64;
+
// Forked from:
// https://github.com/netty/netty/blob/11e6a77fba9ec7184a558d869373d0ce506d7236/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java
// https://github.com/netty/netty/blob/11e6a77fba9ec7184a558d869373d0ce506d7236/handler/src/main/java/io/netty/handler/ssl/util/BouncyCastleSelfSignedCertGenerator.java
@@ -235,7 +242,17 @@ public SelfSignedCertificate(String fqdn, Random random, int bits, Date notBefor
public SelfSignedCertificate(String fqdn, Random random, int bits, Date notBefore, Date notAfter,
String algorithm, Iterable subjectAlternativeNames, boolean isCA)
throws CertificateException {
- super(new CertificateParams(fqdn, random, bits, notBefore, notAfter, algorithm,
- subjectAlternativeNames, isCA));
+ super(new CertificateParams(truncateToCommonNameLength(fqdn), random, bits, notBefore, notAfter,
+ algorithm, subjectAlternativeNames, isCA));
+ }
+
+ private static String truncateToCommonNameLength(String fqdn) {
+ if (fqdn.length() <= MAX_COMMON_NAME_LENGTH) {
+ return fqdn;
+ }
+ final String truncated = fqdn.substring(0, MAX_COMMON_NAME_LENGTH);
+ logger.debug("Truncating the fqdn '{}' to '{}' to satisfy " +
+ "the RFC 5280 common name length limit (64).", fqdn, truncated);
+ return truncated;
}
}
diff --git a/core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java b/core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java
index 6871c89ec75..4d6eadda0af 100644
--- a/core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java
+++ b/core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java
@@ -31,6 +31,7 @@
import org.junit.jupiter.api.Test;
import com.linecorp.armeria.common.annotation.Nullable;
+import com.linecorp.armeria.common.util.SystemInfo;
import com.linecorp.armeria.internal.common.util.SelfSignedCertificate;
class TlsKeyPairTest {
@@ -45,7 +46,14 @@ void selfSignedIsAValidPair() throws CertificateException {
@Test
void ofSelfSignedIsAValidPair() {
- assertThat(TlsKeyPair.ofSelfSigned().privateKey()).isNotNull();
+ // Must not fail even on a machine whose hostname exceeds the 64-character common name limit
+ // of RFC 5280, such as a GitHub Actions macOS runner.
+ final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned();
+ assertThat(keyPair.privateKey()).isNotNull();
+ final String hostname = SystemInfo.hostname();
+ final String expectedCommonName = hostname.length() <= 64 ? hostname : hostname.substring(0, 64);
+ assertThat(keyPair.certificateChain().get(0).getSubjectX500Principal().getName())
+ .isEqualTo("CN=" + expectedCommonName);
}
@Test
diff --git a/core/src/test/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificateTest.java b/core/src/test/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificateTest.java
index 4aabc4aa3d9..5b8406e21b3 100644
--- a/core/src/test/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificateTest.java
+++ b/core/src/test/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificateTest.java
@@ -26,6 +26,7 @@
import org.junit.jupiter.api.Test;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
class SelfSignedCertificateTest {
@@ -42,6 +43,18 @@ void fqdnAsteriskFileNameTest() throws CertificateException {
assertThat(ssc.privateKey().getName()).doesNotContain("*");
}
+ @Test
+ void fqdnLongerThanCommonNameLimitTest() throws Exception {
+ // The hostname of some machines, such as a GitHub Actions macOS runner, exceeds the
+ // 64-character common name limit of RFC 5280.
+ final String fqdn = "very-long-hostname-" + Strings.repeat("a", 42) + ".local";
+ assertThat(fqdn).hasSize(67);
+
+ final SelfSignedCertificate ssc = new SelfSignedCertificate(fqdn);
+ assertThat(ssc.cert().getSubjectX500Principal().getName())
+ .isEqualTo("CN=" + fqdn.substring(0, 64));
+ }
+
@Test
void subjectAlternativeNamesWithUriTest() throws Exception {
final List additionalSans = ImmutableList.of(
diff --git a/core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java b/core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java
index 60db3fad596..6433f787c32 100644
--- a/core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java
+++ b/core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java
@@ -97,7 +97,7 @@ void handshakeSuccess(SessionProtocol sessionProtocol, String tlsProtocol, Strin
final Counter counter =
meterRegistry.find("armeria.server.tls.handshakes")
.tag("cipher.suite", value -> value.startsWith("TLS_"))
- .tag("hostname", server.server().defaultHostname())
+ .tag("hostname", certificateHostname())
.tag("protocol", expectedProtocol)
.tag("result", "success")
.tag("tls.protocol", tlsProtocol)
@@ -134,7 +134,7 @@ void handshakeFailure() {
final Counter counter =
meterRegistry.find("armeria.server.tls.handshakes")
.tag("cipher.suite", "")
- .tag("hostname", server.server().defaultHostname())
+ .tag("hostname", certificateHostname())
.tag("protocol", "")
.tag("result", "failure")
.tag("tls.protocol", "")
@@ -145,4 +145,11 @@ void handshakeFailure() {
.isNotNull();
assertThat(counter.count()).isOne();
}
+
+ private static String certificateHostname() {
+ // The hostname tag is derived from the self-signed certificate, whose common name is
+ // truncated to the 64-character limit of RFC 5280.
+ final String hostname = server.server().defaultHostname();
+ return hostname.length() <= 64 ? hostname : hostname.substring(0, 64);
+ }
}
diff --git a/core/src/test/java/com/linecorp/armeria/server/VirtualHostBuilderTest.java b/core/src/test/java/com/linecorp/armeria/server/VirtualHostBuilderTest.java
index 9e1ef550866..dc303a0cab1 100644
--- a/core/src/test/java/com/linecorp/armeria/server/VirtualHostBuilderTest.java
+++ b/core/src/test/java/com/linecorp/armeria/server/VirtualHostBuilderTest.java
@@ -32,6 +32,7 @@
import org.junit.jupiter.params.provider.CsvSource;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.linecorp.armeria.common.Flags;
@@ -89,6 +90,19 @@ void defaultVirtualHostSetDefaultHostname() {
assertThat(virtualHost.defaultHostname()).isEqualTo("foo");
}
+ @Test
+ void tlsSelfSignedWithLongDefaultHostname() {
+ // The hostname of some machines exceeds the 64-character common name limit of RFC 5280.
+ final String hostname = "very-long-hostname-" + Strings.repeat("a", 42) + ".local";
+ final Server server = Server.builder()
+ .defaultHostname(hostname)
+ .tlsSelfSigned()
+ .service("/test", (ctx, req) -> HttpResponse.of(OK))
+ .build();
+
+ assertThat(server.config().defaultVirtualHost().defaultHostname()).isEqualTo(hostname);
+ }
+
@Test
void defaultVirtualHostWithImplicitStyle() {
final ServerBuilder sb = Server.builder();