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="keystorePassword" 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:complexType>
</xs:schema>

View file

@ -16,8 +16,10 @@ import pl.touk.mockserver.client.Util
import pl.touk.mockserver.server.HttpMockServer
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLHandshakeException
import java.security.KeyStore
class MockServerHttpsTest extends Specification {
@ -27,14 +29,20 @@ class MockServerHttpsTest extends Specification {
HttpMockServer httpMockServer
@Shared
SSLContext sslContext = SSLContexts.custom()
SSLContext noClientAuthSslContext = SSLContexts.custom()
.loadTrustMaterial(trustStore())
.build()
@Shared
CloseableHttpClient client = HttpClients.custom()
.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.setSslcontext(sslContext)
SSLContext trustedCertificateSslContext = SSLContexts.custom()
.loadKeyMaterial(trustedCertificateKeystore(), 'changeit'.toCharArray())
.loadTrustMaterial(trustStore())
.build()
@Shared
SSLContext untrustedCertificateSslContext = SSLContexts.custom()
.loadKeyMaterial(untrustedCertificateKeystore(), 'changeit'.toCharArray())
.loadTrustMaterial(trustStore())
.build()
def setup() {
@ -64,17 +72,90 @@ class MockServerHttpsTest extends Specification {
when:
HttpPost restPost = new HttpPost('https://localhost:10443/testEndpoint')
restPost.entity = new StringEntity('<request/>', ContentType.create("text/xml", "UTF-8"))
CloseableHttpResponse response = client.execute(restPost)
CloseableHttpResponse response = client(noClientAuthSslContext).execute(restPost)
then:
GPathResult restPostResponse = Util.extractXmlResponse(response)
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() {
KeyStore truststore = KeyStore.getInstance(KeyStore.defaultType)
truststore.load(new FileInputStream(MockServerHttpsTest.classLoader.getResource('truststore.jks').path), "changeit".toCharArray());
return loadKeystore('truststore.jks')
}
private KeyStore loadKeystore(String fileName) {
KeyStore truststore = KeyStore.getInstance(KeyStore.defaultType)
truststore.load(new FileInputStream(MockServerHttpsTest.classLoader.getResource(fileName).path), "changeit".toCharArray());
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.HttpServer
import com.sun.net.httpserver.HttpsConfigurator
import com.sun.net.httpserver.HttpsServer
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import pl.touk.mockserver.api.common.Https
import javax.net.ssl.KeyManager
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.TrustManagerFactory
import java.security.KeyStore
import java.security.SecureRandom
import java.util.concurrent.Executor
@ -36,7 +37,7 @@ class HttpServerWrapper {
private HttpServer buildServer(InetSocketAddress addr, Https https) {
if (https) {
HttpsServer httpsServer = HttpsServer.create(addr, 0)
httpsServer.httpsConfigurator = new HttpsConfigurator(buildSslContext(https))
httpsServer.httpsConfigurator = new HttpsConfig(buildSslContext(https), https)
return httpsServer
} else {
return HttpServer.create(addr, 0)
@ -44,14 +45,32 @@ class HttpServerWrapper {
}
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.load(new FileInputStream(https.keystorePath), https.keystorePassword.toCharArray())
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.defaultAlgorithm)
kmf.init(keyStore, https.keyPassword.toCharArray())
return kmf.keyManagers
}
SSLContext ssl = SSLContext.getInstance('TLSv1')
ssl.init(kmf.keyManagers, [] as TrustManager[], new SecureRandom())
return ssl
private TrustManager[] buildTrustManager(Https https) {
if (https.requireClientAuth) {
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) {

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
}
}