mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +00:00
Merge commit '22af527b7943c0c4376af582de6505893817111a' into andr3ej-ross-merge
# Conflicts: # docs/source/changelog.rst
This commit is contained in:
commit
a652c8b19f
@ -79,20 +79,19 @@ class SwapIdentitiesFlowTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `verifies signature`() {
|
fun `verifies signature`() {
|
||||||
// Set up values we'll need
|
// Set up values we'll need
|
||||||
val notaryNode = mockNet.defaultNotaryNode
|
|
||||||
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
val aliceNode = mockNet.createPartyNode(ALICE_NAME)
|
||||||
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
val bobNode = mockNet.createPartyNode(BOB_NAME)
|
||||||
val alice: PartyAndCertificate = aliceNode.info.singleIdentityAndCert()
|
val alice: PartyAndCertificate = aliceNode.info.singleIdentityAndCert()
|
||||||
val bob: PartyAndCertificate = bobNode.info.singleIdentityAndCert()
|
val bob: PartyAndCertificate = bobNode.info.singleIdentityAndCert()
|
||||||
val notary: PartyAndCertificate = mockNet.defaultNotaryIdentityAndCert
|
// Check that the right name but wrong key is rejected
|
||||||
// Check that the wrong signature is rejected
|
val evilBobNode = mockNet.createPartyNode(BOB_NAME)
|
||||||
notaryNode.database.transaction {
|
val evilBob = evilBobNode.info.singleIdentityAndCert()
|
||||||
notaryNode.services.keyManagementService.freshKeyAndCert(notary, false)
|
evilBobNode.database.transaction {
|
||||||
}.let { anonymousNotary ->
|
val anonymousEvilBob = evilBobNode.services.keyManagementService.freshKeyAndCert(evilBob, false)
|
||||||
val sigData = SwapIdentitiesFlow.buildDataToSign(anonymousNotary)
|
val sigData = SwapIdentitiesFlow.buildDataToSign(evilBob)
|
||||||
val signature = notaryNode.services.keyManagementService.sign(sigData, anonymousNotary.owningKey)
|
val signature = evilBobNode.services.keyManagementService.sign(sigData, anonymousEvilBob.owningKey)
|
||||||
assertFailsWith<SwapIdentitiesException>("Signature does not match the given identity and nonce") {
|
assertFailsWith<SwapIdentitiesException>("Signature does not match the given identity and nonce") {
|
||||||
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob.party, anonymousNotary, signature.withoutKey())
|
SwapIdentitiesFlow.validateAndRegisterIdentity(aliceNode.services.identityService, bob.party, anonymousEvilBob, signature.withoutKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check that the right signing key, but wrong identity is rejected
|
// Check that the right signing key, but wrong identity is rejected
|
||||||
|
17
core/src/main/kotlin/net/corda/core/CordaOID.kt
Normal file
17
core/src/main/kotlin/net/corda/core/CordaOID.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package net.corda.core
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OIDs used for the Corda platform. Entries MUST NOT be removed from this file; if an OID is incorrectly assigned it
|
||||||
|
* should be marked deprecated.
|
||||||
|
*/
|
||||||
|
object CordaOID {
|
||||||
|
/** Assigned to R3, see http://www.oid-info.com/cgi-bin/display?oid=1.3.6.1.4.1.50530&action=display */
|
||||||
|
const val R3_ROOT = "1.3.6.1.4.1.50530"
|
||||||
|
/** OIDs issued for the Corda platform */
|
||||||
|
const val CORDA_PLATFORM = R3_ROOT + ".1"
|
||||||
|
/**
|
||||||
|
* Identifier for the X.509 certificate extension specifying the Corda role. See
|
||||||
|
* https://r3-cev.atlassian.net/wiki/spaces/AWG/pages/156860572/Certificate+identity+type+extension for details.
|
||||||
|
*/
|
||||||
|
const val X509_EXTENSION_CORDA_ROLE = CORDA_PLATFORM + ".1"
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.core.identity
|
package net.corda.core.identity
|
||||||
|
|
||||||
|
import net.corda.core.internal.CertRole
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.cert.*
|
import java.security.cert.*
|
||||||
@ -19,6 +20,8 @@ class PartyAndCertificate(val certPath: CertPath) {
|
|||||||
val certs = certPath.certificates
|
val certs = certPath.certificates
|
||||||
require(certs.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
|
require(certs.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
|
||||||
certificate = certs[0] as X509Certificate
|
certificate = certs[0] as X509Certificate
|
||||||
|
val role = CertRole.extract(certificate)
|
||||||
|
require(role?.isIdentity ?: false) { "Party certificate ${certificate.subjectDN} does not have a well known or confidential identity role. Found: $role" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
@ -38,6 +41,24 @@ class PartyAndCertificate(val certPath: CertPath) {
|
|||||||
fun verify(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult {
|
fun verify(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult {
|
||||||
val parameters = PKIXParameters(setOf(trustAnchor)).apply { isRevocationEnabled = false }
|
val parameters = PKIXParameters(setOf(trustAnchor)).apply { isRevocationEnabled = false }
|
||||||
val validator = CertPathValidator.getInstance("PKIX")
|
val validator = CertPathValidator.getInstance("PKIX")
|
||||||
return validator.validate(certPath, parameters) as PKIXCertPathValidatorResult
|
val result = validator.validate(certPath, parameters) as PKIXCertPathValidatorResult
|
||||||
|
// Apply Corda-specific validity rules to the chain. This only applies to chains with any roles present, so
|
||||||
|
// an all-null chain is in theory valid.
|
||||||
|
var parentRole: CertRole? = CertRole.extract(result.trustAnchor.trustedCert)
|
||||||
|
for (certIdx in (0 until certPath.certificates.size).reversed()) {
|
||||||
|
val certificate = certPath.certificates[certIdx]
|
||||||
|
val role = CertRole.extract(certificate)
|
||||||
|
if (parentRole != null) {
|
||||||
|
if (role == null) {
|
||||||
|
throw CertPathValidatorException("Child certificate whose issuer includes a Corda role, must also specify Corda role")
|
||||||
|
}
|
||||||
|
if (!role.isValidParent(parentRole)) {
|
||||||
|
val certificateString = (certificate as? X509Certificate)?.subjectDN?.toString() ?: certificate.toString()
|
||||||
|
throw CertPathValidatorException("The issuing certificate for $certificateString has role $parentRole, expected one of ${role.validParents}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parentRole = role
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
109
core/src/main/kotlin/net/corda/core/internal/CertRole.kt
Normal file
109
core/src/main/kotlin/net/corda/core/internal/CertRole.kt
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import net.corda.core.CordaOID
|
||||||
|
import org.bouncycastle.asn1.ASN1Encodable
|
||||||
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
|
import org.bouncycastle.asn1.ASN1Primitive
|
||||||
|
import org.bouncycastle.asn1.DEROctetString
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.cert.Certificate
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the Corda role a certificate is used for. This is used both to verify the hierarchy of certificates is
|
||||||
|
* correct, and to determine which is the well known identity's certificate.
|
||||||
|
*
|
||||||
|
* @property validParents the parent role of this role - must match exactly for the certificate hierarchy to be valid for use
|
||||||
|
* in Corda. Null indicates the parent certificate must have no role (the extension must be absent).
|
||||||
|
* @property isIdentity true if the role is valid for use as a [net.corda.core.identity.Party] identity, false otherwise (the role is Corda
|
||||||
|
* infrastructure of some kind).
|
||||||
|
* @property isWellKnown true if the role is a well known identity type (legal entity or service). This only makes sense
|
||||||
|
* where [isIdentity] is true.
|
||||||
|
*/
|
||||||
|
// NOTE: The order of the entries in the enum MUST NOT be changed, as their ordinality is used as an identifier. Please
|
||||||
|
// also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications.
|
||||||
|
// TODO: Link to the specification once it has a permanent URL
|
||||||
|
enum class CertRole(val validParents: Set<CertRole?>, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable {
|
||||||
|
/**
|
||||||
|
* Intermediate CA (Doorman service).
|
||||||
|
*/
|
||||||
|
INTERMEDIATE_CA(setOf(null), false, false),
|
||||||
|
/** Signing certificate for the network map. */
|
||||||
|
NETWORK_MAP(setOf(null), false, false),
|
||||||
|
/** Well known (publicly visible) identity of a service (such as notary). */
|
||||||
|
SERVICE_IDENTITY(setOf(INTERMEDIATE_CA), true, true),
|
||||||
|
/** Node level CA from which the TLS and well known identity certificates are issued. */
|
||||||
|
NODE_CA(setOf(INTERMEDIATE_CA), false, false),
|
||||||
|
/** Transport layer security certificate for a node. */
|
||||||
|
TLS(setOf(NODE_CA), false, false),
|
||||||
|
/** Well known (publicly visible) identity of a legal entity. */
|
||||||
|
LEGAL_IDENTITY(setOf(INTERMEDIATE_CA, NODE_CA), true, true),
|
||||||
|
/** Confidential (limited visibility) identity of a legal entity. */
|
||||||
|
CONFIDENTIAL_LEGAL_IDENTITY(setOf(LEGAL_IDENTITY), true, false);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var cachedRoles: Array<CertRole>? = null
|
||||||
|
/**
|
||||||
|
* Get a role from its ASN.1 encoded form.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if the encoded data is not a valid role.
|
||||||
|
*/
|
||||||
|
fun getInstance(id: ASN1Integer): CertRole {
|
||||||
|
if (cachedRoles == null) {
|
||||||
|
cachedRoles = CertRole.values()
|
||||||
|
}
|
||||||
|
val idVal = id.value
|
||||||
|
require(idVal.compareTo(BigInteger.ZERO) > 0) { "Invalid role ID" }
|
||||||
|
return try {
|
||||||
|
val ordinal = idVal.intValueExact() - 1
|
||||||
|
cachedRoles!![ordinal]
|
||||||
|
} catch (ex: ArithmeticException) {
|
||||||
|
throw IllegalArgumentException("Invalid role ID")
|
||||||
|
} catch (ex: ArrayIndexOutOfBoundsException) {
|
||||||
|
throw IllegalArgumentException("Invalid role ID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a role from its ASN.1 encoded form.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if the encoded data is not a valid role.
|
||||||
|
*/
|
||||||
|
fun getInstance(data: ByteArray): CertRole = getInstance(ASN1Integer.getInstance(data))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a role from a certificate.
|
||||||
|
*
|
||||||
|
* @return the role if the extension is present, or null otherwise.
|
||||||
|
* @throws IllegalArgumentException if the extension is present but is invalid.
|
||||||
|
*/
|
||||||
|
fun extract(cert: Certificate): CertRole? {
|
||||||
|
val x509Cert = cert as? X509Certificate
|
||||||
|
return if (x509Cert != null) {
|
||||||
|
extract(x509Cert)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a role from a certificate.
|
||||||
|
*
|
||||||
|
* @return the role if the extension is present, or null otherwise.
|
||||||
|
* @throws IllegalArgumentException if the extension is present but is invalid.
|
||||||
|
*/
|
||||||
|
fun extract(cert: X509Certificate): CertRole? {
|
||||||
|
val extensionData: ByteArray? = cert.getExtensionValue(CordaOID.X509_EXTENSION_CORDA_ROLE)
|
||||||
|
return if (extensionData != null) {
|
||||||
|
val extensionString = DEROctetString.getInstance(extensionData)
|
||||||
|
getInstance(extensionString.octets)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isValidParent(parent: CertRole?): Boolean = parent in validParents
|
||||||
|
|
||||||
|
override fun toASN1Primitive(): ASN1Primitive = ASN1Integer(this.ordinal + 1L)
|
||||||
|
}
|
@ -337,7 +337,7 @@ class CompositeKeyTests {
|
|||||||
val ca = X509Utilities.createSelfSignedCACertificate(caName, caKeyPair)
|
val ca = X509Utilities.createSelfSignedCACertificate(caName, caKeyPair)
|
||||||
|
|
||||||
// Sign the composite key with the self sign CA.
|
// Sign the composite key with the self sign CA.
|
||||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, ca, caKeyPair, caName.copy(commonName = "CompositeKey"), compositeKey)
|
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, ca, caKeyPair, caName.copy(commonName = "CompositeKey"), compositeKey)
|
||||||
|
|
||||||
// Store certificate to keystore.
|
// Store certificate to keystore.
|
||||||
val keystorePath = tempFolder.root.toPath() / "keystore.jks"
|
val keystorePath = tempFolder.root.toPath() / "keystore.jks"
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package net.corda.core.identity
|
package net.corda.core.identity
|
||||||
|
|
||||||
import net.corda.core.crypto.entropyToKeyPair
|
import net.corda.core.crypto.entropyToKeyPair
|
||||||
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.internal.read
|
import net.corda.core.internal.read
|
||||||
import net.corda.core.serialization.deserialize
|
import net.corda.core.serialization.deserialize
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.nodeapi.internal.crypto.KEYSTORE_TYPE
|
import net.corda.nodeapi.internal.crypto.KEYSTORE_TYPE
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||||
import net.corda.nodeapi.internal.crypto.save
|
import net.corda.nodeapi.internal.crypto.save
|
||||||
|
import net.corda.testing.DEV_CA
|
||||||
import net.corda.testing.SerializationEnvironmentRule
|
import net.corda.testing.SerializationEnvironmentRule
|
||||||
import net.corda.testing.getTestPartyAndCertificate
|
import net.corda.testing.getTestPartyAndCertificate
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
@ -14,12 +17,19 @@ import org.junit.Test
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
class PartyAndCertificateTest {
|
class PartyAndCertificateTest {
|
||||||
@Rule
|
@Rule
|
||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule()
|
val testSerialization = SerializationEnvironmentRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should reject a path with no roles`() {
|
||||||
|
val path = X509CertificateFactory().generateCertPath(DEV_CA.certificate.cert)
|
||||||
|
assertFailsWith<IllegalArgumentException> { PartyAndCertificate(path) }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `kryo serialisation`() {
|
fun `kryo serialisation`() {
|
||||||
val original = getTestPartyAndCertificate(Party(
|
val original = getTestPartyAndCertificate(Party(
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package net.corda.core.internal
|
||||||
|
|
||||||
|
import org.bouncycastle.asn1.ASN1Integer
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
class CertRoleTests {
|
||||||
|
@Test
|
||||||
|
fun `should deserialize valid value`() {
|
||||||
|
val expected = CertRole.INTERMEDIATE_CA
|
||||||
|
val actual = CertRole.getInstance(ASN1Integer(1L))
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should reject invalid values`() {
|
||||||
|
// Below the lowest used value
|
||||||
|
assertFailsWith<IllegalArgumentException> { CertRole.getInstance(ASN1Integer(0L)) }
|
||||||
|
// Outside of the array size, but a valid integer
|
||||||
|
assertFailsWith<IllegalArgumentException> { CertRole.getInstance(ASN1Integer(Integer.MAX_VALUE - 1L)) }
|
||||||
|
// Outside of the range of integers
|
||||||
|
assertFailsWith<IllegalArgumentException> { CertRole.getInstance(ASN1Integer(Integer.MAX_VALUE + 1L)) }
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,15 @@ from the previous milestone release.
|
|||||||
|
|
||||||
UNRELEASED
|
UNRELEASED
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
* X.509 certificates now have an extension that specifies the Corda role the certificate is used for, and the role
|
||||||
|
hierarchy is now enforced in the validation code. See ``net.corda.core.internal.CertRole`` for the current implementation
|
||||||
|
until final documentation is prepared. Certificates at ``NODE_CA``, ``WELL_KNOWN_SERVICE_IDENTITY`` and above must
|
||||||
|
only ever by issued by network services and therefore issuance constraints are not relevant to end users.
|
||||||
|
The ``TLS``, ``WELL_KNOWN_LEGAL_IDENTITY`` roles must be issued by the ``NODE_CA`` certificate issued by the
|
||||||
|
Doorman, and ``CONFIDENTIAL_IDENTITY`` certificates must be issued from a ``WELL_KNOWN_LEGAL_IDENTITY`` certificate.
|
||||||
|
For a detailed specification of the extension please see :doc:`permissioning-certificate-specification`.
|
||||||
|
|
||||||
* The network map service concept has been re-designed. More information can be found in :doc:`network-map`.
|
* The network map service concept has been re-designed. More information can be found in :doc:`network-map`.
|
||||||
|
|
||||||
* The previous design was never intended to be final but was rather a quick implementation in the earliest days of the
|
* The previous design was never intended to be final but was rather a quick implementation in the earliest days of the
|
||||||
|
@ -8,3 +8,4 @@ Corda networks
|
|||||||
permissioning
|
permissioning
|
||||||
network-map
|
network-map
|
||||||
versioning
|
versioning
|
||||||
|
permissioning-certificate-spec
|
||||||
|
43
docs/source/permissioning-certificate-specification.rst
Normal file
43
docs/source/permissioning-certificate-specification.rst
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
Network Permissioning - Certificate Specification
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
Certificates used by Corda have additional constraints in their contents and hierarchical structure. In a typical
|
||||||
|
installation node administrators should not need to be aware of these, however in some cases node certificates may
|
||||||
|
be managed by external tools (such as an existing PKI solution deployed within an organisation), in which case it is
|
||||||
|
important to understand these constraints.
|
||||||
|
|
||||||
|
There are a number of roles in Corda that certificates are used for:
|
||||||
|
|
||||||
|
* Doorman (Intermediate CA)
|
||||||
|
* Well known service identity (network map and notary)
|
||||||
|
* Node CA
|
||||||
|
* TLS
|
||||||
|
* Well known legal identity
|
||||||
|
* Confidential legal identity
|
||||||
|
|
||||||
|
Extension
|
||||||
|
---------
|
||||||
|
|
||||||
|
The Corda role that a certificate relates to is specified by custom X.509 v3 extension. This extension has OID 1.3.6.1.4.1.50530.1.1
|
||||||
|
and is non-critical, as it is safe for implementations outside of Corda nodes to ignore the extension. The extension
|
||||||
|
contains a single ASN.1 integer identifying the type of identity the certificate is for:
|
||||||
|
|
||||||
|
1. Doorman
|
||||||
|
2. Well known service identity
|
||||||
|
3. Node CA
|
||||||
|
4. TLS
|
||||||
|
5. Well known legal identity
|
||||||
|
6. Confidential legal identity
|
||||||
|
|
||||||
|
Hierarchy
|
||||||
|
---------
|
||||||
|
|
||||||
|
Certificate path validation is extended to enforce that the extension must be present where its issuer's certificate included the extension, and that:
|
||||||
|
|
||||||
|
* Doorman certificates are issued by a certificate without the extension present
|
||||||
|
* Well known service identity certificates are issued by a certificate marked as Doorman
|
||||||
|
* Node CA certificates are issued by a certificate marked as Doorman
|
||||||
|
* Well known legal identity/TLS certificates are issued by a certificate marked as node CA
|
||||||
|
* Confidential legal identity certificates are issued by a certificate marked as well known legal identity
|
||||||
|
* Party certificates are marked as either a well known identity or a confidential identity
|
||||||
|
* The structure of certificates above Doorman/Network map is intentionally left untouched, as they are not relevant to the identity service and therefore there is no advantage in enforcing a specific structure on those certificates. The certificate hierarchy consistency checks are required because nodes can issue their own certificates and can set their own role flags on certificates, and it's important to verify that these are set consistently with the certificate hierarchy design. As as side-effect this also acts as a secondary depth restriction on issued certificates.
|
@ -5,6 +5,10 @@ Here are release notes for each snapshot release from M9 onwards.
|
|||||||
|
|
||||||
Unreleased
|
Unreleased
|
||||||
----------
|
----------
|
||||||
|
* X.509 certificates now have an extension that specifies the Corda role the certificate is used for, and the role
|
||||||
|
hierarchy is now enforced in the validation code. This only has impact on those developing integrations with external
|
||||||
|
PKI solutions, in most cases it is managed transparently by Corda. A formal specification of the extension can be
|
||||||
|
found at see :doc:`permissioning-certificate-specification`.
|
||||||
|
|
||||||
* **Enum Class Evolution**
|
* **Enum Class Evolution**
|
||||||
With the addition of AMQP serialization Corda now supports enum constant evolution.
|
With the addition of AMQP serialization Corda now supports enum constant evolution.
|
||||||
|
@ -37,16 +37,16 @@ object ServiceIdentityGenerator {
|
|||||||
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
val notaryKey = CompositeKey.Builder().addKeys(keyPairs.map { it.public }).build(threshold)
|
||||||
|
|
||||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||||
val issuer = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||||
val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
val rootCert = customRootCert ?: caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||||
|
|
||||||
keyPairs.zip(dirs) { keyPair, dir ->
|
keyPairs.zip(dirs) { keyPair, dir ->
|
||||||
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, keyPair.public)
|
val serviceKeyCert = X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, serviceName, keyPair.public)
|
||||||
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.NODE_CA, issuer.certificate, issuer.keyPair, serviceName, notaryKey)
|
val compositeKeyCert = X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, serviceName, notaryKey)
|
||||||
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
|
val certPath = (dir / "certificates").createDirectories() / "distributedService.jks"
|
||||||
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
|
val keystore = loadOrCreateKeyStore(certPath, "cordacadevpass")
|
||||||
keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert)
|
keystore.setCertificateEntry("$serviceId-composite-key", compositeKeyCert.cert)
|
||||||
keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, issuer.certificate.cert, rootCert))
|
keystore.setKeyEntry("$serviceId-private-key", keyPair.private, "cordacadevkeypass".toCharArray(), arrayOf(serviceKeyCert.cert, intermediateCa.certificate.cert, rootCert))
|
||||||
keystore.save(certPath, "cordacadevpass")
|
keystore.save(certPath, "cordacadevpass")
|
||||||
}
|
}
|
||||||
return Party(serviceName, notaryKey)
|
return Party(serviceName, notaryKey)
|
||||||
|
@ -17,7 +17,7 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St
|
|||||||
// Assume key password = store password.
|
// Assume key password = store password.
|
||||||
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
val clientCA = certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||||
// Create new keys and store in keystore.
|
// Create new keys and store in keystore.
|
||||||
val cert = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
|
val cert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, pubKey)
|
||||||
val certPath = X509CertificateFactory().generateCertPath(cert.cert, *clientCertPath)
|
val certPath = X509CertificateFactory().generateCertPath(cert.cert, *clientCertPath)
|
||||||
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
|
require(certPath.certificates.isNotEmpty()) { "Certificate path cannot be empty" }
|
||||||
// TODO: X509Utilities.validateCertificateChain()
|
// TODO: X509Utilities.validateCertificateChain()
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
package net.corda.nodeapi.internal.crypto
|
package net.corda.nodeapi.internal.crypto
|
||||||
|
|
||||||
|
import net.corda.core.CordaOID
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SignatureScheme
|
import net.corda.core.crypto.SignatureScheme
|
||||||
import net.corda.core.crypto.random63BitValue
|
import net.corda.core.crypto.random63BitValue
|
||||||
|
import net.corda.core.internal.CertRole
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.cert
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.internal.read
|
import net.corda.core.internal.read
|
||||||
import net.corda.core.internal.x500Name
|
import net.corda.core.internal.x500Name
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.millis
|
import net.corda.core.utilities.millis
|
||||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
import org.bouncycastle.asn1.*
|
||||||
import org.bouncycastle.asn1.ASN1Sequence
|
|
||||||
import org.bouncycastle.asn1.DERSequence
|
|
||||||
import org.bouncycastle.asn1.DERUTF8String
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||||
import org.bouncycastle.asn1.x509.*
|
import org.bouncycastle.asn1.x509.*
|
||||||
@ -221,6 +220,7 @@ object X509Utilities {
|
|||||||
val serial = BigInteger.valueOf(random63BitValue())
|
val serial = BigInteger.valueOf(random63BitValue())
|
||||||
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
|
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
|
||||||
val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded))
|
val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded))
|
||||||
|
val role = certificateType.role
|
||||||
|
|
||||||
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second,
|
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second,
|
||||||
subject, subjectPublicKey)
|
subject, subjectPublicKey)
|
||||||
@ -229,9 +229,13 @@ object X509Utilities {
|
|||||||
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
|
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
|
||||||
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
|
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
|
||||||
|
|
||||||
|
if (role != null) {
|
||||||
|
builder.addExtension(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), false, role)
|
||||||
|
}
|
||||||
if (nameConstraints != null) {
|
if (nameConstraints != null) {
|
||||||
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
|
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,13 +326,14 @@ class X509CertificateFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
|
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean, val role: CertRole?) {
|
||||||
ROOT_CA(
|
ROOT_CA(
|
||||||
KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign),
|
KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign),
|
||||||
KeyPurposeId.id_kp_serverAuth,
|
KeyPurposeId.id_kp_serverAuth,
|
||||||
KeyPurposeId.id_kp_clientAuth,
|
KeyPurposeId.id_kp_clientAuth,
|
||||||
KeyPurposeId.anyExtendedKeyUsage,
|
KeyPurposeId.anyExtendedKeyUsage,
|
||||||
isCA = true
|
isCA = true,
|
||||||
|
role = null
|
||||||
),
|
),
|
||||||
|
|
||||||
INTERMEDIATE_CA(
|
INTERMEDIATE_CA(
|
||||||
@ -336,7 +341,26 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo
|
|||||||
KeyPurposeId.id_kp_serverAuth,
|
KeyPurposeId.id_kp_serverAuth,
|
||||||
KeyPurposeId.id_kp_clientAuth,
|
KeyPurposeId.id_kp_clientAuth,
|
||||||
KeyPurposeId.anyExtendedKeyUsage,
|
KeyPurposeId.anyExtendedKeyUsage,
|
||||||
isCA = true
|
isCA = true,
|
||||||
|
role = CertRole.INTERMEDIATE_CA
|
||||||
|
),
|
||||||
|
|
||||||
|
NETWORK_MAP(
|
||||||
|
KeyUsage(KeyUsage.digitalSignature),
|
||||||
|
KeyPurposeId.id_kp_serverAuth,
|
||||||
|
KeyPurposeId.id_kp_clientAuth,
|
||||||
|
KeyPurposeId.anyExtendedKeyUsage,
|
||||||
|
isCA = false,
|
||||||
|
role = CertRole.NETWORK_MAP
|
||||||
|
),
|
||||||
|
|
||||||
|
SERVICE_IDENTITY(
|
||||||
|
KeyUsage(KeyUsage.digitalSignature),
|
||||||
|
KeyPurposeId.id_kp_serverAuth,
|
||||||
|
KeyPurposeId.id_kp_clientAuth,
|
||||||
|
KeyPurposeId.anyExtendedKeyUsage,
|
||||||
|
isCA = false,
|
||||||
|
role = CertRole.SERVICE_IDENTITY
|
||||||
),
|
),
|
||||||
|
|
||||||
NODE_CA(
|
NODE_CA(
|
||||||
@ -344,7 +368,8 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo
|
|||||||
KeyPurposeId.id_kp_serverAuth,
|
KeyPurposeId.id_kp_serverAuth,
|
||||||
KeyPurposeId.id_kp_clientAuth,
|
KeyPurposeId.id_kp_clientAuth,
|
||||||
KeyPurposeId.anyExtendedKeyUsage,
|
KeyPurposeId.anyExtendedKeyUsage,
|
||||||
isCA = true
|
isCA = true,
|
||||||
|
role = CertRole.NODE_CA
|
||||||
),
|
),
|
||||||
|
|
||||||
TLS(
|
TLS(
|
||||||
@ -352,24 +377,27 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo
|
|||||||
KeyPurposeId.id_kp_serverAuth,
|
KeyPurposeId.id_kp_serverAuth,
|
||||||
KeyPurposeId.id_kp_clientAuth,
|
KeyPurposeId.id_kp_clientAuth,
|
||||||
KeyPurposeId.anyExtendedKeyUsage,
|
KeyPurposeId.anyExtendedKeyUsage,
|
||||||
isCA = false
|
isCA = false,
|
||||||
|
role = CertRole.TLS
|
||||||
),
|
),
|
||||||
|
|
||||||
// TODO: Identity certs should have only limited depth (i.e. 1) CA signing capability, with tight name constraints
|
// TODO: Identity certs should have tight name constraints on child certificates
|
||||||
WELL_KNOWN_IDENTITY(
|
LEGAL_IDENTITY(
|
||||||
KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign),
|
KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign),
|
||||||
KeyPurposeId.id_kp_serverAuth,
|
KeyPurposeId.id_kp_serverAuth,
|
||||||
KeyPurposeId.id_kp_clientAuth,
|
KeyPurposeId.id_kp_clientAuth,
|
||||||
KeyPurposeId.anyExtendedKeyUsage,
|
KeyPurposeId.anyExtendedKeyUsage,
|
||||||
isCA = true
|
isCA = true,
|
||||||
|
role = CertRole.LEGAL_IDENTITY
|
||||||
),
|
),
|
||||||
|
|
||||||
CONFIDENTIAL_IDENTITY(
|
CONFIDENTIAL_LEGAL_IDENTITY(
|
||||||
KeyUsage(KeyUsage.digitalSignature),
|
KeyUsage(KeyUsage.digitalSignature),
|
||||||
KeyPurposeId.id_kp_serverAuth,
|
KeyPurposeId.id_kp_serverAuth,
|
||||||
KeyPurposeId.id_kp_clientAuth,
|
KeyPurposeId.id_kp_clientAuth,
|
||||||
KeyPurposeId.anyExtendedKeyUsage,
|
KeyPurposeId.anyExtendedKeyUsage,
|
||||||
isCA = false
|
isCA = false,
|
||||||
|
role = CertRole.CONFIDENTIAL_LEGAL_IDENTITY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.node
|
package net.corda.node
|
||||||
|
|
||||||
|
import com.typesafe.config.ConfigException
|
||||||
import joptsimple.OptionParser
|
import joptsimple.OptionParser
|
||||||
import joptsimple.util.EnumConverter
|
import joptsimple.util.EnumConverter
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
|
@ -38,9 +38,12 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
* @return true if the node startup was successful. This value is intended to be the exit code of the process.
|
* @return true if the node startup was successful. This value is intended to be the exit code of the process.
|
||||||
*/
|
*/
|
||||||
open fun run(): Boolean {
|
open fun run(): Boolean {
|
||||||
try {
|
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
assertCanNormalizeEmptyPath()
|
if (!canNormalizeEmptyPath()) {
|
||||||
|
println("You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest version.")
|
||||||
|
println("Corda will now exit...")
|
||||||
|
return false
|
||||||
|
}
|
||||||
val (argsParser, cmdlineOptions) = parseArguments()
|
val (argsParser, cmdlineOptions) = parseArguments()
|
||||||
|
|
||||||
// We do the single node check before we initialise logging so that in case of a double-node start it
|
// We do the single node check before we initialise logging so that in case of a double-node start it
|
||||||
@ -66,9 +69,9 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
|
|
||||||
drawBanner(versionInfo)
|
drawBanner(versionInfo)
|
||||||
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
||||||
|
val conf = try {
|
||||||
val conf0 = loadConfigFile(cmdlineOptions)
|
val conf0 = loadConfigFile(cmdlineOptions)
|
||||||
|
if (cmdlineOptions.bootstrapRaftCluster) {
|
||||||
val conf = if (cmdlineOptions.bootstrapRaftCluster) {
|
|
||||||
if (conf0 is NodeConfigurationImpl) {
|
if (conf0 is NodeConfigurationImpl) {
|
||||||
println("Bootstrapping raft cluster (starting up as seed node).")
|
println("Bootstrapping raft cluster (starting up as seed node).")
|
||||||
// Ignore the configured clusterAddresses to make the node bootstrap a cluster instead of joining.
|
// Ignore the configured clusterAddresses to make the node bootstrap a cluster instead of joining.
|
||||||
@ -80,7 +83,12 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
} else {
|
} else {
|
||||||
conf0
|
conf0
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("Exception during node configuration", e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
banJavaSerialisation(conf)
|
banJavaSerialisation(conf)
|
||||||
preNetworkRegistration(conf)
|
preNetworkRegistration(conf)
|
||||||
if (shouldRegisterWithNetwork(cmdlineOptions, conf)) {
|
if (shouldRegisterWithNetwork(cmdlineOptions, conf)) {
|
||||||
@ -88,6 +96,10 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
logStartupInfo(versionInfo, cmdlineOptions, conf)
|
logStartupInfo(versionInfo, cmdlineOptions, conf)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("Exception during node registration", e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cmdlineOptions.baseDirectory.createDirectories()
|
cmdlineOptions.baseDirectory.createDirectories()
|
||||||
@ -104,9 +116,6 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
|
|
||||||
logger.info("Node exiting successfully")
|
logger.info("Node exiting successfully")
|
||||||
return true
|
return true
|
||||||
} catch (e: Exception) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open protected fun preNetworkRegistration(conf: NodeConfiguration) = Unit
|
open protected fun preNetworkRegistration(conf: NodeConfiguration) = Unit
|
||||||
@ -190,14 +199,7 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
|
NetworkRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL)).buildKeystore()
|
||||||
}
|
}
|
||||||
|
|
||||||
open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration {
|
open protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): NodeConfiguration = cmdlineOptions.loadConfig()
|
||||||
try {
|
|
||||||
return cmdlineOptions.loadConfig()
|
|
||||||
} catch (configException: ConfigException) {
|
|
||||||
println("Unable to load the configuration file: ${configException.rootCause.message}")
|
|
||||||
throw configException
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open protected fun banJavaSerialisation(conf: NodeConfiguration) {
|
open protected fun banJavaSerialisation(conf: NodeConfiguration) {
|
||||||
SerialFilter.install(if (conf.notary?.bftSMaRt != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
|
SerialFilter.install(if (conf.notary?.bftSMaRt != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
|
||||||
@ -289,12 +291,13 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
return hostName
|
return hostName
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertCanNormalizeEmptyPath() {
|
private fun canNormalizeEmptyPath(): Boolean {
|
||||||
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
|
// Check we're not running a version of Java with a known bug: https://github.com/corda/corda/issues/83
|
||||||
try {
|
return try {
|
||||||
Paths.get("").normalize()
|
Paths.get("").normalize()
|
||||||
|
true
|
||||||
} catch (e: ArrayIndexOutOfBoundsException) {
|
} catch (e: ArrayIndexOutOfBoundsException) {
|
||||||
Node.failStartUp("You are using a version of Java that is not supported (${System.getProperty("java.version")}). Please upgrade to the latest version.")
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ fun createKeystoreForCordaNode(sslKeyStorePath: Path,
|
|||||||
val clientName = legalName.copy(commonName = null)
|
val clientName = legalName.copy(commonName = null)
|
||||||
|
|
||||||
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, clientName.x500Name))), arrayOf())
|
val nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, clientName.x500Name))), arrayOf())
|
||||||
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA,
|
val clientCACert = X509Utilities.createCertificate(CertificateType.NODE_CA,
|
||||||
intermediateCACert,
|
intermediateCACert,
|
||||||
intermediateCAKeyPair,
|
intermediateCAKeyPair,
|
||||||
clientName.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN),
|
clientName.copy(commonName = X509Utilities.CORDA_CLIENT_CA_CN),
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.node.services.identity
|
|||||||
import net.corda.core.contracts.PartyAndReference
|
import net.corda.core.contracts.PartyAndReference
|
||||||
import net.corda.core.crypto.toStringShort
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.identity.*
|
import net.corda.core.identity.*
|
||||||
|
import net.corda.core.internal.CertRole
|
||||||
import net.corda.core.internal.cert
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.internal.toX509CertHolder
|
import net.corda.core.internal.toX509CertHolder
|
||||||
import net.corda.core.node.services.UnknownAnonymousPartyException
|
import net.corda.core.node.services.UnknownAnonymousPartyException
|
||||||
@ -61,14 +62,10 @@ class InMemoryIdentityService(identities: Array<out PartyAndCertificate>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we record the first identity of the same name, first
|
// Ensure we record the first identity of the same name, first
|
||||||
val identityPrincipal = identity.name.x500Principal
|
val wellKnownCert: Certificate = identity.certPath.certificates.single { CertRole.extract(it)?.isWellKnown ?: false }
|
||||||
val firstCertWithThisName: Certificate = identity.certPath.certificates.last { it ->
|
if (wellKnownCert != identity.certificate) {
|
||||||
val principal = (it as? X509Certificate)?.subjectX500Principal
|
|
||||||
principal == identityPrincipal
|
|
||||||
}
|
|
||||||
if (firstCertWithThisName != identity.certificate) {
|
|
||||||
val certificates = identity.certPath.certificates
|
val certificates = identity.certPath.certificates
|
||||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
val idx = certificates.lastIndexOf(wellKnownCert)
|
||||||
val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size))
|
val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size))
|
||||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import net.corda.core.contracts.PartyAndReference
|
|||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.crypto.toStringShort
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.identity.*
|
import net.corda.core.identity.*
|
||||||
|
import net.corda.core.internal.CertRole
|
||||||
import net.corda.core.internal.cert
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.internal.toX509CertHolder
|
import net.corda.core.internal.toX509CertHolder
|
||||||
import net.corda.core.node.services.UnknownAnonymousPartyException
|
import net.corda.core.node.services.UnknownAnonymousPartyException
|
||||||
@ -13,8 +14,8 @@ import net.corda.core.utilities.contextLogger
|
|||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.node.services.api.IdentityServiceInternal
|
import net.corda.node.services.api.IdentityServiceInternal
|
||||||
import net.corda.node.utilities.AppendOnlyPersistentMap
|
import net.corda.node.utilities.AppendOnlyPersistentMap
|
||||||
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
|
||||||
|
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -125,14 +126,10 @@ class PersistentIdentityService(override val trustRoot: X509Certificate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we record the first identity of the same name, first
|
// Ensure we record the first identity of the same name, first
|
||||||
val identityPrincipal = identity.name.x500Principal
|
val wellKnownCert: Certificate = identity.certPath.certificates.single { CertRole.extract(it)?.isWellKnown ?: false }
|
||||||
val firstCertWithThisName: Certificate = identity.certPath.certificates.last { it ->
|
if (wellKnownCert != identity.certificate) {
|
||||||
val principal = (it as? X509Certificate)?.subjectX500Principal
|
|
||||||
principal == identityPrincipal
|
|
||||||
}
|
|
||||||
if (firstCertWithThisName != identity.certificate) {
|
|
||||||
val certificates = identity.certPath.certificates
|
val certificates = identity.certPath.certificates
|
||||||
val idx = certificates.lastIndexOf(firstCertWithThisName)
|
val idx = certificates.lastIndexOf(wellKnownCert)
|
||||||
val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size))
|
val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size))
|
||||||
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
verifyAndRegisterIdentity(PartyAndCertificate(firstPath))
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.node.services.keys
|
|||||||
|
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.identity.PartyAndCertificate
|
import net.corda.core.identity.PartyAndCertificate
|
||||||
|
import net.corda.core.internal.CertRole
|
||||||
import net.corda.core.internal.cert
|
import net.corda.core.internal.cert
|
||||||
import net.corda.core.internal.toX509CertHolder
|
import net.corda.core.internal.toX509CertHolder
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
@ -33,9 +34,11 @@ fun freshCertificate(identityService: IdentityServiceInternal,
|
|||||||
issuer: PartyAndCertificate,
|
issuer: PartyAndCertificate,
|
||||||
issuerSigner: ContentSigner,
|
issuerSigner: ContentSigner,
|
||||||
revocationEnabled: Boolean = false): PartyAndCertificate {
|
revocationEnabled: Boolean = false): PartyAndCertificate {
|
||||||
|
val issuerRole = CertRole.extract(issuer.certificate)
|
||||||
|
require(issuerRole == CertRole.LEGAL_IDENTITY) { "Confidential identities can only be issued from well known identities, provided issuer ${issuer.name} has role $issuerRole" }
|
||||||
val issuerCert = issuer.certificate.toX509CertHolder()
|
val issuerCert = issuer.certificate.toX509CertHolder()
|
||||||
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCert)
|
||||||
val ourCertificate = X509Utilities.createCertificate(CertificateType.WELL_KNOWN_IDENTITY, issuerCert.subject,
|
val ourCertificate = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuerCert.subject,
|
||||||
issuerSigner, issuer.name, subjectPublicKey, window)
|
issuerSigner, issuer.name, subjectPublicKey, window)
|
||||||
val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
|
||||||
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
|
||||||
|
@ -169,7 +169,7 @@ class InMemoryIdentityServiceTests {
|
|||||||
val issuerKeyPair = generateKeyPair()
|
val issuerKeyPair = generateKeyPair()
|
||||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||||
val txKey = Crypto.generateKeyPair()
|
val txKey = Crypto.generateKeyPair()
|
||||||
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
||||||
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,7 @@ class PersistentIdentityServiceTests {
|
|||||||
val issuerKeyPair = generateKeyPair()
|
val issuerKeyPair = generateKeyPair()
|
||||||
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public)
|
||||||
val txKey = Crypto.generateKeyPair()
|
val txKey = Crypto.generateKeyPair()
|
||||||
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuer.certificate.toX509CertHolder(), issuerKeyPair, x500Name, txKey.public)
|
||||||
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
|
||||||
return Pair(issuer, PartyAndCertificate(txCertPath))
|
return Pair(issuer, PartyAndCertificate(txCertPath))
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
package net.corda.node.services.keys
|
||||||
|
|
||||||
|
import net.corda.core.CordaOID
|
||||||
|
import net.corda.core.crypto.generateKeyPair
|
||||||
|
import net.corda.core.internal.CertRole
|
||||||
|
import net.corda.testing.*
|
||||||
|
import net.corda.testing.node.MockServices
|
||||||
|
import net.corda.testing.node.makeTestIdentityService
|
||||||
|
import org.bouncycastle.asn1.DEROctetString
|
||||||
|
import org.junit.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class KMSUtilsTests {
|
||||||
|
@Test
|
||||||
|
fun `should generate certificates with the correct role`() {
|
||||||
|
val aliceKey = generateKeyPair()
|
||||||
|
val alice = getTestPartyAndCertificate(ALICE_NAME, aliceKey.public)
|
||||||
|
val cordappPackages = emptyList<String>()
|
||||||
|
val ledgerIdentityService = makeTestIdentityService(alice)
|
||||||
|
val mockServices = MockServices(cordappPackages, ledgerIdentityService, alice.name, aliceKey)
|
||||||
|
val wellKnownIdentity = mockServices.myInfo.singleIdentityAndCert()
|
||||||
|
val confidentialIdentity = mockServices.keyManagementService.freshKeyAndCert(wellKnownIdentity, false)
|
||||||
|
val cert = confidentialIdentity.certificate
|
||||||
|
val extensionData = DEROctetString.getInstance(cert.getExtensionValue(CordaOID.X509_EXTENSION_CORDA_ROLE))
|
||||||
|
val expected = CertRole.CONFIDENTIAL_LEGAL_IDENTITY
|
||||||
|
val actual = CertRole.getInstance(extensionData.octets)
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
}
|
@ -102,7 +102,7 @@ fun getTestPartyAndCertificate(party: Party): PartyAndCertificate {
|
|||||||
nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, party.name.x500Name))), arrayOf()))
|
nameConstraints = NameConstraints(arrayOf(GeneralSubtree(GeneralName(GeneralName.directoryName, party.name.x500Name))), arrayOf()))
|
||||||
|
|
||||||
val identityCert = X509Utilities.createCertificate(
|
val identityCert = X509Utilities.createCertificate(
|
||||||
CertificateType.WELL_KNOWN_IDENTITY,
|
CertificateType.LEGAL_IDENTITY,
|
||||||
nodeCaCert,
|
nodeCaCert,
|
||||||
nodeCaKeyPair,
|
nodeCaKeyPair,
|
||||||
party.name,
|
party.name,
|
||||||
|
@ -7,7 +7,10 @@ import net.corda.core.contracts.TypeOnlyCommandData
|
|||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.toX509CertHolder
|
import net.corda.core.internal.toX509CertHolder
|
||||||
import net.corda.nodeapi.internal.crypto.*
|
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
|
import net.corda.nodeapi.internal.crypto.getCertificateAndKeyPair
|
||||||
|
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||||
import org.bouncycastle.cert.X509CertificateHolder
|
import org.bouncycastle.cert.X509CertificateHolder
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
Loading…
Reference in New Issue
Block a user