Skip to content

Commit 11b5886

Browse files
authored
Truncate a hostname longer than 64 characters for self-signed certificates (#6900)
Motivation: Self-signed certificate generation uses the local hostname as the certificate CN. On a machine whose hostname exceeds 64 characters — e.g. a GitHub Actions macOS runner named `sjc20-cw714-0e535ccf-aeaf-489f-8a27-1ab749ed681c-fe6ab157f2e4.local` (67 characters) — it fails with: ``` java.lang.IllegalArgumentException: commonName length 67 exceeds RFC 5280 ub-common-name (64) ``` Both `TlsKeyPair.ofSelfSigned()` and `ServerBuilder.tlsSelfSigned()` are affected, so any test using them fails whenever such a runner is assigned ([example failure](https://github.com/line/armeria/actions/runs/30982702653/job/92230410002#step:10:1982): `:athenz:shadedTest` failed at class initialization). Modifications: - Truncate an fqdn longer than 64 characters in the `SelfSignedCertificate` constructor, which covers every self-signed certificate generation path, and log a debug message when truncation happens. Result: - Self-signed certificate generation no longer fails on a machine whose hostname exceeds 64 characters.
1 parent d787534 commit 11b5886

6 files changed

Lines changed: 67 additions & 5 deletions

File tree

core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ public static TlsKeyPair ofSelfSigned(String hostname) {
138138

139139
/**
140140
* Generates a self-signed certificate for the local hostname.
141+
*
142+
* <p>Note that if the local hostname exceeds 64 characters, it is truncated to satisfy the
143+
* RFC 5280 common name length limit.
141144
*/
142145
public static TlsKeyPair ofSelfSigned() {
143146
return ofSelfSigned(SystemInfo.hostname());

core/src/main/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificate.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
import java.util.Random;
3737
import java.util.concurrent.ThreadLocalRandom;
3838

39+
import org.slf4j.Logger;
40+
import org.slf4j.LoggerFactory;
41+
3942
import com.google.common.collect.ImmutableList;
4043

4144
/**
@@ -53,6 +56,10 @@
5356
*/
5457
public final class SelfSignedCertificate extends SignedCertificate {
5558

59+
private static final Logger logger = LoggerFactory.getLogger(SelfSignedCertificate.class);
60+
61+
private static final int MAX_COMMON_NAME_LENGTH = 64;
62+
5663
// Forked from:
5764
// https://github.com/netty/netty/blob/11e6a77fba9ec7184a558d869373d0ce506d7236/handler/src/main/java/io/netty/handler/ssl/util/SelfSignedCertificate.java
5865
// 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
235242
public SelfSignedCertificate(String fqdn, Random random, int bits, Date notBefore, Date notAfter,
236243
String algorithm, Iterable<String> subjectAlternativeNames, boolean isCA)
237244
throws CertificateException {
238-
super(new CertificateParams(fqdn, random, bits, notBefore, notAfter, algorithm,
239-
subjectAlternativeNames, isCA));
245+
super(new CertificateParams(truncateToCommonNameLength(fqdn), random, bits, notBefore, notAfter,
246+
algorithm, subjectAlternativeNames, isCA));
247+
}
248+
249+
private static String truncateToCommonNameLength(String fqdn) {
250+
if (fqdn.length() <= MAX_COMMON_NAME_LENGTH) {
251+
return fqdn;
252+
}
253+
final String truncated = fqdn.substring(0, MAX_COMMON_NAME_LENGTH);
254+
logger.debug("Truncating the fqdn '{}' to '{}' to satisfy " +
255+
"the RFC 5280 common name length limit (64).", fqdn, truncated);
256+
return truncated;
240257
}
241258
}

core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.junit.jupiter.api.Test;
3232

3333
import com.linecorp.armeria.common.annotation.Nullable;
34+
import com.linecorp.armeria.common.util.SystemInfo;
3435
import com.linecorp.armeria.internal.common.util.SelfSignedCertificate;
3536

3637
class TlsKeyPairTest {
@@ -45,7 +46,14 @@ void selfSignedIsAValidPair() throws CertificateException {
4546

4647
@Test
4748
void ofSelfSignedIsAValidPair() {
48-
assertThat(TlsKeyPair.ofSelfSigned().privateKey()).isNotNull();
49+
// Must not fail even on a machine whose hostname exceeds the 64-character common name limit
50+
// of RFC 5280, such as a GitHub Actions macOS runner.
51+
final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned();
52+
assertThat(keyPair.privateKey()).isNotNull();
53+
final String hostname = SystemInfo.hostname();
54+
final String expectedCommonName = hostname.length() <= 64 ? hostname : hostname.substring(0, 64);
55+
assertThat(keyPair.certificateChain().get(0).getSubjectX500Principal().getName())
56+
.isEqualTo("CN=" + expectedCommonName);
4957
}
5058

5159
@Test

core/src/test/java/com/linecorp/armeria/internal/common/util/SelfSignedCertificateTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.junit.jupiter.api.Test;
2828

29+
import com.google.common.base.Strings;
2930
import com.google.common.collect.ImmutableList;
3031

3132
class SelfSignedCertificateTest {
@@ -42,6 +43,18 @@ void fqdnAsteriskFileNameTest() throws CertificateException {
4243
assertThat(ssc.privateKey().getName()).doesNotContain("*");
4344
}
4445

46+
@Test
47+
void fqdnLongerThanCommonNameLimitTest() throws Exception {
48+
// The hostname of some machines, such as a GitHub Actions macOS runner, exceeds the
49+
// 64-character common name limit of RFC 5280.
50+
final String fqdn = "very-long-hostname-" + Strings.repeat("a", 42) + ".local";
51+
assertThat(fqdn).hasSize(67);
52+
53+
final SelfSignedCertificate ssc = new SelfSignedCertificate(fqdn);
54+
assertThat(ssc.cert().getSubjectX500Principal().getName())
55+
.isEqualTo("CN=" + fqdn.substring(0, 64));
56+
}
57+
4558
@Test
4659
void subjectAlternativeNamesWithUriTest() throws Exception {
4760
final List<String> additionalSans = ImmutableList.of(

core/src/test/java/com/linecorp/armeria/server/ServerTlsHandshakeMetricsTest.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ void handshakeSuccess(SessionProtocol sessionProtocol, String tlsProtocol, Strin
9797
final Counter counter =
9898
meterRegistry.find("armeria.server.tls.handshakes")
9999
.tag("cipher.suite", value -> value.startsWith("TLS_"))
100-
.tag("hostname", server.server().defaultHostname())
100+
.tag("hostname", certificateHostname())
101101
.tag("protocol", expectedProtocol)
102102
.tag("result", "success")
103103
.tag("tls.protocol", tlsProtocol)
@@ -134,7 +134,7 @@ void handshakeFailure() {
134134
final Counter counter =
135135
meterRegistry.find("armeria.server.tls.handshakes")
136136
.tag("cipher.suite", "")
137-
.tag("hostname", server.server().defaultHostname())
137+
.tag("hostname", certificateHostname())
138138
.tag("protocol", "")
139139
.tag("result", "failure")
140140
.tag("tls.protocol", "")
@@ -145,4 +145,11 @@ void handshakeFailure() {
145145
.isNotNull();
146146
assertThat(counter.count()).isOne();
147147
}
148+
149+
private static String certificateHostname() {
150+
// The hostname tag is derived from the self-signed certificate, whose common name is
151+
// truncated to the 64-character limit of RFC 5280.
152+
final String hostname = server.server().defaultHostname();
153+
return hostname.length() <= 64 ? hostname : hostname.substring(0, 64);
154+
}
148155
}

core/src/test/java/com/linecorp/armeria/server/VirtualHostBuilderTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.junit.jupiter.params.provider.CsvSource;
3333
import org.slf4j.LoggerFactory;
3434

35+
import com.google.common.base.Strings;
3536
import com.google.common.collect.ImmutableSet;
3637

3738
import com.linecorp.armeria.common.Flags;
@@ -89,6 +90,19 @@ void defaultVirtualHostSetDefaultHostname() {
8990
assertThat(virtualHost.defaultHostname()).isEqualTo("foo");
9091
}
9192

93+
@Test
94+
void tlsSelfSignedWithLongDefaultHostname() {
95+
// The hostname of some machines exceeds the 64-character common name limit of RFC 5280.
96+
final String hostname = "very-long-hostname-" + Strings.repeat("a", 42) + ".local";
97+
final Server server = Server.builder()
98+
.defaultHostname(hostname)
99+
.tlsSelfSigned()
100+
.service("/test", (ctx, req) -> HttpResponse.of(OK))
101+
.build();
102+
103+
assertThat(server.config().defaultVirtualHost().defaultHostname()).isEqualTo(hostname);
104+
}
105+
92106
@Test
93107
void defaultVirtualHostWithImplicitStyle() {
94108
final ServerBuilder sb = Server.builder();

0 commit comments

Comments
 (0)