Skip to content

Commit

Permalink
CORDA-2221: Fix clustered notary identity generation (corda#4230)
Browse files Browse the repository at this point in the history
- Don't generate a composite key certificate for CFT notaries
- Don't require a composite key certificate for CFT notaries on startup
  • Loading branch information
adagys authored Nov 14, 2018
1 parent eb9bd10 commit e1e5d13
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -43,50 +45,67 @@ object DevIdentityGenerator {
return identity.party
}

/** Generates a CFT notary identity, where the entire cluster shares a key pair. */
fun generateDistributedNotarySingularIdentity(dirs: List<Path>, 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 { nodeDir ->
val keyStore = getKeyStore(nodeDir)
setPrivateKey(keyStore, keyPair, notaryName.x500Principal)
}
return Party(notaryName, notaryKey)
}

/** Generates a BFT notary identity: individual key pairs for each cluster member, and a shared composite key. */
fun generateDistributedNotaryCompositeIdentity(dirs: List<Path>, 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)
val keyStore = getKeyStore(nodeDir)
setPrivateKey(keyStore, keyPair, notaryName.x500Principal)
setCompositeKey(keyStore, notaryKey, notaryName.x500Principal)
}
return Party(notaryName, notaryKey)
}

fun generateDistributedNotarySingularIdentity(dirs: List<Path>, notaryName: CordaX500Name): Party {
require(dirs.isNotEmpty())
private fun getKeyStore(nodeDir: Path): X509KeyStore {
val distServKeyStoreFile = nodeDir / "certificates/distributedService.jks"
return X509KeyStore.fromFile(distServKeyStoreFile, DEV_CA_KEY_STORE_PASS, createNew = true)
}

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)
}
return Party(notaryName, notaryKey)
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 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*/)
}
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)
}
}
6 changes: 5 additions & 1 deletion node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -864,13 +864,17 @@ abstract class AbstractNode<S>(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) {
Expand Down

0 comments on commit e1e5d13

Please sign in to comment.