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:
Patrick Kuo
2017-05-24 16:13:37 +01:00
committed by GitHub
parent bbe4c170c2
commit 246de55433
22 changed files with 513 additions and 299 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

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