Add https client authentication

Change-Id: Ib04eaa8e534e2ac83fb4c11a169f110a5f3a580d
This commit is contained in:
Piotr Fus 2018-01-29 21:14:11 +01:00
parent 0323749ff4
commit 79e7530390
7 changed files with 145 additions and 14 deletions

View file

@ -24,6 +24,9 @@
<xs:element name="keystorePath" type="xs:string" /> <xs:element name="keystorePath" type="xs:string" />
<xs:element name="keystorePassword" type="xs:string" /> <xs:element name="keystorePassword" type="xs:string" />
<xs:element name="keyPassword" type="xs:string" /> <xs:element name="keyPassword" type="xs:string" />
<xs:element name="truststorePath" type="xs:string" />
<xs:element name="truststorePassword" type="xs:string" />
<xs:element name="requireClientAuth" type="xs:boolean" />
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
</xs:schema> </xs:schema>

View file

@ -16,8 +16,10 @@ import pl.touk.mockserver.client.Util
import pl.touk.mockserver.server.HttpMockServer import pl.touk.mockserver.server.HttpMockServer
import spock.lang.Shared import spock.lang.Shared
import spock.lang.Specification import spock.lang.Specification
import spock.lang.Unroll
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.SSLHandshakeException
import java.security.KeyStore import java.security.KeyStore
class MockServerHttpsTest extends Specification { class MockServerHttpsTest extends Specification {
@ -27,14 +29,20 @@ class MockServerHttpsTest extends Specification {
HttpMockServer httpMockServer HttpMockServer httpMockServer
@Shared @Shared
SSLContext sslContext = SSLContexts.custom() SSLContext noClientAuthSslContext = SSLContexts.custom()
.loadTrustMaterial(trustStore()) .loadTrustMaterial(trustStore())
.build() .build()
@Shared @Shared
CloseableHttpClient client = HttpClients.custom() SSLContext trustedCertificateSslContext = SSLContexts.custom()
.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) .loadKeyMaterial(trustedCertificateKeystore(), 'changeit'.toCharArray())
.setSslcontext(sslContext) .loadTrustMaterial(trustStore())
.build()
@Shared
SSLContext untrustedCertificateSslContext = SSLContexts.custom()
.loadKeyMaterial(untrustedCertificateKeystore(), 'changeit'.toCharArray())
.loadTrustMaterial(trustStore())
.build() .build()
def setup() { def setup() {
@ -64,17 +72,90 @@ class MockServerHttpsTest extends Specification {
when: when:
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint') HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8")) restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost) CloseableHttpResponse response = client(noClientAuthSslContext).execute(restPost)
then: then:
GPathResult restPostResponse = Util.extractXmlResponse(response) GPathResult restPostResponse = Util.extractXmlResponse(response)
restPostResponse.name() == 'goodResponse-request' restPostResponse.name() == 'goodResponse-request'
and: }
remoteMockServer.removeMock('testHttps')?.size() == 1
def 'should handle HTTPS server with client auth' () {
expect:
remoteMockServer.addMock(new AddMock(
name: 'testHttps',
path: 'testEndpoint',
port: 10443,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> "<goodResponse-${req.xml.name()}/>"}''',
https: new Https(
keyPassword: 'changeit',
keystorePassword: 'changeit',
keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path,
truststorePath: MockServerHttpsTest.classLoader.getResource('truststore.jks').path,
truststorePassword: 'changeit',
requireClientAuth: true
),
soap: false
))
when:
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client(trustedCertificateSslContext).execute(restPost)
then:
GPathResult restPostResponse = Util.extractXmlResponse(response)
restPostResponse.name() == 'goodResponse-request'
}
@Unroll
def 'should handle HTTPS server with wrong client auth' () {
expect:
remoteMockServer.addMock(new AddMock(
name: 'testHttps',
path: 'testEndpoint',
port: 10443,
predicate: '''{req -> req.xml.name() == 'request'}''',
response: '''{req -> "<goodResponse-${req.xml.name()}/>"}''',
https: new Https(
keyPassword: 'changeit',
keystorePassword: 'changeit',
keystorePath: MockServerHttpsTest.classLoader.getResource('keystore.jks').path,
truststorePath: MockServerHttpsTest.classLoader.getResource('truststore.jks').path,
truststorePassword: 'changeit',
requireClientAuth: true
),
soap: false
))
when:
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
client(sslContext).execute(restPost)
then:
thrown(SSLHandshakeException)
where:
sslContext << [noClientAuthSslContext, untrustedCertificateSslContext]
}
private CloseableHttpClient client(SSLContext sslContext) {
return HttpClients.custom()
.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.setSslcontext(sslContext)
.build()
}
private KeyStore trustedCertificateKeystore() {
return loadKeystore('trusted.jks')
}
private KeyStore untrustedCertificateKeystore() {
return loadKeystore('untrusted.jks')
} }
private KeyStore trustStore() { private KeyStore trustStore() {
KeyStore truststore = KeyStore.getInstance(KeyStore.defaultType) return loadKeystore('truststore.jks')
truststore.load(new FileInputStream(MockServerHttpsTest.classLoader.getResource('truststore.jks').path), "changeit".toCharArray()); }
private KeyStore loadKeystore(String fileName) {
KeyStore truststore = KeyStore.getInstance(KeyStore.defaultType)
truststore.load(new FileInputStream(MockServerHttpsTest.classLoader.getResource(fileName).path), "changeit".toCharArray());
return truststore return truststore
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -2,15 +2,16 @@ package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpHandler import com.sun.net.httpserver.HttpHandler
import com.sun.net.httpserver.HttpServer import com.sun.net.httpserver.HttpServer
import com.sun.net.httpserver.HttpsConfigurator
import com.sun.net.httpserver.HttpsServer import com.sun.net.httpserver.HttpsServer
import groovy.transform.PackageScope import groovy.transform.PackageScope
import groovy.util.logging.Slf4j import groovy.util.logging.Slf4j
import pl.touk.mockserver.api.common.Https import pl.touk.mockserver.api.common.Https
import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import java.security.KeyStore import java.security.KeyStore
import java.security.SecureRandom import java.security.SecureRandom
import java.util.concurrent.Executor import java.util.concurrent.Executor
@ -36,7 +37,7 @@ class HttpServerWrapper {
private HttpServer buildServer(InetSocketAddress addr, Https https) { private HttpServer buildServer(InetSocketAddress addr, Https https) {
if (https) { if (https) {
HttpsServer httpsServer = HttpsServer.create(addr, 0) HttpsServer httpsServer = HttpsServer.create(addr, 0)
httpsServer.httpsConfigurator = new HttpsConfigurator(buildSslContext(https)) httpsServer.httpsConfigurator = new HttpsConfig(buildSslContext(https), https)
return httpsServer return httpsServer
} else { } else {
return HttpServer.create(addr, 0) return HttpServer.create(addr, 0)
@ -44,14 +45,32 @@ class HttpServerWrapper {
} }
private SSLContext buildSslContext(Https https) { private SSLContext buildSslContext(Https https) {
KeyManager[] keyManagers = buildKeyManager(https)
TrustManager[] trustManagers = buildTrustManager(https)
SSLContext ssl = SSLContext.getInstance('TLSv1')
ssl.init(keyManagers, trustManagers, new SecureRandom())
return ssl
}
private KeyManager[] buildKeyManager(Https https) {
KeyStore keyStore = KeyStore.getInstance(KeyStore.defaultType) KeyStore keyStore = KeyStore.getInstance(KeyStore.defaultType)
keyStore.load(new FileInputStream(https.keystorePath), https.keystorePassword.toCharArray()) keyStore.load(new FileInputStream(https.keystorePath), https.keystorePassword.toCharArray())
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.defaultAlgorithm) KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.defaultAlgorithm)
kmf.init(keyStore, https.keyPassword.toCharArray()) kmf.init(keyStore, https.keyPassword.toCharArray())
return kmf.keyManagers
}
SSLContext ssl = SSLContext.getInstance('TLSv1') private TrustManager[] buildTrustManager(Https https) {
ssl.init(kmf.keyManagers, [] as TrustManager[], new SecureRandom()) if (https.requireClientAuth) {
return ssl KeyStore trustStore = KeyStore.getInstance(KeyStore.defaultType)
trustStore.load(new FileInputStream(https.truststorePath), https.truststorePassword.toCharArray())
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.defaultAlgorithm)
tmf.init(trustStore)
return tmf.trustManagers
} else {
return []
}
} }
void createContext(String context, HttpHandler handler) { void createContext(String context, HttpHandler handler) {

View file

@ -0,0 +1,28 @@
package pl.touk.mockserver.server
import com.sun.net.httpserver.HttpsConfigurator
import com.sun.net.httpserver.HttpsParameters
import groovy.transform.CompileStatic
import pl.touk.mockserver.api.common.Https
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLParameters
@CompileStatic
class HttpsConfig extends HttpsConfigurator {
private final Https https
HttpsConfig(SSLContext sslContext, Https https) {
super(sslContext)
this.https = https
}
@Override
void configure(HttpsParameters httpsParameters) {
SSLContext sslContext = getSSLContext()
SSLParameters sslParameters = sslContext.defaultSSLParameters
sslParameters.needClientAuth = https.requireClientAuth
httpsParameters.needClientAuth = https.requireClientAuth
httpsParameters.SSLParameters = sslParameters
}
}