From 1afc93ddb4afc226562043822f6c5e9dc0ed4b2a Mon Sep 17 00:00:00 2001 From: andrzejszukuc Date: Wed, 7 Nov 2018 12:51:05 +0100 Subject: TLS mutual authentication has been added. Change-Id: I60ebe8e1b06d72413940935396cb7a56af437c0d Issue-ID: DCAEGEN2-959 Signed-off-by: ANDRZEJ SZUKUC --- README.md | 45 +++++- certs/password | 1 + certs/rootCA.crt | 31 +++++ certs/rootCA.key | 54 ++++++++ dpo/spec/vescollector-componentspec.json | 24 ++++ etc/collector.properties | 5 + etc/trustpasswordfile | 1 + etc/truststore | Bin 0 -> 1453 bytes pom.xml | 41 ++++-- .../java/org/onap/dcae/ApplicationSettings.java | 13 +- .../dcae/commonFunction/SSLContextCreator.java | 82 +++++++++++ .../org/onap/dcae/restapi/ApiConfiguration.java | 2 + .../java/org/onap/dcae/restapi/ServletConfig.java | 56 ++++++-- src/test/java/org/onap/dcae/TLSTest.java | 138 +++++++++++++++++++ src/test/java/org/onap/dcae/TLSTestBase.java | 152 +++++++++++++++++++++ src/test/java/org/onap/dcae/TestingUtilities.java | 57 +++++++- src/test/resources/keystore | Bin 0 -> 3717 bytes src/test/resources/passwordfile | 1 + src/test/resources/trustpasswordfile | 1 + src/test/resources/truststore | Bin 0 -> 5145 bytes version.properties | 2 +- 21 files changed, 673 insertions(+), 33 deletions(-) create mode 100644 certs/password create mode 100644 certs/rootCA.crt create mode 100644 certs/rootCA.key create mode 100644 etc/trustpasswordfile create mode 100644 etc/truststore create mode 100644 src/main/java/org/onap/dcae/commonFunction/SSLContextCreator.java create mode 100644 src/test/java/org/onap/dcae/TLSTest.java create mode 100644 src/test/java/org/onap/dcae/TLSTestBase.java create mode 100644 src/test/resources/keystore create mode 100644 src/test/resources/passwordfile create mode 100644 src/test/resources/trustpasswordfile create mode 100644 src/test/resources/truststore diff --git a/README.md b/README.md index 9e38dd6b..09037680 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,9 @@ STEPS FOR SETUP/TEST Note: If DMAAPHOST provided is invalid, you will see exception around publish on the collector.logs (collector queues and attempts to resend the event hence exceptions reported will be periodic). If you don’t want to see the error, publish to dmaap can be disabled by changing either “collector.dmaap.streamid” on etc/collector.properties OR by modifying the “name” defined on etc/DmaapConfig.json. Any changes to property within container requires collector restart - /opt/app/VESCollector/bin/VESrestfulCollector.sh stop - /opt/app/VESCollector/bin/VESrestfulCollector.sh start + cd /opt/app/VESCollector/ + ./bin/appController.sh stop + ./bin/appController.sh start 5) If DMAAP instance (and DMAAPHOST passed during VESCollector startup) and VES input is valid, then events will be pushed to below topics depending on the domain Fault :http://:3904/events/unauthenticated.SEC_FAULT_OUTPUT @@ -119,9 +120,9 @@ Authentication is disabled on the container for R1; below are the steps for enab Note: The actual credentials is stored part of header.authlist parameter. This is list of userid,password (base64encoded) values. Default configuration has below set sample1,c2FtcGxlMQ==|vdnsagg,dmRuc2FnZw==, where password maps to same value as username. 3) Restart the collector - cd /opt/app/VESCollector/bin - ./VESrestfulCollector.sh stop - ./VESrestfulCollector.sh start + cd /opt/app/VESCollector + ./bin/appController.sh stop + ./bin/appController.sh start 4) Exit from container and ensure tcp port on VM is not hanging on finwait – you can execute “netstat -an | grep 8443” . If under FIN_WAIT2, wait for server to release. 5) Simulate via curl (Note - username/pwd will be required) Example of successfull POST: @@ -147,3 +148,37 @@ Authentication is disabled on the container for R1; below are the steps for enab Note: In general support for HTTPS also require certificate/keystore be installed on target VM with FS mapped into the container for VESCollector to load. For demo and testing purpose - a self signed certificate is included within docker build. When deployed via DCAEGEN2 platform - these configuration will be overridden dynamically to map to required path/certificate name. This will be exercised post R1 though. ``` + +A client's certificate verification is disabled on the container by default; below are the steps for enabling mutual TLS authentication for VESCollector. +``` +1) Login to the container +2) Open /opt/app/VESCollector/etc/collector.properties and edit below properties + a) Comment below property (with authentication enabled, standard http should be disabled) + collector.service.port=8080 + b) Enable a client's certificate verification + collector.service.secure.clientauth=1 +3) Restart the collector + cd /opt/app/VESCollector + ./bin/appController.sh stop + ./bin/appController.sh start +4) Exit from container and ensure tcp port on VM is not hanging on finwait – you can execute “netstat -an | grep 8443” . If under FIN_WAIT2, wait for server to release. +5) In order for VESCollector to accept a connection from a client, the client has to use TLS certificate signed by CA that is present in VESCollector truststore. If a default VESCollector truststore is used then a client's certificate may be generated using following steps: + a) Generate a client's private key + openssl genrsa -out client.key 2048 + b) Create the signing + openssl req -new -key client.key -out client.csr + c) Create the client's certificate (CA key password should be obtained from [VESCollectorRepository]/certs/password) + openssl x509 -req -in client.csr -CA [VESCollectorRepository]/certs/rootCA.crt -CAkey [VESCollectorRepository]/certs/rootCA.key -CAcreateserial -out client.crt -days 500 -sha256 +6) Simulate via curl (assuming that the certificate was created via step 5) + Example of successfull POST: + curl -i -X POST -d @event.json --header "Content-Type: application/json" https://localhost:8443/eventListener/v7 -k --cert client.crt --key client.key + HTTP/1.1 100 + + HTTP/1.1 202 + Content-Type: application/json + Content-Length: 8 + Date: Wed, 21 Nov 2018 11:37:58 GMT + + Example of authentication failure (without a client's certificate): + curl -i -X POST -d @event.json --header "Content-Type: application/json" https://localhost:8443/eventListener/v7 -k + curl: (35) error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate \ No newline at end of file diff --git a/certs/password b/certs/password new file mode 100644 index 00000000..702a4cbd --- /dev/null +++ b/certs/password @@ -0,0 +1 @@ +collector diff --git a/certs/rootCA.crt b/certs/rootCA.crt new file mode 100644 index 00000000..6a869ffc --- /dev/null +++ b/certs/rootCA.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIUHt/v/SBbEGqP7K3veoJTDUgmiG4wDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xODExMDcwODU5MDRaFw0yMTA4 +MjcwODU5MDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQC+yIztigSbID4dT33lEc+UF69gKpgdD0o7/9nn3Qey +ZrHExdNdF9RFnEReswLe64ZEL8BwBaPzveAPYzk7jN6J1LS3vfTDkGnzNXVyokcT +rGL4xIGAFe13ZBj1xTlxBojRuJsAJ2n2ihnk2pcQa+QCOy7HA7iORVrrf4hzUthH +ccvVFY5Z17sNLtTCrso9P9xIkMq4E+8GS7ufTZIUVSi6oruSOlhD0K0w/aeKwPjB +HTk5o8ymhc/HSOcxVbUe+fkcsK8izHLToPT4lCAPCrsveHb6eIQzd+yzdgCSdOfL +sCoSZP7s56DkmaPNxk8Ua5VnunTKqGOSmTIET+hwRTKW5lCyJGWNIML99rWkp4E+ +TXaSMyQ86S1ffm2qsBwHuNYbTc6KzjeN0Gr54+ApageVIpOM43bPEYd5BLM3StgV +5GdLMH5ndX30zMoIvlgF7olcmdORdA7WgFRAmivN4fn66f6Kie6lvX2FTgjFlArb +abHFLjiqzW4XXcS7SV+opnd67stZAbdkLx+nOZjjJZDmpI5rvh1eZOz0hL9gkbi0 +yYTHXmpkfJoikn2FeCZHrqHXbC5FkDeR0zDEUHC6o7YZvQ9h1yogWRGa6/OZU8Ni +h3hc6ZbVWGWGvG7tX6I5ft5y5GJgwkqOqcG43UCw/Gz2u7mDi0VoNw+PhFIQG94J +VQIDAQABo1MwUTAdBgNVHQ4EFgQUUTgeHVFkmRuXF3IL3eR1nFM1TQowHwYDVR0j +BBgwFoAUUTgeHVFkmRuXF3IL3eR1nFM1TQowDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAgEAqnpeSRF8bp4GKwnqU5HSg3uB5BbmfB3YF2D2NIVxYGfK +Z4KOY6Iea5sxMUEACfWQrI50GBy3J/Wgw2wBMBcKSLXa0VF7WAY61B6VPKVrKBPo +hVdSiWQ+UuwMbV00DPub3myB0rLYLagHrdZ0y9HHpDLQrqW+LR+lEbxckhrVmUsy +wO2d4AmdLCnjqUUVG5xktmpq2Q5aEPkNhQPJEggn9hFlIybklSyRMoWyK36KUo5F +szEt0teI+T4Q6lqJTiVrV3N5w9fNlcJ4ye5uTFrMwBjMS4I0kId2PHfv6KZ+LUQc +4fu22RtNtFmcIfNAxaYyNb53EmcsDLf/v5WM/ZzietU1p0UHzxECdSTFt7cV1Ldt +yFjbWkyZ4Jdn+aMadxpUdjT/Egm31hovE+KPbZUdd9xNEbQ+JZc/kx2Cuyxw1c8a +UcFAxwPzwqU8+LpHLiXx6rZrGuZ7Nc8RpgR4ylHbyRr2ynJgi66QPyp6HhwvBEgK +ig+8qUaczUGlSbssMN11tJ235/j6FFn+V8yp3UJEU7ax7epQ9YMyiw3maY+x1L0Z +ZOXrZCkXCf1LQ3x52e1gvfGqYCBSxY217+bNNieQe3RsxPwaGd/KVv5C62GzIVG+ +r1k7gCv3gIHk8kTVeHpv22qvqrIT6lnGpUgJTAPzvtb+gZn/ByrvGHs5AnZkOkk= +-----END CERTIFICATE----- diff --git a/certs/rootCA.key b/certs/rootCA.key new file mode 100644 index 00000000..08347661 --- /dev/null +++ b/certs/rootCA.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,00244E4EB5B0693C + +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 +-----END RSA PRIVATE KEY----- diff --git a/dpo/spec/vescollector-componentspec.json b/dpo/spec/vescollector-componentspec.json index 3c588fb1..e683345b 100644 --- a/dpo/spec/vescollector-componentspec.json +++ b/dpo/spec/vescollector-componentspec.json @@ -248,6 +248,14 @@ "policy_editable": false, "designer_editable": true }, + { + "name": "collector.service.secure.clientauth", + "value": 0, + "description": "Mutual TLS authentication flag; enables an authentication of the client to the server", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, { "name": "collector.keystore.file.location", "value": "/opt/app/dcae-certificate/keystore.jks", @@ -272,6 +280,22 @@ "policy_editable": false, "designer_editable": false }, + { + "name": "collector.truststore.file.location", + "value": "/opt/app/dcae-certificate/truststore.jks", + "description": "fs location of truststore file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, + { + "name": "collector.truststore.passwordfile", + "value": "/opt/app/dcae-certificate/.trustpassword", + "description": "location of truststore password file in vm", + "sourced_at_deployment": false, + "policy_editable": false, + "designer_editable": false + }, { "name": "collector.inputQueue.maxPending", "value": 8096, diff --git a/etc/collector.properties b/etc/collector.properties index c9ccc133..475c49b0 100755 --- a/etc/collector.properties +++ b/etc/collector.properties @@ -23,11 +23,16 @@ collector.service.port=8080 ## When enabled - require valid keystore defined collector.service.secure.port=8443 +collector.service.secure.clientauth=0 + ## The keystore must be setup per installation when secure port is configured collector.keystore.file.location=etc/keystore collector.keystore.passwordfile=etc/passwordfile collector.keystore.alias=tomcat +## The truststore must be setup per installation when mutual tls support is configured +collector.truststore.file.location=etc/truststore +collector.truststore.passwordfile=etc/trustpasswordfile ############################################################################### ## Processing diff --git a/etc/trustpasswordfile b/etc/trustpasswordfile new file mode 100644 index 00000000..25acfbf5 --- /dev/null +++ b/etc/trustpasswordfile @@ -0,0 +1 @@ +collector \ No newline at end of file diff --git a/etc/truststore b/etc/truststore new file mode 100644 index 00000000..1fbf7ef9 Binary files /dev/null and b/etc/truststore differ diff --git a/pom.xml b/pom.xml index c4347888..eeaa6b9d 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ limitations under the License. org.onap.dcaegen2.collectors.ves VESCollector - 1.4.0-SNAPSHOT + 1.4.1-SNAPSHOT dcaegen2-collectors-ves VESCollector @@ -86,6 +86,11 @@ limitations under the License. maven-project-info-reports-plugin 2.9 + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M1 + com.spotify docker-maven-plugin @@ -334,7 +339,7 @@ limitations under the License. com.google.code.gson gson - 2.3.1 + 2.8.5 org.json @@ -404,15 +409,27 @@ limitations under the License. - org.mockito - mockito-core - 2.18.0 + org.junit.jupiter + junit-jupiter-api + 5.3.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.3.1 + test + + + org.junit.vintage + junit-vintage-engine + 5.3.1 test - junit - junit - 4.12 + org.mockito + mockito-junit-jupiter + 2.23.0 test @@ -436,7 +453,13 @@ limitations under the License. org.springframework.security spring-security-test - 5.1.0.RELEASE + 5.1.1.RELEASE + test + + + org.springframework.boot + spring-boot-starter-test + 2.1.0.RELEASE test diff --git a/src/main/java/org/onap/dcae/ApplicationSettings.java b/src/main/java/org/onap/dcae/ApplicationSettings.java index 189d4a9a..ead148c4 100644 --- a/src/main/java/org/onap/dcae/ApplicationSettings.java +++ b/src/main/java/org/onap/dcae/ApplicationSettings.java @@ -37,7 +37,6 @@ import org.apache.commons.configuration.PropertiesConfiguration; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import javax.annotation.Nullable; import java.io.IOException; @@ -167,10 +166,22 @@ public class ApplicationSettings { return prependWithUserDirOnRelative(properties.getString("collector.keystore.file.location", "etc/keystore")); } + public boolean clientTlsAuthenticationEnabled() { + return httpsEnabled() && properties.getInt("collector.service.secure.clientauth", 0) > 0; + } + public String keystoreAlias() { return properties.getString("collector.keystore.alias", "tomcat"); } + public String truststorePasswordFileLocation() { + return prependWithUserDirOnRelative(properties.getString("collector.truststore.passwordfile", "etc/trustpasswordfile")); + } + + public String truststoreFileLocation() { + return prependWithUserDirOnRelative(properties.getString("collector.truststore.file.location", "etc/truststore")); + } + public String exceptionConfigFileLocation() { return properties.getString("exceptionConfig", null); } diff --git a/src/main/java/org/onap/dcae/commonFunction/SSLContextCreator.java b/src/main/java/org/onap/dcae/commonFunction/SSLContextCreator.java new file mode 100644 index 00000000..29e974ef --- /dev/null +++ b/src/main/java/org/onap/dcae/commonFunction/SSLContextCreator.java @@ -0,0 +1,82 @@ +/*- + * ============LICENSE_START======================================================= + * PROJECT + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.dcae.commonFunction; + +import org.springframework.boot.web.server.Ssl; + +import java.nio.file.Path; + +public class SSLContextCreator { + private final String keyStorePassword; + private final String certAlias; + private final Path keyStoreFile; + + private Path trustStoreFile; + private String trustStorePassword; + private boolean hasTlsClientAuthentication = false; + + public static SSLContextCreator create(final Path keyStoreFile, final String certAlias, final String password) { + return new SSLContextCreator(keyStoreFile, certAlias, password); + } + + private SSLContextCreator(final Path keyStoreFile, final String certAlias, final String password) { + this.certAlias = certAlias; + this.keyStoreFile = keyStoreFile; + this.keyStorePassword = password; + } + + public SSLContextCreator withTlsClientAuthentication(final Path trustStoreFile, final String password) { + hasTlsClientAuthentication = true; + this.trustStoreFile = trustStoreFile; + this.trustStorePassword = password; + + return this; + } + + private void configureKeyStore(final Ssl ssl) { + final String keyStore = keyStoreFile.toAbsolutePath().toString(); + + ssl.setKeyStore(keyStore); + ssl.setKeyPassword(keyStorePassword); + ssl.setKeyAlias(certAlias); + } + + private void configureTrustStore(final Ssl ssl) { + final String trustStore = trustStoreFile.toAbsolutePath().toString(); + + ssl.setTrustStore(trustStore); + ssl.setTrustStorePassword(trustStorePassword); + ssl.setClientAuth(Ssl.ClientAuth.NEED); + } + + public Ssl build() { + final Ssl ssl = new Ssl(); + ssl.setEnabled(true); + + configureKeyStore(ssl); + + if (hasTlsClientAuthentication) { + configureTrustStore(ssl); + } + + return ssl; + } +} \ No newline at end of file diff --git a/src/main/java/org/onap/dcae/restapi/ApiConfiguration.java b/src/main/java/org/onap/dcae/restapi/ApiConfiguration.java index 85db81df..9ebb5394 100644 --- a/src/main/java/org/onap/dcae/restapi/ApiConfiguration.java +++ b/src/main/java/org/onap/dcae/restapi/ApiConfiguration.java @@ -25,9 +25,11 @@ import org.onap.dcae.ApplicationSettings; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +@EnableWebMvc @Configuration public class ApiConfiguration implements WebMvcConfigurer { private final ApplicationSettings applicationSettings; diff --git a/src/main/java/org/onap/dcae/restapi/ServletConfig.java b/src/main/java/org/onap/dcae/restapi/ServletConfig.java index 871904c6..49455e54 100644 --- a/src/main/java/org/onap/dcae/restapi/ServletConfig.java +++ b/src/main/java/org/onap/dcae/restapi/ServletConfig.java @@ -22,6 +22,7 @@ package org.onap.dcae.restapi; import org.onap.dcae.ApplicationSettings; +import org.onap.dcae.commonFunction.SSLContextCreator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -31,6 +32,7 @@ import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerF import org.springframework.stereotype.Component; import java.io.IOException; +import java.nio.file.Path; import java.nio.file.Paths; import static java.nio.file.Files.readAllBytes; @@ -45,31 +47,57 @@ public class ServletConfig implements WebServerFactoryCustomizer queue; + + @LocalServerPort + private int port; + + private final String keyStorePassword; + private final String trustStorePassword; + + public TestClassBase() { + keyStorePassword = readFile(KEYSTORE_PASSWORD_FILE); + trustStorePassword = readFile(TRUSTSTORE_PASSWORD_FILE); + } + + private String getURL(final String protocol, final String uri) { + return protocol + "://localhost:" + port + uri; + } + + private RestTemplate addBasicAuth(final RestTemplate template, final String username, final String password) { + template.getInterceptors() + .add(new BasicAuthenticationInterceptor(username, password)); + + return template; + } + + public String createHttpURL(String uri) { + return getURL("http", uri); + } + + public String createHttpsURL(String uri) { + return getURL("https", uri); + } + + public RestTemplate createHttpRestTemplate() { + return new RestTemplate(); + } + + public RestTemplate createHttpsRestTemplate() { + return rethrow(() -> + createRestTemplateWithSsl( + sslBuilderWithTrustStore(KEYSTORE, keyStorePassword).build() + )); + } + + public RestTemplate createHttpsRestTemplateWithKeyStore() { + return rethrow(() -> + createRestTemplateWithSsl( + configureKeyStore( + sslBuilderWithTrustStore(KEYSTORE, keyStorePassword), + TRUSTSTORE, + trustStorePassword + ).build()) + ); + } + + public ResponseEntity makeHttpRequest() { + return createHttpRestTemplate().getForEntity(createHttpURL("/"), String.class); + } + + public ResponseEntity makeHttpsRequest() { + return createHttpsRestTemplate().getForEntity(createHttpsURL("/"), String.class); + } + + + public ResponseEntity makeHttpsRequestWithBasicAuth(final String username, final String password) { + return addBasicAuth(createHttpsRestTemplate(), username, password) + .getForEntity(createHttpsURL("/"), String.class); + + } + + public ResponseEntity makeHttpsRequestWithClientCert() { + return createHttpsRestTemplateWithKeyStore().getForEntity(createHttpsURL("/"), String.class); + } + + public ResponseEntity makeHttpsRequestWithClientCertAndBasicAuth( + final String username, + final String password) { + return addBasicAuth(createHttpsRestTemplateWithKeyStore(), username, password) + .getForEntity(createHttpsURL("/"), String.class); + } + } +} diff --git a/src/test/java/org/onap/dcae/TestingUtilities.java b/src/test/java/org/onap/dcae/TestingUtilities.java index bd05c4ea..4c0d5382 100644 --- a/src/test/java/org/onap/dcae/TestingUtilities.java +++ b/src/test/java/org/onap/dcae/TestingUtilities.java @@ -24,13 +24,30 @@ import static java.nio.file.Files.readAllBytes; import static org.assertj.core.api.Assertions.assertThat; import io.vavr.control.Try; + import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; import org.assertj.core.api.AbstractThrowableAssert; import org.assertj.core.api.Java6Assertions; import org.json.JSONObject; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import javax.net.ssl.SSLContext; /** * @author Pawel Szalapski (pawel.szalapski@nokia.com) @@ -67,11 +84,11 @@ public final class TestingUtilities { * Exception in test case usually means there is something wrong, it should never be catched, but rather thrown to * be handled by JUnit framework. */ - private static T rethrow(CheckedSupplier supplier) { + public static T rethrow(CheckedSupplier supplier) { try { return supplier.get(); } catch (Exception e) { - throw new RuntimeException(); + throw new RuntimeException(e); } } @@ -84,9 +101,43 @@ public final class TestingUtilities { public static void assertFailureHasInfo(Try any, String... msgPart) { Java6Assertions.assertThat(any.isFailure()).isTrue(); AbstractThrowableAssert o = Java6Assertions.assertThat(any.getCause()) - .hasCauseInstanceOf(Exception.class); + .hasCauseInstanceOf(Exception.class); for (String s : msgPart) { o.hasStackTraceContaining(s); } } + + public static SSLContextBuilder sslBuilderWithTrustStore(final Path trustStore, final String pass) { + return rethrow(() -> + new SSLContextBuilder() + .loadTrustMaterial(trustStore.toFile(), pass.toCharArray()) + ); + } + + public static SSLContextBuilder configureKeyStore( + final SSLContextBuilder builder, + final Path keyStore, + final String pass) { + return rethrow(() -> { + KeyStore cks = KeyStore.getInstance(KeyStore.getDefaultType()); + cks.load(new FileInputStream(keyStore.toFile()), pass.toCharArray()); + + builder.loadKeyMaterial(cks, pass.toCharArray()); + + return builder; + }); + } + + public static RestTemplate createRestTemplateWithSsl(final SSLContext context) { + final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(context); + final HttpClient httpClient = HttpClients + .custom() + .setSSLSocketFactory(socketFactory) + .build(); + + final HttpComponentsClientHttpRequestFactory factory = + new HttpComponentsClientHttpRequestFactory(httpClient); + + return new RestTemplate(factory); + } } diff --git a/src/test/resources/keystore b/src/test/resources/keystore new file mode 100644 index 00000000..f0822cb8 Binary files /dev/null and b/src/test/resources/keystore differ diff --git a/src/test/resources/passwordfile b/src/test/resources/passwordfile new file mode 100644 index 00000000..11e1e240 --- /dev/null +++ b/src/test/resources/passwordfile @@ -0,0 +1 @@ +vestest \ No newline at end of file diff --git a/src/test/resources/trustpasswordfile b/src/test/resources/trustpasswordfile new file mode 100644 index 00000000..11e1e240 --- /dev/null +++ b/src/test/resources/trustpasswordfile @@ -0,0 +1 @@ +vestest \ No newline at end of file diff --git a/src/test/resources/truststore b/src/test/resources/truststore new file mode 100644 index 00000000..b0802a72 Binary files /dev/null and b/src/test/resources/truststore differ diff --git a/version.properties b/version.properties index 9e0d73d4..f3529928 100644 --- a/version.properties +++ b/version.properties @@ -1,6 +1,6 @@ major=1 minor=4 -patch=0 +patch=1 base_version=${major}.${minor}.${patch} release_version=${base_version} snapshot_version=${base_version}-SNAPSHOT -- cgit 1.2.3-korg