Skip to content

Commit

Permalink
Add SslContext validation
Browse files Browse the repository at this point in the history
Motivation:

In some case, servers can start with miss-configured `SslContext` and
fail when users request ssl/tls connections.
If servers refuse to start in that case, users can fix their
configuration before severs start.

Modifications:

Add `VirtualHostBuilder.validateSslContext()`

Result:

Fixes line#1844
  • Loading branch information
jyblue committed Sep 28, 2019
1 parent 45c1633 commit ff9180b
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 0 deletions.
Expand Up @@ -38,6 +38,7 @@

import javax.annotation.Nullable;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;

import org.slf4j.Logger;
Expand All @@ -62,6 +63,7 @@
import com.linecorp.armeria.server.annotation.ResponseConverterFunction;
import com.linecorp.armeria.server.logging.AccessLogWriter;

import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
Expand Down Expand Up @@ -828,6 +830,14 @@ VirtualHost build() {
}
}

if (sslContext != null) {
try {
validateSslContext(sslContext);
} catch (Exception e) {
throw new RuntimeException("failed to validate SSL/TLS configuration", e);
}
}

final Function<VirtualHost, Logger> accessLoggerMapper =
this.accessLoggerMapper != null ? this.accessLoggerMapper : serverBuilder.accessLoggerMapper();

Expand All @@ -841,6 +851,19 @@ VirtualHost build() {
return decorator != null ? virtualHost.decorate(decorator) : virtualHost;
}

void validateSslContext(SslContext sslContext) throws SSLException {
SSLEngine engine = sslContext.newEngine(ByteBufAllocator.DEFAULT);
engine.setUseClientMode(false);

if (engine.getEnabledProtocols().length == 0) {
throw new RuntimeException("failed to enable protocols");
}

if (engine.getEnabledCipherSuites().length == 0) {
throw new RuntimeException("failed to enable cipher suites");
}
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this).omitNullValues()
Expand Down
149 changes: 149 additions & 0 deletions core/src/test/java/com/linecorp/armeria/server/ServerTlsTest.java
@@ -0,0 +1,149 @@
/*
* Copyright 2019 LINE Corporation
*
* LINE 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.server;

import static org.junit.jupiter.api.Assertions.assertThrows;

import java.io.File;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.List;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;

import io.netty.handler.ssl.util.SelfSignedCertificate;

class ServerTlsTest {

static final SelfSignedCertificate ssc;

static {
try {
ssc = new SelfSignedCertificate("a.com");
} catch (Exception e) {
throw new Error(e);
}
}

@ParameterizedTest
@ValueSource(strings = {"SSLv2Hello", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"})
void testTlsCustomizerValidProtocols(String protocol) throws SSLException {
Server server
= new ServerBuilder().service("/", (ctx, res) -> HttpResponse.of(HttpStatus.OK))
.tls(ssc.certificate(), ssc.privateKey(), sslContextBuilder -> {
sslContextBuilder.protocols(protocol);
})
.build();
}

@ParameterizedTest
@ValueSource(strings = {"SSLv2", "SSLv2", "SSLv3", "WrongProtocolName"})
void testTlsCustomizerInvalidProtocols(String protocol) {
assertThrows(RuntimeException.class, () -> {
Server server
= new ServerBuilder().service("/", (ctx, res) -> HttpResponse.of(HttpStatus.OK))
.tls(ssc.certificate(), ssc.privateKey(), sslContextBuilder -> {
sslContextBuilder.protocols(protocol);
})
.build();
});
}

@ParameterizedTest
@ValueSource(strings = {"", " ", "WrongPassword"})
void testTlsWithInvalidKeyPassword(String password) throws CertificateException, SSLException {
assertThrows(RuntimeException.class, () -> {
Server server
= new ServerBuilder().service("/", (ctx, res) -> HttpResponse.of(HttpStatus.OK))
.tls(ssc.certificate(), ssc.privateKey(), password)
.build();
});
}

@Test
void testTlsWithNeverInitializedKeyMangerFactory() {
assertThrows(RuntimeException.class, () -> {
KeyStore keyStore = KeyStore.getInstance("JKS");
File file = new File(getClass().getResource("keystore.jks").toURI());
keyStore.load(file.toURI().toURL().openStream(), "4t[9Pxc".toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

TrustManagerFactory tmf
= TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);

Server server
= new ServerBuilder().service("/", (ctx, res) -> HttpResponse.of(HttpStatus.OK))
.tls(kmf, sslContextBuilder -> {
sslContextBuilder.keyManager(kmf);
sslContextBuilder.trustManager(tmf);
})
.build();
});
}

@Test
void testTlsWithNeverInitializedTrustMangerFactory() {
assertThrows(RuntimeException.class, () -> {
KeyStore keyStore = KeyStore.getInstance("JKS");
File file = new File(getClass().getResource("keystore.jks").toURI());
keyStore.load(file.toURI().toURL().openStream(), "4t[9Pxc".toCharArray());

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "B3g9s%ds".toCharArray());

TrustManagerFactory tmf
= TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

Server server
= new ServerBuilder().service("/", (ctx, res) -> HttpResponse.of(HttpStatus.OK))
.tls(kmf, sslContextBuilder -> {
sslContextBuilder.keyManager(kmf);
sslContextBuilder.trustManager(tmf);
})
.build();
});
}

@Test
void testTlsWrongCipherSuites() throws CertificateException, SSLException {
List<String> ciphers = Arrays.asList(
"WrongCipherName1",
"WrongCipherName2"
);

assertThrows(RuntimeException.class, () -> {
Server server
= new ServerBuilder().service("/", (ctx, res) -> HttpResponse.of("Hello world"))
.tls(ssc.certificate(), ssc.privateKey(), sslContextBuilder -> {
sslContextBuilder.ciphers(ciphers);
})
.build();
});
}
}
Binary file not shown.

0 comments on commit ff9180b

Please sign in to comment.