CORDA-3201 - Enforce separate key for notary identity (#6308)

This commit is contained in:
Oliver Knowles 2020-07-20 10:59:08 +01:00 committed by GitHub
parent e6d5842a23
commit a500084d38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 263 additions and 90 deletions

View File

@ -1435,7 +1435,7 @@
<ID>ThrowsCount:JarScanningCordappLoader.kt$JarScanningCordappLoader$private fun parseVersion(versionStr: String?, attributeName: String): Int</ID> <ID>ThrowsCount:JarScanningCordappLoader.kt$JarScanningCordappLoader$private fun parseVersion(versionStr: String?, attributeName: String): Int</ID>
<ID>ThrowsCount:LedgerDSLInterpreter.kt$Verifies$ fun failsWith(expectedMessage: String?): EnforceVerifyOrFail</ID> <ID>ThrowsCount:LedgerDSLInterpreter.kt$Verifies$ fun failsWith(expectedMessage: String?): EnforceVerifyOrFail</ID>
<ID>ThrowsCount:MockServices.kt$ fun &lt;T : SerializeAsToken&gt; createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -&gt; T): T</ID> <ID>ThrowsCount:MockServices.kt$ fun &lt;T : SerializeAsToken&gt; createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -&gt; T): T</ID>
<ID>ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List&lt;X509Certificate&gt;)</ID> <ID>ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates( registeringPublicKey: PublicKey, registeringLegalName: CordaX500Name, expectedCertRole: CertRole, certificates: List&lt;X509Certificate&gt; )</ID>
<ID>ThrowsCount:NodeInfoFilesCopier.kt$NodeInfoFilesCopier$private fun atomicCopy(source: Path, destination: Path)</ID> <ID>ThrowsCount:NodeInfoFilesCopier.kt$NodeInfoFilesCopier$private fun atomicCopy(source: Path, destination: Path)</ID>
<ID>ThrowsCount:NodeVaultService.kt$NodeVaultService$@Throws(VaultQueryException::class) private fun &lt;T : ContractState&gt; _queryBy(criteria: QueryCriteria, paging_: PageSpecification, sorting: Sort, contractStateType: Class&lt;out T&gt;, skipPagingChecks: Boolean): Vault.Page&lt;T&gt;</ID> <ID>ThrowsCount:NodeVaultService.kt$NodeVaultService$@Throws(VaultQueryException::class) private fun &lt;T : ContractState&gt; _queryBy(criteria: QueryCriteria, paging_: PageSpecification, sorting: Sort, contractStateType: Class&lt;out T&gt;, skipPagingChecks: Boolean): Vault.Page&lt;T&gt;</ID>
<ID>ThrowsCount:NodeVaultService.kt$NodeVaultService$private fun makeUpdates(batch: Iterable&lt;CoreTransaction&gt;, statesToRecord: StatesToRecord, previouslySeen: Boolean): List&lt;Vault.Update&lt;ContractState&gt;&gt;</ID> <ID>ThrowsCount:NodeVaultService.kt$NodeVaultService$private fun makeUpdates(batch: Iterable&lt;CoreTransaction&gt;, statesToRecord: StatesToRecord, previouslySeen: Boolean): List&lt;Vault.Update&lt;ContractState&gt;&gt;</ID>

View File

