blob: 7b084f45d740c9948ae9feab5d65e2bc108c7ee7 [file] [log] [blame]
/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF 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 *
* *
* http://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 org.apache.james;
import static io.restassured.RestAssured.given;
import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
import static org.apache.james.jmap.JMAPTestingConstants.LOCALHOST_IP;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.james.data.UsersRepositoryModuleChooser;
import org.apache.james.modules.data.MemoryUsersRepositoryModule;
import org.apache.james.modules.protocols.ImapGuiceProbe;
import org.apache.james.modules.protocols.SmtpGuiceProbe;
import org.apache.james.utils.WebAdminGuiceProbe;
import org.apache.james.webadmin.WebAdminConfiguration;
import org.apache.james.webadmin.WebAdminUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import com.google.common.collect.ImmutableList;
import io.restassured.RestAssured;
class CertificateReloadTest {
public static class BlindTrustManager implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
}
private static final List<String> BASE_CONFIGURATION_FILE_NAMES = ImmutableList.of("dnsservice.xml",
"dnsservice.xml",
"imapserver.xml",
"imapserver2.xml",
"jwt_publickey",
"lmtpserver.xml",
"keystore",
"mailetcontainer.xml",
"mailrepositorystore.xml",
"managesieveserver.xml",
"pop3server.xml",
"smtpserver.xml",
"smtpserver2.xml");
private GuiceJamesServer jamesServer;
private TemporaryJamesServer temporaryJamesServer;
@BeforeEach
void beforeEach(@TempDir Path workingPath) {
temporaryJamesServer = new TemporaryJamesServer(workingPath.toFile(), BASE_CONFIGURATION_FILE_NAMES);
jamesServer = temporaryJamesServer.getJamesServer()
.combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
.combineWith(new UsersRepositoryModuleChooser(new MemoryUsersRepositoryModule())
.chooseModules(UsersRepositoryModuleChooser.Implementation.DEFAULT))
.overrideWith(binder -> binder.bind(WebAdminConfiguration.class).toInstance(WebAdminConfiguration.TEST_CONFIGURATION));
}
@AfterEach
void afterEach() {
if (jamesServer != null && jamesServer.isStarted()) {
jamesServer.stop();
}
}
@Test
void subjectShouldBeKeptWhenNoRestart() throws Exception {
temporaryJamesServer.copyResources("smtpserver2.xml", "smtpserver.xml");
jamesServer.start();
assertThat(getServerCertificate(jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpSslPort().getValue()).getSubjectX500Principal().getName())
.isEqualTo("CN=Benoit Tellier,OU=Linagora,O=James,L=Puteaux,ST=Unknown,C=FR");
}
private X509Certificate getServerCertificate(int port) throws NoSuchAlgorithmException, KeyManagementException, IOException {
SSLSocket clientConnection = openSSLConnection(port);
return Arrays.stream(clientConnection.getSession()
.getPeerCertificates())
.filter(X509Certificate.class::isInstance)
.map(X509Certificate.class::cast)
.findFirst()
.orElseThrow();
}
private SSLSocket openSSLConnection(int port) throws NoSuchAlgorithmException, KeyManagementException, IOException {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[]{new BlindTrustManager()}, null);
SSLSocket clientConnection = (SSLSocket) ctx.getSocketFactory().createSocket(LOCALHOST_IP, port);
return clientConnection;
}
@Test
void reloadShouldUpdateCertificates() throws Exception {
temporaryJamesServer.copyResources("smtpserver2.xml", "smtpserver.xml");
jamesServer.start();
temporaryJamesServer.copyResources("keystore2", "keystore");
WebAdminGuiceProbe webAdminGuiceProbe = jamesServer.getProbe(WebAdminGuiceProbe.class);
RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminGuiceProbe.getWebAdminPort())
.build();
int port = jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpSslPort().getValue();
given()
.queryParam("reload-certificate")
.queryParam("port", port)
.when()
.post("/servers")
.then()
.statusCode(204);
assertThat(getServerCertificate(port).getSubjectX500Principal().getName())
.isEqualTo("CN=Testing,OU=Testing,O=Testing,L=Testing,ST=Testing,C=Te");
}
@Test
void reloadShouldUpdateCertificatesForImap() throws Exception {
temporaryJamesServer.copyResources("imapserver2.xml", "imapserver.xml");
jamesServer.start();
temporaryJamesServer.copyResources("keystore2", "keystore");
WebAdminGuiceProbe webAdminGuiceProbe = jamesServer.getProbe(WebAdminGuiceProbe.class);
RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminGuiceProbe.getWebAdminPort())
.build();
int port = jamesServer.getProbe(ImapGuiceProbe.class).getImapSSLPort();
given()
.queryParam("reload-certificate")
.queryParam("port", port)
.when()
.post("/servers")
.then()
.statusCode(204);
assertThat(getServerCertificate(port).getSubjectX500Principal().getName())
.isEqualTo("CN=Testing,OU=Testing,O=Testing,L=Testing,ST=Testing,C=Te");
}
@Test
void reloadShouldNotAbortExistingConnections() throws Exception {
temporaryJamesServer.copyResources("smtpserver2.xml", "smtpserver.xml");
jamesServer.start();
int port = jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpSslPort().getValue();
SSLSocket channel = openSSLConnection(port);
temporaryJamesServer.copyResources("keystore2", "keystore");
WebAdminGuiceProbe webAdminGuiceProbe = jamesServer.getProbe(WebAdminGuiceProbe.class);
RestAssured.requestSpecification = WebAdminUtils.buildRequestSpecification(webAdminGuiceProbe.getWebAdminPort())
.build();
given()
.queryParam("reload-certificate")
.queryParam("port", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpSslPort().getValue())
.when()
.post("/servers")
.then()
.statusCode(204);
readBytes(channel);
channel.getOutputStream().write("EHLO toto.com\r\n".getBytes(StandardCharsets.UTF_8));
assertThat(readBytes(channel))
.contains("250 8BITMIME");
}
private String readBytes(SSLSocket sslSocket) throws IOException {
byte[] bline = new byte[1024];
final int read = sslSocket.getInputStream().read(bline);
return new String(bline, 0, read);
}
}