Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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()) {
Expand Down
12 changes: 11 additions & 1 deletion core/src/main/java/com/linecorp/armeria/common/TlsKeyPair.java
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -122,9 +124,17 @@ public static TlsKeyPair ofSelfSigned(String hostname) {

/**
* Generates a self-signed certificate for the local hostname.
*
* <p>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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.");

Expand All @@ -302,7 +302,7 @@ void disallowTlsProviderWhenTlsSettingsIsSet() {

assertThatThrownBy(() -> {
ClientFactory.builder()
.tls(TlsKeyPair.ofSelfSigned())
.tls(TlsKeyPair.ofSelfSigned("localhost"))
.tlsProvider(tlsProvider);
}).isInstanceOf(IllegalStateException.class)
.hasMessageContaining(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -60,7 +60,7 @@ void shouldReturnKeyTlsKeyPairOnUpdate() throws InterruptedException {
@Test
void shouldNotifyListenerOnKeyPair() throws InterruptedException {
final AtomicReference<TlsKeyPair> keyPairRef = new AtomicReference<>();
keyPairRef.set(TlsKeyPair.ofSelfSigned());
keyPairRef.set(TlsKeyPair.ofSelfSigned("localhost"));

final AtomicReference<TlsKeyPair> capturedKeyPairRef = new AtomicReference<>();
final TlsProvider tlsProvider =
Expand All @@ -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());
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand All @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean> future = new CompletableFuture<>();
Expand All @@ -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 -> {
Expand Down