Add certificate to node identity (#769)

Add certificate and path to node identity via the `NodeInfo` class.
This commit is contained in:
Ross Nicoll 2017-05-31 11:52:50 +01:00 committed by GitHub
parent f148765c57
commit 08c91bd611
10 changed files with 49 additions and 23 deletions

View File

@ -10,6 +10,7 @@ import net.corda.core.identity.Party
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.flows.NotaryError
import net.corda.flows.NotaryException
@ -66,6 +67,7 @@ class BFTNotaryServiceTests : NodeBasedTest() {
val replicaNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
ServiceIdentityGenerator.generateToDisk(
replicaNames.map { baseDirectory(it) },
DUMMY_CA,
serviceType.id,
clusterName,
minCorrectReplicas(clusterSize))

View File

@ -64,6 +64,7 @@ class P2PMessagingTest : NodeBasedTest() {
fun `communicating with a distributed service which the network map node is part of`() {
ServiceIdentityGenerator.generateToDisk(
listOf(DUMMY_MAP.name, SERVICE_2_NAME).map { baseDirectory(it) },
DUMMY_CA,
RaftValidatingNotaryService.type.id,
DISTRIBUTED_SERVICE_NAME)

View File

@ -3,13 +3,13 @@ package net.corda.services.messaging
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.crypto.X509Utilities
import net.corda.core.getOrThrow
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.random63BitValue
import net.corda.core.seconds
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_BANK_A
import net.corda.core.utilities.DUMMY_BANK_B
import net.corda.core.utilities.getTestPartyAndCertificate
import net.corda.node.internal.NetworkMapInfo
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.messaging.sendRequest
@ -66,7 +66,7 @@ class P2PSecurityTest : NodeBasedTest() {
}
private fun SimpleNode.registerWithNetworkMap(registrationName: X500Name): ListenableFuture<NetworkMapService.RegistrationResponse> {
val nodeInfo = NodeInfo(net.myAddress, Party(registrationName, identity.public), MOCK_VERSION_INFO.platformVersion)
val nodeInfo = NodeInfo(net.myAddress, getTestPartyAndCertificate(registrationName, identity.public), MOCK_VERSION_INFO.platformVersion)
val registration = NodeRegistration(nodeInfo, System.currentTimeMillis(), AddOrRemove.ADD, Instant.MAX)
val request = RegistrationRequest(registration.toWire(keyService, identity.public), net.myAddress)
return net.sendRequest<NetworkMapService.RegistrationResponse>(NetworkMapService.REGISTER_TOPIC, request, networkMapNode.net.myAddress)

View File

@ -556,7 +556,7 @@ class DriverDSL(
): ListenableFuture<Pair<Party, List<NodeHandle>>> {
val nodeNames = (0 until clusterSize).map { DUMMY_NOTARY.name.appendToCommonName(" $it") }
val paths = nodeNames.map { baseDirectory(it) }
ServiceIdentityGenerator.generateToDisk(paths, type.id, notaryName)
ServiceIdentityGenerator.generateToDisk(paths, DUMMY_CA, type.id, notaryName)
val advertisedServices = setOf(ServiceInfo(type, notaryName))
val notaryClusterAddress = portAllocation.nextHostAndPort()

View File

@ -12,6 +12,7 @@ import net.corda.core.*
import net.corda.core.crypto.*
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.RPCOps
import net.corda.core.messaging.SingleMessageRecipient
@ -708,11 +709,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
stateMachineRecordedTransactionMappingStorage: StateMachineRecordedTransactionMappingStorage) =
StorageServiceImpl(attachments, transactionStorage, stateMachineRecordedTransactionMappingStorage)
protected fun obtainLegalIdentity(): Party = identityKeyPair.first
protected fun obtainLegalIdentity(): PartyAndCertificate = identityKeyPair.first
protected fun obtainLegalIdentityKey(): KeyPair = identityKeyPair.second
private val identityKeyPair by lazy { obtainKeyPair("identity", configuration.myLegalName) }
private fun obtainKeyPair(serviceId: String, serviceName: X500Name): Pair<Party, KeyPair> {
private fun obtainKeyPair(serviceId: String, serviceName: X500Name): Pair<PartyAndCertificate, KeyPair> {
// Load the private identity key, creating it if necessary. The identity key is a long term well known key that
// is distributed to other peers and we use it (or a key signed by it) when we need to do something
// "permissioned". The identity file is what gets distributed and contains the node's legal name along with
@ -724,21 +725,24 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
val privateKeyAlias = "$serviceId-private-key"
val privKeyFile = configuration.baseDirectory / privateKeyAlias
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
val identityAndKey = keyStore.certificateAndKeyPair(privateKeyAlias)?.let { (cert, keyPair) ->
val certificateAndKeyPair = keyStore.certificateAndKeyPair(privateKeyAlias)
val identityCertPathAndKey: Pair<PartyAndCertificate, KeyPair> = if (certificateAndKeyPair != null) {
val (cert, keyPair) = certificateAndKeyPair
// Get keys from keystore.
val loadedServiceName = X509CertificateHolder(cert.encoded).subject
if (loadedServiceName != serviceName) {
throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:" +
"$serviceName vs $loadedServiceName")
}
Pair(Party(loadedServiceName, keyPair.public), keyPair)
} ?: if (privKeyFile.exists()) {
val certPath = X509Utilities.createCertificatePath(cert, cert, revocationEnabled = false)
Pair(PartyAndCertificate(loadedServiceName, keyPair.public, cert, certPath), keyPair)
} else if (privKeyFile.exists()) {
// Get keys from key file.
// TODO: this is here to smooth out the key storage transition, remove this in future release.
// Check that the identity in the config file matches the identity file we have stored to disk.
// This is just a sanity check. It shouldn't fail unless the admin has fiddled with the files and messed
// things up for us.
val myIdentity = pubIdentityFile.readAll().deserialize<Party>()
val myIdentity = pubIdentityFile.readAll().deserialize<PartyAndCertificate>()
if (myIdentity.name != serviceName)
throw ConfigurationException("The legal name in the config file doesn't match the stored identity file:" +
"$serviceName vs ${myIdentity.name}")
@ -749,14 +753,18 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
}
Pair(myIdentity, keyPair)
} else {
val clientCA = keyStore.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)!!
// Create new keys and store in keystore.
log.info("Identity key not found, generating fresh key!")
val keyPair: KeyPair = generateKeyPair()
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
val certPath = X509Utilities.createCertificatePath(cert, cert, revocationEnabled = false)
keyStore.save(serviceName, privateKeyAlias, keyPair)
Pair(Party(serviceName, keyPair.public), keyPair)
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
Pair(PartyAndCertificate(serviceName, keyPair.public, cert, certPath), keyPair)
}
partyKeys += identityAndKey.second
return identityAndKey
partyKeys += identityCertPathAndKey.second
return identityCertPathAndKey
}
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
@ -783,11 +791,10 @@ private class KeyStoreWrapper(private val storePath: Path, private val storePass
}
fun save(serviceName: X500Name, privateKeyAlias: String, keyPair: KeyPair) {
val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, storePassword)
val converter = JcaX509CertificateConverter()
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(),
arrayOf(converter.getCertificate(cert), *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, storePassword)
val cert = converter.getCertificate(X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public))
keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), arrayOf(cert, *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
keyStore.save(storePath, storePassword)
}
}

View File

@ -1,8 +1,8 @@
package net.corda.node.utilities
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.serialization.serialize
import net.corda.core.serialization.storageKryo
import net.corda.core.utilities.loggerFor
@ -20,16 +20,27 @@ object ServiceIdentityGenerator {
* This method should be called *before* any of the nodes are started.
*
* @param dirs List of node directories to place the generated identity and key pairs in.
* @param serviceCa Certificate authority to use when signing identity certificates.
* @param serviceId The service id of the distributed service.
* @param serviceName The legal name of the distributed service.
* @param threshold The threshold for the generated group [CompositeKey].
*/
fun generateToDisk(dirs: List<Path>, serviceId: String, serviceName: X500Name, threshold: Int = 1) {
// TODO: This needs to write out to the key store, not just files on disk
fun generateToDisk(dirs: List<Path>,
serviceCa: CertificateAndKeyPair,
serviceId: String,
serviceName: X500Name,
threshold: Int = 1) {
log.trace { "Generating a group identity \"serviceName\" for nodes: ${dirs.joinToString()}" }
val keyPairs = (1..dirs.size).map { generateKeyPair() }
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
val notaryParty = Party(serviceName, notaryKey).serialize()
// TODO: This doesn't work until we have composite keys in X.509 certificates, so we make up a certificate that nothing checks
// val notaryCert = X509Utilities.createCertificate(CertificateType.IDENTITY, serviceCa.certificate,
// serviceCa.keyPair, serviceName, notaryKey)
val notaryCert = X509Utilities.createSelfSignedCACertificate(serviceName, generateKeyPair())
val notaryCertPath = X509Utilities.createCertificatePath(serviceCa.certificate, notaryCert, revocationEnabled = false)
val notaryParty = PartyAndCertificate(serviceName, notaryKey, notaryCert, notaryCertPath).serialize()
keyPairs.zip(dirs) { keyPair, dir ->
Files.createDirectories(dir)

View File

@ -5,6 +5,7 @@ import net.corda.core.div
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_CA
import net.corda.demorun.util.*
import net.corda.demorun.runNodes
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
@ -64,6 +65,6 @@ object BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", not
}
override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, advertisedService.type.id, clusterName, minCorrectReplicas(clusterSize))
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, DUMMY_CA, advertisedService.type.id, clusterName, minCorrectReplicas(clusterSize))
}
}

