| /**************************************************************** |
| * 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); |
| } |
| |
| } |