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