mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Add certificate to node identity (#769)
Add certificate and path to node identity via the `NodeInfo` class.
This commit is contained in:
parent
f148765c57
commit
08c91bd611
@ -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))
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user