mirror of
https://github.com/corda/corda.git
synced 2025-06-17 22:58:19 +00:00
Enforce certificate constraints on all identities
* Enforce that the identity service must always have a root CA specified, which all identities have certificates signed by (or intermediaries of). Also adds a certificate store to the identity service for help building/verifying certificate paths. * Add a certificate store for the CA certificate and intermediaries * Use the certificate factory directly to build paths rather than assembling them via an interim API call. After reducing the complexity of the utility API, it's replacing two lines of code, at which point it seems better to make the behaviour clearer rather than having a function hide what's actually going on.
This commit is contained in:
@ -2,14 +2,12 @@ package net.corda.services.messaging
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.cert
|
||||
import net.corda.core.getOrThrow
|
||||
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.core.utilities.*
|
||||
import net.corda.node.internal.NetworkMapInfo
|
||||
import net.corda.node.services.config.configureWithDevSSLCertificate
|
||||
import net.corda.node.services.messaging.sendRequest
|
||||
@ -24,8 +22,8 @@ import net.corda.testing.node.SimpleNode
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.junit.Test
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
@ -46,7 +44,7 @@ class P2PSecurityTest : NodeBasedTest() {
|
||||
|
||||
@Test
|
||||
fun `register with the network map service using a legal name different from the TLS CN`() {
|
||||
startSimpleNode(DUMMY_BANK_A.name).use {
|
||||
startSimpleNode(DUMMY_BANK_A.name, DUMMY_CA.certificate.cert).use {
|
||||
// Register with the network map using a different legal name
|
||||
val response = it.registerWithNetworkMap(DUMMY_BANK_B.name)
|
||||
// We don't expect a response because the network map's host verification will prevent a connection back
|
||||
@ -58,7 +56,7 @@ class P2PSecurityTest : NodeBasedTest() {
|
||||
}
|
||||
|
||||
private fun startSimpleNode(legalName: X500Name,
|
||||
trustRoot: X509CertificateHolder? = null): SimpleNode {
|
||||
trustRoot: X509Certificate): SimpleNode {
|
||||
val config = TestNodeConfiguration(
|
||||
baseDirectory = baseDirectory(legalName),
|
||||
myLegalName = legalName,
|
||||
|
@ -221,7 +221,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
|
||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||
initialiseDatabasePersistence {
|
||||
val tokenizableServices = makeServices()
|
||||
val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||
val tokenizableServices = makeServices(keyStoreWrapper)
|
||||
|
||||
smm = StateMachineManager(services,
|
||||
checkpointStorage,
|
||||
@ -452,7 +453,8 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
* Builds node internal, advertised, and plugin services.
|
||||
* Returns a list of tokenizable services to be added to the serialisation context.
|
||||
*/
|
||||
private fun makeServices(): MutableList<Any> {
|
||||
private fun makeServices(keyStoreWrapper: KeyStoreWrapper): MutableList<Any> {
|
||||
val keyStore = keyStoreWrapper.keyStore
|
||||
val storageServices = initialiseStorageService(configuration.baseDirectory)
|
||||
storage = storageServices.first
|
||||
checkpointStorage = storageServices.second
|
||||
@ -465,7 +467,9 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
auditService = DummyAuditService()
|
||||
|
||||
info = makeInfo()
|
||||
identity = makeIdentityService()
|
||||
identity = makeIdentityService(keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)!! as X509Certificate,
|
||||
keyStoreWrapper.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA),
|
||||
info.legalIdentityAndCert)
|
||||
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
||||
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
||||
// the identity key. But the infrastructure to make that easy isn't here yet.
|
||||
@ -701,10 +705,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
|
||||
protected abstract fun makeUniquenessProvider(type: ServiceType): UniquenessProvider
|
||||
|
||||
protected open fun makeIdentityService(): IdentityService {
|
||||
val keyStore = KeyStoreUtilities.loadKeyStore(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||
val trustRoot = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as? X509Certificate
|
||||
val service = InMemoryIdentityService(setOf(info.legalIdentityAndCert), trustRoot = trustRoot)
|
||||
protected open fun makeIdentityService(trustRoot: X509Certificate,
|
||||
clientCa: CertificateAndKeyPair?,
|
||||
legalIdentity: PartyAndCertificate): IdentityService {
|
||||
val caCertificates: Array<X509Certificate> = listOf(legalIdentity.certificate.cert, clientCa?.certificate?.cert)
|
||||
.filterNotNull()
|
||||
.toTypedArray()
|
||||
val service = InMemoryIdentityService(setOf(info.legalIdentityAndCert), trustRoot = trustRoot, caCertificates = *caCertificates)
|
||||
services.networkMapCache.partyNodes.forEach { service.registerIdentity(it.legalIdentityAndCert) }
|
||||
netMapCache.changed.subscribe { mapChange ->
|
||||
// TODO how should we handle network map removal
|
||||
|
@ -31,22 +31,30 @@ import kotlin.collections.ArrayList
|
||||
* @param certPaths initial set of certificate paths for the service, typically only used for unit tests.
|
||||
*/
|
||||
@ThreadSafe
|
||||
class InMemoryIdentityService(identities: Iterable<PartyAndCertificate>,
|
||||
class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptySet(),
|
||||
certPaths: Map<AnonymousParty, CertPath> = emptyMap(),
|
||||
val trustRoot: X509Certificate?) : SingletonSerializeAsToken(), IdentityService {
|
||||
override val trustRoot: X509Certificate,
|
||||
vararg caCertificates: X509Certificate) : SingletonSerializeAsToken(), IdentityService {
|
||||
constructor(identities: Iterable<PartyAndCertificate> = emptySet(),
|
||||
certPaths: Map<AnonymousParty, CertPath> = emptyMap(),
|
||||
trustRoot: X509CertificateHolder?) : this(identities, certPaths, trustRoot?.cert)
|
||||
trustRoot: X509CertificateHolder) : this(identities, certPaths, trustRoot.cert)
|
||||
companion object {
|
||||
private val log = loggerFor<InMemoryIdentityService>()
|
||||
}
|
||||
|
||||
private val trustAnchor: TrustAnchor? = trustRoot?.let { cert -> TrustAnchor(cert, null) }
|
||||
/**
|
||||
* Certificate store for certificate authority and intermediary certificates.
|
||||
*/
|
||||
override val caCertStore: CertStore
|
||||
override val trustRootHolder = X509CertificateHolder(trustRoot.encoded)
|
||||
private val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null)
|
||||
private val keyToParties = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
|
||||
private val principalToParties = ConcurrentHashMap<X500Name, PartyAndCertificate>()
|
||||
private val partyToPath = ConcurrentHashMap<AbstractParty, CertPath>()
|
||||
|
||||
init {
|
||||
val caCertificatesWithRoot: Set<X509Certificate> = caCertificates.toSet() + trustRoot
|
||||
caCertStore = CertStore.getInstance("Collection", CollectionCertStoreParameters(caCertificatesWithRoot))
|
||||
keyToParties.putAll(identities.associateBy { it.owningKey } )
|
||||
principalToParties.putAll(identities.associateBy { it.name })
|
||||
partyToPath.putAll(certPaths)
|
||||
@ -57,7 +65,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate>,
|
||||
override fun registerIdentity(party: PartyAndCertificate) {
|
||||
require(party.certPath.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
// Validate the chain first, before we do anything clever with it
|
||||
if (trustRoot != null) validateCertificatePath(party.party, party.certPath)
|
||||
validateCertificatePath(party.party, party.certPath)
|
||||
|
||||
log.trace { "Registering identity $party" }
|
||||
require(Arrays.equals(party.certificate.subjectPublicKeyInfo.encoded, party.owningKey.encoded)) { "Party certificate must end with party's public key" }
|
||||
@ -122,7 +130,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate>,
|
||||
val fullParty = certificateFromParty(party) ?: throw IllegalArgumentException("Unknown identity ${party.name}")
|
||||
require(path.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
// Validate the chain first, before we do anything clever with it
|
||||
if (trustRoot != null) validateCertificatePath(anonymousParty, path)
|
||||
validateCertificatePath(anonymousParty, path)
|
||||
val subjectCertificate = path.certificates.first()
|
||||
require(subjectCertificate is X509Certificate && subjectCertificate.subject == fullParty.name) { "Subject of the transaction certificate must match the well known identity" }
|
||||
|
||||
|
Reference in New Issue
Block a user