mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
CORDA-3201 - Enforce separate key for notary identity (#6308)
This commit is contained in:
parent
e6d5842a23
commit
a500084d38
@ -1435,7 +1435,7 @@
|
||||
<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:MockServices.kt$ fun <T : SerializeAsToken> createMockCordaService(serviceHub: MockServices, serviceConstructor: (AppServiceHub) -> T): T</ID>
|
||||
<ID>ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List<X509Certificate>)</ID>
|
||||
<ID>ThrowsCount:NetworkRegistrationHelper.kt$NetworkRegistrationHelper$private fun validateCertificates( registeringPublicKey: PublicKey, registeringLegalName: CordaX500Name, expectedCertRole: CertRole, certificates: List<X509Certificate> )</ID>
|
||||
<ID>ThrowsCount:NodeInfoFilesCopier.kt$NodeInfoFilesCopier$private fun atomicCopy(source: Path, destination: Path)</ID>
|
||||
<ID>ThrowsCount:NodeVaultService.kt$NodeVaultService$@Throws(VaultQueryException::class) private fun <T : ContractState> _queryBy(criteria: QueryCriteria, paging_: PageSpecification, sorting: Sort, contractStateType: Class<out T>, skipPagingChecks: Boolean): Vault.Page<T></ID>
|
||||
<ID>ThrowsCount:NodeVaultService.kt$NodeVaultService$private fun makeUpdates(batch: Iterable<CoreTransaction>, statesToRecord: StatesToRecord, previouslySeen: Boolean): List<Vault.Update<ContractState>></ID>
|
||||
|
@ -614,11 +614,22 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
|
||||
val myNotaryIdentity = configuration.notary?.let {
|
||||
if (it.serviceLegalName != null) {
|
||||
val (notaryIdentity, notaryIdentityKeyPair) = loadNotaryClusterIdentity(it.serviceLegalName)
|
||||
val (notaryIdentity, notaryIdentityKeyPair) = loadNotaryServiceIdentity(it.serviceLegalName)
|
||||
keyPairs += notaryIdentityKeyPair
|
||||
notaryIdentity
|
||||
} 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
|
||||
}
|
||||
}
|
||||
@ -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 compositeKeyAlias = "$DISTRIBUTED_NOTARY_COMPOSITE_KEY_ALIAS"
|
||||
|
||||
|
@ -151,7 +151,7 @@ fun NodeConfiguration.shouldInitCrashShell() = shouldStartLocalShell() || should
|
||||
data class NotaryConfig(
|
||||
/** Specifies whether the notary validates transactions or not. */
|
||||
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,
|
||||
/** The name of the notary service class to load. */
|
||||
val className: String? = null,
|
||||
|
@ -25,10 +25,10 @@ import org.bouncycastle.operator.ContentSigner
|
||||
import org.bouncycastle.util.io.pem.PemObject
|
||||
import java.io.IOException
|
||||
import java.io.StringWriter
|
||||
import java.lang.IllegalStateException
|
||||
import java.net.ConnectException
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
@ -63,6 +63,7 @@ open class NetworkRegistrationHelper(
|
||||
private val requestIdStore = certificatesDirectory / "certificate-request-id.txt"
|
||||
protected val rootTrustStore: X509KeyStore
|
||||
protected val rootCert: X509Certificate
|
||||
private val notaryServiceConfig: NotaryServiceConfig? = config.notaryServiceConfig
|
||||
|
||||
init {
|
||||
require(networkRootTrustStorePath.exists()) {
|
||||
@ -95,34 +96,70 @@ open class NetworkRegistrationHelper(
|
||||
return
|
||||
}
|
||||
|
||||
notaryServiceConfig?.let { validateNotaryServiceKeyAndCert(certStore, it.notaryServiceKeyAlias, it.notaryServiceLegalName) }
|
||||
|
||||
val tlsCrlIssuerCert = getTlsCrlIssuerCert()
|
||||
|
||||
// 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.
|
||||
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))
|
||||
|
||||
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())
|
||||
onSuccess(entityPublicKey, cryptoService.getSigner(nodeCaKeyAlias), receivedCertificates, tlsCrlIssuerCert?.subjectX500Principal?.toX500Name())
|
||||
// All done, clean up temp files.
|
||||
requestIdStore.deleteIfExists()
|
||||
}
|
||||
|
||||
private fun loadOrGenerateKeyPair(): PublicKey {
|
||||
return if (cryptoService.containsKey(nodeCaKeyAlias)) {
|
||||
cryptoService.getPublicKey(nodeCaKeyAlias)!!
|
||||
private fun generateKeyPairAndCertificate(keyAlias: String, legalName: CordaX500Name, certificateRole: CertRole, certStore: CertificateStore): Pair<PublicKey, List<X509Certificate>> {
|
||||
val entityPublicKey = loadOrGenerateKeyPair(keyAlias)
|
||||
|
||||
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 {
|
||||
cryptoService.generateKeyPair(nodeCaKeyAlias, cryptoService.defaultTLSSignatureScheme())
|
||||
cryptoService.generateKeyPair(keyAlias, cryptoService.defaultTLSSignatureScheme())
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,26 +174,31 @@ open class NetworkRegistrationHelper(
|
||||
return tlsCrlIssuerCert
|
||||
}
|
||||
|
||||
private fun validateCertificates(registeringPublicKey: PublicKey, certificates: List<X509Certificate>) {
|
||||
val nodeCACertificate = certificates.first()
|
||||
private fun validateCertificates(
|
||||
registeringPublicKey: PublicKey,
|
||||
registeringLegalName: CordaX500Name,
|
||||
expectedCertRole: CertRole,
|
||||
certificates: List<X509Certificate>
|
||||
) {
|
||||
val receivedCertificate = certificates.first()
|
||||
|
||||
val nodeCaSubject = try {
|
||||
CordaX500Name.build(nodeCACertificate.subjectX500Principal)
|
||||
val certificateSubject = try {
|
||||
CordaX500Name.build(receivedCertificate.subjectX500Principal)
|
||||
} 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) {
|
||||
throw CertificateRequestException("Subject of received node CA cert doesn't match with node legal name: $nodeCaSubject")
|
||||
if (certificateSubject != registeringLegalName) {
|
||||
throw CertificateRequestException("Subject of received cert doesn't match with legal name: $certificateSubject")
|
||||
}
|
||||
|
||||
val nodeCaCertRole = try {
|
||||
CertRole.extract(nodeCACertificate)
|
||||
val receivedCertRole = try {
|
||||
CertRole.extract(receivedCertificate)
|
||||
} 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) {
|
||||
throw CertificateRequestException("Received certificate contains invalid cert role, expected '$certRole', got '$nodeCaCertRole'.")
|
||||
if (expectedCertRole != receivedCertRole) {
|
||||
throw CertificateRequestException("Received certificate contains invalid cert role, expected '$expectedCertRole', got '$receivedCertRole'.")
|
||||
}
|
||||
|
||||
// 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.")
|
||||
}
|
||||
|
||||
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,
|
||||
* 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.
|
||||
* New request ID will be stored in requestId.txt
|
||||
* @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.
|
||||
* @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 {
|
||||
// Retrieve request id from file if exists, else post a request to server.
|
||||
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()
|
||||
JcaPEMWriter(writer).use {
|
||||
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("Legal Name: $myLegalName")
|
||||
logProgress("Legal Name: $legalName")
|
||||
logProgress("Email: $emailAddress")
|
||||
logProgress("Public Key: $publicKey")
|
||||
logProgress("$writer")
|
||||
@ -277,7 +310,8 @@ class NodeRegistrationConfiguration(
|
||||
val certificatesDirectory: Path,
|
||||
val emailAddress: String,
|
||||
val cryptoService: CryptoService,
|
||||
val certificateStore: CertificateStore) {
|
||||
val certificateStore: CertificateStore,
|
||||
val notaryServiceConfig: NotaryServiceConfig? = null) {
|
||||
|
||||
constructor(config: NodeConfiguration) : this(
|
||||
p2pSslOptions = config.p2pSslOptions,
|
||||
@ -287,10 +321,29 @@ class NodeRegistrationConfiguration(
|
||||
certificatesDirectory = config.certificatesDirectory,
|
||||
emailAddress = config.emailAddress,
|
||||
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(
|
||||
message: String?,
|
||||
cause: Throwable?
|
||||
|
@ -28,6 +28,8 @@ import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||
import net.corda.coretesting.internal.rigorousMock
|
||||
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.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
@ -37,6 +39,7 @@ import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.lang.IllegalStateException
|
||||
import java.nio.file.Files
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPathValidatorException
|
||||
@ -71,6 +74,7 @@ class NetworkRegistrationHelperTest {
|
||||
doReturn(null).whenever(it).tlsCertCrlDistPoint
|
||||
doReturn(null).whenever(it).tlsCertCrlIssuer
|
||||
doReturn(true).whenever(it).crlCheckSoftFail
|
||||
doReturn(null).whenever(it).notary
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +124,7 @@ class NetworkRegistrationHelperTest {
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `missing truststore`() {
|
||||
val nodeCaCertPath = createNodeCaCertPath()
|
||||
val nodeCaCertPath = createCertPath()
|
||||
assertThatThrownBy {
|
||||
createFixedResponseRegistrationHelper(nodeCaCertPath)
|
||||
}.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)
|
||||
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())
|
||||
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
|
||||
assertThatExceptionOfType(CertificateRequestException::class.java)
|
||||
@ -139,7 +143,7 @@ class NetworkRegistrationHelperTest {
|
||||
@Test(timeout=300_000)
|
||||
fun `node CA with incorrect subject`() {
|
||||
val invalidName = CordaX500Name("Foo", "MU", "GB")
|
||||
val nodeCaCertPath = createNodeCaCertPath(legalName = invalidName)
|
||||
val nodeCaCertPath = createCertPath(legalName = invalidName)
|
||||
saveNetworkTrustStore(CORDA_ROOT_CA to nodeCaCertPath.last())
|
||||
val registrationHelper = createFixedResponseRegistrationHelper(nodeCaCertPath)
|
||||
assertThatExceptionOfType(CertificateRequestException::class.java)
|
||||
@ -220,36 +224,118 @@ class NetworkRegistrationHelperTest {
|
||||
createRegistrationHelper(rootAndIntermediateCA = rootAndIntermediateCA).generateKeysAndRegister()
|
||||
}
|
||||
|
||||
private fun createNodeCaCertPath(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> {
|
||||
@Test(timeout=300_000)
|
||||
fun `successful registration for notary node`() {
|
||||
val notaryServiceLegalName = DUMMY_NOTARY_NAME
|
||||
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 nameConstraints = if (type == CertificateType.NODE_CA) {
|
||||
NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, legalName.toX500Name()))), arrayOf())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val nodeCaCert = X509Utilities.createCertificate(
|
||||
val cert = X509Utilities.createCertificate(
|
||||
type,
|
||||
intermediateCa.certificate,
|
||||
intermediateCa.keyPair,
|
||||
legalName.x500Principal,
|
||||
publicKey,
|
||||
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 {
|
||||
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 }
|
||||
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 requests = mutableMapOf<String, JcaPKCS10CertificationRequest>()
|
||||
doAnswer {
|
||||
@ -265,11 +351,11 @@ class NetworkRegistrationHelperTest {
|
||||
}
|
||||
|
||||
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(
|
||||
NodeRegistrationConfiguration(config),
|
||||
NodeRegistrationConfiguration(nodeConfig),
|
||||
certService,
|
||||
config.certificatesDirectory / networkRootTrustStoreFileName,
|
||||
nodeConfig.certificatesDirectory / networkRootTrustStoreFileName,
|
||||
networkRootTrustStorePassword,
|
||||
DISTRIBUTED_NOTARY_KEY_ALIAS,
|
||||
CertRole.SERVICE_IDENTITY)
|
||||
|
@ -92,8 +92,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
|
||||
cordapp project(':samples:attachment-demo:workflows')
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating: true]
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
notary = [validating: true,
|
||||
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
|
||||
]
|
||||
p2pPort 10002
|
||||
cordapps = []
|
||||
rpcUsers = ext.rpcUsers
|
||||
|
@ -50,8 +50,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
|
||||
cordapp project(':finance:contracts')
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating: true]
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
notary = [validating: true,
|
||||
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
|
||||
]
|
||||
p2pPort 10002
|
||||
rpcSettings {
|
||||
address "localhost:10003"
|
||||
|
@ -27,8 +27,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
|
||||
cordapp project(':samples:cordapp-configuration:workflows')
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating : true]
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
notary = [validating : true,
|
||||
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
|
||||
]
|
||||
p2pPort 10002
|
||||
rpcSettings {
|
||||
port 10003
|
||||
|
@ -62,8 +62,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
|
||||
cordapp project(':samples:irs-demo:cordapp:workflows-irs')
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating : true]
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
notary = [validating : true,
|
||||
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
|
||||
]
|
||||
p2pPort 10002
|
||||
rpcSettings {
|
||||
address("localhost:10003")
|
||||
@ -121,7 +123,9 @@ task prepareDockerNodes(type: net.corda.plugins.Dockerform, dependsOn: ['jar', n
|
||||
}
|
||||
node {
|
||||
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"]
|
||||
rpcUsers = rpcUsersList
|
||||
useTestClock true
|
||||
|
@ -38,8 +38,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
|
||||
cordapp project(':samples:network-verifier:workflows')
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating : false]
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
notary = [validating : false,
|
||||
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
|
||||
]
|
||||
p2pPort 10002
|
||||
rpcSettings {
|
||||
port 10003
|
||||
|
@ -55,13 +55,15 @@ task deployNodesSingle(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) {
|
||||
rpcUsers = [[user: "demou", password: "demop", permissions: ["ALL"]]]
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
p2pPort 10009
|
||||
rpcSettings {
|
||||
address "localhost:10010"
|
||||
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"]]]
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
p2pPort 10009
|
||||
rpcSettings {
|
||||
address "localhost:10010"
|
||||
@ -93,7 +95,8 @@ task deployNodesCustom(type: Cordform, dependsOn: ['jar', nodeTask, webTask]) {
|
||||
}
|
||||
notary = [
|
||||
validating: true,
|
||||
className: "net.corda.notarydemo.MyCustomValidatingNotaryService"
|
||||
className: "net.corda.notarydemo.MyCustomValidatingNotaryService",
|
||||
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -93,8 +93,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask,
|
||||
rpcUsers = [['username': "default", 'password': "default", 'permissions': [ 'ALL' ]]]
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating : true]
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
notary = [validating : true,
|
||||
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
|
||||
]
|
||||
p2pPort 10002
|
||||
rpcSettings {
|
||||
address "localhost:10014"
|
||||
|
@ -83,8 +83,10 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar', nodeTask])
|
||||
cordapp project(':samples:trader-demo:workflows-trader')
|
||||
}
|
||||
node {
|
||||
name "O=Notary Service,L=Zurich,C=CH"
|
||||
notary = [validating : true]
|
||||
name "O=Notary Node,L=Zurich,C=CH"
|
||||
notary = [validating : true,
|
||||
serviceLegalName: "O=Notary Service,L=Zurich,C=CH"
|
||||
]
|
||||
p2pPort 10002
|
||||
rpcSettings {
|
||||
address "localhost:10003"
|
||||
|
Loading…
Reference in New Issue
Block a user