From f75df0ae7e742137807f20aa65117641bde22262 Mon Sep 17 00:00:00 2001 From: Ikhun Um Date: Wed, 5 Aug 2026 16:31:59 +0900 Subject: [PATCH 1/7] Pin the self-signed certificate hostname to "localhost" in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: `TlsKeyPair.ofSelfSigned()` uses `SystemInfo.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) — certificate generation fails with `IllegalArgumentException: commonName length 67 exceeds RFC 5280 ub-common-name (64)`, so every test that calls the no-arg overload fails before it starts. This broke `:athenz:shadedTest` on a macOS CI runner (`RoleTokenClientTest` and `AthenzTokenClientErrorHandlingTest` failed at class initialization) and can hit any test that uses the no-arg overload whenever such a runner is assigned. Modifications: - Replace all 20 no-arg `TlsKeyPair.ofSelfSigned()` call sites in tests with `TlsKeyPair.ofSelfSigned("localhost")`. None of these tests depend on the certificate CN. Result: - Tests no longer derive the self-signed certificate CN from the machine hostname, so they pass regardless of the hostname of the machine they run on. --- .../athenz/AthenzTokenClientErrorHandlingTest.java | 2 +- .../armeria/client/athenz/RoleTokenClientTest.java | 4 ++-- .../armeria/client/ClientTlsProviderBuilderTest.java | 10 +++++----- .../linecorp/armeria/client/ClientTlsProviderTest.java | 6 +++--- .../armeria/common/RefreshingTlsProviderTest.java | 8 ++++---- .../armeria/server/ConnectionAcceptorTest.java | 6 +++--- .../armeria/server/DelayedConnectionAcceptorTest.java | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/athenz/src/test/java/com/linecorp/armeria/client/athenz/AthenzTokenClientErrorHandlingTest.java b/athenz/src/test/java/com/linecorp/armeria/client/athenz/AthenzTokenClientErrorHandlingTest.java index 7555c0f740b..5c65fb5fa97 100644 --- a/athenz/src/test/java/com/linecorp/armeria/client/athenz/AthenzTokenClientErrorHandlingTest.java +++ b/athenz/src/test/java/com/linecorp/armeria/client/athenz/AthenzTokenClientErrorHandlingTest.java @@ -56,7 +56,7 @@ protected void configure(ServerBuilder sb) { } }; - private static final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned(); + private static final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned("localhost"); private static ZtsBaseClient forbiddenZtsClient; private static ZtsBaseClient unreachableZtsClient; diff --git a/athenz/src/test/java/com/linecorp/armeria/client/athenz/RoleTokenClientTest.java b/athenz/src/test/java/com/linecorp/armeria/client/athenz/RoleTokenClientTest.java index 707ed45d513..4071cfbf75a 100644 --- a/athenz/src/test/java/com/linecorp/armeria/client/athenz/RoleTokenClientTest.java +++ b/athenz/src/test/java/com/linecorp/armeria/client/athenz/RoleTokenClientTest.java @@ -58,7 +58,7 @@ protected void configure(ServerBuilder sb) { @BeforeAll static void beforeAll() { - final TlsKeyPair tlsKeyPair = TlsKeyPair.ofSelfSigned(); + final TlsKeyPair tlsKeyPair = TlsKeyPair.ofSelfSigned("localhost"); ztsBaseClient = ZtsBaseClient.builder(mockServer.httpUri()) .keyPair(() -> tlsKeyPair) .build(); @@ -113,7 +113,7 @@ void shouldPreloadToken() { @Test void shouldRefreshTokenBeforeExpiry() throws Exception { - final TlsKeyPair tlsKeyPair = TlsKeyPair.ofSelfSigned(); + final TlsKeyPair tlsKeyPair = TlsKeyPair.ofSelfSigned("localhost"); try (ZtsBaseClient ztsBaseClient = ZtsBaseClient.builder(mockServer.httpUri()) .keyPair(() -> tlsKeyPair) .build()) { diff --git a/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderBuilderTest.java b/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderBuilderTest.java index 3763819d982..4d9efb9126d 100644 --- a/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderBuilderTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderBuilderTest.java @@ -37,11 +37,11 @@ void testBuild() { @Test void testMapping() { - final TlsKeyPair exactKeyPair = TlsKeyPair.ofSelfSigned(); - final TlsKeyPair wildcardKeyPair = TlsKeyPair.ofSelfSigned(); - final TlsKeyPair defaultKeyPair = TlsKeyPair.ofSelfSigned(); - final TlsKeyPair barKeyPair = TlsKeyPair.ofSelfSigned(); - final TlsKeyPair barWildKeyPair = TlsKeyPair.ofSelfSigned(); + final TlsKeyPair exactKeyPair = TlsKeyPair.ofSelfSigned("localhost"); + final TlsKeyPair wildcardKeyPair = TlsKeyPair.ofSelfSigned("localhost"); + final TlsKeyPair defaultKeyPair = TlsKeyPair.ofSelfSigned("localhost"); + final TlsKeyPair barKeyPair = TlsKeyPair.ofSelfSigned("localhost"); + final TlsKeyPair barWildKeyPair = TlsKeyPair.ofSelfSigned("localhost"); final TlsProvider tlsProvider = TlsProvider.builder() .keyPair(defaultKeyPair) diff --git a/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderTest.java b/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderTest.java index 094394ec964..8c3c4859f85 100644 --- a/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderTest.java @@ -270,12 +270,12 @@ void testNoMtls() { @Test void disallowTlsProviderWhenTlsSettingsIsSet() { final TlsProvider tlsProvider = - TlsProvider.of(TlsKeyPair.ofSelfSigned()); + TlsProvider.of(TlsKeyPair.ofSelfSigned("localhost")); assertThatThrownBy(() -> { ClientFactory.builder() .tlsProvider(tlsProvider) - .tls(TlsKeyPair.ofSelfSigned()); + .tls(TlsKeyPair.ofSelfSigned("localhost")); }).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Cannot configure TLS settings because a TlsProvider has been set."); @@ -302,7 +302,7 @@ void disallowTlsProviderWhenTlsSettingsIsSet() { assertThatThrownBy(() -> { ClientFactory.builder() - .tls(TlsKeyPair.ofSelfSigned()) + .tls(TlsKeyPair.ofSelfSigned("localhost")) .tlsProvider(tlsProvider); }).isInstanceOf(IllegalStateException.class) .hasMessageContaining( diff --git a/core/src/test/java/com/linecorp/armeria/common/RefreshingTlsProviderTest.java b/core/src/test/java/com/linecorp/armeria/common/RefreshingTlsProviderTest.java index a91abd92b24..223e63f1d37 100644 --- a/core/src/test/java/com/linecorp/armeria/common/RefreshingTlsProviderTest.java +++ b/core/src/test/java/com/linecorp/armeria/common/RefreshingTlsProviderTest.java @@ -31,7 +31,7 @@ class RefreshingTlsProviderTest { @Test void shouldRefreshTlsKeyPairPeriodically() throws InterruptedException { - final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned(); + final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned("localhost"); final AtomicInteger counter = new AtomicInteger(); final TlsProvider tlsProvider = TlsProvider.ofScheduled(() -> { counter.incrementAndGet(); @@ -47,7 +47,7 @@ void shouldReturnKeyTlsKeyPairOnUpdate() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); final TlsProvider tlsProvider = TlsProvider.ofScheduled(() -> { counter.incrementAndGet(); - return TlsKeyPair.ofSelfSigned(); + return TlsKeyPair.ofSelfSigned("localhost"); }, Duration.ofSeconds(1)); final TlsKeyPair initialKeyPair = tlsProvider.keyPair("*"); Thread.sleep(2000); @@ -60,7 +60,7 @@ void shouldReturnKeyTlsKeyPairOnUpdate() throws InterruptedException { @Test void shouldNotifyListenerOnKeyPair() throws InterruptedException { final AtomicReference keyPairRef = new AtomicReference<>(); - keyPairRef.set(TlsKeyPair.ofSelfSigned()); + keyPairRef.set(TlsKeyPair.ofSelfSigned("localhost")); final AtomicReference capturedKeyPairRef = new AtomicReference<>(); final TlsProvider tlsProvider = @@ -71,7 +71,7 @@ void shouldNotifyListenerOnKeyPair() throws InterruptedException { assertThat(capturedKeyPairRef).hasNullValue(); Thread.sleep(1000); assertThat(capturedKeyPairRef).hasNullValue(); - keyPairRef.set(TlsKeyPair.ofSelfSigned()); + keyPairRef.set(TlsKeyPair.ofSelfSigned("localhost")); await().untilAsserted(() -> { assertThat(capturedKeyPairRef).hasValue(keyPairRef.get()); }); diff --git a/core/src/test/java/com/linecorp/armeria/server/ConnectionAcceptorTest.java b/core/src/test/java/com/linecorp/armeria/server/ConnectionAcceptorTest.java index 91c2bab4051..59449b141f9 100644 --- a/core/src/test/java/com/linecorp/armeria/server/ConnectionAcceptorTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/ConnectionAcceptorTest.java @@ -55,7 +55,7 @@ class ConnectionAcceptorTest { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned()) + .tls(TlsKeyPair.ofSelfSigned("localhost")) .connectionAcceptor(ConnectionAcceptor.of(ctx -> { ctx.setAttr(TEST_ATTR, "from-connection"); acceptCount.incrementAndGet(); @@ -75,7 +75,7 @@ protected void configure(ServerBuilder sb) { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned()) + .tls(TlsKeyPair.ofSelfSigned("localhost")) .connectionAcceptor(ConnectionAcceptor.of(ctx -> { acceptCount.incrementAndGet(); return false; @@ -90,7 +90,7 @@ protected void configure(ServerBuilder sb) { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned()) + .tls(TlsKeyPair.ofSelfSigned("localhost")) .connectionAcceptor(ConnectionAcceptor.of(ctx -> { acceptCount.incrementAndGet(); throw new RuntimeException("acceptor failed"); diff --git a/core/src/test/java/com/linecorp/armeria/server/DelayedConnectionAcceptorTest.java b/core/src/test/java/com/linecorp/armeria/server/DelayedConnectionAcceptorTest.java index 91751c0f0de..efc485fe51c 100644 --- a/core/src/test/java/com/linecorp/armeria/server/DelayedConnectionAcceptorTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/DelayedConnectionAcceptorTest.java @@ -57,7 +57,7 @@ class DelayedConnectionAcceptorTest { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned()) + .tls(TlsKeyPair.ofSelfSigned("localhost")) .connectionAcceptor(ctx -> { acceptCount.incrementAndGet(); final CompletableFuture future = new CompletableFuture<>(); @@ -74,7 +74,7 @@ protected void configure(ServerBuilder sb) { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned()) + .tls(TlsKeyPair.ofSelfSigned("localhost")) .idleTimeoutMillis(1000) // Never completes — should be closed by the accept timeout. .connectionAcceptor(ctx -> { From 935411c44cb15311645f9963b9ffc8c063edf42f Mon Sep 17 00:00:00 2001 From: Ikhun Um Date: Thu, 6 Aug 2026 11:56:34 +0900 Subject: [PATCH 2/7] Truncate the local hostname to 64 characters in TlsKeyPair.ofSelfSigned() Motivation: `TlsKeyPair.ofSelfSigned()` uses the local hostname as the certificate CN as is, so it fails with `IllegalArgumentException: commonName length 67 exceeds RFC 5280 ub-common-name (64)` on a machine whose hostname exceeds 64 characters, such as a GitHub Actions macOS runner. Modifications: - Truncate the local hostname to 64 characters in `TlsKeyPair.ofSelfSigned()` before generating a self-signed certificate, and document the behavior. Result: - `TlsKeyPair.ofSelfSigned()` no longer fails on a machine whose hostname exceeds 64 characters. --- .../linecorp/armeria/common/TlsKeyPair.java | 12 +++++- .../armeria/common/TlsKeyPairTest.java | 38 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java 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 51f5f2a8092..13790365cd6 100644 --- a/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java +++ b/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java @@ -42,6 +42,8 @@ @UnstableApi public final class TlsKeyPair { + private static final int MAX_COMMON_NAME_LENGTH = 64; + /** * Creates a new {@link TlsKeyPair} from the specified key {@link InputStream}, and certificate chain * {@link InputStream}. @@ -122,9 +124,17 @@ 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()); + String hostname = SystemInfo.hostname(); + if (hostname.length() > MAX_COMMON_NAME_LENGTH) { + // RFC 5280 limits the length of a common name to 64 characters. + hostname = hostname.substring(0, MAX_COMMON_NAME_LENGTH); + } + return ofSelfSigned(hostname); } private final PrivateKey privateKey; diff --git a/core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java b/core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java new file mode 100644 index 00000000000..22758ef2e24 --- /dev/null +++ b/core/src/test/java/com/linecorp/armeria/common/TlsKeyPairTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2026 LY Corporation + * + * LY Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.linecorp.armeria.common; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.linecorp.armeria.common.util.SystemInfo; + +class TlsKeyPairTest { + + @Test + void selfSignedWithLocalHostname() { + // 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.certificateChain()).hasSize(1); + 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); + } +} From 0ef56e6c44cc527b7de24f2f8b4f374a73b1eada Mon Sep 17 00:00:00 2001 From: Ikhun Um Date: Thu, 6 Aug 2026 12:20:54 +0900 Subject: [PATCH 3/7] Trigger CI From a0644fcbbceb99b30aaa06da38fe4646dd1e651f Mon Sep 17 00:00:00 2001 From: Ikhun Um Date: Thu, 6 Aug 2026 12:39:08 +0900 Subject: [PATCH 4/7] Log a debug message when the local hostname is truncated Motivation: Without a log, a truncated certificate CN is hard to trace back to the hostname length limit. Modifications: - Log a debug message with the original and truncated hostname when `TlsKeyPair.ofSelfSigned()` truncates the local hostname. Result: - Easier to diagnose a certificate whose CN differs from the local hostname. --- .../main/java/com/linecorp/armeria/common/TlsKeyPair.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 f26f6d55f04..fa25e8acd92 100644 --- a/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java +++ b/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java @@ -147,8 +147,10 @@ public static TlsKeyPair ofSelfSigned(String hostname) { public static TlsKeyPair ofSelfSigned() { String hostname = SystemInfo.hostname(); if (hostname.length() > MAX_COMMON_NAME_LENGTH) { - // RFC 5280 limits the length of a common name to 64 characters. - hostname = hostname.substring(0, MAX_COMMON_NAME_LENGTH); + final String truncated = hostname.substring(0, MAX_COMMON_NAME_LENGTH); + logger.debug("Truncating the local hostname '{}' to '{}' to satisfy " + + "the RFC 5280 common name length limit (64).", hostname, truncated); + hostname = truncated; } return ofSelfSigned(hostname); } From 52cf5f9df7959b533d8052a40f0fc507fdcfebbd Mon Sep 17 00:00:00 2001 From: Ikhun Um Date: Thu, 6 Aug 2026 12:44:43 +0900 Subject: [PATCH 5/7] Revert "Pin the self-signed certificate hostname to "localhost" in tests" This reverts commit f75df0ae7e742137807f20aa65117641bde22262. --- .../athenz/AthenzTokenClientErrorHandlingTest.java | 2 +- .../armeria/client/athenz/RoleTokenClientTest.java | 4 ++-- .../armeria/client/ClientTlsProviderBuilderTest.java | 10 +++++----- .../linecorp/armeria/client/ClientTlsProviderTest.java | 6 +++--- .../armeria/common/RefreshingTlsProviderTest.java | 8 ++++---- .../armeria/server/ConnectionAcceptorTest.java | 6 +++--- .../armeria/server/DelayedConnectionAcceptorTest.java | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/athenz/src/test/java/com/linecorp/armeria/client/athenz/AthenzTokenClientErrorHandlingTest.java b/athenz/src/test/java/com/linecorp/armeria/client/athenz/AthenzTokenClientErrorHandlingTest.java index 5c65fb5fa97..7555c0f740b 100644 --- a/athenz/src/test/java/com/linecorp/armeria/client/athenz/AthenzTokenClientErrorHandlingTest.java +++ b/athenz/src/test/java/com/linecorp/armeria/client/athenz/AthenzTokenClientErrorHandlingTest.java @@ -56,7 +56,7 @@ protected void configure(ServerBuilder sb) { } }; - private static final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned("localhost"); + private static final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned(); private static ZtsBaseClient forbiddenZtsClient; private static ZtsBaseClient unreachableZtsClient; diff --git a/athenz/src/test/java/com/linecorp/armeria/client/athenz/RoleTokenClientTest.java b/athenz/src/test/java/com/linecorp/armeria/client/athenz/RoleTokenClientTest.java index 4071cfbf75a..707ed45d513 100644 --- a/athenz/src/test/java/com/linecorp/armeria/client/athenz/RoleTokenClientTest.java +++ b/athenz/src/test/java/com/linecorp/armeria/client/athenz/RoleTokenClientTest.java @@ -58,7 +58,7 @@ protected void configure(ServerBuilder sb) { @BeforeAll static void beforeAll() { - final TlsKeyPair tlsKeyPair = TlsKeyPair.ofSelfSigned("localhost"); + final TlsKeyPair tlsKeyPair = TlsKeyPair.ofSelfSigned(); ztsBaseClient = ZtsBaseClient.builder(mockServer.httpUri()) .keyPair(() -> tlsKeyPair) .build(); @@ -113,7 +113,7 @@ void shouldPreloadToken() { @Test void shouldRefreshTokenBeforeExpiry() throws Exception { - final TlsKeyPair tlsKeyPair = TlsKeyPair.ofSelfSigned("localhost"); + final TlsKeyPair tlsKeyPair = TlsKeyPair.ofSelfSigned(); try (ZtsBaseClient ztsBaseClient = ZtsBaseClient.builder(mockServer.httpUri()) .keyPair(() -> tlsKeyPair) .build()) { diff --git a/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderBuilderTest.java b/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderBuilderTest.java index 4d9efb9126d..3763819d982 100644 --- a/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderBuilderTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderBuilderTest.java @@ -37,11 +37,11 @@ void testBuild() { @Test void testMapping() { - final TlsKeyPair exactKeyPair = TlsKeyPair.ofSelfSigned("localhost"); - final TlsKeyPair wildcardKeyPair = TlsKeyPair.ofSelfSigned("localhost"); - final TlsKeyPair defaultKeyPair = TlsKeyPair.ofSelfSigned("localhost"); - final TlsKeyPair barKeyPair = TlsKeyPair.ofSelfSigned("localhost"); - final TlsKeyPair barWildKeyPair = TlsKeyPair.ofSelfSigned("localhost"); + final TlsKeyPair exactKeyPair = TlsKeyPair.ofSelfSigned(); + final TlsKeyPair wildcardKeyPair = TlsKeyPair.ofSelfSigned(); + final TlsKeyPair defaultKeyPair = TlsKeyPair.ofSelfSigned(); + final TlsKeyPair barKeyPair = TlsKeyPair.ofSelfSigned(); + final TlsKeyPair barWildKeyPair = TlsKeyPair.ofSelfSigned(); final TlsProvider tlsProvider = TlsProvider.builder() .keyPair(defaultKeyPair) diff --git a/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderTest.java b/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderTest.java index 8c3c4859f85..094394ec964 100644 --- a/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/ClientTlsProviderTest.java @@ -270,12 +270,12 @@ void testNoMtls() { @Test void disallowTlsProviderWhenTlsSettingsIsSet() { final TlsProvider tlsProvider = - TlsProvider.of(TlsKeyPair.ofSelfSigned("localhost")); + TlsProvider.of(TlsKeyPair.ofSelfSigned()); assertThatThrownBy(() -> { ClientFactory.builder() .tlsProvider(tlsProvider) - .tls(TlsKeyPair.ofSelfSigned("localhost")); + .tls(TlsKeyPair.ofSelfSigned()); }).isInstanceOf(IllegalStateException.class) .hasMessageContaining("Cannot configure TLS settings because a TlsProvider has been set."); @@ -302,7 +302,7 @@ void disallowTlsProviderWhenTlsSettingsIsSet() { assertThatThrownBy(() -> { ClientFactory.builder() - .tls(TlsKeyPair.ofSelfSigned("localhost")) + .tls(TlsKeyPair.ofSelfSigned()) .tlsProvider(tlsProvider); }).isInstanceOf(IllegalStateException.class) .hasMessageContaining( diff --git a/core/src/test/java/com/linecorp/armeria/common/RefreshingTlsProviderTest.java b/core/src/test/java/com/linecorp/armeria/common/RefreshingTlsProviderTest.java index 223e63f1d37..a91abd92b24 100644 --- a/core/src/test/java/com/linecorp/armeria/common/RefreshingTlsProviderTest.java +++ b/core/src/test/java/com/linecorp/armeria/common/RefreshingTlsProviderTest.java @@ -31,7 +31,7 @@ class RefreshingTlsProviderTest { @Test void shouldRefreshTlsKeyPairPeriodically() throws InterruptedException { - final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned("localhost"); + final TlsKeyPair keyPair = TlsKeyPair.ofSelfSigned(); final AtomicInteger counter = new AtomicInteger(); final TlsProvider tlsProvider = TlsProvider.ofScheduled(() -> { counter.incrementAndGet(); @@ -47,7 +47,7 @@ void shouldReturnKeyTlsKeyPairOnUpdate() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); final TlsProvider tlsProvider = TlsProvider.ofScheduled(() -> { counter.incrementAndGet(); - return TlsKeyPair.ofSelfSigned("localhost"); + return TlsKeyPair.ofSelfSigned(); }, Duration.ofSeconds(1)); final TlsKeyPair initialKeyPair = tlsProvider.keyPair("*"); Thread.sleep(2000); @@ -60,7 +60,7 @@ void shouldReturnKeyTlsKeyPairOnUpdate() throws InterruptedException { @Test void shouldNotifyListenerOnKeyPair() throws InterruptedException { final AtomicReference keyPairRef = new AtomicReference<>(); - keyPairRef.set(TlsKeyPair.ofSelfSigned("localhost")); + keyPairRef.set(TlsKeyPair.ofSelfSigned()); final AtomicReference capturedKeyPairRef = new AtomicReference<>(); final TlsProvider tlsProvider = @@ -71,7 +71,7 @@ void shouldNotifyListenerOnKeyPair() throws InterruptedException { assertThat(capturedKeyPairRef).hasNullValue(); Thread.sleep(1000); assertThat(capturedKeyPairRef).hasNullValue(); - keyPairRef.set(TlsKeyPair.ofSelfSigned("localhost")); + keyPairRef.set(TlsKeyPair.ofSelfSigned()); await().untilAsserted(() -> { assertThat(capturedKeyPairRef).hasValue(keyPairRef.get()); }); diff --git a/core/src/test/java/com/linecorp/armeria/server/ConnectionAcceptorTest.java b/core/src/test/java/com/linecorp/armeria/server/ConnectionAcceptorTest.java index 59449b141f9..91c2bab4051 100644 --- a/core/src/test/java/com/linecorp/armeria/server/ConnectionAcceptorTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/ConnectionAcceptorTest.java @@ -55,7 +55,7 @@ class ConnectionAcceptorTest { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned("localhost")) + .tls(TlsKeyPair.ofSelfSigned()) .connectionAcceptor(ConnectionAcceptor.of(ctx -> { ctx.setAttr(TEST_ATTR, "from-connection"); acceptCount.incrementAndGet(); @@ -75,7 +75,7 @@ protected void configure(ServerBuilder sb) { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned("localhost")) + .tls(TlsKeyPair.ofSelfSigned()) .connectionAcceptor(ConnectionAcceptor.of(ctx -> { acceptCount.incrementAndGet(); return false; @@ -90,7 +90,7 @@ protected void configure(ServerBuilder sb) { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned("localhost")) + .tls(TlsKeyPair.ofSelfSigned()) .connectionAcceptor(ConnectionAcceptor.of(ctx -> { acceptCount.incrementAndGet(); throw new RuntimeException("acceptor failed"); diff --git a/core/src/test/java/com/linecorp/armeria/server/DelayedConnectionAcceptorTest.java b/core/src/test/java/com/linecorp/armeria/server/DelayedConnectionAcceptorTest.java index efc485fe51c..91751c0f0de 100644 --- a/core/src/test/java/com/linecorp/armeria/server/DelayedConnectionAcceptorTest.java +++ b/core/src/test/java/com/linecorp/armeria/server/DelayedConnectionAcceptorTest.java @@ -57,7 +57,7 @@ class DelayedConnectionAcceptorTest { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned("localhost")) + .tls(TlsKeyPair.ofSelfSigned()) .connectionAcceptor(ctx -> { acceptCount.incrementAndGet(); final CompletableFuture future = new CompletableFuture<>(); @@ -74,7 +74,7 @@ protected void configure(ServerBuilder sb) { protected void configure(ServerBuilder sb) { sb.http(0) .https(0) - .tls(TlsKeyPair.ofSelfSigned("localhost")) + .tls(TlsKeyPair.ofSelfSigned()) .idleTimeoutMillis(1000) // Never completes — should be closed by the accept timeout. .connectionAcceptor(ctx -> { From 394ea1c7b0fce4b5161a68a8e2e40cdd80600247 Mon Sep 17 00:00:00 2001 From: Ikhun Um Date: Thu, 6 Aug 2026 15:03:14 +0900 Subject: [PATCH 6/7] Truncate a long fqdn in SelfSignedCertificate instead of TlsKeyPair Motivation: macOS CI runners kept failing through `ServerBuilder.tlsSelfSigned()`, which generates a self-signed certificate via `VirtualHostBuilder` and bypasses `TlsKeyPair.ofSelfSigned()`. Modifications: - Move the 64-character truncation from `TlsKeyPair.ofSelfSigned()` to the `SelfSignedCertificate` constructor so that every self-signed certificate generation path is covered. - Add regression tests with a synthetic 67-character fqdn. Result: - Self-signed certificate generation no longer fails on a machine whose hostname exceeds 64 characters, regardless of the API used. --- .../linecorp/armeria/common/TlsKeyPair.java | 11 +--------- .../common/util/SelfSignedCertificate.java | 21 +++++++++++++++++-- .../util/SelfSignedCertificateTest.java | 13 ++++++++++++ .../server/VirtualHostBuilderTest.java | 14 +++++++++++++ 4 files changed, 47 insertions(+), 12 deletions(-) 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 fa25e8acd92..c85308d1526 100644 --- a/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java +++ b/core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java @@ -58,8 +58,6 @@ public final class TlsKeyPair { private static final byte[] VALIDATION_PROBE = "Armeria TlsKeyPair validation probe".getBytes(StandardCharsets.UTF_8); - private static final int MAX_COMMON_NAME_LENGTH = 64; - /** * Creates a new {@link TlsKeyPair} from the specified key {@link InputStream}, and certificate chain * {@link InputStream}. @@ -145,14 +143,7 @@ public static TlsKeyPair ofSelfSigned(String hostname) { * RFC 5280 common name length limit. */ public static TlsKeyPair ofSelfSigned() { - String hostname = SystemInfo.hostname(); - if (hostname.length() > MAX_COMMON_NAME_LENGTH) { - final String truncated = hostname.substring(0, MAX_COMMON_NAME_LENGTH); - logger.debug("Truncating the local hostname '{}' to '{}' to satisfy " + - "the RFC 5280 common name length limit (64).", hostname, truncated); - hostname = truncated; - } - return ofSelfSigned(hostname); + return ofSelfSigned(SystemInfo.hostname()); } private final PrivateKey privateKey; 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/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/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(); From 865a05667981fceb40aadb7f473546506dd4fea8 Mon Sep 17 00:00:00 2001 From: Ikhun Um Date: Thu, 6 Aug 2026 15:44:15 +0900 Subject: [PATCH 7/7] Expect the truncated hostname in ServerTlsHandshakeMetricsTest Motivation: The hostname tag of `armeria.server.tls.handshakes` is derived from the server certificate, whose common name is now truncated to 64 characters. The test compared it against the full default hostname, so it failed on a machine whose hostname exceeds 64 characters. Modifications: - Compare the hostname tag against the certificate hostname, truncated to 64 characters. Result: - `ServerTlsHandshakeMetricsTest` passes on a machine whose hostname exceeds 64 characters. --- .../armeria/server/ServerTlsHandshakeMetricsTest.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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); + } }