diff --git a/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt b/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt index 17794be789..7f01393eaf 100644 --- a/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt +++ b/core/src/main/kotlin/net/corda/core/identity/PartyAndCertificate.kt @@ -1,6 +1,7 @@ package net.corda.core.identity import net.corda.core.internal.CertRole +import net.corda.core.internal.uncheckedCast import net.corda.core.serialization.CordaSerializable import java.security.PublicKey import java.security.cert.* @@ -45,15 +46,16 @@ class PartyAndCertificate(val certPath: CertPath) { // 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 certChain: List = uncheckedCast(certPath.certificates) + for (certIdx in (0 until certChain.size).reversed()) { + val certificate = certChain[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() + val certificateString = certificate.subjectDN.toString() throw CertPathValidatorException("The issuing certificate for $certificateString has role $parentRole, expected one of ${role.validParents}") } } diff --git a/core/src/main/kotlin/net/corda/core/internal/CertRole.kt b/core/src/main/kotlin/net/corda/core/internal/CertRole.kt index f14633c883..82f4a42deb 100644 --- a/core/src/main/kotlin/net/corda/core/internal/CertRole.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CertRole.kt @@ -7,7 +7,6 @@ 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 /** @@ -70,14 +69,6 @@ enum class CertRole(val validParents: NonEmptySet, val isIdentity: Bo */ 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? = (cert as? X509Certificate)?.let { extract(it) } - /** * Get a role from a certificate. * diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt index 3e51ad7b9f..42a221862e 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt @@ -1,7 +1,9 @@ package net.corda.core.crypto import net.corda.core.identity.CordaX500Name -import net.corda.nodeapi.internal.crypto.* +import net.corda.nodeapi.internal.crypto.CertificateType +import net.corda.nodeapi.internal.crypto.X509KeyStore +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.internal.createDevIntermediateCaCertPath import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.GeneralName @@ -9,7 +11,6 @@ import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.jce.provider.BouncyCastleProvider import org.junit.Test -import java.security.KeyStore import java.security.cert.CertPathValidator import java.security.cert.CertPathValidatorException import java.security.cert.PKIXParameters @@ -19,37 +20,32 @@ import kotlin.test.assertTrue class X509NameConstraintsTest { - private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair { + private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair { val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() - val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val nodeCaCert = X509Utilities.createCertificate( - CertificateType.NODE_CA, - intermediateCa.certificate, - intermediateCa.keyPair, - CordaX500Name("Corda Client CA", "R3 Ltd", "London", "GB").x500Principal, - nodeCaKeyPair.public, - nameConstraints = nameConstraints) - val keyPass = "password" - val trustStore = KeyStore.getInstance(KEYSTORE_TYPE) - trustStore.load(null, keyPass.toCharArray()) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa.certificate) + val trustStore = X509KeyStore("password").apply { + setCertificate(X509Utilities.CORDA_ROOT_CA, rootCa.certificate) + } - val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) - val tlsCert = X509Utilities.createCertificate( - CertificateType.TLS, - nodeCaCert, - nodeCaKeyPair, - X500Principal(subjectName.encoded), - tlsKeyPair.public) + val keyStore = X509KeyStore("password").apply { + val nodeCaKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val nodeCaCert = X509Utilities.createCertificate( + CertificateType.NODE_CA, + intermediateCa.certificate, + intermediateCa.keyPair, + CordaX500Name("Corda Client CA", "R3 Ltd", "London", "GB").x500Principal, + nodeCaKeyPair.public, + nameConstraints = nameConstraints) + val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + val tlsCert = X509Utilities.createCertificate( + CertificateType.TLS, + nodeCaCert, + nodeCaKeyPair, + X500Principal(subjectName.encoded), + tlsKeyPair.public) + setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCa.certificate)) + } - val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - keyStore.load(null, keyPass.toCharArray()) - keyStore.addOrReplaceKey( - X509Utilities.CORDA_CLIENT_TLS, - tlsKeyPair.private, - keyPass.toCharArray(), - arrayOf(tlsCert, nodeCaCert, intermediateCa.certificate, rootCa.certificate)) return Pair(keyStore, trustStore) } @@ -60,30 +56,29 @@ class X509NameConstraintsTest { val nameConstraints = NameConstraints(acceptableNames, arrayOf()) val pathValidator = CertPathValidator.getInstance("PKIX") - val certFactory = X509CertificateFactory() assertFailsWith(CertPathValidatorException::class) { val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank B"), nameConstraints) - val params = PKIXParameters(trustStore) + val params = PKIXParameters(trustStore.internal) params.isRevocationEnabled = false - val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList()) + val certPath = X509Utilities.buildCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)) pathValidator.validate(certPath, params) } assertTrue { val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A TLS, O=Bank A"), nameConstraints) - val params = PKIXParameters(trustStore) + val params = PKIXParameters(trustStore.internal) params.isRevocationEnabled = false - val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList()) + val certPath = X509Utilities.buildCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)) pathValidator.validate(certPath, params) true } assertTrue { val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A"), nameConstraints) - val params = PKIXParameters(trustStore) + val params = PKIXParameters(trustStore.internal) params.isRevocationEnabled = false - val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList()) + val certPath = X509Utilities.buildCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)) pathValidator.validate(certPath, params) true } @@ -95,40 +90,39 @@ class X509NameConstraintsTest { .map { GeneralSubtree(GeneralName(X500Name(it))) }.toTypedArray() val nameConstraints = NameConstraints(acceptableNames, arrayOf()) - val certFactory = X509CertificateFactory().delegate Crypto.ECDSA_SECP256R1_SHA256 val pathValidator = CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME) assertFailsWith(CertPathValidatorException::class) { val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A"), nameConstraints) - val params = PKIXParameters(trustStore) + val params = PKIXParameters(trustStore.internal) params.isRevocationEnabled = false - val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList()) + val certPath = X509Utilities.buildCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)) pathValidator.validate(certPath, params) } assertFailsWith(CertPathValidatorException::class) { val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A, UID=12345"), nameConstraints) - val params = PKIXParameters(trustStore) + val params = PKIXParameters(trustStore.internal) params.isRevocationEnabled = false - val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList()) + val certPath = X509Utilities.buildCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)) pathValidator.validate(certPath, params) } assertTrue { val (keystore, trustStore) = makeKeyStores(X500Name("CN=Bank A TLS, UID=, E=me@email.com, C=GB"), nameConstraints) - val params = PKIXParameters(trustStore) + val params = PKIXParameters(trustStore.internal) params.isRevocationEnabled = false - val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList()) + val certPath = X509Utilities.buildCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)) pathValidator.validate(certPath, params) true } assertTrue { val (keystore, trustStore) = makeKeyStores(X500Name("O=Bank A, UID=, E=me@email.com, C=GB"), nameConstraints) - val params = PKIXParameters(trustStore) + val params = PKIXParameters(trustStore.internal) params.isRevocationEnabled = false - val certPath = certFactory.generateCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).asList()) + val certPath = X509Utilities.buildCertPath(keystore.getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)) pathValidator.validate(certPath, params) true } diff --git a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt index 39cdbbe8ba..f89f6a7829 100644 --- a/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt +++ b/core/src/test/kotlin/net/corda/core/identity/PartyAndCertificateTest.kt @@ -5,8 +5,8 @@ import com.google.common.jimfs.Jimfs import net.corda.core.crypto.entropyToKeyPair import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509KeyStore +import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.SerializationEnvironmentRule import net.corda.testing.core.getTestPartyAndCertificate @@ -23,7 +23,7 @@ class PartyAndCertificateTest { @Test fun `reject a path with no roles`() { - val path = X509CertificateFactory().generateCertPath(DEV_ROOT_CA.certificate) + val path = X509Utilities.buildCertPath(DEV_ROOT_CA.certificate) assertFailsWith { PartyAndCertificate(path) } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt index 2ecc461771..70c32ed6cd 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/KeyStoreConfigHelpers.kt @@ -62,7 +62,7 @@ fun X509KeyStore.storeLegalIdentity(alias: String, keyPair: KeyPair = Crypto.gen val identityCertPath = listOf(identityCert) + nodeCaCertPath setPrivateKey(alias, keyPair.private, identityCertPath) save() - return PartyAndCertificate(X509CertificateFactory().generateCertPath(identityCertPath)) + return PartyAndCertificate(X509Utilities.buildCertPath(identityCertPath)) } fun createDevNetworkMapCa(rootCa: CertificateAndKeyPair = DEV_ROOT_CA): CertificateAndKeyPair { diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt index 53eb020224..1fa56bebea 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509KeyStore.kt @@ -13,7 +13,13 @@ import java.security.cert.X509Certificate */ class X509KeyStore private constructor(val internal: KeyStore, private val storePassword: String, private val keyStoreFile: Path? = null) { /** Wrap an existing [KeyStore]. [save] is not supported. */ - constructor(internal: KeyStore, storePassword: String) : this(internal, storePassword, null) + constructor(keyStore: KeyStore, storePassword: String) : this(keyStore, storePassword, null) + + /** Create an empty [KeyStore] using the given password. [save] is not supported. */ + constructor(storePassword: String) : this( + KeyStore.getInstance(KEYSTORE_TYPE).apply { load(null, storePassword.toCharArray()) }, + storePassword + ) companion object { /** @@ -49,12 +55,10 @@ class X509KeyStore private constructor(val internal: KeyStore, private val store } fun setPrivateKey(alias: String, key: PrivateKey, certificates: List, keyPassword: String = storePassword) { - checkWritableToFile() internal.setKeyEntry(alias, key, keyPassword.toCharArray(), certificates.toTypedArray()) } fun setCertificate(alias: String, certificate: X509Certificate) { - checkWritableToFile() internal.setCertificateEntry(alias, certificate) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index 959fc83a21..f950ccdc7b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -6,6 +6,7 @@ import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.random63BitValue import net.corda.core.internal.CertRole import net.corda.core.internal.reader +import net.corda.core.internal.uncheckedCast import net.corda.core.internal.writer import net.corda.core.utilities.days import net.corda.core.utilities.millis @@ -93,15 +94,19 @@ object X509Utilities { return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window) } - // TODO Provide an overload which takes in a List or a CertPath - @Throws(CertPathValidatorException::class) - fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) { + fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: X509Certificate) { + validateCertificateChain(trustedRoot, certificates.asList()) + } + + fun validateCertificateChain(trustedRoot: X509Certificate, certificates: List) { require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } + validateCertPath(trustedRoot, buildCertPath(certificates)) + } + + fun validateCertPath(trustedRoot: X509Certificate, certPath: CertPath) { val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null))) params.isRevocationEnabled = false - val certPath = X509CertificateFactory().generateCertPath(*certificates) - val pathValidator = CertPathValidator.getInstance("PKIX") - pathValidator.validate(certPath, params) + CertPathValidator.getInstance("PKIX").validate(certPath, params) } /** @@ -265,6 +270,21 @@ object X509Utilities { fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair): PKCS10CertificationRequest { return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME) } + + fun buildCertPath(first: X509Certificate, remaining: List): CertPath { + val certificates = ArrayList(1 + remaining.size) + certificates += first + certificates += remaining + return buildCertPath(certificates) + } + + fun buildCertPath(vararg certificates: X509Certificate): CertPath { + return X509CertificateFactory().generateCertPath(*certificates) + } + + fun buildCertPath(certificates: List): CertPath { + return X509CertificateFactory().generateCertPath(certificates) + } } /** @@ -275,6 +295,16 @@ object X509Utilities { fun X509Certificate.toBc() = X509CertificateHolder(encoded) fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().generateCertificate(encoded.inputStream()) +val CertPath.x509Certificates: List get() { + require(type == "X.509") { "Not an X.509 cert path: $this" } + // We're not mapping the list to avoid creating a new one. + return uncheckedCast(certificates) +} + +val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certificate) { "Not an X.509 certificate: $this" } + +val Array.x509: List get() = map { it.x509 } + /** * Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best * so assume this class is not. @@ -282,19 +312,11 @@ fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().ge class X509CertificateFactory { val delegate: CertificateFactory = CertificateFactory.getInstance("X.509") - fun generateCertificate(input: InputStream): X509Certificate { - return delegate.generateCertificate(input) as X509Certificate - } + fun generateCertificate(input: InputStream): X509Certificate = delegate.generateCertificate(input).x509 - // TODO X509Certificate - fun generateCertPath(certificates: List): CertPath { - return delegate.generateCertPath(certificates) - } + fun generateCertPath(vararg certificates: X509Certificate): CertPath = generateCertPath(certificates.asList()) - // TODO X509Certificate - fun generateCertPath(vararg certificates: Certificate): CertPath { - return delegate.generateCertPath(certificates.asList()) - } + fun generateCertPath(certificates: List): CertPath = delegate.generateCertPath(certificates) } enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean, val role: CertRole?) { diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt index 2f12970874..c37d6b6eb1 100644 --- a/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/crypto/X509UtilitiesTest.kt @@ -266,10 +266,10 @@ class X509UtilitiesTest { assertTrue(clientSocket.isConnected) // Double check hostname manually - val peerChain = clientSocket.session.peerCertificates - val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal + val peerChain = clientSocket.session.peerCertificates.x509 + val peerX500Principal = peerChain[0].subjectX500Principal assertEquals(MEGA_CORP.name.x500Principal, peerX500Principal) - X509Utilities.validateCertificateChain(rootCa.certificate, *peerChain) + X509Utilities.validateCertificateChain(rootCa.certificate, peerChain) val output = DataOutputStream(clientSocket.outputStream) output.writeUTF("Hello World") var timeout = 0 @@ -338,7 +338,7 @@ class X509UtilitiesTest { val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey) val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey) - val expected = X509CertificateFactory().generateCertPath(certificate, rootCACert) + val expected = X509Utilities.buildCertPath(certificate, rootCACert) val serialized = expected.serialize(factory, context).bytes val actual: CertPath = serialized.deserialize(factory, context) assertEquals(expected, actual) diff --git a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt index b9af17dcb9..ea71007414 100644 --- a/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt +++ b/node/src/integration-test/kotlin/net/corda/node/utilities/registration/NodeRegistrationTest.kt @@ -12,25 +12,27 @@ import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_INTERMEDIATE_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.core.DEV_ROOT_CA import net.corda.testing.core.SerializationEnvironmentRule -import net.corda.testing.common.internal.testNetworkParameters +import net.corda.testing.core.singleIdentity import net.corda.testing.driver.PortAllocation import net.corda.testing.node.NotarySpec import net.corda.testing.node.internal.CompatibilityZoneParams import net.corda.testing.node.internal.internalDriver import net.corda.testing.node.internal.network.NetworkMapServer -import net.corda.testing.core.singleIdentity import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest -import org.junit.* +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test import java.io.ByteArrayOutputStream import java.io.InputStream import java.net.URL @@ -151,7 +153,7 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) val (certPath, name) = createSignedClientCertificate( certificationRequest, rootCertAndKeyPair.keyPair, - arrayOf(rootCertAndKeyPair.certificate)) + listOf(rootCertAndKeyPair.certificate)) require(!name.organisation.contains("\\s".toRegex())) { "Whitespace in the organisation name not supported" } certPaths[name.organisation] = certPath return Response.ok(name.organisation).build() @@ -180,17 +182,17 @@ class RegistrationHandler(private val rootCertAndKeyPair: CertificateAndKeyPair) private fun createSignedClientCertificate(certificationRequest: PKCS10CertificationRequest, caKeyPair: KeyPair, - caCertPath: Array): Pair { + caCertPath: List): Pair { val request = JcaPKCS10CertificationRequest(certificationRequest) val name = CordaX500Name.parse(request.subject.toString()) val nodeCaCert = X509Utilities.createCertificate( CertificateType.NODE_CA, - caCertPath[0] as X509Certificate , + caCertPath[0], caKeyPair, name.x500Principal, request.publicKey, nameConstraints = null) - val certPath = X509CertificateFactory().generateCertPath(nodeCaCert, *caCertPath) + val certPath = X509Utilities.buildCertPath(nodeCaCert, caCertPath) return Pair(certPath, name) } } diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 899d6618e7..8b3e992ef1 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -58,7 +58,6 @@ import net.corda.node.shell.InteractiveShell import net.corda.node.utilities.AffinityExecutor import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.network.NETWORK_PARAMS_FILE_NAME import net.corda.nodeapi.internal.network.NetworkParameters @@ -755,7 +754,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject") } - val certPath = X509CertificateFactory().generateCertPath(certificates) + val certPath = X509Utilities.buildCertPath(certificates) return Pair(PartyAndCertificate(certPath), keyPair) } diff --git a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt index f31f9fbe88..115dcb1da8 100644 --- a/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt +++ b/node/src/main/kotlin/net/corda/node/internal/protonwrapper/netty/AMQPChannelHandler.kt @@ -14,6 +14,7 @@ import net.corda.node.internal.protonwrapper.engine.EventProcessor import net.corda.node.internal.protonwrapper.messages.ReceivedMessage import net.corda.node.internal.protonwrapper.messages.impl.ReceivedMessageImpl import net.corda.node.internal.protonwrapper.messages.impl.SendableMessageImpl +import net.corda.nodeapi.internal.crypto.x509 import org.apache.qpid.proton.engine.ProtonJTransport import org.apache.qpid.proton.engine.Transport import org.apache.qpid.proton.engine.impl.ProtocolTracer @@ -80,8 +81,8 @@ internal class AMQPChannelHandler(private val serverMode: Boolean, if (evt is SslHandshakeCompletionEvent) { if (evt.isSuccess) { val sslHandler = ctx.pipeline().get(SslHandler::class.java) - localCert = sslHandler.engine().session.localCertificates[0] as X509Certificate - remoteCert = sslHandler.engine().session.peerCertificates[0] as X509Certificate + localCert = sslHandler.engine().session.localCertificates[0].x509 + remoteCert = sslHandler.engine().session.peerCertificates[0].x509 try { val remoteX500Name = CordaX500Name.build(remoteCert.subjectX500Principal) require(allowedRemoteLegalNames == null || remoteX500Name in allowedRemoteLegalNames) diff --git a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt index 4876533003..d016a56371 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/InMemoryIdentityService.kt @@ -9,7 +9,8 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.contextLogger import net.corda.core.utilities.trace import net.corda.node.services.api.IdentityServiceInternal -import net.corda.nodeapi.internal.crypto.X509CertificateFactory +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.x509Certificates import java.security.InvalidAlgorithmParameterException import java.security.PublicKey import java.security.cert.* @@ -45,24 +46,24 @@ class InMemoryIdentityService(identities: Array, @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { // Validate the chain first, before we do anything clever with it + val identityCertChain = identity.certPath.x509Certificates try { identity.verify(trustAnchor) } catch (e: CertPathValidatorException) { log.warn("Certificate validation failed for ${identity.name} against trusted root ${trustAnchor.trustedCert.subjectX500Principal}.") log.warn("Certificate path :") - identity.certPath.certificates.reversed().forEachIndexed { index, certificate -> + identityCertChain.reversed().forEachIndexed { index, certificate -> val space = (0 until index).joinToString("") { " " } - log.warn("$space${(certificate as X509Certificate).subjectX500Principal}") + log.warn("$space${certificate.subjectX500Principal}") } throw e } // Ensure we record the first identity of the same name, first - val wellKnownCert: Certificate = identity.certPath.certificates.single { CertRole.extract(it)?.isWellKnown ?: false } + val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false } if (wellKnownCert != identity.certificate) { - val certificates = identity.certPath.certificates - val idx = certificates.lastIndexOf(wellKnownCert) - val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size)) + val idx = identityCertChain.lastIndexOf(wellKnownCert) + val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size)) verifyAndRegisterIdentity(PartyAndCertificate(firstPath)) } @@ -70,7 +71,7 @@ class InMemoryIdentityService(identities: Array, keyToParties[identity.owningKey] = identity // Always keep the first party we registered, as that's the well known identity principalToParties.computeIfAbsent(identity.name) { identity } - return keyToParties[identity.certPath.certificates[1].publicKey] + return keyToParties[identityCertChain[1].publicKey] } override fun certificateFromKey(owningKey: PublicKey): PartyAndCertificate? = keyToParties[owningKey] @@ -103,7 +104,7 @@ class InMemoryIdentityService(identities: Array, val results = LinkedHashSet() for ((x500name, partyAndCertificate) in principalToParties) { val party = partyAndCertificate.party - val components = listOf(x500name.commonName, x500name.organisationUnit, x500name.organisation, x500name.locality, x500name.state, x500name.country).filterNotNull() + val components = listOfNotNull(x500name.commonName, x500name.organisationUnit, x500name.organisation, x500name.locality, x500name.state, x500name.country) components.forEach { component -> if (exactMatch && component == query) { results += party diff --git a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt index c21f7e9c91..9c77d45ded 100644 --- a/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt +++ b/node/src/main/kotlin/net/corda/node/services/identity/PersistentIdentityService.kt @@ -13,6 +13,8 @@ import net.corda.core.utilities.debug import net.corda.node.services.api.IdentityServiceInternal import net.corda.node.utilities.AppendOnlyPersistentMap import net.corda.nodeapi.internal.crypto.X509CertificateFactory +import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX import java.security.InvalidAlgorithmParameterException import java.security.PublicKey @@ -111,23 +113,23 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? { // Validate the chain first, before we do anything clever with it + val identityCertChain = identity.certPath.x509Certificates try { identity.verify(trustAnchor) } catch (e: CertPathValidatorException) { log.warn(e.localizedMessage) log.warn("Path = ") - identity.certPath.certificates.reversed().forEach { - log.warn((it as X509Certificate).subjectX500Principal.toString()) + identityCertChain.reversed().forEach { + log.warn(it.subjectX500Principal.toString()) } throw e } // Ensure we record the first identity of the same name, first - val wellKnownCert: Certificate = identity.certPath.certificates.single { CertRole.extract(it)?.isWellKnown ?: false } + val wellKnownCert = identityCertChain.single { CertRole.extract(it)?.isWellKnown ?: false } if (wellKnownCert != identity.certificate) { - val certificates = identity.certPath.certificates - val idx = certificates.lastIndexOf(wellKnownCert) - val firstPath = X509CertificateFactory().generateCertPath(certificates.slice(idx until certificates.size)) + val idx = identityCertChain.lastIndexOf(wellKnownCert) + val firstPath = X509Utilities.buildCertPath(identityCertChain.slice(idx until identityCertChain.size)) verifyAndRegisterIdentity(PartyAndCertificate(firstPath)) } @@ -136,7 +138,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, keyToParties.addWithDuplicatesAllowed(key, identity) // Always keep the first party we registered, as that's the well known identity principalToParties.addWithDuplicatesAllowed(identity.name, key, false) - val parentId = mapToKey(identity.certPath.certificates[1].publicKey) + val parentId = mapToKey(identityCertChain[1].publicKey) return keyToParties[parentId] } @@ -175,7 +177,7 @@ class PersistentIdentityService(override val trustRoot: X509Certificate, val results = LinkedHashSet() for ((x500name, partyId) in principalToParties.allPersisted()) { val party = keyToParties[partyId]!!.party - val components = listOf(x500name.commonName, x500name.organisationUnit, x500name.organisation, x500name.locality, x500name.state, x500name.country).filterNotNull() + val components = listOfNotNull(x500name.commonName, x500name.organisationUnit, x500name.organisation, x500name.locality, x500name.state, x500name.country) components.forEach { component -> if (exactMatch && component == query) { results += party diff --git a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt index dad6a7b2a7..460845a652 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/KMSUtils.kt @@ -7,8 +7,8 @@ import net.corda.core.utilities.days import net.corda.node.services.api.IdentityServiceInternal import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.ContentSignerBuilder -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.x509Certificates import org.bouncycastle.operator.ContentSigner import java.security.KeyPair import java.security.PublicKey @@ -43,7 +43,7 @@ fun freshCertificate(identityService: IdentityServiceInternal, issuer.name.x500Principal, subjectPublicKey, window) - val ourCertPath = X509CertificateFactory().generateCertPath(listOf(ourCertificate) + issuer.certPath.certificates) + val ourCertPath = X509Utilities.buildCertPath(ourCertificate, issuer.certPath.x509Certificates) val anonymisedIdentity = PartyAndCertificate(ourCertPath) identityService.justVerifyAndRegisterIdentity(anonymisedIdentity) return anonymisedIdentity diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt b/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt index 208bc3c946..c3c9a1041d 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/CoreBridgeManager.kt @@ -13,6 +13,7 @@ import net.corda.nodeapi.internal.ArtemisMessagingComponent import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.x509 import org.apache.activemq.artemis.api.core.Message import org.apache.activemq.artemis.core.config.BridgeConfiguration import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection @@ -23,7 +24,6 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer import org.apache.activemq.artemis.core.server.cluster.Transformer import org.apache.activemq.artemis.spi.core.remoting.* import org.apache.activemq.artemis.utils.ConfigurationHelper -import java.security.cert.X509Certificate import java.time.Duration import java.util.concurrent.Executor import java.util.concurrent.ScheduledExecutorService @@ -162,7 +162,9 @@ class VerifyingNettyConnectorFactory : NettyConnectorFactory() { "Peer has wrong subject name in the certificate - expected $expectedLegalNames but got $peerCertificateName. This is either a fatal " + "misconfiguration by the remote peer or an SSL man-in-the-middle attack!" } - X509Utilities.validateCertificateChain(session.localCertificates.last() as X509Certificate, *session.peerCertificates) + X509Utilities.validateCertificateChain( + session.localCertificates.last().x509, + session.peerCertificates.x509) } catch (e: IllegalArgumentException) { connection.close() log.error(e.message) diff --git a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt index ac49461b05..509ade8a32 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelper.kt @@ -10,6 +10,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.nodeapi.internal.crypto.X509Utilities.CORDA_ROOT_CA +import net.corda.nodeapi.internal.crypto.x509 import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemObject import java.io.StringWriter @@ -43,7 +44,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v "This file must contain the root CA cert of your compatibility zone. " + "Please contact your CZ operator." } - this.rootCert = rootCert as X509Certificate + this.rootCert = rootCert.x509 } /** @@ -109,7 +110,7 @@ class NetworkRegistrationHelper(private val config: NodeConfiguration, private v } println("Checking root of the certificate path is what we expect.") - X509Utilities.validateCertificateChain(rootCert, *certificates.toTypedArray()) + X509Utilities.validateCertificateChain(rootCert, certificates) println("Certificate signing request approved, storing private key with the certificate chain.") // Save private key and certificate chain to the key store. diff --git a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt index 91c216f12d..f1256db27d 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/InMemoryIdentityServiceTests.kt @@ -8,8 +8,8 @@ import net.corda.core.identity.Party import net.corda.core.identity.PartyAndCertificate import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.nodeapi.internal.crypto.CertificateType -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.testing.core.* import org.junit.Rule import org.junit.Test @@ -173,7 +173,7 @@ class InMemoryIdentityServiceTests { issuerKeyPair, x500Name.x500Principal, txKeyPair.public) - val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert) + issuer.certPath.certificates) + val txCertPath = X509Utilities.buildCertPath(txCert, issuer.certPath.x509Certificates) return Pair(issuer, PartyAndCertificate(txCertPath)) } diff --git a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt index 7e3a062885..dda6516173 100644 --- a/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/identity/PersistentIdentityServiceTests.kt @@ -10,8 +10,8 @@ import net.corda.core.node.services.IdentityService import net.corda.core.node.services.UnknownAnonymousPartyException import net.corda.node.internal.configureDatabase import net.corda.nodeapi.internal.crypto.CertificateType -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.testing.core.* @@ -264,8 +264,13 @@ class PersistentIdentityServiceTests { val issuerKeyPair = generateKeyPair() val issuer = getTestPartyAndCertificate(x500Name, issuerKeyPair.public) val txKey = Crypto.generateKeyPair() - val txCert = X509Utilities.createCertificate(CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, issuer.certificate, issuerKeyPair, x500Name.x500Principal, txKey.public) - val txCertPath = X509CertificateFactory().generateCertPath(listOf(txCert) + issuer.certPath.certificates) + val txCert = X509Utilities.createCertificate( + CertificateType.CONFIDENTIAL_LEGAL_IDENTITY, + issuer.certificate, + issuerKeyPair, + x500Name.x500Principal, + txKey.public) + val txCertPath = X509Utilities.buildCertPath(txCert, issuer.certPath.x509Certificates) return Pair(issuer, PartyAndCertificate(txCertPath)) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt index f7614e2424..762ea036be 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/core/TestUtils.kt @@ -87,7 +87,7 @@ fun getTestPartyAndCertificate(party: Party): PartyAndCertificate { party.name.x500Principal, party.owningKey) - val certPath = X509CertificateFactory().generateCertPath(identityCert, nodeCaCert, intermediate.certificate, trustRoot) + val certPath = X509Utilities.buildCertPath(identityCert, nodeCaCert, intermediate.certificate, trustRoot) return PartyAndCertificate(certPath) } diff --git a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt index 83cd569679..c99ca190cf 100644 --- a/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt +++ b/testing/test-utils/src/main/kotlin/net/corda/testing/internal/TestNodeInfoBuilder.kt @@ -11,7 +11,6 @@ import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.createDevNodeCa import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType -import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.testing.core.DEV_INTERMEDIATE_CA import net.corda.testing.core.DEV_ROOT_CA @@ -31,11 +30,11 @@ class TestNodeInfoBuilder(private val intermediateAndRoot: Pair