mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
Create client CA certificate with X509 name constraint (#731)
* The node will be issued a CA certificate with name constraint which will allow the node to create keys with a valid certificate chain.
This commit is contained in:
parent
bbe4c170c2
commit
246de55433
@ -14,7 +14,10 @@ import org.bouncycastle.asn1.bc.BCObjectIdentifiers
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
|
||||
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
@ -558,26 +561,26 @@ object Crypto {
|
||||
/**
|
||||
* Use bouncy castle utilities to sign completed X509 certificate with CA cert private key.
|
||||
*/
|
||||
fun createCertificate(issuer: X500Name, issuerKeyPair: KeyPair,
|
||||
fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair,
|
||||
subject: X500Name, subjectPublicKey: PublicKey,
|
||||
keyUsage: KeyUsage, purposes: List<KeyPurposeId>,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
pathLength: Int? = null, subjectAlternativeName: List<GeneralName>? = null): X509Certificate {
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
|
||||
val signatureScheme = findSignatureScheme(issuerKeyPair.private)
|
||||
val provider = providerMap[signatureScheme.providerName]
|
||||
val serial = BigInteger.valueOf(random63BitValue())
|
||||
val keyPurposes = DERSequence(ASN1EncodableVector().apply { purposes.forEach { add(it) } })
|
||||
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
|
||||
|
||||
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
|
||||
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectPublicKey.encoded)))
|
||||
.addExtension(Extension.basicConstraints, pathLength != null, if (pathLength == null) BasicConstraints(false) else BasicConstraints(pathLength))
|
||||
.addExtension(Extension.keyUsage, false, keyUsage)
|
||||
.addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA))
|
||||
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
|
||||
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
|
||||
|
||||
if (subjectAlternativeName != null && subjectAlternativeName.isNotEmpty()) {
|
||||
builder.addExtension(Extension.subjectAlternativeName, false, DERSequence(subjectAlternativeName.toTypedArray()))
|
||||
if (nameConstraints != null) {
|
||||
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
|
||||
}
|
||||
|
||||
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
|
||||
return JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer)).apply {
|
||||
checkValidity(Date())
|
||||
|
@ -5,23 +5,19 @@ import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.X500NameBuilder
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.KeyPurposeId
|
||||
import org.bouncycastle.asn1.x509.KeyUsage
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.util.IPAddress
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.InputStream
|
||||
import java.net.InetAddress
|
||||
import java.nio.file.Path
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStore
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
import java.security.cert.Certificate
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
@ -31,20 +27,12 @@ object X509Utilities {
|
||||
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
|
||||
|
||||
// Aliases for private keys and certificates.
|
||||
val CORDA_ROOT_CA_PRIVATE_KEY = "cordarootcaprivatekey"
|
||||
val CORDA_ROOT_CA = "cordarootca"
|
||||
val CORDA_INTERMEDIATE_CA_PRIVATE_KEY = "cordaintermediatecaprivatekey"
|
||||
val CORDA_INTERMEDIATE_CA = "cordaintermediateca"
|
||||
val CORDA_CLIENT_CA_PRIVATE_KEY = "cordaclientcaprivatekey"
|
||||
val CORDA_CLIENT_TLS = "cordaclienttls"
|
||||
val CORDA_CLIENT_CA = "cordaclientca"
|
||||
|
||||
private val CA_KEY_USAGE = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign)
|
||||
private val CLIENT_KEY_USAGE = KeyUsage(KeyUsage.digitalSignature)
|
||||
private val CA_KEY_PURPOSES = listOf(KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage)
|
||||
private val CLIENT_KEY_PURPOSES = listOf(KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth)
|
||||
|
||||
private val DEFAULT_VALIDITY_WINDOW = Pair(Duration.ofMillis(0), Duration.ofDays(365 * 10))
|
||||
|
||||
/**
|
||||
* Helper function to return the latest out of an instant and an optional date.
|
||||
*/
|
||||
@ -110,101 +98,59 @@ object X509Utilities {
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a de novo root self-signed X509 v3 CA cert and [KeyPair].
|
||||
* @param subject the cert Subject will be populated with the domain string.
|
||||
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return A data class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates.
|
||||
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates.
|
||||
* Create a de novo root self-signed X509 v3 CA cert.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACert(subject: X500Name,
|
||||
keyPair: KeyPair,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
|
||||
fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
|
||||
val cert = Crypto.createCertificate(subject, keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 2)
|
||||
return CertificateAndKeyPair(cert, keyPair)
|
||||
val cert = Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
|
||||
return cert
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair
|
||||
= createSelfSignedCACert(subject, generateKeyPair(signatureScheme), validityWindow)
|
||||
|
||||
/**
|
||||
* Create a de novo root intermediate X509 v3 CA cert and KeyPair.
|
||||
* Create a X509 v3 cert.
|
||||
* @param issuerCertificate The Public certificate of the root CA above this used to sign it.
|
||||
* @param issuerKeyPair The KeyPair of the root CA above this used to sign it.
|
||||
* @param subject subject of the generated certificate.
|
||||
* @param ca The Public certificate and KeyPair of the root CA certificate above this used to sign it.
|
||||
* @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided.
|
||||
* @param subjectPublicKey subject 's public key.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates.
|
||||
* Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createIntermediateCert(subject: X500Name,
|
||||
ca: CertificateAndKeyPair,
|
||||
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): CertificateAndKeyPair {
|
||||
val keyPair = generateKeyPair(signatureScheme)
|
||||
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate)
|
||||
val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, window, pathLength = 1)
|
||||
return CertificateAndKeyPair(cert, keyPair)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an X509v3 certificate suitable for use in TLS roles.
|
||||
* @param subject The contents to put in the subject field of the certificate.
|
||||
* @param publicKey The PublicKey to be wrapped in the certificate.
|
||||
* @param ca The Public certificate and KeyPair of the parent CA that will sign this certificate.
|
||||
* @param subjectAlternativeNameDomains A set of alternate DNS names to be supported by the certificate during validation of the TLS handshakes.
|
||||
* @param subjectAlternativeNameIps A set of alternate IP addresses to be supported by the certificate during validation of the TLS handshakes.
|
||||
* @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided.
|
||||
* @return The generated X509Certificate suitable for use as a Server/Client certificate in TLS.
|
||||
* This certificate is not marked as a CA cert to be similar in nature to commercial certificates.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createTlsServerCert(subject: X500Name, publicKey: PublicKey,
|
||||
ca: CertificateAndKeyPair,
|
||||
subjectAlternativeNameDomains: List<String>,
|
||||
subjectAlternativeNameIps: List<String>,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
|
||||
|
||||
val issuer = X509CertificateHolder(ca.certificate.encoded).subject
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate)
|
||||
val dnsNames = subjectAlternativeNameDomains.map { GeneralName(GeneralName.dNSName, it) }
|
||||
val ipAddresses = subjectAlternativeNameIps.filter {
|
||||
IPAddress.isValidIPv6WithNetmask(it) || IPAddress.isValidIPv6(it) || IPAddress.isValidIPv4WithNetmask(it) || IPAddress.isValidIPv4(it)
|
||||
}.map { GeneralName(GeneralName.iPAddress, it) }
|
||||
return Crypto.createCertificate(issuer, ca.keyPair, subject, publicKey, CLIENT_KEY_USAGE, CLIENT_KEY_PURPOSES, window, subjectAlternativeName = dnsNames + ipAddresses)
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuerCertificate: X509Certificate, issuerKeyPair: KeyPair,
|
||||
subject: X500Name, subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
|
||||
val cert = Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
|
||||
return cert
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a certificate path from a trusted root certificate to a target certificate. This will always return a path
|
||||
* directly from the root to the target, with no intermediate certificates (presuming that path is valid).
|
||||
* directly from the target to the root.
|
||||
*
|
||||
* @param rootCertAndKey trusted root certificate that will be the start of the path.
|
||||
* @param targetCertAndKey certificate the path ends at.
|
||||
* @param trustedRoot trusted root certificate that will be the start of the path.
|
||||
* @param certificates certificates in the path.
|
||||
* @param revocationEnabled whether revocation of certificates in the path should be checked.
|
||||
*/
|
||||
fun createCertificatePath(rootCertAndKey: CertificateAndKeyPair,
|
||||
targetCertAndKey: X509Certificate,
|
||||
revocationEnabled: Boolean): CertPathBuilderResult {
|
||||
val intermediateCertificates = setOf(targetCertAndKey)
|
||||
val certStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(intermediateCertificates))
|
||||
val certPathFactory = CertPathBuilder.getInstance("PKIX")
|
||||
val trustAnchor = TrustAnchor(rootCertAndKey.certificate, null)
|
||||
val certPathParameters = try {
|
||||
PKIXBuilderParameters(setOf(trustAnchor), X509CertSelector().apply {
|
||||
certificate = targetCertAndKey
|
||||
})
|
||||
} catch (ex: InvalidAlgorithmParameterException) {
|
||||
throw RuntimeException(ex)
|
||||
}.apply {
|
||||
addCertStore(certStore)
|
||||
isRevocationEnabled = revocationEnabled
|
||||
}
|
||||
return certPathFactory.build(certPathParameters)
|
||||
fun createCertificatePath(trustedRoot: X509Certificate, vararg certificates: X509Certificate, revocationEnabled: Boolean): CertPath {
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
params.isRevocationEnabled = revocationEnabled
|
||||
return certFactory.generateCertPath(certificates.toList())
|
||||
}
|
||||
|
||||
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
|
||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(certificates.toList())
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
pathValidator.validate(certPath, params)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,7 +183,8 @@ object X509Utilities {
|
||||
|
||||
/**
|
||||
* An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine.
|
||||
* @param keyStoreFilePath KeyStore path to save output to.
|
||||
* @param sslKeyStorePath KeyStore path to save ssl key and cert to.
|
||||
* @param clientCAKeystorePath KeyStore path to save client CA key and cert to.
|
||||
* @param storePassword access password for KeyStore.
|
||||
* @param keyPassword PrivateKey access password for the generated keys.
|
||||
* It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same.
|
||||
@ -245,57 +192,66 @@ object X509Utilities {
|
||||
* @param caKeyPassword password to unlock private keys in the CA KeyStore.
|
||||
* @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications.
|
||||
*/
|
||||
fun createKeystoreForSSL(keyStoreFilePath: Path,
|
||||
storePassword: String,
|
||||
keyPassword: String,
|
||||
caKeyStore: KeyStore,
|
||||
caKeyPassword: String,
|
||||
commonName: X500Name,
|
||||
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME): KeyStore {
|
||||
fun createKeystoreForCordaNode(sslKeyStorePath: Path,
|
||||
clientCAKeystorePath: Path,
|
||||
storePassword: String,
|
||||
keyPassword: String,
|
||||
caKeyStore: KeyStore,
|
||||
caKeyPassword: String,
|
||||
legalName: X500Name,
|
||||
signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) {
|
||||
|
||||
val rootCA = caKeyStore.getCertificateAndKeyPair(CORDA_ROOT_CA_PRIVATE_KEY, caKeyPassword)
|
||||
val intermediateCA = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, caKeyPassword)
|
||||
val rootCACert = caKeyStore.getX509Certificate(CORDA_ROOT_CA)
|
||||
val (intermediateCACert, intermediateCAKeyPair) = caKeyStore.getCertificateAndKeyPair(CORDA_INTERMEDIATE_CA, caKeyPassword)
|
||||
|
||||
val serverKey = generateKeyPair(signatureScheme)
|
||||
val host = InetAddress.getLocalHost()
|
||||
val serverCert = createTlsServerCert(commonName, serverKey.public, intermediateCA, listOf(host.hostName), listOf(host.hostAddress))
|
||||
val clientKey = generateKeyPair(signatureScheme)
|
||||
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf())
|
||||
val clientCACert = createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, legalName, clientKey.public, nameConstraints = nameConstraints)
|
||||
|
||||
val tlsKey = generateKeyPair(signatureScheme)
|
||||
val clientTLSCert = createCertificate(CertificateType.TLS, clientCACert, clientKey, legalName, tlsKey.public)
|
||||
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
|
||||
|
||||
keyStore.addOrReplaceKey(
|
||||
CORDA_CLIENT_CA_PRIVATE_KEY,
|
||||
serverKey.private,
|
||||
val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(clientCAKeystorePath, storePassword)
|
||||
clientCAKeystore.addOrReplaceKey(
|
||||
CORDA_CLIENT_CA,
|
||||
clientKey.private,
|
||||
keyPass,
|
||||
arrayOf(serverCert, intermediateCA.certificate, rootCA.certificate))
|
||||
keyStore.addOrReplaceCertificate(CORDA_CLIENT_CA, serverCert)
|
||||
keyStore.save(keyStoreFilePath, storePassword)
|
||||
return keyStore
|
||||
arrayOf(clientCACert, intermediateCACert, rootCACert))
|
||||
clientCAKeystore.save(clientCAKeystorePath, storePassword)
|
||||
|
||||
val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeyStorePath, storePassword)
|
||||
tlsKeystore.addOrReplaceKey(
|
||||
CORDA_CLIENT_TLS,
|
||||
tlsKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))
|
||||
tlsKeystore.save(sslKeyStorePath, storePassword)
|
||||
}
|
||||
|
||||
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) = Crypto.createCertificateSigningRequest(subject, keyPair, signatureScheme)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present, this throws an
|
||||
* exception.
|
||||
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
|
||||
* adds one.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
|
||||
|
||||
/**
|
||||
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
|
||||
*
|
||||
* @param mutator a function to generate the new value from the previous one.
|
||||
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
|
||||
val builder = X500NameBuilder(BCStyle.INSTANCE)
|
||||
var matched = false
|
||||
@ -319,6 +275,7 @@ private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500N
|
||||
val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
|
||||
val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
|
||||
val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
|
||||
val X509Certificate.subject: X500Name get() = X509CertificateHolder(encoded).subject
|
||||
|
||||
class CertificateStream(val input: InputStream) {
|
||||
private val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
@ -327,3 +284,11 @@ class CertificateStream(val input: InputStream) {
|
||||
}
|
||||
|
||||
data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair)
|
||||
|
||||
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
|
||||
ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
CLIENT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
|
||||
TLS(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.keyAgreement), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false),
|
||||
IDENTITY(KeyUsage(KeyUsage.digitalSignature), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false)
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.junit.Test
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertPathValidator
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.PKIXParameters
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class X509NameConstraintsTest {
|
||||
|
||||
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
|
||||
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Root CA"), rootKeys)
|
||||
|
||||
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, X509Utilities.getDevX509Name("Corda Intermediate CA"), intermediateCAKeyPair.public)
|
||||
|
||||
val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, X509Utilities.getDevX509Name("Corda Client CA"), clientCAKeyPair.public, nameConstraints = nameConstraints)
|
||||
|
||||
val keyPass = "password"
|
||||
val trustStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE)
|
||||
trustStore.load(null, keyPass.toCharArray())
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert)
|
||||
|
||||
val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public)
|
||||
|
||||
val keyStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE)
|
||||
keyStore.load(null, keyPass.toCharArray())
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), arrayOf(tlsCert, clientCACert, intermediateCACert, rootCACert))
|
||||
return Pair(keyStore, trustStore)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `illegal common name`() {
|
||||
val acceptableNames = listOf("CN=Bank A TLS, O=Bank A", "CN=Bank A")
|
||||
.map { GeneralSubtree(GeneralName(X500Name(it))) }.toTypedArray()
|
||||
|
||||
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
|
||||
assertFailsWith(CertPathValidatorException::class) {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints)
|
||||
val params = PKIXParameters(trustStore)
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
|
||||
pathValidator.validate(certPath, params)
|
||||
}
|
||||
|
||||
assertTrue {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A TLS, O=Bank A"), nameConstraints)
|
||||
val params = PKIXParameters(trustStore)
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
|
||||
pathValidator.validate(certPath, params)
|
||||
true
|
||||
}
|
||||
|
||||
assertTrue {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A"), nameConstraints)
|
||||
val params = PKIXParameters(trustStore)
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
|
||||
pathValidator.validate(certPath, params)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `x500 name with correct cn and extra attribute`() {
|
||||
val acceptableNames = listOf("CN=Bank A TLS, UID=", "O=Bank A")
|
||||
.map { GeneralSubtree(GeneralName(X500Name(it))) }.toTypedArray()
|
||||
|
||||
val nameConstraints = NameConstraints(acceptableNames, arrayOf())
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
Crypto.ECDSA_SECP256R1_SHA256
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME)
|
||||
|
||||
assertFailsWith(CertPathValidatorException::class) {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A"), nameConstraints)
|
||||
val params = PKIXParameters(trustStore)
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
|
||||
pathValidator.validate(certPath, params)
|
||||
}
|
||||
|
||||
assertFailsWith(CertPathValidatorException::class) {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A, UID=12345"), nameConstraints)
|
||||
val params = PKIXParameters(trustStore)
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
|
||||
pathValidator.validate(certPath, params)
|
||||
}
|
||||
|
||||
assertTrue {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A TLS, UID=, E=me@email.com, C=UK"), nameConstraints)
|
||||
val params = PKIXParameters(trustStore)
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
|
||||
pathValidator.validate(certPath, params)
|
||||
true
|
||||
}
|
||||
|
||||
assertTrue {
|
||||
val (keystore, trustStore) = makeKeyStores(X500Name("O=Bank A, UID=, E=me@email.com, C=UK"), nameConstraints)
|
||||
val params = PKIXParameters(trustStore)
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList())
|
||||
pathValidator.validate(certPath, params)
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,10 +1,13 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
|
||||
import net.corda.core.crypto.Crypto.generateKeyPair
|
||||
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
|
||||
import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate
|
||||
import net.corda.core.div
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.getTestX509Name
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -33,61 +36,53 @@ class X509UtilitiesTest {
|
||||
|
||||
@Test
|
||||
fun `create valid self-signed CA certificate`() {
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test Cert"))
|
||||
assertTrue { caCertAndKey.certificate.subjectDN.name.contains("CN=Test Cert") } // using our subject common name
|
||||
assertEquals(caCertAndKey.certificate.issuerDN, caCertAndKey.certificate.subjectDN) //self-signed
|
||||
caCertAndKey.certificate.checkValidity(Date()) // throws on verification problems
|
||||
caCertAndKey.certificate.verify(caCertAndKey.keyPair.public) // throws on verification problems
|
||||
assertTrue { caCertAndKey.certificate.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertTrue { caCertAndKey.certificate.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate
|
||||
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
|
||||
assertTrue { caCert.subjectDN.name.contains("CN=Test Cert") } // using our subject common name
|
||||
assertEquals(caCert.issuerDN, caCert.subjectDN) //self-signed
|
||||
caCert.checkValidity(Date()) // throws on verification problems
|
||||
caCert.verify(caKey.public) // throws on verification problems
|
||||
assertTrue { caCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertTrue { caCert.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `load and save a PEM file certificate`() {
|
||||
val tmpCertificateFile = tempFile("cacert.pem")
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test Cert"))
|
||||
X509Utilities.saveCertificateAsPEMFile(caCertAndKey.certificate, tmpCertificateFile)
|
||||
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
|
||||
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
|
||||
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
||||
assertEquals(caCertAndKey.certificate, readCertificate)
|
||||
assertEquals(caCert, readCertificate)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create valid server certificate chain`() {
|
||||
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test CA Cert"))
|
||||
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = createSelfSignedCACertificate(getTestX509Name("Test CA Cert"), caKey)
|
||||
val subjectDN = getTestX509Name("Server Cert")
|
||||
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val serverCert = X509Utilities.createTlsServerCert(subjectDN, keyPair.public, caCertAndKey, listOf("alias name"), listOf("10.0.0.54"))
|
||||
val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subjectDN, keyPair.public)
|
||||
assertTrue { serverCert.subjectDN.name.contains("CN=Server Cert") } // using our subject common name
|
||||
assertEquals(caCertAndKey.certificate.issuerDN, serverCert.issuerDN) // Issued by our CA cert
|
||||
assertEquals(caCert.issuerDN, serverCert.issuerDN) // Issued by our CA cert
|
||||
serverCert.checkValidity(Date()) // throws on verification problems
|
||||
serverCert.verify(caCertAndKey.keyPair.public) // throws on verification problems
|
||||
serverCert.verify(caKey.public) // throws on verification problems
|
||||
assertFalse { serverCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertTrue { serverCert.basicConstraints == -1 } // This returns the signing path length should be -1 for non-CA certificate
|
||||
assertEquals(2, serverCert.subjectAlternativeNames.size)
|
||||
var foundAliasDnsName = false
|
||||
for (entry in serverCert.subjectAlternativeNames) {
|
||||
val typeId = entry[0] as Int
|
||||
val value = entry[1] as String
|
||||
if (typeId == GeneralName.iPAddress) {
|
||||
assertEquals("10.0.0.54", value)
|
||||
} else if (value == "alias name") {
|
||||
foundAliasDnsName = true
|
||||
}
|
||||
}
|
||||
assertTrue(foundAliasDnsName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storing EdDSA key in java keystore`() {
|
||||
val tmpKeyStore = tempFile("keystore.jks")
|
||||
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), Crypto.EDDSA_ED25519_SHA512)
|
||||
val keyPair = generateKeyPair(EDDSA_ED25519_SHA512)
|
||||
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
|
||||
|
||||
assertEquals(selfSignCert.certificate.publicKey, selfSignCert.keyPair.public)
|
||||
assertEquals(selfSignCert.publicKey, keyPair.public)
|
||||
|
||||
// Save the EdDSA private key with self sign cert in the keystore.
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
|
||||
keyStore.setKeyEntry("Key", selfSignCert.keyPair.private, "password".toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), arrayOf(selfSignCert))
|
||||
keyStore.save(tmpKeyStore, "keystorepass")
|
||||
|
||||
// Load the keystore from file and make sure keys are intact.
|
||||
@ -97,20 +92,21 @@ class X509UtilitiesTest {
|
||||
|
||||
assertNotNull(pubKey)
|
||||
assertNotNull(privateKey)
|
||||
assertEquals(selfSignCert.keyPair.public, pubKey)
|
||||
assertEquals(selfSignCert.keyPair.private, privateKey)
|
||||
assertEquals(keyPair.public, pubKey)
|
||||
assertEquals(keyPair.private, privateKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `signing EdDSA key with EcDSA certificate`() {
|
||||
val tmpKeyStore = tempFile("keystore.jks")
|
||||
val ecDSACert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"))
|
||||
val edDSAKeypair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512")
|
||||
val edDSACert = X509Utilities.createTlsServerCert(X500Name("CN=TestEdDSA"), edDSAKeypair.public, ecDSACert, listOf("alias name"), listOf("10.0.0.54"))
|
||||
val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val ecDSACert = createSelfSignedCACertificate(X500Name("CN=Test"), ecDSAKey)
|
||||
val edDSAKeypair = generateKeyPair(EDDSA_ED25519_SHA512)
|
||||
val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public)
|
||||
|
||||
// Save the EdDSA private key with cert chains.
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
|
||||
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert.certificate, edDSACert))
|
||||
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert, edDSACert))
|
||||
keyStore.save(tmpKeyStore, "keystorepass")
|
||||
|
||||
// Load the keystore from file and make sure keys are intact.
|
||||
@ -138,8 +134,8 @@ class X509UtilitiesTest {
|
||||
// Load back generated root CA Cert and private key from keystore and check against copy in truststore
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass")
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
|
||||
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY) as X509Certificate
|
||||
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey
|
||||
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
|
||||
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA, "keypass".toCharArray()) as PrivateKey
|
||||
val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
|
||||
assertEquals(rootCaCert, rootCaFromTrustStore)
|
||||
rootCaCert.checkValidity(Date())
|
||||
@ -147,24 +143,25 @@ class X509UtilitiesTest {
|
||||
|
||||
// Now sign something with private key and verify against certificate public key
|
||||
val testData = "12345".toByteArray()
|
||||
val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
|
||||
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
|
||||
val caSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
|
||||
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
|
||||
|
||||
// Load back generated intermediate CA Cert and private key
|
||||
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) as X509Certificate
|
||||
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey
|
||||
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA) as X509Certificate
|
||||
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA, "keypass".toCharArray()) as PrivateKey
|
||||
intermediateCaCert.checkValidity(Date())
|
||||
intermediateCaCert.verify(rootCaCert.publicKey)
|
||||
|
||||
// Now sign something with private key and verify against certificate public key
|
||||
val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
|
||||
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
|
||||
val intermediateSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
|
||||
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create server certificate in keystore for SSL`() {
|
||||
val tmpCAKeyStore = tempFile("keystore.jks")
|
||||
val tmpTrustStore = tempFile("truststore.jks")
|
||||
val tmpSSLKeyStore = tempFile("sslkeystore.jks")
|
||||
val tmpServerKeyStore = tempFile("serverkeystore.jks")
|
||||
|
||||
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
|
||||
@ -176,30 +173,39 @@ class X509UtilitiesTest {
|
||||
|
||||
// Load signing intermediate CA cert
|
||||
val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass")
|
||||
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "cakeypass")
|
||||
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cakeypass")
|
||||
|
||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||
X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
|
||||
// Load back server certificate
|
||||
val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass")
|
||||
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY, "serverkeypass")
|
||||
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass")
|
||||
|
||||
serverCertAndKey.certificate.checkValidity(Date())
|
||||
serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey)
|
||||
|
||||
assertTrue { serverCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) }
|
||||
|
||||
// Load back server certificate
|
||||
val sslKeyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass")
|
||||
val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass")
|
||||
|
||||
sslCertAndKey.certificate.checkValidity(Date())
|
||||
sslCertAndKey.certificate.verify(serverCertAndKey.certificate.publicKey)
|
||||
|
||||
assertTrue { sslCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) }
|
||||
// Now sign something with private key and verify against certificate public key
|
||||
val testData = "123456".toByteArray()
|
||||
val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
|
||||
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
|
||||
val signature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
|
||||
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create server cert and use in SSL socket`() {
|
||||
val tmpCAKeyStore = tempFile("keystore.jks")
|
||||
val tmpTrustStore = tempFile("truststore.jks")
|
||||
val tmpSSLKeyStore = tempFile("sslkeystore.jks")
|
||||
val tmpServerKeyStore = tempFile("serverkeystore.jks")
|
||||
|
||||
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
|
||||
@ -210,7 +216,8 @@ class X509UtilitiesTest {
|
||||
"trustpass")
|
||||
|
||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||
val keyStore = X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
X509Utilities.createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name)
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass")
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
|
||||
|
||||
val context = SSLContext.getInstance("TLS")
|
||||
@ -235,7 +242,7 @@ class X509UtilitiesTest {
|
||||
arrayOf("TLSv1.2"))
|
||||
serverParams.wantClientAuth = true
|
||||
serverParams.needClientAuth = true
|
||||
serverParams.endpointIdentificationAlgorithm = "HTTPS" // enable hostname checking
|
||||
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
|
||||
serverSocket.sslParameters = serverParams
|
||||
serverSocket.useClientMode = false
|
||||
|
||||
@ -247,7 +254,7 @@ class X509UtilitiesTest {
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"),
|
||||
arrayOf("TLSv1.2"))
|
||||
clientParams.endpointIdentificationAlgorithm = "HTTPS" // enable hostname checking
|
||||
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
|
||||
@ -284,8 +291,7 @@ class X509UtilitiesTest {
|
||||
val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
|
||||
val x500name = X500Name(peerX500Principal.name)
|
||||
assertEquals(MEGA_CORP.name, x500name)
|
||||
|
||||
|
||||
X509Utilities.validateCertificateChain(trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), *peerChain)
|
||||
val output = DataOutputStream(clientSocket.outputStream)
|
||||
output.writeUTF("Hello World")
|
||||
var timeout = 0
|
||||
@ -324,36 +330,40 @@ class X509UtilitiesTest {
|
||||
trustStoreFilePath: Path,
|
||||
trustStorePassword: String
|
||||
): KeyStore {
|
||||
val rootCA = X509Utilities.createSelfSignedCACert(X509Utilities.getDevX509Name("Corda Node Root CA"))
|
||||
val intermediateCA = X509Utilities.createIntermediateCert(X509Utilities.getDevX509Name("Corda Node Intermediate CA"), rootCA)
|
||||
val rootCAKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Node Root CA"), rootCAKey)
|
||||
|
||||
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X509Utilities.getDevX509Name("Corda Node Intermediate CA"), intermediateCAKeyPair.public)
|
||||
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
|
||||
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, rootCA.keyPair.private, keyPass, arrayOf(rootCA.certificate))
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(rootCACert))
|
||||
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY,
|
||||
intermediateCA.keyPair.private,
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA,
|
||||
intermediateCAKeyPair.private,
|
||||
keyPass,
|
||||
arrayOf(intermediateCA.certificate, rootCA.certificate))
|
||||
arrayOf(intermediateCACert, rootCACert))
|
||||
|
||||
keyStore.save(keyStoreFilePath, storePassword)
|
||||
|
||||
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword)
|
||||
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCA.certificate)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCA.certificate)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert)
|
||||
|
||||
trustStore.save(trustStoreFilePath, trustStorePassword)
|
||||
|
||||
return keyStore
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Get correct private key type from Keystore`() {
|
||||
val keyPair = Crypto.generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), keyPair)
|
||||
val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert))
|
||||
|
||||
val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
|
||||
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")
|
||||
|
@ -143,7 +143,7 @@ class KryoTests {
|
||||
|
||||
@Test
|
||||
fun `serialize - deserialize X509Certififcate`() {
|
||||
val expected = X509Utilities.createSelfSignedCACert(ALICE.name).certificate
|
||||
val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
val serialized = expected.serialize(kryo).bytes
|
||||
val actual: X509Certificate = serialized.deserialize(kryo)
|
||||
assertEquals(expected, actual)
|
||||
@ -151,9 +151,10 @@ class KryoTests {
|
||||
|
||||
@Test
|
||||
fun `serialize - deserialize X509CertPath`() {
|
||||
val rootCA = X509Utilities.createSelfSignedCACert(ALICE.name)
|
||||
val certificate = X509Utilities.createTlsServerCert(BOB.name, BOB_PUBKEY, rootCA, emptyList(), emptyList())
|
||||
val expected = X509Utilities.createCertificatePath(rootCA, certificate, false).certPath
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY)
|
||||
val expected = X509Utilities.createCertificatePath(rootCACert, certificate, revocationEnabled = false)
|
||||
val serialized = expected.serialize(kryo).bytes
|
||||
val actual: CertPath = serialized.deserialize(kryo)
|
||||
assertEquals(expected, actual)
|
||||
|
@ -73,6 +73,11 @@ UNRELEASED
|
||||
point we will support the ability for a node to have multiple versions of the same flow registered, enabling backwards
|
||||
compatibility of CorDapp flows.
|
||||
|
||||
* The certificate hierarchy has been changed in order to allow corda node to sign keys with proper certificate chain.
|
||||
* The corda node will now be issued a restricted client CA for identity/transaction key signing.
|
||||
* TLS certificate are now stored in `sslkeystore.jks` and identity keys are stored in `nodekeystore.jks`
|
||||
.. warning:: The old keystore will need to be removed when upgrading to this version.
|
||||
|
||||
Milestone 11.1
|
||||
--------------
|
||||
|
||||
|
@ -107,8 +107,10 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
|
||||
*/
|
||||
fun checkStorePasswords() {
|
||||
val config = config ?: return
|
||||
config.keyStoreFile.read {
|
||||
KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray())
|
||||
arrayOf(config.sslKeystore, config.nodeKeystore).forEach {
|
||||
it.read {
|
||||
KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray())
|
||||
}
|
||||
}
|
||||
config.trustStoreFile.read {
|
||||
KeyStore.getInstance("JKS").load(it, config.trustStorePassword.toCharArray())
|
||||
|
@ -53,14 +53,14 @@ class ArtemisTcpTransport {
|
||||
)
|
||||
|
||||
if (config != null && enableSSL) {
|
||||
config.keyStoreFile.expectedOnDefaultFileSystem()
|
||||
config.sslKeystore.expectedOnDefaultFileSystem()
|
||||
config.trustStoreFile.expectedOnDefaultFileSystem()
|
||||
val tlsOptions = mapOf<String, Any?>(
|
||||
val tlsOptions = mapOf(
|
||||
// Enable TLS transport layer with client certs and restrict to at least SHA256 in handshake
|
||||
// and AES encryption
|
||||
TransportConstants.SSL_ENABLED_PROP_NAME to true,
|
||||
TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||
TransportConstants.KEYSTORE_PATH_PROP_NAME to config.keyStoreFile,
|
||||
TransportConstants.KEYSTORE_PATH_PROP_NAME to config.sslKeystore,
|
||||
TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to config.keyStorePassword, // TODO proper management of keystores and password
|
||||
TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||
TransportConstants.TRUSTSTORE_PATH_PROP_NAME to config.trustStoreFile,
|
||||
|
@ -7,6 +7,7 @@ interface SSLConfiguration {
|
||||
val keyStorePassword: String
|
||||
val trustStorePassword: String
|
||||
val certificatesDirectory: Path
|
||||
val keyStoreFile: Path get() = certificatesDirectory / "sslkeystore.jks"
|
||||
val sslKeystore: Path get() = certificatesDirectory / "sslkeystore.jks"
|
||||
val nodeKeystore: Path get() = certificatesDirectory / "nodekeystore.jks"
|
||||
val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks"
|
||||
}
|
@ -1,14 +1,26 @@
|
||||
package net.corda.services.messaging
|
||||
|
||||
import net.corda.core.copyTo
|
||||
import net.corda.core.createDirectories
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.exists
|
||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
|
||||
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.PEER_USER
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.config.SSLConfiguration
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.messaging.SimpleMQClient
|
||||
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQClusterSecurityException
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
|
||||
/**
|
||||
* Runs the security tests with the attacker pretending to be a node on the network.
|
||||
@ -66,4 +78,56 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
attacker.start(PEER_USER, PEER_USER, enableSSL = false) // Login as a peer
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login with invalid certificate chain`() {
|
||||
val sslConfig = object : SSLConfiguration {
|
||||
override val certificatesDirectory = Files.createTempDirectory("certs")
|
||||
override val keyStorePassword: String get() = "cordacadevpass"
|
||||
override val trustStorePassword: String get() = "trustpass"
|
||||
|
||||
init {
|
||||
val legalName = MEGA_CORP.name
|
||||
certificatesDirectory.createDirectories()
|
||||
if (!trustStoreFile.exists()) {
|
||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
}
|
||||
|
||||
val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
|
||||
val rootCACert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val intermediateCA = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
val clientKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
|
||||
// Set name constrain to the legal name.
|
||||
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName))), arrayOf())
|
||||
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCA.certificate, intermediateCA.keyPair, legalName, clientKey.public, nameConstraints = nameConstraints)
|
||||
val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
// Using different x500 name in the TLS cert which is not allowed in the name constraints.
|
||||
val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKey, MINI_CORP.name, tlsKey.public)
|
||||
val keyPass = keyStorePassword.toCharArray()
|
||||
val clientCAKeystore = KeyStoreUtilities.loadOrCreateKeyStore(nodeKeystore, keyStorePassword)
|
||||
clientCAKeystore.addOrReplaceKey(
|
||||
X509Utilities.CORDA_CLIENT_CA,
|
||||
clientKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientCACert, intermediateCA.certificate, rootCACert))
|
||||
clientCAKeystore.save(nodeKeystore, keyStorePassword)
|
||||
|
||||
val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeystore, keyStorePassword)
|
||||
tlsKeystore.addOrReplaceKey(
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))
|
||||
tlsKeystore.save(sslKeystore, keyStorePassword)
|
||||
}
|
||||
}
|
||||
|
||||
val attacker = clientTo(alice.configuration.p2pAddress, sslConfig)
|
||||
|
||||
assertThatExceptionOfType(ActiveMQNotConnectedException::class.java).isThrownBy {
|
||||
attacker.start(PEER_USER, PEER_USER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -391,7 +391,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
protected open fun makeServiceEntries(): List<ServiceEntry> {
|
||||
return advertisedServices.map {
|
||||
val serviceId = it.type.id
|
||||
val serviceName = it.name ?: configuration.myLegalName.replaceCommonName(serviceId)
|
||||
val serviceName = it.name ?: X500Name("${configuration.myLegalName},OU=$serviceId")
|
||||
val identity = obtainKeyPair(serviceId, serviceName).first
|
||||
ServiceEntry(it, identity)
|
||||
}
|
||||
@ -401,16 +401,16 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
protected open fun acceptableLiveFiberCountOnStop(): Int = 0
|
||||
|
||||
private fun hasSSLCertificates(): Boolean {
|
||||
val keyStore = try {
|
||||
val (sslKeystore, keystore) = try {
|
||||
// This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect.
|
||||
KeyStoreUtilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
Pair(KeyStoreUtilities.loadKeyStore(configuration.sslKeystore, configuration.keyStorePassword), KeyStoreUtilities.loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword))
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
return false
|
||||
} catch (e: KeyStoreException) {
|
||||
log.warn("Certificate key store found but key store password does not match configuration.")
|
||||
null
|
||||
return false
|
||||
}
|
||||
return keyStore?.containsAlias(X509Utilities.CORDA_CLIENT_CA) ?: false
|
||||
return sslKeystore.containsAlias(X509Utilities.CORDA_CLIENT_TLS) && keystore.containsAlias(X509Utilities.CORDA_CLIENT_CA)
|
||||
}
|
||||
|
||||
// Specific class so that MockNode can catch it.
|
||||
@ -600,12 +600,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
// the legal name is actually validated in some way.
|
||||
|
||||
// TODO: Integrate with Key management service?
|
||||
val keystore = KeyStoreUtilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
val keystore = KeyStoreUtilities.loadKeyStore(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
val clientCA = keystore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, configuration.keyStorePassword)
|
||||
val privateKeyAlias = "$serviceId-private-key"
|
||||
val privKeyFile = configuration.baseDirectory / privateKeyAlias
|
||||
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
|
||||
|
||||
val identityAndKey = if (configuration.keyStoreFile.exists() && keystore.containsAlias(privateKeyAlias)) {
|
||||
val identityAndKey = if (configuration.nodeKeystore.exists() && keystore.containsAlias(privateKeyAlias)) {
|
||||
// Get keys from keystore.
|
||||
val (cert, keyPair) = keystore.getCertificateAndKeyPair(privateKeyAlias, configuration.keyStorePassword)
|
||||
val loadedServiceName = X509CertificateHolder(cert.encoded).subject
|
||||
@ -626,19 +627,18 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
"$serviceName vs ${myIdentity.name}")
|
||||
// Load the private key.
|
||||
val keyPair = privKeyFile.readAll().deserialize<KeyPair>()
|
||||
// TODO: Use a proper certificate chain.
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(serviceName, keyPair)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keystore.save(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(cert, *keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
|
||||
keystore.save(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
Pair(myIdentity, keyPair)
|
||||
} else {
|
||||
// Create new keys and store in keystore.
|
||||
log.info("Identity key not found, generating fresh key!")
|
||||
val keyPair: KeyPair = generateKeyPair()
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(serviceName, keyPair)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, selfSignCert.keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
keystore.save(configuration.keyStoreFile, configuration.keyStorePassword)
|
||||
Pair(Party(serviceName, selfSignCert.keyPair.public), selfSignCert.keyPair)
|
||||
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
|
||||
keystore.addOrReplaceKey(privateKeyAlias, keyPair.private, configuration.keyStorePassword.toCharArray(), arrayOf(cert, *keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
|
||||
keystore.save(configuration.nodeKeystore, configuration.keyStorePassword)
|
||||
Pair(Party(serviceName, keyPair.public), keyPair)
|
||||
}
|
||||
partyKeys += identityAndKey.second
|
||||
return identityAndKey
|
||||
|
@ -54,8 +54,8 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: X500Name) {
|
||||
if (!trustStoreFile.exists()) {
|
||||
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
|
||||
}
|
||||
if (!keyStoreFile.exists()) {
|
||||
if (!sslKeystore.exists() || !nodeKeystore.exists()) {
|
||||
val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
X509Utilities.createKeystoreForSSL(keyStoreFile, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
|
||||
X509Utilities.createKeystoreForCordaNode(sslKeystore, nodeKeystore, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import com.google.common.util.concurrent.SettableFuture
|
||||
import io.netty.handler.ssl.SslHandler
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
@ -30,10 +30,7 @@ import org.apache.activemq.artemis.core.config.Configuration
|
||||
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
|
||||
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
|
||||
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
|
||||
import org.apache.activemq.artemis.core.remoting.impl.netty.*
|
||||
import org.apache.activemq.artemis.core.security.Role
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
||||
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
||||
@ -46,8 +43,11 @@ import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateCallback
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
|
||||
import org.apache.activemq.artemis.utils.ConfigurationHelper
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import rx.Subscription
|
||||
import sun.security.x509.X509CertImpl
|
||||
import java.io.IOException
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyStore
|
||||
@ -259,9 +259,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
|
||||
@Throws(IOException::class, KeyStoreException::class)
|
||||
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword)
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_CA)
|
||||
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_TLS)
|
||||
|
||||
val ourSubjectDN = X500Name(ourCertificate.subjectDN.name)
|
||||
// This is a sanity check and should not fail unless things have been misconfigured
|
||||
@ -423,9 +423,8 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
private fun getBridgeName(queueName: String, hostAndPort: HostAndPort): String = "$queueName -> $hostAndPort"
|
||||
|
||||
// This is called on one of Artemis' background threads
|
||||
internal fun hostVerificationFail(peerLegalName: X500Name, expectedLegalName: X500Name) {
|
||||
log.error("Peer has wrong CN - expected $expectedLegalName but got $peerLegalName. This is either a fatal " +
|
||||
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!")
|
||||
internal fun hostVerificationFail(expectedLegalName: X500Name, errorMsg: String?) {
|
||||
log.error(errorMsg)
|
||||
if (expectedLegalName == config.networkMapService?.legalName) {
|
||||
// If the peer that failed host verification was the network map node then we're in big trouble and need to bail!
|
||||
_networkMapConnectionFuture!!.setException(IOException("${config.networkMapService} failed host verification check"))
|
||||
@ -466,7 +465,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
}
|
||||
|
||||
class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
|
||||
override fun createConnector(configuration: MutableMap<String, Any>?,
|
||||
override fun createConnector(configuration: MutableMap<String, Any>,
|
||||
handler: BufferHandler?,
|
||||
listener: ClientConnectionLifeCycleListener?,
|
||||
closeExecutor: Executor?,
|
||||
@ -478,7 +477,7 @@ class VerifyingNettyConnectorFactory : NettyConnectorFactory() {
|
||||
}
|
||||
}
|
||||
|
||||
private class VerifyingNettyConnector(configuration: MutableMap<String, Any>?,
|
||||
private class VerifyingNettyConnector(configuration: MutableMap<String, Any>,
|
||||
handler: BufferHandler?,
|
||||
listener: ClientConnectionLifeCycleListener?,
|
||||
closeExecutor: Executor?,
|
||||
@ -486,27 +485,37 @@ private class VerifyingNettyConnector(configuration: MutableMap<String, Any>?,
|
||||
scheduledThreadPool: ScheduledExecutorService?,
|
||||
protocolManager: ClientProtocolManager?) :
|
||||
NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) {
|
||||
private val server = configuration?.get(ArtemisMessagingServer::class.java.name) as? ArtemisMessagingServer
|
||||
private val expecteLegalName: X500Name? = configuration?.get(ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME) as X500Name?
|
||||
private val server = configuration[ArtemisMessagingServer::class.java.name] as ArtemisMessagingServer
|
||||
private val sslEnabled = ConfigurationHelper.getBooleanProperty(TransportConstants.SSL_ENABLED_PROP_NAME, TransportConstants.DEFAULT_SSL_ENABLED, configuration)
|
||||
|
||||
override fun createConnection(): Connection? {
|
||||
val connection = super.createConnection() as NettyConnection?
|
||||
if (connection != null && expecteLegalName != null) {
|
||||
val peerLegalName: X500Name = connection
|
||||
.channel
|
||||
.pipeline()
|
||||
.get(SslHandler::class.java)
|
||||
.engine()
|
||||
.session
|
||||
.peerPrincipal
|
||||
.name
|
||||
.let(::X500Name)
|
||||
if (peerLegalName != expecteLegalName) {
|
||||
val connection = super.createConnection() as? NettyConnection
|
||||
if (sslEnabled && connection != null) {
|
||||
val expectedLegalName = configuration[ArtemisTcpTransport.VERIFY_PEER_LEGAL_NAME] as X500Name
|
||||
try {
|
||||
val session = connection.channel
|
||||
.pipeline()
|
||||
.get(SslHandler::class.java)
|
||||
.engine()
|
||||
.session
|
||||
// Checks the peer name is the one we are expecting.
|
||||
val peerLegalName = session.peerPrincipal.name.let(::X500Name)
|
||||
require(peerLegalName == expectedLegalName) {
|
||||
"Peer has wrong CN - expected $expectedLegalName but got $peerLegalName. This is either a fatal " +
|
||||
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
|
||||
}
|
||||
// Make sure certificate has the same name.
|
||||
val peerCertificate = X509CertificateHolder(session.peerCertificateChain.first().encoded)
|
||||
require(peerCertificate.subject == expectedLegalName) {
|
||||
"Peer has wrong subject name in the certificate - expected $expectedLegalName but got ${peerCertificate.subject}. This is either a fatal " +
|
||||
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
|
||||
}
|
||||
X509Utilities.validateCertificateChain(X509CertImpl(session.localCertificates.last().encoded), *session.peerCertificates)
|
||||
server.onTcpConnection(peerLegalName)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
connection.close()
|
||||
server!!.hostVerificationFail(peerLegalName, expecteLegalName)
|
||||
return null // Artemis will keep trying to reconnect until it's told otherwise
|
||||
} else {
|
||||
server!!.onTcpConnection(peerLegalName)
|
||||
server.hostVerificationFail(expectedLegalName, e.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
return connection
|
||||
@ -547,7 +556,7 @@ sealed class CertificateChainCheckPolicy {
|
||||
|
||||
object LeafMustMatch : CertificateChainCheckPolicy() {
|
||||
override fun createCheck(keyStore: KeyStore, trustStore: KeyStore): Check {
|
||||
val ourPublicKey = keyStore.getCertificate(CORDA_CLIENT_CA).publicKey
|
||||
val ourPublicKey = keyStore.getCertificate(CORDA_CLIENT_TLS).publicKey
|
||||
return object : Check {
|
||||
override fun checkCertificateChain(theirChain: Array<X509Certificate>) {
|
||||
val theirLeaf = theirChain.first().publicKey
|
||||
|
@ -127,7 +127,7 @@ class RaftUniquenessProvider(
|
||||
return NettyTransport.builder()
|
||||
.withSsl()
|
||||
.withSslProtocol(SslProtocol.TLSv1_2)
|
||||
.withKeyStorePath(config.keyStoreFile.toString())
|
||||
.withKeyStorePath(config.sslKeystore.toString())
|
||||
.withKeyStorePassword(config.keyStorePassword)
|
||||
.withTrustStorePath(config.trustStoreFile.toString())
|
||||
.withTrustStorePassword(config.trustStorePassword)
|
||||
|
@ -3,6 +3,7 @@ package net.corda.node.utilities.registration
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
|
||||
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
@ -31,15 +32,16 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
|
||||
fun buildKeystore() {
|
||||
config.certificatesDirectory.createDirectories()
|
||||
val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword)
|
||||
val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.nodeKeystore, keystorePassword)
|
||||
if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) {
|
||||
// Create or load self signed keypair from the key store.
|
||||
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
|
||||
if (!caKeyStore.containsAlias(SELF_SIGNED_PRIVATE_KEY)) {
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACert(config.myLegalName)
|
||||
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair)
|
||||
// Save to the key store.
|
||||
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, selfSignCert.keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate))
|
||||
caKeyStore.save(config.keyStoreFile, keystorePassword)
|
||||
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert))
|
||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
}
|
||||
val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
|
||||
val requestId = submitOrResumeCertificateSigningRequest(keyPair)
|
||||
@ -58,13 +60,22 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
// Save private key and certificate chain to the key store.
|
||||
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
|
||||
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
|
||||
caKeyStore.save(config.keyStoreFile, keystorePassword)
|
||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
// Save root certificates to trust store.
|
||||
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
// Assumes certificate chain always starts with client certificate and end with root certificate.
|
||||
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last())
|
||||
trustStore.save(config.trustStoreFile, config.trustStorePassword)
|
||||
println("Certificate and private key stored in ${config.keyStoreFile}.")
|
||||
println("Node private key and certificate stored in ${config.nodeKeystore}.")
|
||||
|
||||
println("Generating SSL certificate for node messaging service.")
|
||||
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA)
|
||||
val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, caCert.subject, sslKey.public)
|
||||
val sslKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.sslKeystore, keystorePassword)
|
||||
sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates))
|
||||
sslKeyStore.save(config.sslKeystore, config.keyStorePassword)
|
||||
println("SSL private key and certificate stored in ${config.sslKeystore}.")
|
||||
// All done, clean up temp files.
|
||||
requestIdStore.deleteIfExists()
|
||||
} else {
|
||||
@ -72,6 +83,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Poll Certificate Signing Server for approved certificate,
|
||||
* enter a slow polling loop if server return null.
|
||||
|
Binary file not shown.
Binary file not shown.
@ -1,7 +1,6 @@
|
||||
package net.corda.node.services.network
|
||||
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
@ -66,13 +65,13 @@ class InMemoryIdentityServiceTests {
|
||||
*/
|
||||
@Test
|
||||
fun `assert unknown anonymous key is unrecognised`() {
|
||||
val rootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name)
|
||||
val txCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, rootCertAndKey)
|
||||
val rootKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootKey)
|
||||
val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val service = InMemoryIdentityService()
|
||||
val rootKey = rootCertAndKey.keyPair
|
||||
// TODO: Generate certificate with an EdDSA key rather than ECDSA
|
||||
val identity = Party(rootCertAndKey)
|
||||
val txIdentity = AnonymousParty(txCertAndKey.keyPair.public)
|
||||
val identity = Party(CertificateAndKeyPair(rootCert, rootKey))
|
||||
val txIdentity = AnonymousParty(txKey.public)
|
||||
|
||||
assertFailsWith<IdentityService.UnknownAnonymousPartyException> {
|
||||
service.assertOwnership(identity, txIdentity)
|
||||
@ -85,20 +84,26 @@ class InMemoryIdentityServiceTests {
|
||||
*/
|
||||
@Test
|
||||
fun `assert ownership`() {
|
||||
val aliceRootCertAndKey = X509Utilities.createSelfSignedCACert(ALICE.name)
|
||||
val aliceTxCertAndKey = X509Utilities.createIntermediateCert(ALICE.name, aliceRootCertAndKey)
|
||||
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCertAndKey, aliceTxCertAndKey.certificate, false).certPath
|
||||
val bobRootCertAndKey = X509Utilities.createSelfSignedCACert(BOB.name)
|
||||
val bobTxCertAndKey = X509Utilities.createIntermediateCert(BOB.name, bobRootCertAndKey)
|
||||
val bobCertPath = X509Utilities.createCertificatePath(bobRootCertAndKey, bobTxCertAndKey.certificate, false).certPath
|
||||
val service = InMemoryIdentityService()
|
||||
val alice = Party(aliceRootCertAndKey)
|
||||
val anonymousAlice = AnonymousParty(aliceTxCertAndKey.keyPair.public)
|
||||
val bob = Party(bobRootCertAndKey)
|
||||
val anonymousBob = AnonymousParty(bobTxCertAndKey.keyPair.public)
|
||||
val aliceRootKey = Crypto.generateKeyPair()
|
||||
val aliceRootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, aliceRootKey)
|
||||
val aliceTxKey = Crypto.generateKeyPair()
|
||||
val aliceTxCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, aliceRootCert, aliceRootKey, ALICE.name, aliceTxKey.public)
|
||||
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCert, aliceTxCert, revocationEnabled = false)
|
||||
|
||||
service.registerPath(aliceRootCertAndKey.certificate, anonymousAlice, aliceCertPath)
|
||||
service.registerPath(bobRootCertAndKey.certificate, anonymousBob, bobCertPath)
|
||||
val bobRootKey = Crypto.generateKeyPair()
|
||||
val bobRootCert = X509Utilities.createSelfSignedCACertificate(BOB.name, bobRootKey)
|
||||
val bobTxKey = Crypto.generateKeyPair()
|
||||
val bobTxCert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, bobRootCert, bobRootKey, BOB.name, bobTxKey.public)
|
||||
val bobCertPath = X509Utilities.createCertificatePath(bobRootCert, bobTxCert, revocationEnabled = false)
|
||||
|
||||
val service = InMemoryIdentityService()
|
||||
val alice = Party(CertificateAndKeyPair(aliceRootCert, aliceRootKey))
|
||||
val anonymousAlice = AnonymousParty(aliceTxKey.public)
|
||||
val bob = Party(CertificateAndKeyPair(bobRootCert, bobRootKey))
|
||||
val anonymousBob = AnonymousParty(bobTxKey.public)
|
||||
|
||||
service.registerPath(aliceRootCert, anonymousAlice, aliceCertPath)
|
||||
service.registerPath(bobRootCert, anonymousBob, bobCertPath)
|
||||
|
||||
// Verify that paths are verified
|
||||
service.assertOwnership(alice, anonymousAlice)
|
||||
|
@ -3,14 +3,12 @@ package net.corda.node.utilities.registration
|
||||
import com.nhaarman.mockito_kotlin.any
|
||||
import com.nhaarman.mockito_kotlin.eq
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import net.corda.core.crypto.KeyStoreUtilities
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.exists
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.testing.TestNodeConfiguration
|
||||
import net.corda.testing.getTestX509Name
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -31,7 +29,7 @@ class NetworkRegistrationHelperTest {
|
||||
"CORDA_INTERMEDIATE_CA",
|
||||
"CORDA_ROOT_CA")
|
||||
.map { getTestX509Name(it) }
|
||||
val certs = identities.map { X509Utilities.createSelfSignedCACert(it).certificate }
|
||||
val certs = identities.map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
|
||||
.toTypedArray()
|
||||
|
||||
val certService: NetworkRegistrationService = mock {
|
||||
@ -44,32 +42,45 @@ class NetworkRegistrationHelperTest {
|
||||
myLegalName = ALICE.name,
|
||||
networkMapService = null)
|
||||
|
||||
assertFalse(config.keyStoreFile.exists())
|
||||
assertFalse(config.nodeKeystore.exists())
|
||||
assertFalse(config.sslKeystore.exists())
|
||||
assertFalse(config.trustStoreFile.exists())
|
||||
|
||||
NetworkRegistrationHelper(config, certService).buildKeystore()
|
||||
|
||||
assertTrue(config.keyStoreFile.exists())
|
||||
assertTrue(config.nodeKeystore.exists())
|
||||
assertTrue(config.sslKeystore.exists())
|
||||
assertTrue(config.trustStoreFile.exists())
|
||||
|
||||
KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword).run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
|
||||
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||
assertEquals(3, certificateChain.size)
|
||||
val nodeKeystore = KeyStoreUtilities.loadKeyStore(config.nodeKeystore, config.keyStorePassword)
|
||||
val sslKeystore = KeyStoreUtilities.loadKeyStore(config.sslKeystore, config.keyStorePassword)
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
|
||||
|
||||
nodeKeystore.run {
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
|
||||
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||
assertEquals(3, certificateChain.size)
|
||||
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { X509CertificateHolder(it.encoded).subject.commonName })
|
||||
}
|
||||
|
||||
KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword).run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
|
||||
sslKeystore.run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
|
||||
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)
|
||||
assertEquals(4, certificateChain.size)
|
||||
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { X509CertificateHolder(it.encoded).subject.commonName })
|
||||
}
|
||||
|
||||
trustStore.run {
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY))
|
||||
assertTrue(containsAlias(X509Utilities.CORDA_ROOT_CA))
|
||||
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +217,6 @@ fun getTestX509Name(commonName: String): X500Name {
|
||||
val nameBuilder = X500NameBuilder(BCStyle.INSTANCE)
|
||||
nameBuilder.addRDN(BCStyle.CN, commonName)
|
||||
nameBuilder.addRDN(BCStyle.O, "R3")
|
||||
nameBuilder.addRDN(BCStyle.OU, "Corda QA Department")
|
||||
nameBuilder.addRDN(BCStyle.L, "New York")
|
||||
nameBuilder.addRDN(BCStyle.C, "US")
|
||||
return nameBuilder.build()
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.corda.testing.messaging
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.nodeapi.ArtemisMessagingComponent
|
||||
import net.corda.nodeapi.ArtemisTcpTransport
|
||||
import net.corda.nodeapi.ConnectionDirection
|
||||
@ -37,6 +36,10 @@ class SimpleMQClient(val target: HostAndPort,
|
||||
fun createMessage(): ClientMessage = session.createMessage(false)
|
||||
|
||||
fun stop() {
|
||||
sessionFactory.close()
|
||||
try {
|
||||
sessionFactory.close()
|
||||
} catch (e: Exception) {
|
||||
// sessionFactory might not have initialised.
|
||||
}
|
||||
}
|
||||
}
|
@ -79,7 +79,7 @@ class NodeWebServer(val config: WebServerConfig) {
|
||||
httpsConfiguration.outputBufferSize = 32768
|
||||
httpsConfiguration.addCustomizer(SecureRequestCustomizer())
|
||||
val sslContextFactory = SslContextFactory()
|
||||
sslContextFactory.keyStorePath = config.keyStoreFile.toString()
|
||||
sslContextFactory.keyStorePath = config.sslKeystore.toString()
|
||||
sslContextFactory.setKeyStorePassword(config.keyStorePassword)
|
||||
sslContextFactory.setKeyManagerPassword(config.keyStorePassword)
|
||||
sslContextFactory.setTrustStorePath(config.trustStoreFile.toString())
|
||||
|
Loading…
Reference in New Issue
Block a user