View File

@ -6,6 +6,7 @@ import net.corda.core.div
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demorun.util.*
import net.corda.node.services.transactions.RaftValidatingNotaryService
@ -65,6 +66,6 @@ object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes", no
}
override fun setup(context: CordformContext) {
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, advertisedService.type.id, clusterName)
ServiceIdentityGenerator.generateToDisk(notaryNames.map { context.baseDirectory(it) }, DUMMY_CA, advertisedService.type.id, clusterName)
}
}

View File

@ -12,6 +12,7 @@ import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.getTestPartyAndCertificate
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.keys.freshKeyAndCert
import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransactionMappingStorage
@ -68,7 +69,7 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
override val vaultService: VaultService get() = throw UnsupportedOperationException()
override val networkMapCache: NetworkMapCache get() = throw UnsupportedOperationException()
override val clock: Clock get() = Clock.systemUTC()
override val myInfo: NodeInfo get() = NodeInfo(object : SingleMessageRecipient {}, Party(MEGA_CORP.name, key.public), MOCK_VERSION_INFO.platformVersion)
override val myInfo: NodeInfo get() = NodeInfo(object : SingleMessageRecipient {}, getTestPartyAndCertificate(MEGA_CORP.name, key.public), MOCK_VERSION_INFO.platformVersion)
override val transactionVerifierService: TransactionVerifierService get() = InMemoryTransactionVerifierService(2)
fun makeVaultService(dataSourceProps: Properties): VaultService {

View File

@ -8,6 +8,7 @@ import net.corda.core.crypto.appendToCommonName
import net.corda.core.crypto.commonName
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.DUMMY_MAP
import net.corda.core.utilities.WHITESPACE
import net.corda.node.driver.addressMustNotBeBound
@ -110,6 +111,7 @@ abstract class NodeBasedTest {
serviceType: ServiceType = RaftValidatingNotaryService.type): ListenableFuture<List<Node>> {
ServiceIdentityGenerator.generateToDisk(
(0 until clusterSize).map { baseDirectory(notaryName.appendToCommonName("-$it")) },
DUMMY_CA,
serviceType.id,
notaryName)