From c003ec004251a5ce787f70b03b533217d03ed9df Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Wed, 24 May 2017 17:41:59 +0100 Subject: [PATCH] Replace X509Certificate with X509CertificateHolder Replace X509Certificate with X509CertificateHolder for consistency in implementation of how X.509 certificates are managed. Using the Java standard class entails the actual implementing class being one of several options depending how a certificate is built, which makes serialization/deserialization with Kryo inconsistent as some of these forms cannot be directly built from outside restricted classes. --- .../kotlin/net/corda/core/crypto/Crypto.kt | 84 ++++++++++++++++--- .../net/corda/core/crypto/CryptoUtils.kt | 1 + .../corda/core/crypto/KeyStoreUtilities.kt | 27 +++++- .../net/corda/core/crypto/X509Utilities.kt | 46 +++++----- .../kotlin/net/corda/core/flows/TxKeyFlow.kt | 13 +-- .../kotlin/net/corda/core/identity/Party.kt | 4 +- .../core/node/services/IdentityService.kt | 3 +- .../net/corda/core/node/services/Services.kt | 10 ++- .../serialization/DefaultKryoCustomizer.kt | 6 +- .../net/corda/core/serialization/Kryo.kt | 12 +-- .../core/crypto/X509NameConstraintsTest.kt | 14 ++-- .../corda/core/crypto/X509UtilitiesTest.kt | 83 ++++++++++-------- .../net/corda/core/serialization/KryoTests.kt | 7 +- .../messaging/MQSecurityAsNodeTest.kt | 5 +- .../net/corda/node/internal/AbstractNode.kt | 5 +- .../identity/InMemoryIdentityService.kt | 7 +- .../keys/E2ETestKeyManagementService.kt | 5 +- .../net/corda/node/services/keys/KMSUtils.kt | 7 +- .../keys/PersistentKeyManagementService.kt | 6 +- .../messaging/ArtemisMessagingServer.kt | 7 +- .../services/network/NetworkMapService.kt | 4 +- .../registration/NetworkRegistrationHelper.kt | 9 +- .../NetworkisRegistrationHelperTest.kt | 5 +- .../net/corda/testing/node/MockServices.kt | 4 +- .../net/corda/testing/node/SimpleNode.kt | 5 +- 25 files changed, 257 insertions(+), 122 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt index 1f5f7693e2..cd4c28f497 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -9,6 +9,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import org.bouncycastle.asn1.ASN1EncodableVector import org.bouncycastle.asn1.ASN1ObjectIdentifier +import org.bouncycastle.asn1.ASN1Sequence import org.bouncycastle.asn1.DERSequence import org.bouncycastle.asn1.bc.BCObjectIdentifiers import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers @@ -19,8 +20,9 @@ import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.asn1.x509.NameConstraints import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.asn1.x9.X9ObjectIdentifiers +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.X509v3CertificateBuilder import org.bouncycastle.cert.bc.BcX509ExtensionUtils -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey @@ -29,6 +31,8 @@ import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.operator.ContentSigner +import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider @@ -42,7 +46,6 @@ import java.math.BigInteger import java.security.* import java.security.KeyFactory import java.security.KeyPairGenerator -import java.security.cert.X509Certificate import java.security.spec.InvalidKeySpecException import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec @@ -559,20 +562,25 @@ object Crypto { } /** - * Use bouncy castle utilities to sign completed X509 certificate with CA cert private key. + * Build a partial X.509 certificate ready for signing. + * + * @param issuer name of the issuing entity. + * @param subject name of the certificate subject. + * @param subjectPublicKey public key of the certificate subject. + * @param validityWindow the time period the certificate is valid for. + * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. */ - fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair, + fun createCertificate(certificateType: CertificateType, issuer: X500Name, subject: X500Name, subjectPublicKey: PublicKey, validityWindow: Pair, - nameConstraints: NameConstraints? = null): X509Certificate { + nameConstraints: NameConstraints? = null): X509v3CertificateBuilder { - val signatureScheme = findSignatureScheme(issuerKeyPair.private) - val provider = providerMap[signatureScheme.providerName] val serial = BigInteger.valueOf(random63BitValue()) val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } }) + val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded)) val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey) - .addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectPublicKey.encoded))) + .addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo)) .addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA)) .addExtension(Extension.keyUsage, false, certificateType.keyUsage) .addExtension(Extension.extendedKeyUsage, false, keyPurposes) @@ -580,11 +588,52 @@ object Crypto { if (nameConstraints != null) { builder.addExtension(Extension.nameConstraints, true, nameConstraints) } + return builder + } + + /** + * Build and sign an X.509 certificate with the given signer. + * + * @param issuer name of the issuing entity. + * @param issuerSigner content signer to sign the certificate with. + * @param subject name of the certificate subject. + * @param subjectPublicKey public key of the certificate subject. + * @param validityWindow the time period the certificate is valid for. + * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. + */ + fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerSigner: ContentSigner, + subject: X500Name, subjectPublicKey: PublicKey, + validityWindow: Pair, + nameConstraints: NameConstraints? = null): X509CertificateHolder { + val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints) + return builder.build(issuerSigner).apply { + require(isValidOn(Date())) + } + } + + /** + * Build and sign an X.509 certificate with CA cert private key. + * + * @param issuer name of the issuing entity. + * @param issuerKeyPair the public & private key to sign the certificate with. + * @param subject name of the certificate subject. + * @param subjectPublicKey public key of the certificate subject. + * @param validityWindow the time period the certificate is valid for. + * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate. + */ + fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair, + subject: X500Name, subjectPublicKey: PublicKey, + validityWindow: Pair, + nameConstraints: NameConstraints? = null): X509CertificateHolder { + + val signatureScheme = findSignatureScheme(issuerKeyPair.private) + val provider = providerMap[signatureScheme.providerName] + val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints) val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider) - return JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer)).apply { - checkValidity(Date()) - verify(issuerKeyPair.public, provider) + return builder.build(signer).apply { + require(isValidOn(Date())) + require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public))) } } @@ -671,6 +720,19 @@ object Crypto { } } + /** + * Convert a public key to a supported implementation. This method is usually required to retrieve a key from an + * [X509CertificateHolder]. + * + * @param key a public key. + * @return a supported implementation of the input public key. + * @throws IllegalArgumentException on not supported scheme or if the given key specification + * is inappropriate for a supported key factory to produce a private key. + */ + fun toSupportedPublicKey(key: SubjectPublicKeyInfo): PublicKey { + return Crypto.decodePublicKey(key.encoded) + } + /** * Convert a public key to a supported implementation. This can be used to convert a SUN's EC key to an BC key. * This method is usually required to retrieve a key (via its corresponding cert) from JKS keystores that by default return SUN implementations. diff --git a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt index c55d515d74..83df4d82ad 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/CryptoUtils.kt @@ -22,6 +22,7 @@ val NULL_PARTY = AnonymousParty(NullPublicKey) // TODO: Clean up this duplication between Null and Dummy public key @CordaSerializable +@Deprecated("Has encoding format problems, consider entropyToKeyPair() instead") class DummyPublicKey(val s: String) : PublicKey, Comparable { override fun getAlgorithm() = "DUMMY" override fun getEncoded() = s.toByteArray() diff --git a/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt b/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt index 138eed3c56..21ec1094ee 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt @@ -3,6 +3,10 @@ package net.corda.core.crypto import net.corda.core.exists import net.corda.core.read import net.corda.core.write +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.cert.path.CertPath +import org.bouncycastle.crypto.util.PublicKeyFactory import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -67,6 +71,19 @@ object KeyStoreUtilities { } } +/** + * Helper extension method to add, or overwrite any key data in store. + * @param alias name to record the private key and certificate chain under. + * @param key cryptographic key to store. + * @param password password for unlocking the key entry in the future. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @param chain the sequence of certificates starting with the public key certificate for this key and extending to the root CA cert. + */ +fun KeyStore.addOrReplaceKey(alias: String, key: Key, password: CharArray, chain: CertPath) { + val converter = JcaX509CertificateConverter() + addOrReplaceKey(alias, key, password, chain.certificates.map { it -> converter.getCertificate(it) }.toTypedArray()) +} + /** * Helper extension method to add, or overwrite any key data in store. * @param alias name to record the private key and certificate chain under. @@ -122,8 +139,9 @@ fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertif * @param keyPassword The password for the PrivateKey (not the store access password). */ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): CertificateAndKeyPair { - val cert = getCertificate(alias) as X509Certificate - return CertificateAndKeyPair(cert, KeyPair(Crypto.toSupportedPublicKey(cert.publicKey), getSupportedKey(alias, keyPassword))) + val cert = getX509Certificate(alias) + val publicKey = Crypto.toSupportedPublicKey(cert.subjectPublicKeyInfo) + return CertificateAndKeyPair(cert, KeyPair(publicKey, getSupportedKey(alias, keyPassword))) } /** @@ -131,7 +149,10 @@ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): Certi * @param alias The name to lookup the Key and Certificate chain from. * @return The X509Certificate found in the KeyStore under the specified alias. */ -fun KeyStore.getX509Certificate(alias: String): X509Certificate = getCertificate(alias) as X509Certificate +fun KeyStore.getX509Certificate(alias: String): X509CertificateHolder { + val encoded = getCertificate(alias)?.encoded ?: throw IllegalArgumentException("No certificate under alias \"${alias}\"") + return X509CertificateHolder(encoded) +} /** * Extract a private key from a KeyStore file assuming storage alias is known. diff --git a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt index 706ffdbd26..865ab09c8e 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -1,16 +1,17 @@ package net.corda.core.crypto import net.corda.core.crypto.Crypto.generateKeyPair -import net.corda.core.identity.Party -import net.corda.core.node.ServiceHub +import net.corda.core.mapToArray import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.asn1.x509.* import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemReader +import java.io.ByteArrayInputStream import java.io.FileReader import java.io.FileWriter import java.io.InputStream @@ -62,7 +63,7 @@ object X509Utilities { * @param after duration to roll forward returned end date relative to current date. * @param parent if provided certificate whose validity should bound the date interval returned. */ - private fun getCertificateValidityWindow(before: Duration, after: Duration, parent: X509Certificate? = null): Pair { + fun getCertificateValidityWindow(before: Duration, after: Duration, parent: X509CertificateHolder? = null): Pair { val startOfDayUTC = Instant.now().truncatedTo(ChronoUnit.DAYS) val notBefore = max(startOfDayUTC - before, parent?.notBefore) val notAfter = min(startOfDayUTC + after, parent?.notAfter) @@ -104,10 +105,9 @@ object X509Utilities { * Create a de novo root self-signed X509 v3 CA cert. */ @JvmStatic - fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): X509Certificate { + fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder { val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second) - val cert = Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window) - return cert + return Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window) } /** @@ -122,13 +122,12 @@ object X509Utilities { */ @JvmStatic fun createCertificate(certificateType: CertificateType, - issuerCertificate: X509Certificate, issuerKeyPair: KeyPair, + issuerCertificate: X509CertificateHolder, issuerKeyPair: KeyPair, subject: X500Name, subjectPublicKey: PublicKey, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW, - nameConstraints: NameConstraints? = null): X509Certificate { + nameConstraints: NameConstraints? = null): X509CertificateHolder { val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate) - val cert = Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints) - return cert + return Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints) } /** @@ -139,17 +138,19 @@ object X509Utilities { * @param certificates certificates in the path. * @param revocationEnabled whether revocation of certificates in the path should be checked. */ - fun createCertificatePath(trustedRoot: X509Certificate, vararg certificates: X509Certificate, revocationEnabled: Boolean): CertPath { + fun createCertificatePath(trustedRoot: X509CertificateHolder, vararg certificates: X509CertificateHolder, revocationEnabled: Boolean): CertPath { val certFactory = CertificateFactory.getInstance("X509") - val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null))) + val trustedRootX509 = certFactory.generateCertificate(ByteArrayInputStream(trustedRoot.encoded)) as X509Certificate + val params = PKIXParameters(setOf(TrustAnchor(trustedRootX509, null))) params.isRevocationEnabled = revocationEnabled - return certFactory.generateCertPath(certificates.toList()) + return certFactory.generateCertPath(certificates.map { certFactory.generateCertificate(ByteArrayInputStream(it.encoded)) }.toList()) } - fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) { + fun validateCertificateChain(trustedRoot: X509CertificateHolder, vararg certificates: Certificate) { require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } + val converter = JcaX509CertificateConverter() val certFactory = CertificateFactory.getInstance("X509") - val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null))) + val params = PKIXParameters(setOf(TrustAnchor(converter.getCertificate(trustedRoot), null))) params.isRevocationEnabled = false val certPath = certFactory.generateCertPath(certificates.toList()) val pathValidator = CertPathValidator.getInstance("PKIX") @@ -162,7 +163,7 @@ object X509Utilities { * @param filename Target filename. */ @JvmStatic - fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, filename: Path) { + fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, filename: Path) { FileWriter(filename.toFile()).use { JcaPEMWriter(it).use { it.writeObject(x509Certificate) @@ -176,11 +177,12 @@ object X509Utilities { * @return The X509Certificate that was encoded in the file. */ @JvmStatic - fun loadCertificateFromPEMFile(filename: Path): X509Certificate { + fun loadCertificateFromPEMFile(filename: Path): X509CertificateHolder { val reader = PemReader(FileReader(filename.toFile())) val pemObject = reader.readPemObject() - return CertificateStream(pemObject.content.inputStream()).nextCertificate().apply { - checkValidity() + val cert = X509CertificateHolder(pemObject.content) + return cert.apply { + isValidOn(Date()) } } @@ -221,7 +223,7 @@ object X509Utilities { CORDA_CLIENT_CA, clientKey.private, keyPass, - arrayOf(clientCACert, intermediateCACert, rootCACert)) + org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert))) clientCAKeystore.save(clientCAKeystorePath, storePassword) val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeyStorePath, storePassword) @@ -229,7 +231,7 @@ object X509Utilities { CORDA_CLIENT_TLS, tlsKey.private, keyPass, - arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert)) + org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))) tlsKeystore.save(sslKeyStorePath, storePassword) } @@ -286,7 +288,7 @@ class CertificateStream(val input: InputStream) { fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate } -data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair) +data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair) enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) { ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true), diff --git a/core/src/main/kotlin/net/corda/core/flows/TxKeyFlow.kt b/core/src/main/kotlin/net/corda/core/flows/TxKeyFlow.kt index 32ba268709..05a48c6895 100644 --- a/core/src/main/kotlin/net/corda/core/flows/TxKeyFlow.kt +++ b/core/src/main/kotlin/net/corda/core/flows/TxKeyFlow.kt @@ -6,6 +6,7 @@ import net.corda.core.identity.Party import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.unwrap import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder import java.security.cert.CertPath import java.security.cert.X509Certificate @@ -15,7 +16,7 @@ import java.security.cert.X509Certificate */ object TxKeyFlow { abstract class AbstractIdentityFlow(val otherSide: Party, val revocationEnabled: Boolean): FlowLogic>() { - fun validateIdentity(untrustedIdentity: Pair): AnonymousIdentity { + fun validateIdentity(untrustedIdentity: Pair): AnonymousIdentity { val (wellKnownCert, certPath) = untrustedIdentity val theirCert = certPath.certificates.last() // TODO: Don't trust self-signed certificates @@ -24,7 +25,7 @@ object TxKeyFlow { if (certName == otherSide.name) { val anonymousParty = AnonymousParty(theirCert.publicKey) serviceHub.identityService.registerPath(wellKnownCert, anonymousParty, certPath) - AnonymousIdentity(certPath, theirCert, anonymousParty) + AnonymousIdentity(certPath, X509CertificateHolder(theirCert.encoded), anonymousParty) } else throw IllegalStateException("Expected certificate subject to be ${otherSide.name} but found ${certName}") } else @@ -50,7 +51,7 @@ object TxKeyFlow { override fun call(): Map { progressTracker.currentStep = AWAITING_KEY val myIdentityFragment = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentity, revocationEnabled) - val theirIdentity = receive>(otherSide).unwrap { validateIdentity(it) } + val theirIdentity = receive>(otherSide).unwrap { validateIdentity(it) } send(otherSide, myIdentityFragment) return mapOf(Pair(otherSide, AnonymousIdentity(myIdentityFragment)), Pair(serviceHub.myInfo.legalIdentity, theirIdentity)) @@ -78,7 +79,7 @@ object TxKeyFlow { progressTracker.currentStep = SENDING_KEY val myIdentityFragment = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentity, revocationEnabled) send(otherSide, myIdentityFragment) - val theirIdentity = receive>(otherSide).unwrap { validateIdentity(it) } + val theirIdentity = receive>(otherSide).unwrap { validateIdentity(it) } return mapOf(Pair(otherSide, AnonymousIdentity(myIdentityFragment)), Pair(serviceHub.myInfo.legalIdentity, theirIdentity)) } @@ -86,9 +87,9 @@ object TxKeyFlow { data class AnonymousIdentity( val certPath: CertPath, - val certificate: X509Certificate, + val certificate: X509CertificateHolder, val identity: AnonymousParty) { - constructor(myIdentity: Pair) : this(myIdentity.second, + constructor(myIdentity: Pair) : this(myIdentity.second, myIdentity.first, AnonymousParty(myIdentity.second.certificates.last().publicKey)) } diff --git a/core/src/main/kotlin/net/corda/core/identity/Party.kt b/core/src/main/kotlin/net/corda/core/identity/Party.kt index b94f266b8c..45486049e8 100644 --- a/core/src/main/kotlin/net/corda/core/identity/Party.kt +++ b/core/src/main/kotlin/net/corda/core/identity/Party.kt @@ -26,8 +26,8 @@ import java.security.PublicKey * * @see CompositeKey */ -class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) { - constructor(certAndKey: CertificateAndKeyPair) : this(X500Name(certAndKey.certificate.subjectDN.name), certAndKey.keyPair.public) +open class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) { + constructor(certAndKey: CertificateAndKeyPair) : this(certAndKey.certificate.subject, certAndKey.keyPair.public) override fun toString() = name.toString() override fun nameOrNull(): X500Name? = name diff --git a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt index d066d1007c..71cb0c7b38 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/IdentityService.kt @@ -6,6 +6,7 @@ import net.corda.core.identity.AbstractParty import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder import java.security.PublicKey import java.security.cert.CertPath import java.security.cert.X509Certificate @@ -30,7 +31,7 @@ interface IdentityService { */ // TODO: Move this into internal identity service once available @Throws(IllegalArgumentException::class) - fun registerPath(trustedRoot: X509Certificate, anonymousParty: AnonymousParty, path: CertPath) + fun registerPath(trustedRoot: X509CertificateHolder, anonymousParty: AnonymousParty, path: CertPath) /** * Asserts that an anonymous party maps to the given full party, by looking up the certificate chain associated with diff --git a/core/src/main/kotlin/net/corda/core/node/services/Services.kt b/core/src/main/kotlin/net/corda/core/node/services/Services.kt index 65fd283927..a673001f05 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/Services.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/Services.kt @@ -3,10 +3,12 @@ package net.corda.core.node.services import co.paralleluniverse.fibers.Suspendable import com.google.common.util.concurrent.ListenableFuture import net.corda.core.contracts.* -import net.corda.core.crypto.* +import net.corda.core.crypto.CompositeKey +import net.corda.core.crypto.DigitalSignature +import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.keys import net.corda.core.flows.FlowException import net.corda.core.identity.AbstractParty -import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.QueryCriteria @@ -17,6 +19,8 @@ import net.corda.core.toFuture import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.WireTransaction +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.operator.ContentSigner import rx.Observable import java.io.InputStream import java.security.PublicKey @@ -396,7 +400,7 @@ interface KeyManagementService { * @return X.509 certificate and path to the trust root. */ @Suspendable - fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair + fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair /** Using the provided signing [PublicKey] internally looks up the matching [PrivateKey] and signs the data. * @param bytes The data to sign over using the chosen key. diff --git a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt index 825ce18a20..a88e2ec073 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/DefaultKryoCustomizer.kt @@ -18,6 +18,7 @@ import net.corda.core.utilities.NonEmptySetSerializer import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey @@ -102,11 +103,8 @@ object DefaultKryoCustomizer { register(CertPath::class.java, CertPathSerializer) register(X509CertPath::class.java, CertPathSerializer) - // TODO: We shouldn't need to serialize raw certificates, and if we do then we need a cleaner solution - // than this mess. - val x509CertObjectClazz = Class.forName("org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject") - register(x509CertObjectClazz, X509CertificateSerializer) register(X500Name::class.java, X500NameSerializer) + register(X509CertificateHolder::class.java, X509CertificateSerializer) register(BCECPrivateKey::class.java, PrivateKeySerializer) register(BCECPublicKey::class.java, PublicKeySerializer) diff --git a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt index a4315ee500..5a68d5a5c5 100644 --- a/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt +++ b/core/src/main/kotlin/net/corda/core/serialization/Kryo.kt @@ -20,6 +20,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import org.bouncycastle.asn1.ASN1InputStream import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder import org.slf4j.Logger import org.slf4j.LoggerFactory import java.io.ByteArrayInputStream @@ -632,16 +633,15 @@ object CertPathSerializer : Serializer() { } /** - * For serialising an [CX509Certificate] in an X.500 standard format. + * For serialising an [CX509CertificateHolder] in an X.500 standard format. */ @ThreadSafe -object X509CertificateSerializer : Serializer() { - val factory = CertificateFactory.getInstance("X.509") - override fun read(kryo: Kryo, input: Input, type: Class): X509Certificate { - return factory.generateCertificate(input) as X509Certificate +object X509CertificateSerializer : Serializer() { + override fun read(kryo: Kryo, input: Input, type: Class): X509CertificateHolder { + return X509CertificateHolder(input.readBytes()) } - override fun write(kryo: Kryo, output: Output, obj: X509Certificate) { + override fun write(kryo: Kryo, output: Output, obj: X509CertificateHolder) { output.writeBytes(obj.encoded) } } 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 2d687df267..beceab89f1 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509NameConstraintsTest.kt @@ -1,22 +1,23 @@ package net.corda.core.crypto +import net.corda.core.mapToArray import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter 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.CertificateFactory -import java.security.cert.PKIXParameters +import java.security.cert.* import kotlin.test.assertFailsWith import kotlin.test.assertTrue class X509NameConstraintsTest { private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair { + val converter = JcaX509CertificateConverter() val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootCACert = X509Utilities.createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Root CA"), rootKeys) @@ -29,14 +30,15 @@ class X509NameConstraintsTest { val keyPass = "password" val trustStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE) trustStore.load(null, keyPass.toCharArray()) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, converter.getCertificate(rootCACert)) val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public) val keyStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE) keyStore.load(null, keyPass.toCharArray()) - keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), arrayOf(tlsCert, clientCACert, intermediateCACert, rootCACert)) + keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), + listOf(tlsCert, clientCACert, intermediateCACert, rootCACert).mapToArray(converter::getCertificate)) return Pair(keyStore, trustStore) } diff --git a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt index 2a25d8c60f..811c88495a 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt @@ -5,29 +5,35 @@ import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate import net.corda.core.div +import net.corda.core.mapToArray import net.corda.testing.MEGA_CORP import net.corda.testing.getTestX509Name import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.BasicConstraints +import org.bouncycastle.asn1.x509.Extension +import org.bouncycastle.asn1.x509.KeyUsage +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import java.io.DataInputStream import java.io.DataOutputStream import java.io.IOException +import java.math.BigInteger import java.net.InetAddress import java.net.InetSocketAddress import java.nio.file.Path import java.security.KeyStore import java.security.PrivateKey import java.security.SecureRandom +import java.security.cert.Certificate import java.security.cert.X509Certificate import java.util.* import javax.net.ssl.* import kotlin.concurrent.thread -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue +import kotlin.test.* class X509UtilitiesTest { @Rule @@ -38,12 +44,14 @@ class X509UtilitiesTest { fun `create valid self-signed CA certificate`() { val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey) - assertTrue { caCert.subjectDN.name.contains("CN=Test Cert") } // using our subject common name - assertEquals(caCert.issuerDN, caCert.subjectDN) //self-signed - caCert.checkValidity(Date()) // throws on verification problems - caCert.verify(caKey.public) // throws on verification problems - assertTrue { caCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) - assertTrue { caCert.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate + assertTrue { caCert.subject.commonName == "Test Cert" } // using our subject common name + assertEquals(caCert.issuer, caCert.subject) //self-signed + caCert.isValidOn(Date()) // throws on verification problems + caCert.isSignatureValid(JcaContentVerifierProviderBuilder().build(caKey.public)) // throws on verification problems + val basicConstraints = BasicConstraints.getInstance(caCert.getExtension(Extension.basicConstraints).parsedValue) + val keyUsage = KeyUsage.getInstance(caCert.getExtension(Extension.keyUsage).parsedValue) + assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) + assertNull(basicConstraints.pathLenConstraint) // No length constraint specified on this CA certificate } @Test @@ -60,29 +68,33 @@ class X509UtilitiesTest { fun `create valid server certificate chain`() { val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val caCert = createSelfSignedCACertificate(getTestX509Name("Test CA Cert"), caKey) - val subjectDN = getTestX509Name("Server Cert") + val subject = getTestX509Name("Server Cert") val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) - val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subjectDN, keyPair.public) - assertTrue { serverCert.subjectDN.name.contains("CN=Server Cert") } // using our subject common name - assertEquals(caCert.issuerDN, serverCert.issuerDN) // Issued by our CA cert - serverCert.checkValidity(Date()) // throws on verification problems - serverCert.verify(caKey.public) // throws on verification problems - assertFalse { serverCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) - assertTrue { serverCert.basicConstraints == -1 } // This returns the signing path length should be -1 for non-CA certificate + val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subject, keyPair.public) + assertTrue { serverCert.subject.toString().contains("CN=Server Cert") } // using our subject common name + assertEquals(caCert.issuer, serverCert.issuer) // Issued by our CA cert + serverCert.isValidOn(Date()) // throws on verification problems + serverCert.isSignatureValid(JcaContentVerifierProviderBuilder().build(caKey.public)) // throws on verification problems + val basicConstraints = BasicConstraints.getInstance(serverCert.getExtension(Extension.basicConstraints).parsedValue) + val keyUsage = KeyUsage.getInstance(serverCert.getExtension(Extension.keyUsage).parsedValue) + assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property) + assertNull(basicConstraints.pathLenConstraint) // Non-CA certificate } @Test fun `storing EdDSA key in java keystore`() { val tmpKeyStore = tempFile("keystore.jks") + val converter = JcaX509CertificateConverter() val keyPair = generateKeyPair(EDDSA_ED25519_SHA512) val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair) - assertEquals(selfSignCert.publicKey, keyPair.public) + assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.encoded, keyPair.public.encoded)) // Save the EdDSA private key with self sign cert in the keystore. val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") - keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), arrayOf(selfSignCert)) + keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), + listOf(selfSignCert).mapToArray(converter::getCertificate)) keyStore.save(tmpKeyStore, "keystorepass") // Load the keystore from file and make sure keys are intact. @@ -105,8 +117,10 @@ class X509UtilitiesTest { val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public) // Save the EdDSA private key with cert chains. + val converter = JcaX509CertificateConverter() val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") - keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert, edDSACert)) + keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), + listOf(ecDSACert, edDSACert).mapToArray(converter::getCertificate)) keyStore.save(tmpKeyStore, "keystorepass") // Load the keystore from file and make sure keys are intact. @@ -182,23 +196,24 @@ class X509UtilitiesTest { val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass") val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass") - serverCertAndKey.certificate.checkValidity(Date()) - serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey) + serverCertAndKey.certificate.isValidOn(Date()) + serverCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(caCertAndKey.certificate.subjectPublicKeyInfo)) - assertTrue { serverCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) } + assertTrue { serverCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) } // Load back server certificate val sslKeyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass") val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass") - sslCertAndKey.certificate.checkValidity(Date()) - sslCertAndKey.certificate.verify(serverCertAndKey.certificate.publicKey) + sslCertAndKey.certificate.isValidOn(Date()) + sslCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(serverCertAndKey.certificate.subjectPublicKeyInfo)) - assertTrue { sslCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) } + assertTrue { sslCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) } // Now sign something with private key and verify against certificate public key val testData = "123456".toByteArray() val signature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData) - assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) } + val publicKey = Crypto.toSupportedPublicKey(serverCertAndKey.certificate.subjectPublicKeyInfo) + assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) } } @Test @@ -330,6 +345,7 @@ class X509UtilitiesTest { trustStoreFilePath: Path, trustStorePassword: String ): KeyStore { + val converter = JcaX509CertificateConverter() val rootCAKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val rootCACert = createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Node Root CA"), rootCAKey) @@ -339,19 +355,19 @@ class X509UtilitiesTest { val keyPass = keyPassword.toCharArray() val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword) - keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(rootCACert)) + keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(converter.getCertificate(rootCACert) as Certificate)) keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCAKeyPair.private, keyPass, - arrayOf(intermediateCACert, rootCACert)) + listOf(intermediateCACert, rootCACert).mapToArray(converter::getCertificate)) keyStore.save(keyStoreFilePath, storePassword) val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert) - trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, converter.getCertificate(rootCACert)) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, converter.getCertificate(intermediateCACert)) trustStore.save(trustStoreFilePath, trustStorePassword) @@ -360,10 +376,11 @@ class X509UtilitiesTest { @Test fun `Get correct private key type from Keystore`() { + val converter = JcaX509CertificateConverter() val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair) val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") - keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert)) + keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(converter.getCertificate(selfSignCert))) val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray()) val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword") diff --git a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt index 521e0f2b20..6250b85108 100644 --- a/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt +++ b/core/src/test/kotlin/net/corda/core/serialization/KryoTests.kt @@ -10,6 +10,7 @@ import net.corda.node.services.persistence.NodeAttachmentService import net.corda.testing.BOB_PUBKEY import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy +import org.bouncycastle.cert.X509CertificateHolder import org.junit.Before import org.junit.Test import org.slf4j.LoggerFactory @@ -142,10 +143,10 @@ class KryoTests { } @Test - fun `serialize - deserialize X509Certififcate`() { - val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) + fun `serialize - deserialize X509CertififcateHolder`() { + val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) val serialized = expected.serialize(kryo).bytes - val actual: X509Certificate = serialized.deserialize(kryo) + val actual: X509CertificateHolder = serialized.deserialize(kryo) assertEquals(expected, actual) } diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt index f32f267a82..d4bb517abf 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityAsNodeTest.kt @@ -19,6 +19,7 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.NameConstraints +import org.bouncycastle.cert.path.CertPath import org.junit.Test import java.nio.file.Files @@ -111,7 +112,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() { X509Utilities.CORDA_CLIENT_CA, clientKey.private, keyPass, - arrayOf(clientCACert, intermediateCA.certificate, rootCACert)) + CertPath(arrayOf(clientCACert, intermediateCA.certificate, rootCACert))) clientCAKeystore.save(nodeKeystore, keyStorePassword) val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeystore, keyStorePassword) @@ -119,7 +120,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() { X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass, - arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert)) + CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))) tlsKeystore.save(sslKeystore, keyStorePassword) } } 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 dbe68f221c..60cd48af1b 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -57,6 +57,7 @@ import net.corda.node.utilities.transaction import org.apache.activemq.artemis.utils.ReusableLatch import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.jetbrains.exposed.sql.Database import org.slf4j.Logger import java.io.IOException @@ -671,8 +672,10 @@ private class KeyStoreWrapper(private val storePath: Path, private val storePass fun save(serviceName: X500Name, privateKeyAlias: String, keyPair: KeyPair) { val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, storePassword) + val converter = JcaX509CertificateConverter() val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public) - keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), arrayOf(cert, *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA))) + keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), + arrayOf(converter.getCertificate(cert), *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA))) keyStore.save(storePath, storePassword) } } 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 a1f16b0b7d..5e60a04cfd 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 @@ -11,6 +11,8 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import java.security.InvalidAlgorithmParameterException import java.security.PublicKey import java.security.cert.* @@ -82,8 +84,9 @@ class InMemoryIdentityService(identities: Iterable = emptySet(), override fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? = partyToPath[anonymousParty] @Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class) - override fun registerPath(trustedRoot: X509Certificate, anonymousParty: AnonymousParty, path: CertPath) { - val expectedTrustAnchor = TrustAnchor(trustedRoot, null) + override fun registerPath(trustedRoot: X509CertificateHolder, anonymousParty: AnonymousParty, path: CertPath) { + val converter = JcaX509CertificateConverter() + val expectedTrustAnchor = TrustAnchor(converter.getCertificate(trustedRoot), null) require(path.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" } val target = path.certificates.last() as X509Certificate require(target.publicKey == anonymousParty.owningKey) { "Certificate path must end with anonymous party's public key" } diff --git a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt index cb4d6ca321..85d90980bc 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/E2ETestKeyManagementService.kt @@ -9,11 +9,12 @@ import net.corda.core.identity.Party import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SingletonSerializeAsToken +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.operator.ContentSigner import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey import java.security.cert.CertPath -import java.security.cert.X509Certificate import java.util.* import javax.annotation.concurrent.ThreadSafe @@ -56,7 +57,7 @@ class E2ETestKeyManagementService(val identityService: IdentityService, return keyPair.public } - override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair = freshKeyAndCert(this, identityService, identity, revocationEnabled) + override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair = freshKeyAndCert(this, identityService, identity, revocationEnabled) private fun getSigningKeyPair(publicKey: PublicKey): KeyPair { return mutex.locked { 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 24d7fd51bc..03f1dae235 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 @@ -1,12 +1,17 @@ package net.corda.node.services.keys import net.corda.core.crypto.CertificateType +import net.corda.core.crypto.ContentSignerBuilder import net.corda.core.crypto.Crypto import net.corda.core.crypto.X509Utilities import net.corda.core.identity.AnonymousParty import net.corda.core.identity.Party import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.operator.ContentSigner +import java.security.KeyPair +import java.security.Security import java.security.cert.CertPath import java.security.cert.X509Certificate @@ -23,7 +28,7 @@ import java.security.cert.X509Certificate fun freshKeyAndCert(keyManagementService: KeyManagementService, identityService: IdentityService, identity: Party, - revocationEnabled: Boolean = false): Pair { + revocationEnabled: Boolean = false): Pair { val ourPublicKey = keyManagementService.freshKey() // FIXME: Use the actual certificate for the identity the flow is presenting themselves as val issuerKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME) diff --git a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt index 532e05605b..53233a6002 100644 --- a/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt +++ b/node/src/main/kotlin/net/corda/node/services/keys/PersistentKeyManagementService.kt @@ -10,13 +10,14 @@ import net.corda.core.node.services.IdentityService import net.corda.core.node.services.KeyManagementService import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.node.utilities.* +import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.operator.ContentSigner import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.statements.InsertStatement import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey import java.security.cert.CertPath -import java.security.cert.X509Certificate /** * A persistent re-implementation of [E2ETestKeyManagementService] to support node re-start. @@ -66,8 +67,7 @@ class PersistentKeyManagementService(val identityService: IdentityService, } return keyPair.public } - - override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair = freshKeyAndCert(this, identityService, identity, revocationEnabled) + override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair = freshKeyAndCert(this, identityService, identity, revocationEnabled) private fun getSigningKeyPair(publicKey: PublicKey): KeyPair { return mutex.locked { diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index 23c6e1a33a..8d019f95a5 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -263,10 +263,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword) val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_TLS) - val ourSubjectDN = X500Name(ourCertificate.subjectDN.name) // This is a sanity check and should not fail unless things have been misconfigured - require(ourSubjectDN == config.myLegalName) { - "Legal name does not match with our subject CN: $ourSubjectDN" + require(ourCertificate.subject == config.myLegalName) { + "Legal name does not match with our subject CN: ${ourCertificate.subject}" } val defaultCertPolicies = mapOf( PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch, @@ -510,7 +509,7 @@ private class VerifyingNettyConnector(configuration: MutableMap, "Peer has wrong subject name in the certificate - expected $expectedLegalName but got ${peerCertificate.subject}. This is either a fatal " + "misconfiguration by the remote peer or an SSL man-in-the-middle attack!" } - X509Utilities.validateCertificateChain(X509CertImpl(session.localCertificates.last().encoded), *session.peerCertificates) + X509Utilities.validateCertificateChain(X509CertificateHolder(session.localCertificates.last().encoded), *session.peerCertificates) server.onTcpConnection(peerLegalName) } catch (e: IllegalArgumentException) { connection.close() diff --git a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt index 144223d4ca..7c65f3d02c 100644 --- a/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt +++ b/node/src/main/kotlin/net/corda/node/services/network/NetworkMapService.kt @@ -2,7 +2,9 @@ package net.corda.node.services.network import com.google.common.annotations.VisibleForTesting import net.corda.core.ThreadBox -import net.corda.core.crypto.* +import net.corda.core.crypto.DigitalSignature +import net.corda.core.crypto.SignedData +import net.corda.core.crypto.isFulfilledBy import net.corda.core.identity.Party import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.SingleMessageRecipient 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 54343b58a3..79a7f8e69f 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 @@ -6,6 +6,8 @@ import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.node.services.config.NodeConfiguration +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.cert.path.CertPath import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemObject import java.io.StringWriter @@ -40,7 +42,8 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair) // Save to the key store. - caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert)) + caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), + CertPath(arrayOf(selfSignCert))) caKeyStore.save(config.nodeKeystore, keystorePassword) } val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) @@ -69,11 +72,13 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: println("Node private key and certificate stored in ${config.nodeKeystore}.") println("Generating SSL certificate for node messaging service.") + val converter = JcaX509CertificateConverter() val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA) val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, caCert.subject, sslKey.public) val sslKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.sslKeystore, keystorePassword) - sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates)) + sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), + arrayOf(converter.getCertificate(sslCert), *certificates)) sslKeyStore.save(config.sslKeystore, config.keyStorePassword) println("SSL private key and certificate stored in ${config.sslKeystore}.") // All done, clean up temp files. diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt index c1d47292f8..0ada571887 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkisRegistrationHelperTest.kt @@ -5,10 +5,12 @@ import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.mock import net.corda.core.crypto.* import net.corda.core.exists +import net.corda.core.mapToArray import net.corda.core.utilities.ALICE import net.corda.testing.TestNodeConfiguration import net.corda.testing.getTestX509Name import org.bouncycastle.cert.X509CertificateHolder +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder @@ -29,8 +31,9 @@ class NetworkRegistrationHelperTest { "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA") .map { getTestX509Name(it) } + val converter = JcaX509CertificateConverter() val certs = identities.map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) } - .toTypedArray() + .mapToArray(converter::getCertificate) val certService: NetworkRegistrationService = mock { on { submitRequest(any()) }.then { id } diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt index c50f00a420..c09305def7 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/MockServices.kt @@ -21,6 +21,7 @@ import net.corda.node.services.vault.NodeVaultService import net.corda.testing.MEGA_CORP import net.corda.testing.MINI_CORP import net.corda.testing.MOCK_VERSION_INFO +import org.bouncycastle.cert.X509CertificateHolder import rx.Observable import rx.subjects.PublishSubject import java.io.ByteArrayInputStream @@ -32,7 +33,6 @@ import java.security.KeyPair import java.security.PrivateKey import java.security.PublicKey import java.security.cert.CertPath -import java.security.cert.X509Certificate import java.time.Clock import java.util.* import java.util.jar.JarInputStream @@ -91,7 +91,7 @@ class MockKeyManagementService(val identityService: IdentityService, return k.public } - override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair = freshKeyAndCert(this, identityService, identity, revocationEnabled) + override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair = freshKeyAndCert(this, identityService, identity, revocationEnabled) private fun getSigningKeyPair(publicKey: PublicKey): KeyPair { val pk = publicKey.keys.first { keyStore.containsKey(it) } diff --git a/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt b/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt index ea70e70a25..0fb681071c 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/node/SimpleNode.kt @@ -3,6 +3,7 @@ package net.corda.testing.node import com.codahale.metrics.MetricRegistry import com.google.common.net.HostAndPort import com.google.common.util.concurrent.SettableFuture +import net.corda.core.crypto.CertificateAndKeyPair import net.corda.core.crypto.commonName import net.corda.core.crypto.generateKeyPair import net.corda.core.messaging.RPCOps @@ -30,7 +31,9 @@ import kotlin.concurrent.thread * This is a bare-bones node which can only send and receive messages. It doesn't register with a network map service or * any other such task that would make it functional in a network and thus left to the user to do so manually. */ -class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeLocalHostAndPort(), rpcAddress: HostAndPort = freeLocalHostAndPort()) : AutoCloseable { +class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeLocalHostAndPort(), + rpcAddress: HostAndPort = freeLocalHostAndPort(), + networkRoot: CertificateAndKeyPair? = null) : AutoCloseable { private val databaseWithCloseable: Pair = configureDatabase(config.dataSourceProperties) val database: Database get() = databaseWithCloseable.second