diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt index 562af4472f..d08cd750cb 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -18,6 +18,8 @@ import org.slf4j.LoggerFactory import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey +import java.security.cert.X509Certificate +import javax.security.auth.x500.X500Principal /** * Contains utility methods for generating identities for a node. @@ -43,50 +45,67 @@ object DevIdentityGenerator { return identity.party } - fun generateDistributedNotaryCompositeIdentity(dirs: List, notaryName: CordaX500Name, threshold: Int = 1): Party { - require(dirs.isNotEmpty()) - - log.trace { "Generating composite identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } - val keyPairs = (1..dirs.size).map { generateKeyPair() } - val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) - keyPairs.zip(dirs) { keyPair, nodeDir -> - generateCertificates(keyPair, notaryKey, notaryName, nodeDir) - } - return Party(notaryName, notaryKey) - } - + /** Generates a CFT notary identity, where the entire cluster shares a key pair. */ fun generateDistributedNotarySingularIdentity(dirs: List, notaryName: CordaX500Name): Party { require(dirs.isNotEmpty()) log.trace { "Generating singular identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } + val keyPair = generateKeyPair() val notaryKey = keyPair.public - dirs.forEach { dir -> - generateCertificates(keyPair, notaryKey, notaryName, dir) + + dirs.forEach { nodeDir -> + val keyStore = getKeyStore(nodeDir) + setPrivateKey(keyStore, keyPair, notaryName.x500Principal) } return Party(notaryName, notaryKey) } - private fun generateCertificates(keyPair: KeyPair, notaryKey: PublicKey, notaryName: CordaX500Name, nodeDir: Path) { - val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, notaryKey).map { publicKey -> - X509Utilities.createCertificate( - CertificateType.SERVICE_IDENTITY, - DEV_INTERMEDIATE_CA.certificate, - DEV_INTERMEDIATE_CA.keyPair, - notaryName.x500Principal, - publicKey) - } - val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks" - X509KeyStore.fromFile(distServKeyStoreFile, DEV_CA_KEY_STORE_PASS, createNew = true).update { - setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) - setPrivateKey( - "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", - keyPair.private, - listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate), - DEV_CA_KEY_STORE_PASS // Unfortunately we have to use the same password for private key due to Artemis limitation, for more details please see: - // org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeyManagerFactory - // where it is calling `KeyManagerFactory.init()` with store password - /*DEV_CA_PRIVATE_KEY_PASS*/) + /** Generates a BFT notary identity: individual key pairs for each cluster member, and a shared composite key. */ + fun generateDistributedNotaryCompositeIdentity(dirs: List, notaryName: CordaX500Name, threshold: Int = 1): Party { + require(dirs.isNotEmpty()) + + log.trace { "Generating composite identity \"$notaryName\" for nodes: ${dirs.joinToString()}" } + + val keyPairs = (1..dirs.size).map { generateKeyPair() } + val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold) + + keyPairs.zip(dirs) { keyPair, nodeDir -> + val keyStore = getKeyStore(nodeDir) + setPrivateKey(keyStore, keyPair, notaryName.x500Principal) + setCompositeKey(keyStore, notaryKey, notaryName.x500Principal) } + return Party(notaryName, notaryKey) + } + + private fun getKeyStore(nodeDir: Path): X509KeyStore { + val distServKeyStoreFile = nodeDir / "certificates/distributedService.jks" + return X509KeyStore.fromFile(distServKeyStoreFile, DEV_CA_KEY_STORE_PASS, createNew = true) + } + + private fun setPrivateKey(keyStore: X509KeyStore, keyPair: KeyPair, notaryPrincipal: X500Principal) { + val serviceKeyCert = createCertificate(keyPair.public, notaryPrincipal) + keyStore.setPrivateKey( + "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", + keyPair.private, + listOf(serviceKeyCert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate), + DEV_CA_KEY_STORE_PASS // Unfortunately we have to use the same password for private key due to Artemis limitation, for more details please see: + // org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeyManagerFactory + // where it is calling `KeyManagerFactory.init()` with store password + /*DEV_CA_PRIVATE_KEY_PASS*/) + } + + private fun setCompositeKey(keyStore: X509KeyStore, compositeKey: PublicKey, notaryPrincipal: X500Principal) { + val compositeKeyCert = createCertificate(compositeKey, notaryPrincipal) + keyStore.setCertificate("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert) + } + + private fun createCertificate(publicKey: PublicKey, principal: X500Principal): X509Certificate { + return X509Utilities.createCertificate( + CertificateType.SERVICE_IDENTITY, + DEV_INTERMEDIATE_CA.certificate, + DEV_INTERMEDIATE_CA.keyPair, + principal, + publicKey) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 51a9c4633b..65e3bce05b 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -864,13 +864,17 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val compositeKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key" val signingCertificateStore = configuration.signingCertificateStore.get() + // A composite key is only required for BFT notaries. val certificates = if (cryptoService.containsKey(compositeKeyAlias)) { val certificate = signingCertificateStore[compositeKeyAlias] // We have to create the certificate chain for the composite key manually, this is because we don't have a keystore // provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate + // the tail of the private key certificates, as they are both signed by the same certificate chain. listOf(certificate) + signingCertificateStore.query { getCertificateChain(privateKeyAlias) }.drop(1) - } else throw IllegalStateException("The identity public key for the notary service $serviceLegalName was not found in the key store.") + } else { + // We assume the notary is CFT, and each cluster member shares the same notary key pair. + signingCertificateStore.query { getCertificateChain(privateKeyAlias) } + } val subject = CordaX500Name.build(certificates.first().subjectX500Principal) if (subject != serviceLegalName) {