mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +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:
parent
1866f6ff7f
commit
14068f1b96
@ -2,13 +2,11 @@ package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.PartyAndReference
|
||||
import net.corda.core.identity.*
|
||||
import net.corda.core.node.NodeInfo
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.CertificateExpiredException
|
||||
import java.security.cert.CertificateNotYetValidException
|
||||
import java.security.cert.*
|
||||
|
||||
/**
|
||||
* An identity service maintains a directory of parties by their associated distinguished name/public keys and thus
|
||||
@ -16,6 +14,10 @@ import java.security.cert.CertificateNotYetValidException
|
||||
* identities back to the well known identity (i.e. the identity in the network map) of a party.
|
||||
*/
|
||||
interface IdentityService {
|
||||
val trustRoot: X509Certificate
|
||||
val trustRootHolder: X509CertificateHolder
|
||||
val caCertStore: CertStore
|
||||
|
||||
/**
|
||||
* Verify and then store a well known identity.
|
||||
*
|
||||
|
@ -73,6 +73,7 @@ object DefaultKryoCustomizer {
|
||||
|
||||
noReferencesWithin<WireTransaction>()
|
||||
|
||||
register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer)
|
||||
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
|
||||
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
|
||||
|
||||
|
@ -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" }
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"fixedLeg": {
|
||||
"fixedRatePayer": "8Kqd4oWdx4KQAVcA8RDJXNvzFMvBkqWTZPhRtg9dSpNs6T6eZ4cGJWA7FWK",
|
||||
"fixedRatePayer": "8Kqd4oWdx4KQGHGR7xcgpFf9JmP6HiXqTf85NpSgdSu431EGEhujA6ePaFD",
|
||||
"notional": "$25000000",
|
||||
"paymentFrequency": "SemiAnnual",
|
||||
"effectiveDate": "2016-03-11",
|
||||
@ -22,7 +22,7 @@
|
||||
"interestPeriodAdjustment": "Adjusted"
|
||||
},
|
||||
"floatingLeg": {
|
||||
"floatingRatePayer": "8Kqd4oWdx4KQAVc3Si48msuQrMJPGpA3TnGGuWTqXyoshjL25wzzdxtGyjq",
|
||||
"floatingRatePayer": "8Kqd4oWdx4KQGHGJSFTX4kdZukmHohBRN3gvPekticL4eHTdmbJTVZNZJUj",
|
||||
"notional": {
|
||||
"quantity": 2500000000,
|
||||
"token": "USD"
|
||||
|
@ -5,11 +5,7 @@ package net.corda.testing
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.crypto.commonName
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.node.ServiceHub
|
||||
@ -33,7 +29,6 @@ import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@ -89,7 +84,7 @@ val BIG_CORP_PARTY_REF = BIG_CORP.ref(OpaqueBytes.of(1)).reference
|
||||
val ALL_TEST_KEYS: List<KeyPair> get() = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
|
||||
|
||||
val MOCK_IDENTITIES = listOf(MEGA_CORP_IDENTITY, MINI_CORP_IDENTITY, DUMMY_NOTARY_IDENTITY)
|
||||
val MOCK_IDENTITY_SERVICE: IdentityService get() = InMemoryIdentityService(MOCK_IDENTITIES, emptyMap(), DUMMY_CA.certificate)
|
||||
val MOCK_IDENTITY_SERVICE: IdentityService get() = InMemoryIdentityService(MOCK_IDENTITIES, emptyMap(), DUMMY_CA.certificate.cert)
|
||||
|
||||
val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor")
|
||||
|
||||
|
@ -5,8 +5,9 @@ import com.google.common.jimfs.Jimfs
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.*
|
||||
import net.corda.core.crypto.CertificateAndKeyPair
|
||||
import net.corda.core.crypto.cert
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.flows.TxKeyFlow
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.messaging.MessageRecipients
|
||||
import net.corda.core.messaging.RPCOps
|
||||
@ -18,6 +19,7 @@ import net.corda.core.node.services.*
|
||||
import net.corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import net.corda.core.utilities.getTestPartyAndCertificate
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.flows.TxKeyFlow
|
||||
import net.corda.node.internal.AbstractNode
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
@ -166,9 +168,14 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
||||
.getOrThrow()
|
||||
}
|
||||
|
||||
// TODO: Specify a CA to validate registration against
|
||||
override fun makeIdentityService(): IdentityService {
|
||||
return InMemoryIdentityService((mockNet.identities + info.legalIdentityAndCert).toSet(), trustRoot = null as X509Certificate?)
|
||||
override fun makeIdentityService(trustRoot: X509Certificate,
|
||||
clientCa: CertificateAndKeyPair?,
|
||||
legalIdentity: PartyAndCertificate): IdentityService {
|
||||
val caCertificates: Array<X509Certificate> = listOf(legalIdentity.certificate.cert, clientCa?.certificate?.cert)
|
||||
.filterNotNull()
|
||||
.toTypedArray()
|
||||
return InMemoryIdentityService((mockNet.identities + info.legalIdentityAndCert).toSet(),
|
||||
trustRoot = trustRoot, caCertificates = *caCertificates)
|
||||
}
|
||||
|
||||
override fun makeVaultService(dataSourceProperties: Properties): VaultService = NodeVaultService(services, dataSourceProperties)
|
||||
|
@ -21,10 +21,10 @@ import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.node.utilities.transaction
|
||||
import net.corda.testing.MOCK_VERSION_INFO
|
||||
import net.corda.testing.freeLocalHostAndPort
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import java.io.Closeable
|
||||
import java.security.KeyPair
|
||||
import java.security.cert.X509Certificate
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
@ -33,7 +33,7 @@ import kotlin.concurrent.thread
|
||||
*/
|
||||
class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeLocalHostAndPort(),
|
||||
rpcAddress: HostAndPort = freeLocalHostAndPort(),
|
||||
trustRoot: X509CertificateHolder? = null) : AutoCloseable {
|
||||
trustRoot: X509Certificate) : AutoCloseable {
|
||||
|
||||
private val databaseWithCloseable: Pair<Closeable, Database> = configureDatabase(config.dataSourceProperties)
|
||||
val database: Database get() = databaseWithCloseable.second
|
||||
|
Loading…
Reference in New Issue
Block a user