@ -614,11 +614,22 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val myNotaryIdentity = configuration.notary?.let { val myNotaryIdentity = configuration.notary?.let {
if (it.serviceLegalName != null) { if (it.serviceLegalName != null) {
val (notaryIdentity, notaryIdentityKeyPair) = loadNotaryClusterIdentity(it.serviceLegalName) val (notaryIdentity, notaryIdentityKeyPair) = loadNotaryServiceIdentity(it.serviceLegalName)
keyPairs += notaryIdentityKeyPair keyPairs += notaryIdentityKeyPair
notaryIdentity notaryIdentity
} else { } else {
// In case of a single notary service myNotaryIdentity will be the node's single identity. // The only case where the myNotaryIdentity will be the node's legal identity is for existing single notary services running
// an older version. Current single notary services (V4.6+) sign requests using a separate notary service identity so the
// notary identity will be different from the node's legal identity.
// This check is here to ensure that a user does not accidentally/intentionally remove the serviceLegalName configuration
// parameter after a notary has been registered. If that was possible then notary would start and sign incoming requests
// with the node's legal identity key, corrupting the data.
check (!cryptoService.containsKey(DISTRIBUTED_NOTARY_KEY_ALIAS)) {
"The notary service key exists in the key store but no notary service legal name has been configured. " +
"Either include the relevant 'notary.serviceLegalName' configuration or validate this key is not necessary " +
"and remove from the key store."
}
identity identity
} }
} }
@ -1057,8 +1068,12 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
} }
} }
/** Loads pre-generated notary service cluster identity. */ /**
private fun loadNotaryClusterIdentity(serviceLegalName: CordaX500Name): Pair<PartyAndCertificate, KeyPair> { * Loads notary service identity. In the case of the experimental RAFT and BFT notary clusters, this loads the pre-generated
* cluster identity that all worker nodes share. In the case of a simple single notary, this loads the notary service identity
* that is generated during initial registration and is used to sign notarisation requests.
* */
private fun loadNotaryServiceIdentity(serviceLegalName: CordaX500Name): Pair<PartyAndCertificate, KeyPair> {
val privateKeyAlias = "$DISTRIBUTED_NOTARY_KEY_ALIAS" val privateKeyAlias = "$DISTRIBUTED_NOTARY_KEY_ALIAS"
val compositeKeyAlias = "$DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS" val compositeKeyAlias = "$DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS"

View File

@ -151,7 +151,7 @@ fun NodeConfiguration.shouldInitCrashShell() = shouldStartLocalShell() || should
data class NotaryConfig( data class NotaryConfig(
/** Specifies whether the notary validates transactions or not. */ /** Specifies whether the notary validates transactions or not. */
val validating: Boolean, val validating: Boolean,
/** The legal name of cluster in case of a distributed notary service. */ /** The legal name of the notary service identity. */
val serviceLegalName: CordaX500Name? = null, val serviceLegalName: CordaX500Name? = null,
/** The name of the notary service class to load. */ /** The name of the notary service class to load. */
val className: String? = null, val className: String? = null,

View File

@ -25,10 +25,10 @@ import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.util.io.pem.PemObject import org.bouncycastle.util.io.pem.PemObject
import java.io.IOException import java.io.IOException
import java.io.StringWriter import java.io.StringWriter
import java.lang.IllegalStateException
import java.net.ConnectException import java.net.ConnectException
import java.net.URL import java.net.URL
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.time.Duration import java.time.Duration
@ -63,6 +63,7 @@ open class NetworkRegistrationHelper(
private val requestIdStore = certificatesDirectory / "certificate-request-id.txt" private val requestIdStore = certificatesDirectory / "certificate-request-id.txt"
protected val rootTrustStore: X509KeyStore protected val rootTrustStore: X509KeyStore
protected val rootCert: X509Certificate protected val rootCert: X509Certificate
private val notaryServiceConfig: NotaryServiceConfig? = config.notaryServiceConfig
init { init {
require(networkRootTrustStorePath.exists()) { require(networkRootTrustStorePath.exists()) {
@ -95,34 +96,70 @@ open class NetworkRegistrationHelper(
return return
} }
notaryServiceConfig?.let { validateNotaryServiceKeyAndCert(certStore, it.notaryServiceKeyAlias, it.notaryServiceLegalName) }
val tlsCrlIssuerCert = getTlsCrlIssuerCert() val tlsCrlIssuerCert = getTlsCrlIssuerCert()
// We use SELF_SIGNED_PRIVATE_KEY as progress indicator so we just store a dummy key and cert. // We use SELF_SIGNED_PRIVATE_KEY as progress indicator so we just store a dummy key and cert.
// When registration succeeds, this entry should be deleted. // When registration succeeds, this entry should be deleted.
certStore.query { setPrivateKey(SELF_SIGNED_PRIVATE_KEY, AliasPrivateKey(SELF_SIGNED_PRIVATE_KEY), listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), certificateStore.entryPassword) } certStore.query { setPrivateKey(SELF_SIGNED_PRIVATE_KEY, AliasPrivateKey(SELF_SIGNED_PRIVATE_KEY), listOf(NOT_YET_REGISTERED_MARKER_KEYS_AND_CERTS.ECDSAR1_CERT), certificateStore.entryPassword) }
val nodeCaPublicKey = loadOrGenerateKeyPair() val (entityPublicKey, receivedCertificates) = generateKeyPairAndCertificate(nodeCaKeyAlias, myLegalName, certRole, certStore)
val requestId = submitOrResumeCertificateSigningRequest(nodeCaPublicKey, cryptoService.getSigner(nodeCaKeyAlias)) onSuccess(entityPublicKey, cryptoService.getSigner(nodeCaKeyAlias), receivedCertificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name())
val nodeCaCertificates = pollServerForCertificates(requestId)
validateCertificates(nodeCaPublicKey, nodeCaCertificates)
certStore.setCertPathOnly(nodeCaKeyAlias, nodeCaCertificates)
certStore.value.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
certStore.value.save()
logProgress("Private key '$nodeCaKeyAlias' and its certificate-chain stored successfully.")
onSuccess(nodeCaPublicKey, cryptoService.getSigner(nodeCaKeyAlias), nodeCaCertificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name())
// All done, clean up temp files. // All done, clean up temp files.
requestIdStore.deleteIfExists() requestIdStore.deleteIfExists()
} }
private fun loadOrGenerateKeyPair(): PublicKey { private fun generateKeyPairAndCertificate(keyAlias: String, legalName: CordaX500Name, certificateRole: CertRole, certStore: CertificateStore): Pair<PublicKey, List<X509Certificate>> {
return if (cryptoService.containsKey(nodeCaKeyAlias)) { val entityPublicKey = loadOrGenerateKeyPair(keyAlias)
cryptoService.getPublicKey(nodeCaKeyAlias)!!
val requestId = submitOrResumeCertificateSigningRequest(entityPublicKey, legalName, certificateRole, cryptoService.getSigner(keyAlias))
val receivedCertificates = pollServerForCertificates(requestId)
validateCertificates(entityPublicKey, legalName, certificateRole, receivedCertificates)
certStore.setCertPathOnly(keyAlias, receivedCertificates)
certStore.value.internal.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
certStore.value.save()
logProgress("Private key '$keyAlias' and its certificate-chain stored successfully.")
return Pair(entityPublicKey, receivedCertificates)
}
/**
* Used when registering a notary to validate that the shared notary service key and certificate can be accessed.
*
* In the case that the notary service certificate and key is not available, a new key key is generated and a separate CSR is
* submitted to the Identity Manager.
*
* If this method successfully completes then the [cryptoService] will contain the notary service key and the [certStore] will contain
* the notary service certificate chain.
*
* @throws IllegalStateException If the notary service certificate already exists but the private key is not available.
*/
private fun validateNotaryServiceKeyAndCert(certStore: CertificateStore, notaryServiceKeyAlias: String, notaryServiceLegalName: CordaX500Name) {
if (certStore.contains(notaryServiceKeyAlias) && !cryptoService.containsKey(notaryServiceKeyAlias)) {
throw IllegalStateException("Notary service identity certificate exists but key pair missing. " +
"Please check no old certificates exist in the certificate store.")
}
if (certStore.contains(notaryServiceKeyAlias)) {
logProgress("Notary service certificate already exists. Continuing with node registration...")
return
}
logProgress("Generating notary service identity for $notaryServiceLegalName...")
generateKeyPairAndCertificate(notaryServiceKeyAlias, notaryServiceLegalName, CertRole.SERVICE_IDENTITY, certStore)
// The request id store is reused for the next step - registering the node identity.
// Therefore we can remove this to enable it to be reused.
requestIdStore.deleteIfExists()
}
private fun loadOrGenerateKeyPair(keyAlias: String): PublicKey {
return if (cryptoService.containsKey(keyAlias)) {
cryptoService.getPublicKey(keyAlias)!!
} else { } else {
cryptoService.generateKeyPair(nodeCaKeyAlias, cryptoService.defaultTLSSignatureScheme()) cryptoService.generateKeyPair(keyAlias, cryptoService.defaultTLSSignatureScheme())
} }
} }
@ -137,26 +174,31 @@ open class NetworkRegistrationHelper(
return tlsCrlIssuerCert return tlsCrlIssuerCert
} }
private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List<X509Certificate>) { private fun validateCertificates(
val nodeCACertificate = certificates.first() registeringPublicKey: PublicKey,
registeringLegalName: CordaX500Name,
expectedCertRole: CertRole,
certificates: List<X509Certificate>
) {
val receivedCertificate = certificates.first()
val nodeCaSubject = try { val certificateSubject = try {
CordaX500Name.build(nodeCACertificate.subjectX500Principal) CordaX500Name.build(receivedCertificate.subjectX500Principal)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
throw CertificateRequestException("Received node CA cert has invalid subject name: ${e.message}") throw CertificateRequestException("Received cert has invalid subject name: ${e.message}")
} }
if (nodeCaSubject != myLegalName) { if (certificateSubject != registeringLegalName) {
throw CertificateRequestException("Subject of received node CA cert doesn't match with node legal name: $nodeCaSubject") throw CertificateRequestException("Subject of received cert doesn't match with legal name: $certificateSubject")
} }
val nodeCaCertRole = try { val receivedCertRole = try {
CertRole.extract(nodeCACertificate) CertRole.extract(receivedCertificate)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
throw CertificateRequestException("Unable to extract cert role from received node CA cert: ${e.message}") throw CertificateRequestException("Unable to extract cert role from received cert: ${e.message}")
} }
if (certRole != nodeCaCertRole) { if (expectedCertRole != receivedCertRole) {
throw CertificateRequestException("Received certificate contains invalid cert role, expected '$certRole', got '$nodeCaCertRole'.") throw CertificateRequestException("Received certificate contains invalid cert role, expected '$expectedCertRole', got '$receivedCertRole'.")
} }
// Validate returned certificate is for the correct public key. // Validate returned certificate is for the correct public key.
@ -169,22 +211,6 @@ open class NetworkRegistrationHelper(
logProgress("Certificate signing request approved, storing private key with the certificate chain.") logProgress("Certificate signing request approved, storing private key with the certificate chain.")
} }
private fun CertificateStore.loadOrCreateKeyPair(alias: String, entryPassword: String = password): KeyPair {
// 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 (alias !in this) {
// NODE_CA should be TLS compatible due to the cert hierarchy structure.
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair)
// Save to the key store.
with(value) {
setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = entryPassword)
save()
}
}
return query { getCertificateAndKeyPair(alias, entryPassword) }.keyPair
}
/** /**
* Poll Certificate Signing Server for approved certificate, * Poll Certificate Signing Server for approved certificate,
* enter a slow polling loop if server return null. * enter a slow polling loop if server return null.
@ -226,20 +252,27 @@ open class NetworkRegistrationHelper(
* Submit Certificate Signing Request to Certificate signing service if request ID not found in file system. * Submit Certificate Signing Request to Certificate signing service if request ID not found in file system.
* New request ID will be stored in requestId.txt * New request ID will be stored in requestId.txt
* @param publicKey public key for which we need a certificate. * @param publicKey public key for which we need a certificate.
* @param legalName legal name of the entity for which we need a certificate.
* @param certRole desired role of the entities certificate.
* @param contentSigner the [ContentSigner] that will sign the CSR. * @param contentSigner the [ContentSigner] that will sign the CSR.
* @return Request ID return from the server. * @return Request ID return from the server.
*/ */
private fun submitOrResumeCertificateSigningRequest(publicKey: PublicKey, contentSigner: ContentSigner): String { private fun submitOrResumeCertificateSigningRequest(
publicKey: PublicKey,
legalName: CordaX500Name,
certRole: CertRole,
contentSigner: ContentSigner
): String {
try { try {
// Retrieve request id from file if exists, else post a request to server. // Retrieve request id from file if exists, else post a request to server.
return if (!requestIdStore.exists()) { return if (!requestIdStore.exists()) {
val request = X509Utilities.createCertificateSigningRequest(myLegalName.x500Principal, emailAddress, publicKey, contentSigner, certRole) val request = X509Utilities.createCertificateSigningRequest(legalName.x500Principal, emailAddress, publicKey, contentSigner, certRole)
val writer = StringWriter() val writer = StringWriter()
JcaPEMWriter(writer).use { JcaPEMWriter(writer).use {
it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded)) it.writeObject(PemObject("CERTIFICATE REQUEST", request.encoded))
} }
logProgress("Certificate signing request with the following information will be submitted to the Corda certificate signing server.") logProgress("Certificate signing request with the following information will be submitted to the Corda certificate signing server.")
logProgress("Legal Name: $myLegalName") logProgress("Legal Name: $legalName")
logProgress("Email: $emailAddress") logProgress("Email: $emailAddress")
logProgress("Public Key: $publicKey") logProgress("Public Key: $publicKey")
logProgress("$writer") logProgress("$writer")
@ -277,7 +310,8 @@ class NodeRegistrationConfiguration(
val certificatesDirectory: Path, val certificatesDirectory: Path,
val emailAddress: String, val emailAddress: String,
val cryptoService: CryptoService, val cryptoService: CryptoService,
val certificateStore: CertificateStore) { val certificateStore: CertificateStore,
val notaryServiceConfig: NotaryServiceConfig? = null) {
constructor(config: NodeConfiguration) : this( constructor(config: NodeConfiguration) : this(
p2pSslOptions = config.p2pSslOptions, p2pSslOptions = config.p2pSslOptions,
@ -287,10 +321,29 @@ class NodeRegistrationConfiguration(
certificatesDirectory = config.certificatesDirectory, certificatesDirectory = config.certificatesDirectory,
emailAddress = config.emailAddress, emailAddress = config.emailAddress,
cryptoService = BCCryptoService(config.myLegalName.x500Principal, config.signingCertificateStore), cryptoService = BCCryptoService(config.myLegalName.x500Principal, config.signingCertificateStore),
certificateStore = config.signingCertificateStore.get(true) certificateStore = config.signingCertificateStore.get(true),
notaryServiceConfig = config.notary?.let {
// Validation of the presence of the notary service legal name is only done here and not in the top level configuration
// file. This is to maintain backwards compatibility with older notaries using the legacy identity structure. Older
// notaries will be signing requests using the nodes legal identity key and therefore no separate notary service entity
// exists. Just having the validation here prevents any new notaries from being created with the legacy identity scheme
// but still allows drop in JAR replacements for old notaries.
requireNotNull(it.serviceLegalName) {
"The notary service legal name must be provided via the 'notary.serviceLegalName' configuration parameter"
}
require(it.serviceLegalName != config.myLegalName) {
"The notary service legal name must be different from the node legal name"
}
NotaryServiceConfig(X509Utilities.DISTRIBUTED_NOTARY_KEY_ALIAS, it.serviceLegalName!!)
}
) )
} }
data class NotaryServiceConfig(
val notaryServiceKeyAlias: String,
val notaryServiceLegalName: CordaX500Name
)
class NodeRegistrationException( class NodeRegistrationException(
message: String?, message: String?,
cause: Throwable? cause: Throwable?

View File

@ -28,6 +28,8 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.coretesting.internal.rigorousMock import net.corda.coretesting.internal.rigorousMock
import net.corda.coretesting.internal.stubs.CertificateStoreStubs import net.corda.coretesting.internal.stubs.CertificateStoreStubs
import net.corda.node.services.config.NotaryConfig
import net.corda.testing.core.DUMMY_NOTARY_NAME
import org.assertj.core.api.Assertions.* import org.assertj.core.api.Assertions.*
import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.GeneralSubtree
@ -37,6 +39,7 @@ import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.lang.IllegalStateException
import java.nio.file.Files import java.nio.file.Files
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.CertPathValidatorException import java.security.cert.CertPathValidatorException
@ -71,6 +74,7 @@ class NetworkRegistrationHelperTest {
doReturn(null).whenever(it).tlsCertCrlDistPoint doReturn(null).whenever(it).tlsCertCrlDistPoint
doReturn(null).whenever(it).tlsCertCrlIssuer doReturn(null).whenever(it).tlsCertCrlIssuer
doReturn(true).whenever(it).crlCheckSoftFail doReturn(true).whenever(it).crlCheckSoftFail
doReturn(null).whenever(it).notary
} }
} }
@ -120,7 +124,7 @@ class NetworkRegistrationHelperTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `missing truststore`() { fun `missing truststore`() {
val nodeCaCertPath = createNodeCaCertPath() val nodeCaCertPath = createCertPath()
assertThatThrownBy { assertThatThrownBy {
createFixedResponseRegistrationHelper(nodeCaCertPath) createFixedResponseRegistrationHelper(nodeCaCertPath)
}.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.") }.hasMessageContaining("This file must contain the root CA cert of your compatibility zone. Please contact your CZ operator.")
@ -128,7 +132,7 @@ class NetworkRegistrationHelperTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `node CA with incorrect cert role`() { fun `node CA with incorrect cert role`() {
val nodeCaCertPath = createNodeCaCertPath(type = CertificateType.TLS) val nodeCaCertPath = createCertPath(type = CertificateType.TLS)
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last()) saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java) assertThatExceptionOfType(CertificateRequestException::class.java)
@ -139,7 +143,7 @@ class NetworkRegistrationHelperTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `node CA with incorrect subject`() { fun `node CA with incorrect subject`() {
val invalidName = CordaX500Name("Foo", "MU", "GB") val invalidName = CordaX500Name("Foo", "MU", "GB")
val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName) val nodeCaCertPath = createCertPath(legalName = invalidName)
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last()) saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath) val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
assertThatExceptionOfType(CertificateRequestException::class.java) assertThatExceptionOfType(CertificateRequestException::class.java)
@ -220,36 +224,118 @@ class NetworkRegistrationHelperTest {
createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).generateKeysAndRegister() createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).generateKeysAndRegister()
} }
private fun createNodeCaCertPath(type: CertificateType = CertificateType.NODE_CA, @Test(timeout=300_000)
legalName: CordaX500Name = nodeLegalName, fun `successful registration for notary node`() {
publicKey: PublicKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME).public, val notaryServiceLegalName = DUMMY_NOTARY_NAME
rootAndIntermediateCA: Pair<CertificateAndKeyPair, CertificateAndKeyPair> = createDevIntermediateCaCertPath()): List<X509Certificate> { val notaryNodeConfig = createNotaryNodeConfiguration(notaryServiceLegalName = notaryServiceLegalName)
assertThat(notaryNodeConfig.notary).isNotNull
val rootAndIntermediateCA = createDevIntermediateCaCertPath().also {
saveNetworkTrustStore(CORDA_ROOT_CA to it.first.certificate)
}
// Mock out the registration service to ensure notary service registration is handled correctly
createRegistrationHelper(CertRole.NODE_CA, notaryNodeConfig) {
when {
it.subject == nodeLegalName.toX500Name() -> {
val certType = CertificateType.values().first { it.role == CertRole.NODE_CA }
createCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType)
}
it.subject == notaryServiceLegalName.toX500Name() -> {
val certType = CertificateType.values().first { it.role == CertRole.SERVICE_IDENTITY }
createCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType, legalName = notaryServiceLegalName)
}
else -> throw IllegalStateException("Unknown CSR")
}
}.generateKeysAndRegister()
val nodeKeystore = config.signingCertificateStore.get()
nodeKeystore.run {
assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA))
assertFalse(contains(CORDA_ROOT_CA))
assertFalse(contains(X509Utilities.CORDA_CLIENT_TLS))
assertThat(CertRole.extract(this[X509Utilities.CORDA_CLIENT_CA])).isEqualTo(CertRole.NODE_CA)
assertThat(CertRole.extract(this[DISTRIBUTED_NOTARY_KEY_ALIAS])).isEqualTo(CertRole.SERVICE_IDENTITY)
}
}
@Test(timeout=300_000)
fun `notary registration fails when no separate notary service identity configured`() {
val notaryNodeConfig = createNotaryNodeConfiguration(notaryServiceLegalName = null)
assertThat(notaryNodeConfig.notary).isNotNull
assertThatThrownBy {
createRegistrationHelper(nodeConfig = notaryNodeConfig)
}.isInstanceOf(IllegalArgumentException::class.java)
.hasMessageContaining("notary service legal name must be provided")
}
@Test(timeout=300_000)
fun `notary registration fails when notary service identity configured with same legal name as node`() {
val notaryNodeConfig = createNotaryNodeConfiguration(notaryServiceLegalName = config.myLegalName)
assertThat(notaryNodeConfig.notary).isNotNull
assertThatThrownBy {
createRegistrationHelper(nodeConfig = notaryNodeConfig)
}.isInstanceOf(IllegalArgumentException::class.java)
.hasMessageContaining("notary service legal name must be different from the node")
}
private fun createNotaryNodeConfiguration(notaryServiceLegalName: CordaX500Name?): NodeConfiguration {
return rigorousMock<NodeConfiguration>().also {
doReturn(config.baseDirectory).whenever(it).baseDirectory
doReturn(config.certificatesDirectory).whenever(it).certificatesDirectory
doReturn(CertificateStoreStubs.P2P.withCertificatesDirectory(config.certificatesDirectory)).whenever(it).p2pSslOptions
doReturn(CertificateStoreStubs.Signing.withCertificatesDirectory(config.certificatesDirectory)).whenever(it)
.signingCertificateStore
doReturn(nodeLegalName).whenever(it).myLegalName
doReturn("").whenever(it).emailAddress
doReturn(null).whenever(it).tlsCertCrlDistPoint
doReturn(null).whenever(it).tlsCertCrlIssuer
doReturn(true).whenever(it).crlCheckSoftFail
doReturn(NotaryConfig(validating = false, serviceLegalName = notaryServiceLegalName)).whenever(it).notary
}
}
private fun createCertPath(type: CertificateType = CertificateType.NODE_CA,
legalName: CordaX500Name = nodeLegalName,
publicKey: PublicKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME).public,
rootAndIntermediateCA: Pair<CertificateAndKeyPair, CertificateAndKeyPair> = createDevIntermediateCaCertPath()): List<X509Certificate> {
val (rootCa, intermediateCa) = rootAndIntermediateCA val (rootCa, intermediateCa) = rootAndIntermediateCA
val nameConstraints = if (type == CertificateType.NODE_CA) { val nameConstraints = if (type == CertificateType.NODE_CA) {
NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.toX500Name()))), arrayOf()) NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.toX500Name()))), arrayOf())
} else { } else {
null null
} }
val nodeCaCert = X509Utilities.createCertificate( val cert = X509Utilities.createCertificate(
type, type,
intermediateCa.certificate, intermediateCa.certificate,
intermediateCa.keyPair, intermediateCa.keyPair,
legalName.x500Principal, legalName.x500Principal,
publicKey, publicKey,
nameConstraints = nameConstraints) nameConstraints = nameConstraints)
return listOf(nodeCaCert, intermediateCa.certificate, rootCa.certificate) return listOf(cert, intermediateCa.certificate, rootCa.certificate)
} }
private fun createFixedResponseRegistrationHelper(response: List<X509Certificate>, certRole: CertRole = CertRole.NODE_CA): NetworkRegistrationHelper { private fun createFixedResponseRegistrationHelper(response: List<X509Certificate>, certRole: CertRole = CertRole.NODE_CA): NetworkRegistrationHelper {
return createRegistrationHelper(certRole) { response } return createRegistrationHelper(certRole) { response }
} }
private fun createRegistrationHelper(certRole: CertRole = CertRole.NODE_CA, rootAndIntermediateCA: Pair<CertificateAndKeyPair, CertificateAndKeyPair> = createDevIntermediateCaCertPath()) = createRegistrationHelper(certRole) { private fun createRegistrationHelper(
certRole: CertRole = CertRole.NODE_CA,
rootAndIntermediateCA: Pair<CertificateAndKeyPair, CertificateAndKeyPair> = createDevIntermediateCaCertPath(),
nodeConfig: NodeConfiguration = config
) = createRegistrationHelper(certRole, nodeConfig) {
val certType = CertificateType.values().first { it.role == certRole } val certType = CertificateType.values().first { it.role == certRole }
createNodeCaCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType) createCertPath(rootAndIntermediateCA = rootAndIntermediateCA, publicKey = it.publicKey, type = certType)
} }
private fun createRegistrationHelper(certRole: CertRole = CertRole.NODE_CA, dynamicResponse: (JcaPKCS10CertificationRequest) -> List<X509Certificate>): NetworkRegistrationHelper { private fun createRegistrationHelper(
certRole: CertRole = CertRole.NODE_CA,
nodeConfig: NodeConfiguration = config,
dynamicResponse: (JcaPKCS10CertificationRequest) -> List<X509Certificate>
): NetworkRegistrationHelper {
val certService = rigorousMock<NetworkRegistrationService>().also { val certService = rigorousMock<NetworkRegistrationService>().also {
val requests = mutableMapOf<String, JcaPKCS10CertificationRequest>() val requests = mutableMapOf<String, JcaPKCS10CertificationRequest>()
doAnswer { doAnswer {
@ -265,11 +351,11 @@ class NetworkRegistrationHelperTest {
} }
return when (certRole) { return when (certRole) {
CertRole.NODE_CA -> NodeRegistrationHelper(NodeRegistrationConfiguration(config), certService, NodeRegistrationOption(config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword)) CertRole.NODE_CA -> NodeRegistrationHelper(NodeRegistrationConfiguration(nodeConfig), certService, NodeRegistrationOption(nodeConfig.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword))
CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper( CertRole.SERVICE_IDENTITY -> NetworkRegistrationHelper(
NodeRegistrationConfiguration(config), NodeRegistrationConfiguration(nodeConfig),
certService, certService,
config.certificatesDirectory / networkRootTrustStoreFileName, nodeConfig.certificatesDirectory / networkRootTrustStoreFileName,
networkRootTrustStorePassword, networkRootTrustStorePassword,
DISTRIBUTED_NOTARY_KEY_ALIAS, DISTRIBUTED_NOTARY_KEY_ALIAS,
CertRole.SERVICE_IDENTITY) CertRole.SERVICE_IDENTITY)

View File

@ -92,8 +92,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
cordapp project(':samples:attachment-demo:workflows') cordapp project(':samples:attachment-demo:workflows')
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
notary = [validating: true] notary = [validating: true,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
p2pPort 10002 p2pPort 10002
cordapps = [] cordapps = []
rpcUsers = ext.rpcUsers rpcUsers = ext.rpcUsers

View File

@ -50,8 +50,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
cordapp project(':finance:contracts') cordapp project(':finance:contracts')
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
notary = [validating: true] notary = [validating: true,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
p2pPort 10002 p2pPort 10002
rpcSettings { rpcSettings {
address "localhost:10003" address "localhost:10003"

View File

@ -27,8 +27,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
cordapp project(':samples:cordapp-configuration:workflows') cordapp project(':samples:cordapp-configuration:workflows')
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
notary = [validating : true] notary = [validating : true,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
p2pPort 10002 p2pPort 10002
rpcSettings { rpcSettings {
port 10003 port 10003

View File

@ -62,8 +62,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
cordapp project(':samples:irs-demo:cordapp:workflows-irs') cordapp project(':samples:irs-demo:cordapp:workflows-irs')
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
notary = [validating : true] notary = [validating : true,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
p2pPort 10002 p2pPort 10002
rpcSettings { rpcSettings {
address("localhost:10003") address("localhost:10003")
@ -121,7 +123,9 @@ task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar', n
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Service,L=Zurich,C=CH"
notary = [validating : true] notary = [validating : true,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
cordapps = ["${project(":finance").group}:contracts:$corda_release_version", "${project(":finance").group}:workflows:$corda_release_version"] cordapps = ["${project(":finance").group}:contracts:$corda_release_version", "${project(":finance").group}:workflows:$corda_release_version"]
rpcUsers = rpcUsersList rpcUsers = rpcUsersList
useTestClock true useTestClock true

View File

@ -38,8 +38,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
cordapp project(':samples:network-verifier:workflows') cordapp project(':samples:network-verifier:workflows')
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
notary = [validating : false] notary = [validating : false,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
p2pPort 10002 p2pPort 10002
rpcSettings { rpcSettings {
port 10003 port 10003

View File

@ -55,13 +55,15 @@ task deployNodesSingle(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) {
rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]] rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]]
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
p2pPort 10009 p2pPort 10009
rpcSettings { rpcSettings {
address "localhost:10010" address "localhost:10010"
adminAddress "localhost:10110" adminAddress "localhost:10110"
} }
notary = [validating: true] notary = [validating: true,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
} }
} }
@ -85,7 +87,7 @@ task deployNodesCustom(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) {
rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]] rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]]
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
p2pPort 10009 p2pPort 10009
rpcSettings { rpcSettings {
address "localhost:10010" address "localhost:10010"
@ -93,7 +95,8 @@ task deployNodesCustom(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) {
} }
notary = [ notary = [
validating: true, validating: true,
className: "net.corda.notarydemo.MyCustomValidatingNotaryService" className: "net.corda.notarydemo.MyCustomValidatingNotaryService",
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
] ]
} }
} }

View File

@ -93,8 +93,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]] rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
notary = [validating : true] notary = [validating : true,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
p2pPort 10002 p2pPort 10002
rpcSettings { rpcSettings {
address "localhost:10014" address "localhost:10014"

View File

@ -83,8 +83,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
cordapp project(':samples:trader-demo:workflows-trader') cordapp project(':samples:trader-demo:workflows-trader')
} }
node { node {
name "O=Notary Service,L=Zurich,C=CH" name "O=Notary Node,L=Zurich,C=CH"
notary = [validating : true] notary = [validating : true,
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
]
p2pPort 10002 p2pPort 10002
rpcSettings { rpcSettings {
address "localhost:10003" address "localhost:10003"