mirror of
https://github.com/corda/corda.git
synced 2025-01-20 11:39:09 +00:00
Mutual TLS auth - mixed RSA and ECDSA keys (#2095)
This commit is contained in:
parent
5c18c57417
commit
502d0df630
@ -63,21 +63,21 @@ import javax.crypto.spec.SecretKeySpec
|
|||||||
*/
|
*/
|
||||||
object Crypto {
|
object Crypto {
|
||||||
/**
|
/**
|
||||||
* RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function.
|
* RSA_SHA256 signature scheme using SHA256 as hash algorithm.
|
||||||
* Note: Recommended key size >= 3072 bits.
|
* Note: Recommended key size >= 3072 bits.
|
||||||
*/
|
*/
|
||||||
@JvmField
|
@JvmField
|
||||||
val RSA_SHA256 = SignatureScheme(
|
val RSA_SHA256 = SignatureScheme(
|
||||||
1,
|
1,
|
||||||
"RSA_SHA256",
|
"RSA_SHA256",
|
||||||
AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null),
|
AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption, null),
|
||||||
emptyList(),
|
listOf(AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, null)),
|
||||||
BouncyCastleProvider.PROVIDER_NAME,
|
BouncyCastleProvider.PROVIDER_NAME,
|
||||||
"RSA",
|
"RSA",
|
||||||
"SHA256WITHRSAANDMGF1",
|
"SHA256WITHRSAEncryption",
|
||||||
null,
|
null,
|
||||||
3072,
|
3072,
|
||||||
"RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function."
|
"RSA_SHA256 signature scheme using SHA256 as hash algorithm."
|
||||||
)
|
)
|
||||||
|
|
||||||
/** ECDSA signature scheme using the secp256k1 Koblitz curve. */
|
/** ECDSA signature scheme using the secp256k1 Koblitz curve. */
|
||||||
@ -117,7 +117,7 @@ object Crypto {
|
|||||||
"EDDSA_ED25519_SHA512",
|
"EDDSA_ED25519_SHA512",
|
||||||
// OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00
|
// OID taken from https://tools.ietf.org/html/draft-ietf-curdle-pkix-00
|
||||||
AlgorithmIdentifier(ASN1ObjectIdentifier("1.3.101.112"), null),
|
AlgorithmIdentifier(ASN1ObjectIdentifier("1.3.101.112"), null),
|
||||||
emptyList(),
|
emptyList(), // Both keys and the signature scheme use the same OID in i2p library.
|
||||||
// We added EdDSA to bouncy castle for certificate signing.
|
// We added EdDSA to bouncy castle for certificate signing.
|
||||||
BouncyCastleProvider.PROVIDER_NAME,
|
BouncyCastleProvider.PROVIDER_NAME,
|
||||||
"1.3.101.112",
|
"1.3.101.112",
|
||||||
|
@ -0,0 +1,394 @@
|
|||||||
|
package net.corda.node.utilities
|
||||||
|
|
||||||
|
import net.corda.core.crypto.Crypto
|
||||||
|
import net.corda.core.crypto.SignatureScheme
|
||||||
|
import net.corda.core.crypto.newSecureRandom
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.*
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.security.KeyStore
|
||||||
|
import javax.net.ssl.*
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various tests for mixed-scheme mutual TLS authentication, such as:
|
||||||
|
* Both TLS keys and CAs are using EC NIST P-256.
|
||||||
|
* Both TLS keys and CAs are using RSA.
|
||||||
|
* Server EC NIST P-256 - Client RSA.
|
||||||
|
* Server RSA - Client EC NIST P-256.
|
||||||
|
* Mixed CA and TLS keys.
|
||||||
|
*
|
||||||
|
* TLS/SSL protocols support a large number of cipher suites.
|
||||||
|
* A cipher suite is a collection of symmetric and asymmetric encryption algorithms used by hosts to establish
|
||||||
|
* a secure communication. Supported cipher suites can be classified based on encryption algorithm strength,
|
||||||
|
* key length, key exchange and authentication mechanisms. Some cipher suites offer better level of security than others.
|
||||||
|
*
|
||||||
|
* Each TLS cipher suite has a unique name that is used to identify it and to describe the algorithmic contents of it.
|
||||||
|
* Each segment in a cipher suite name stands for a different algorithm or protocol.
|
||||||
|
* An example of a cipher suite name: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
||||||
|
* The meaning of this name is:
|
||||||
|
* TLS defines the protocol that this cipher suite is for; it will usually be TLS.
|
||||||
|
* ECDHE indicates the key exchange algorithm being used.
|
||||||
|
* ECDSA indicates the authentication algorithm (signing the DH keys).
|
||||||
|
* AES_128_GCM indicates the block cipher being used to encrypt the message stream.
|
||||||
|
* SHA256 indicates the message authentication algorithm which is used to authenticate a message.
|
||||||
|
*/
|
||||||
|
class TLSAuthenticationTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val tempFolder: TemporaryFolder = TemporaryFolder()
|
||||||
|
|
||||||
|
// Root CA.
|
||||||
|
private val ROOT_X500 = CordaX500Name(commonName = "Root_CA_1", organisation = "R3CEV", locality = "London", country = "GB")
|
||||||
|
// Intermediate CA.
|
||||||
|
private val INTERMEDIATE_X500 = CordaX500Name(commonName = "Intermediate_CA_1", organisation = "R3CEV", locality = "London", country = "GB")
|
||||||
|
// TLS server (client1).
|
||||||
|
private val CLIENT_1_X500 = CordaX500Name(commonName = "Client_1", organisation = "R3CEV", locality = "London", country = "GB")
|
||||||
|
// TLS client (client2).
|
||||||
|
private val CLIENT_2_X500 = CordaX500Name(commonName = "Client_2", organisation = "R3CEV", locality = "London", country = "GB")
|
||||||
|
// Password for keys and keystores.
|
||||||
|
private val PASSWORD = "dummypassword"
|
||||||
|
// Default supported TLS schemes for Corda nodes.
|
||||||
|
private val CORDA_TLS_CIPHER_SUITES = arrayOf(
|
||||||
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||||
|
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `All EC R1`() {
|
||||||
|
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||||
|
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
val (serverSocket, clientSocket) =
|
||||||
|
buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||||
|
|
||||||
|
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `All RSA`() {
|
||||||
|
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||||
|
rootCAScheme = Crypto.RSA_SHA256,
|
||||||
|
intermediateCAScheme = Crypto.RSA_SHA256,
|
||||||
|
client1CAScheme = Crypto.RSA_SHA256,
|
||||||
|
client1TLSScheme = Crypto.RSA_SHA256,
|
||||||
|
client2CAScheme = Crypto.RSA_SHA256,
|
||||||
|
client2TLSScheme = Crypto.RSA_SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
val (serverSocket, clientSocket) =
|
||||||
|
buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||||
|
|
||||||
|
testConnect(serverSocket, clientSocket, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server's public key type is the one selected if users use different key types (e.g RSA and EC R1).
|
||||||
|
@Test
|
||||||
|
fun `Server RSA - Client EC R1 - CAs all EC R1`() {
|
||||||
|
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||||
|
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client1TLSScheme = Crypto.RSA_SHA256,
|
||||||
|
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
val (serverSocket, clientSocket) =
|
||||||
|
buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||||
|
testConnect(serverSocket, clientSocket, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") // Server's key type is selected.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Server EC R1 - Client RSA - CAs all EC R1`() {
|
||||||
|
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||||
|
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2TLSScheme = Crypto.RSA_SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||||
|
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256") // Server's key type is selected.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Server EC R1 - Client EC R1 - CAs all RSA`() {
|
||||||
|
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||||
|
rootCAScheme = Crypto.RSA_SHA256,
|
||||||
|
intermediateCAScheme = Crypto.RSA_SHA256,
|
||||||
|
client1CAScheme = Crypto.RSA_SHA256,
|
||||||
|
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2CAScheme = Crypto.RSA_SHA256,
|
||||||
|
client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||||
|
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Server EC R1 - Client RSA - Mixed CAs`() {
|
||||||
|
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||||
|
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
intermediateCAScheme = Crypto.RSA_SHA256,
|
||||||
|
client1CAScheme = Crypto.RSA_SHA256,
|
||||||
|
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2TLSScheme = Crypto.RSA_SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
val (serverSocket, clientSocket) = buildTLSSockets(serverSocketFactory, clientSocketFactory, 0, 0)
|
||||||
|
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `All RSA - avoid ECC for DH`() {
|
||||||
|
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||||
|
rootCAScheme = Crypto.RSA_SHA256,
|
||||||
|
intermediateCAScheme = Crypto.RSA_SHA256,
|
||||||
|
client1CAScheme = Crypto.RSA_SHA256,
|
||||||
|
client1TLSScheme = Crypto.RSA_SHA256,
|
||||||
|
client2CAScheme = Crypto.RSA_SHA256,
|
||||||
|
client2TLSScheme = Crypto.RSA_SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
val (serverSocket, clientSocket) = buildTLSSockets(
|
||||||
|
serverSocketFactory,
|
||||||
|
clientSocketFactory,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
CORDA_TLS_CIPHER_SUITES,
|
||||||
|
arrayOf("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")) // Second client accepts DHE only.
|
||||||
|
testConnect(serverSocket, clientSocket, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256")
|
||||||
|
}
|
||||||
|
|
||||||
|
// According to RFC 5246 (TLS 1.2), section 7.4.1.2 ClientHello cipher_suites:
|
||||||
|
// This is a list of the cryptographic options supported by the client, with the client's first preference first.
|
||||||
|
//
|
||||||
|
// However, the server is still free to ignore this order and pick what it thinks is best,
|
||||||
|
// see https://security.stackexchange.com/questions/121608 for more information.
|
||||||
|
@Test
|
||||||
|
fun `TLS cipher suite order matters - client wins`() {
|
||||||
|
val (serverSocketFactory, clientSocketFactory) = buildTLSFactories(
|
||||||
|
rootCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
intermediateCAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client1CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client1TLSScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2CAScheme = Crypto.ECDSA_SECP256R1_SHA256,
|
||||||
|
client2TLSScheme = Crypto.ECDSA_SECP256R1_SHA256
|
||||||
|
)
|
||||||
|
|
||||||
|
val (serverSocket, clientSocket) = buildTLSSockets(
|
||||||
|
serverSocketFactory,
|
||||||
|
clientSocketFactory,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"), // GCM then CBC.
|
||||||
|
arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")) // CBC then GCM.
|
||||||
|
testConnect(serverSocket, clientSocket, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256") // Client order wins.
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name
|
||||||
|
|
||||||
|
private fun buildTLSFactories(
|
||||||
|
rootCAScheme: SignatureScheme,
|
||||||
|
intermediateCAScheme: SignatureScheme,
|
||||||
|
client1CAScheme: SignatureScheme,
|
||||||
|
client1TLSScheme: SignatureScheme,
|
||||||
|
client2CAScheme: SignatureScheme,
|
||||||
|
client2TLSScheme: SignatureScheme
|
||||||
|
): Pair<SSLServerSocketFactory, SSLSocketFactory> {
|
||||||
|
|
||||||
|
val trustStorePath = tempFile("cordaTrustStore.jks")
|
||||||
|
val client1TLSKeyStorePath = tempFile("client1sslkeystore.jks")
|
||||||
|
val client2TLSKeyStorePath = tempFile("client2sslkeystore.jks")
|
||||||
|
|
||||||
|
// ROOT CA key and cert.
|
||||||
|
val rootCAKeyPair = Crypto.generateKeyPair(rootCAScheme)
|
||||||
|
val rootCACert = X509Utilities.createSelfSignedCACertificate(ROOT_X500, rootCAKeyPair)
|
||||||
|
|
||||||
|
// Intermediate CA key and cert.
|
||||||
|
val intermediateCAKeyPair = Crypto.generateKeyPair(intermediateCAScheme)
|
||||||
|
val intermediateCACert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.INTERMEDIATE_CA,
|
||||||
|
rootCACert,
|
||||||
|
rootCAKeyPair,
|
||||||
|
INTERMEDIATE_X500,
|
||||||
|
intermediateCAKeyPair.public
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client 1 keys, certs and SSLKeyStore.
|
||||||
|
val client1CAKeyPair = Crypto.generateKeyPair(client1CAScheme)
|
||||||
|
val client1CACert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.CLIENT_CA,
|
||||||
|
intermediateCACert,
|
||||||
|
intermediateCAKeyPair,
|
||||||
|
CLIENT_1_X500,
|
||||||
|
client1CAKeyPair.public
|
||||||
|
)
|
||||||
|
|
||||||
|
val client1TLSKeyPair = Crypto.generateKeyPair(client1TLSScheme)
|
||||||
|
val client1TLSCert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.TLS,
|
||||||
|
client1CACert,
|
||||||
|
client1CAKeyPair,
|
||||||
|
CLIENT_1_X500,
|
||||||
|
client1TLSKeyPair.public
|
||||||
|
)
|
||||||
|
|
||||||
|
val client1TLSKeyStore = loadOrCreateKeyStore(client1TLSKeyStorePath, PASSWORD)
|
||||||
|
client1TLSKeyStore.addOrReplaceKey(
|
||||||
|
X509Utilities.CORDA_CLIENT_TLS,
|
||||||
|
client1TLSKeyPair.private,
|
||||||
|
PASSWORD.toCharArray(),
|
||||||
|
arrayOf(client1TLSCert, client1CACert, intermediateCACert, rootCACert))
|
||||||
|
// client1TLSKeyStore.save(client1TLSKeyStorePath, PASSWORD)
|
||||||
|
|
||||||
|
// Client 2 keys, certs and SSLKeyStore.
|
||||||
|
val client2CAKeyPair = Crypto.generateKeyPair(client2CAScheme)
|
||||||
|
val client2CACert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.CLIENT_CA,
|
||||||
|
intermediateCACert,
|
||||||
|
intermediateCAKeyPair,
|
||||||
|
CLIENT_2_X500,
|
||||||
|
client2CAKeyPair.public
|
||||||
|
)
|
||||||
|
|
||||||
|
val client2TLSKeyPair = Crypto.generateKeyPair(client2TLSScheme)
|
||||||
|
val client2TLSCert = X509Utilities.createCertificate(
|
||||||
|
CertificateType.TLS,
|
||||||
|
client2CACert,
|
||||||
|
client2CAKeyPair,
|
||||||
|
CLIENT_2_X500,
|
||||||
|
client2TLSKeyPair.public
|
||||||
|
)
|
||||||
|
|
||||||
|
val client2TLSKeyStore = loadOrCreateKeyStore(client2TLSKeyStorePath, PASSWORD)
|
||||||
|
client2TLSKeyStore.addOrReplaceKey(
|
||||||
|
X509Utilities.CORDA_CLIENT_TLS,
|
||||||
|
client2TLSKeyPair.private,
|
||||||
|
PASSWORD.toCharArray(),
|
||||||
|
arrayOf(client2TLSCert, client2CACert, intermediateCACert, rootCACert))
|
||||||
|
// client2TLSKeyStore.save(client2TLSKeyStorePath, PASSWORD)
|
||||||
|
|
||||||
|
val trustStore = loadOrCreateKeyStore(trustStorePath, PASSWORD)
|
||||||
|
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert)
|
||||||
|
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert.cert)
|
||||||
|
// trustStore.save(trustStorePath, PASSWORD)
|
||||||
|
|
||||||
|
val client1SSLContext = sslContext(client1TLSKeyStore, PASSWORD, trustStore)
|
||||||
|
val client2SSLContext = sslContext(client2TLSKeyStore, PASSWORD, trustStore)
|
||||||
|
|
||||||
|
val serverSocketFactory = client1SSLContext.serverSocketFactory
|
||||||
|
val clientSocketFactory = client2SSLContext.socketFactory
|
||||||
|
|
||||||
|
return Pair(serverSocketFactory, clientSocketFactory)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildTLSSockets(
|
||||||
|
serverSocketFactory: SSLServerSocketFactory,
|
||||||
|
clientSocketFactory: SSLSocketFactory,
|
||||||
|
serverPort: Int = 0, // Use 0 to get first free socket.
|
||||||
|
clientPort: Int = 0, // Use 0 to get first free socket.
|
||||||
|
cipherSuitesServer: Array<String> = CORDA_TLS_CIPHER_SUITES,
|
||||||
|
cipherSuitesClient: Array<String> = CORDA_TLS_CIPHER_SUITES
|
||||||
|
): Pair<SSLServerSocket, SSLSocket> {
|
||||||
|
val serverSocket = serverSocketFactory.createServerSocket(serverPort) as SSLServerSocket // use 0 to get first free socket.
|
||||||
|
val serverParams = SSLParameters(cipherSuitesServer, arrayOf("TLSv1.2"))
|
||||||
|
serverParams.needClientAuth = true // Note that needClientAuth is requiring client authentication Vs wantClientAuth, in which client authentication is optional).
|
||||||
|
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||||
|
serverSocket.sslParameters = serverParams
|
||||||
|
serverSocket.useClientMode = false
|
||||||
|
|
||||||
|
val clientSocket = clientSocketFactory.createSocket() as SSLSocket
|
||||||
|
val clientParams = SSLParameters(cipherSuitesClient, arrayOf("TLSv1.2"))
|
||||||
|
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||||
|
clientSocket.sslParameters = clientParams
|
||||||
|
clientSocket.useClientMode = true
|
||||||
|
// We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind
|
||||||
|
// to whatever <hostname> resolves to(as that's what the server binds to). In particular on Debian <hostname>
|
||||||
|
// resolves to 127.0.1.1 instead of the external address of the interface, so the TLS handshake fails.
|
||||||
|
clientSocket.bind(InetSocketAddress(InetAddress.getLocalHost(), clientPort))
|
||||||
|
return Pair(serverSocket, clientSocket)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testConnect(serverSocket: ServerSocket, clientSocket: SSLSocket, expectedCipherSuite: String) {
|
||||||
|
val lock = Object()
|
||||||
|
var done = false
|
||||||
|
var serverError = false
|
||||||
|
|
||||||
|
val serverThread = thread {
|
||||||
|
try {
|
||||||
|
val sslServerSocket = serverSocket.accept()
|
||||||
|
assertTrue(sslServerSocket.isConnected)
|
||||||
|
val serverInput = DataInputStream(sslServerSocket.inputStream)
|
||||||
|
val receivedString = serverInput.readUTF()
|
||||||
|
assertEquals("Hello World", receivedString)
|
||||||
|
synchronized(lock) {
|
||||||
|
done = true
|
||||||
|
lock.notifyAll()
|
||||||
|
}
|
||||||
|
sslServerSocket.close()
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
serverError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSocket.connect(InetSocketAddress(InetAddress.getLocalHost(), serverSocket.localPort))
|
||||||
|
assertTrue(clientSocket.isConnected)
|
||||||
|
assertEquals(expectedCipherSuite, clientSocket.session.cipherSuite)
|
||||||
|
|
||||||
|
// Timeout after 30 secs.
|
||||||
|
val output = DataOutputStream(clientSocket.outputStream)
|
||||||
|
output.writeUTF("Hello World")
|
||||||
|
var timeout = 0
|
||||||
|
synchronized(lock) {
|
||||||
|
while (!done) {
|
||||||
|
timeout++
|
||||||
|
if (timeout > 30) throw IOException("Timed out waiting for server to complete")
|
||||||
|
lock.wait(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSocket.close()
|
||||||
|
serverThread.join(1000)
|
||||||
|
assertFalse { serverError }
|
||||||
|
serverSocket.close()
|
||||||
|
assertTrue(done)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate an SSLContext from a KeyStore and a TrustStore.
|
||||||
|
private fun sslContext(sslKeyStore: KeyStore, sslKeyStorePassword: String, sslTrustStore: KeyStore) : SSLContext {
|
||||||
|
val context = SSLContext.getInstance("TLS")
|
||||||
|
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
|
||||||
|
// Requires the KeyStore password as well.
|
||||||
|
keyManagerFactory.init(sslKeyStore, sslKeyStorePassword.toCharArray())
|
||||||
|
val keyManagers = keyManagerFactory.keyManagers
|
||||||
|
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||||
|
// Password is not required for TrustStore.
|
||||||
|
trustMgrFactory.init(sslTrustStore)
|
||||||
|
val trustManagers = trustMgrFactory.trustManagers
|
||||||
|
return context.apply { init(keyManagers, trustManagers, newSecureRandom()) }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user