diff --git a/core/src/main/kotlin/net/corda/core/crypto/ContentSignerBuilder.kt b/core/src/main/kotlin/net/corda/core/crypto/ContentSignerBuilder.kt new file mode 100644 index 0000000000..ed3222bf18 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/ContentSignerBuilder.kt @@ -0,0 +1,39 @@ +package net.corda.core.crypto + +import org.bouncycastle.asn1.x509.AlgorithmIdentifier +import org.bouncycastle.operator.ContentSigner +import java.io.OutputStream +import java.security.PrivateKey +import java.security.Provider +import java.security.SecureRandom +import java.security.Signature + +/** + * Provide extra OID look up for signature algorithm not supported by bouncy castle. + * This builder will use bouncy castle's JcaContentSignerBuilder as fallback for unknown algorithm. + */ +object ContentSignerBuilder { + fun build(signatureScheme: SignatureScheme, privateKey: PrivateKey, provider: Provider?, random: SecureRandom? = null): ContentSigner { + val sigAlgId = AlgorithmIdentifier(signatureScheme.signatureOID) + val sig = Signature.getInstance(signatureScheme.signatureName, provider).apply { + if (random != null) { + initSign(privateKey, random) + } else { + initSign(privateKey) + } + } + return object : ContentSigner { + private val stream = SignatureOutputStream(sig) + override fun getAlgorithmIdentifier(): AlgorithmIdentifier = sigAlgId + override fun getOutputStream(): OutputStream = stream + override fun getSignature(): ByteArray = stream.signature + } + } + + private class SignatureOutputStream(private val sig: Signature) : OutputStream() { + internal val signature: ByteArray get() = sig.sign() + override fun write(bytes: ByteArray, off: Int, len: Int) = sig.update(bytes, off, len) + override fun write(bytes: ByteArray) = sig.update(bytes) + override fun write(b: Int) = sig.update(b.toByte()) + } +} 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 21593d46da..1be9015c69 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/Crypto.kt @@ -1,18 +1,37 @@ package net.corda.core.crypto +import net.corda.core.random63BitValue import net.i2p.crypto.eddsa.EdDSAEngine import net.i2p.crypto.eddsa.EdDSAKey import net.i2p.crypto.eddsa.EdDSASecurityProvider import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable +import org.bouncycastle.asn1.ASN1EncodableVector +import org.bouncycastle.asn1.ASN1ObjectIdentifier +import org.bouncycastle.asn1.DERSequence +import org.bouncycastle.asn1.bc.BCObjectIdentifiers +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.* +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers +import org.bouncycastle.cert.bc.BcX509ExtensionUtils +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder +import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.interfaces.ECKey import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.pkcs.PKCS10CertificationRequest +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec +import java.math.BigInteger import java.security.* +import java.security.cert.X509Certificate import java.security.spec.InvalidKeySpecException import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec +import java.util.* /** * This object controls and provides the available and supported signature schemes for Corda. @@ -28,15 +47,6 @@ import java.security.spec.X509EncodedKeySpec * */ object Crypto { - // This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider - // that could cause unexpected and suspicious behaviour. - // i.e. if someone removes a Provider and then he/she adds a new one with the same name. - // The val is private to avoid any harmful state changes. - private val providerMap = mapOf( - EdDSASecurityProvider.PROVIDER_NAME to EdDSASecurityProvider(), - BouncyCastleProvider.PROVIDER_NAME to BouncyCastleProvider(), - "BCPQC" to BouncyCastlePQCProvider()) // unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it. - /** * RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function. * Note: Recommended key size >= 3072 bits. @@ -44,6 +54,7 @@ object Crypto { val RSA_SHA256 = SignatureScheme( 1, "RSA_SHA256", + PKCSObjectIdentifiers.id_RSASSA_PSS, BouncyCastleProvider.PROVIDER_NAME, "RSA", "SHA256WITHRSAANDMGF1", @@ -56,6 +67,7 @@ object Crypto { val ECDSA_SECP256K1_SHA256 = SignatureScheme( 2, "ECDSA_SECP256K1_SHA256", + X9ObjectIdentifiers.ecdsa_with_SHA256, BouncyCastleProvider.PROVIDER_NAME, "ECDSA", "SHA256withECDSA", @@ -68,6 +80,7 @@ object Crypto { val ECDSA_SECP256R1_SHA256 = SignatureScheme( 3, "ECDSA_SECP256R1_SHA256", + X9ObjectIdentifiers.ecdsa_with_SHA256, BouncyCastleProvider.PROVIDER_NAME, "ECDSA", "SHA256withECDSA", @@ -80,7 +93,9 @@ object Crypto { val EDDSA_ED25519_SHA512 = SignatureScheme( 4, "EDDSA_ED25519_SHA512", - EdDSASecurityProvider.PROVIDER_NAME, + ASN1ObjectIdentifier("1.3.101.112"), + // We added EdDSA to bouncy castle for certificate signing. + BouncyCastleProvider.PROVIDER_NAME, EdDSAKey.KEY_ALGORITHM, EdDSAEngine.SIGNATURE_ALGORITHM, EdDSANamedCurveTable.getByName("ED25519"), @@ -95,6 +110,7 @@ object Crypto { val SPHINCS256_SHA256 = SignatureScheme( 5, "SPHINCS-256_SHA512", + BCObjectIdentifiers.sphincs256_with_SHA512, "BCPQC", "SPHINCS256", "SHA512WITHSPHINCS256", @@ -119,6 +135,25 @@ object Crypto { SPHINCS256_SHA256 ).associateBy { it.schemeCodeName } + // This map is required to defend against users that forcibly call Security.addProvider / Security.removeProvider + // that could cause unexpected and suspicious behaviour. + // i.e. if someone removes a Provider and then he/she adds a new one with the same name. + // The val is private to avoid any harmful state changes. + private val providerMap: Map = mapOf( + BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(), + "BCPQC" to BouncyCastlePQCProvider()) // unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it. + + private fun getBouncyCastleProvider() = BouncyCastleProvider().apply { + putAll(EdDSASecurityProvider()) + addKeyInfoConverter(EDDSA_ED25519_SHA512.signatureOID, KeyInfoConverter(EDDSA_ED25519_SHA512)) + } + + init { + // This registration is needed for reading back EdDSA key from java keystore. + // TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider. + Security.addProvider(getBouncyCastleProvider()) + } + /** * Factory pattern to retrieve the corresponding [SignatureScheme] based on the type of the [String] input. * This function is usually called by key generators and verify signature functions. @@ -170,7 +205,7 @@ object Crypto { */ @Throws(IllegalArgumentException::class) fun decodePrivateKey(encodedKey: ByteArray): PrivateKey { - for ((_, _, providerName, algorithmName) in supportedSignatureSchemes.values) { + for ((_, _, _, providerName, algorithmName) in supportedSignatureSchemes.values) { try { return KeyFactory.getInstance(algorithmName, providerMap[providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey)) } catch (ikse: InvalidKeySpecException) { @@ -217,7 +252,7 @@ object Crypto { */ @Throws(IllegalArgumentException::class) fun decodePublicKey(encodedKey: ByteArray): PublicKey { - for ((_, _, providerName, algorithmName) in supportedSignatureSchemes.values) { + for ((_, _, _, providerName, algorithmName) in supportedSignatureSchemes.values) { try { return KeyFactory.getInstance(algorithmName, providerMap[providerName]).generatePublic(X509EncodedKeySpec(encodedKey)) } catch (ikse: InvalidKeySpecException) { @@ -459,12 +494,13 @@ object Crypto { /** * Generate a [KeyPair] for the selected [SignatureScheme]. * Note that RSA is the sole algorithm initialized specifically by its supported keySize. - * @param signatureScheme a supported [SignatureScheme], see [Crypto]. + * @param signatureScheme a supported [SignatureScheme], see [Crypto], default to [DEFAULT_SIGNATURE_SCHEME] if not provided. * @return a new [KeyPair] for the requested [SignatureScheme]. * @throws IllegalArgumentException if the requested signature scheme is not supported. */ @Throws(IllegalArgumentException::class) - fun generateKeyPair(signatureScheme: SignatureScheme): KeyPair { + @JvmOverloads + fun generateKeyPair(signatureScheme: SignatureScheme = DEFAULT_SIGNATURE_SCHEME): KeyPair { if (!supportedSignatureSchemes.containsKey(signatureScheme.schemeCodeName)) throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName") val keyPairGenerator = KeyPairGenerator.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]) @@ -475,13 +511,50 @@ object Crypto { return keyPairGenerator.generateKeyPair() } - /** - * Generate a [KeyPair] using the default signature scheme. - * @return a new [KeyPair]. - */ - fun generateKeyPair(): KeyPair = generateKeyPair(DEFAULT_SIGNATURE_SCHEME) - /** Check if the requested signature scheme is supported by the system. */ fun isSupportedSignatureScheme(schemeCodeName: String): Boolean = schemeCodeName in supportedSignatureSchemes + fun isSupportedSignatureScheme(signatureScheme: SignatureScheme): Boolean = signatureScheme.schemeCodeName in supportedSignatureSchemes + + /** + * Use bouncy castle utilities to sign completed X509 certificate with CA cert private key + */ + fun createCertificate(issuer: X500Name, issuerKeyPair: KeyPair, + subject: X500Name, subjectPublicKey: PublicKey, + keyUsage: KeyUsage, purposes: List, + signatureScheme: SignatureScheme, validityWindow: Pair, + pathLength: Int? = null, subjectAlternativeName: List? = null): X509Certificate { + + val provider = providerMap[signatureScheme.providerName] + val serial = BigInteger.valueOf(random63BitValue()) + val keyPurposes = DERSequence(ASN1EncodableVector().apply { purposes.forEach { add(it) } }) + + val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey) + .addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(subjectPublicKey.encoded))) + .addExtension(Extension.basicConstraints, pathLength != null, if (pathLength == null) BasicConstraints(false) else BasicConstraints(pathLength)) + .addExtension(Extension.keyUsage, false, keyUsage) + .addExtension(Extension.extendedKeyUsage, false, keyPurposes) + + if (subjectAlternativeName != null && subjectAlternativeName.isNotEmpty()) { + builder.addExtension(Extension.subjectAlternativeName, false, DERSequence(subjectAlternativeName.toTypedArray())) + } + val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider) + return JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer)).apply { + checkValidity(Date()) + verify(issuerKeyPair.public, provider) + } + } + + /** + * Create certificate signing request using provided information. + */ + fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme): PKCS10CertificationRequest { + val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, providerMap[signatureScheme.providerName]) + return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public).build(signer) + } + + private class KeyInfoConverter(val signatureScheme: SignatureScheme) : AsymmetricKeyInfoConverter { + override fun generatePublic(keyInfo: SubjectPublicKeyInfo?): PublicKey? = keyInfo?.let { decodePublicKey(signatureScheme, it.encoded) } + override fun generatePrivate(keyInfo: PrivateKeyInfo?): PrivateKey? = keyInfo?.let { decodePrivateKey(signatureScheme, it.encoded) } + } } diff --git a/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt b/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt new file mode 100644 index 0000000000..e07ef9d8c5 --- /dev/null +++ b/core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt @@ -0,0 +1,136 @@ +package net.corda.core.crypto + +import net.corda.core.exists +import net.corda.core.read +import net.corda.core.write +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.nio.file.Path +import java.security.* +import java.security.cert.Certificate +import java.security.cert.X509Certificate + +object KeyStoreUtilities { + val KEYSTORE_TYPE = "JKS" + + /** + * Helper method to either open an existing keystore for modification, or create a new blank keystore. + * @param keyStoreFilePath location of KeyStore file + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened/created + */ + fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { + val pass = storePassword.toCharArray() + val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) + if (keyStoreFilePath.exists()) { + keyStoreFilePath.read { keyStore.load(it, pass) } + } else { + keyStore.load(null, pass) + keyStoreFilePath.write { keyStore.store(it, pass) } + } + return keyStore + } + + /** + * Helper method to open an existing keystore for modification/read + * @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened + * @throws IOException if there was an error reading the key store from the file. + * @throws KeyStoreException if the password is incorrect or the key store is damaged. + */ + @Throws(KeyStoreException::class, IOException::class) + fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { + return keyStoreFilePath.read { loadKeyStore(it, storePassword) } + } + + /** + * Helper method to open an existing keystore for modification/read + * @param input stream containing a KeyStore e.g. loaded from a resource file + * @param storePassword password to open the store. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + * @return returns the KeyStore opened + * @throws IOException if there was an error reading the key store from the stream. + * @throws KeyStoreException if the password is incorrect or the key store is damaged. + */ + @Throws(KeyStoreException::class, IOException::class) + fun loadKeyStore(input: InputStream, storePassword: String): KeyStore { + val pass = storePassword.toCharArray() + val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) + input.use { + keyStore.load(input, pass) + } + return keyStore + } +} + +/** + * 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: Array) { + if (containsAlias(alias)) { + this.deleteEntry(alias) + } + this.setKeyEntry(alias, key, password, chain) +} + +/** + * Helper extension method to add, or overwrite any public certificate data in store + * @param alias name to record the public certificate under + * @param cert certificate to store + */ +fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) { + if (containsAlias(alias)) { + this.deleteEntry(alias) + } + this.setCertificateEntry(alias, cert) +} + + +/** + * Helper method save KeyStore to storage + * @param keyStoreFilePath the file location to save to + * @param storePassword password to access the store in future. This does not have to be the same password as any keys stored, + * but for SSL purposes this is recommended. + */ +fun KeyStore.save(keyStoreFilePath: Path, storePassword: String) = keyStoreFilePath.write { store(it, storePassword) } + +fun KeyStore.store(out: OutputStream, password: String) = store(out, password.toCharArray()) + + +/** + * Extract public and private keys from a KeyStore file assuming storage alias is known. + * @param keyPassword Password to unlock the private key entries + * @param alias The name to lookup the Key and Certificate chain from + * @return The KeyPair found in the KeyStore under the specified alias + */ +fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertificateAndKey(alias, keyPassword).keyPair + +/** + * Helper method to load a Certificate and KeyPair from their KeyStore. + * The access details should match those of the createCAKeyStoreAndTrustStore call used to manufacture the keys. + * @param keyPassword The password for the PrivateKey (not the store access password) + * @param alias The name to search for the data. Typically if generated with the methods here this will be one of + * CERT_PRIVATE_KEY_ALIAS, ROOT_CA_CERT_PRIVATE_KEY_ALIAS, INTERMEDIATE_CA_PRIVATE_KEY_ALIAS defined above + */ +fun KeyStore.getCertificateAndKey(alias: String, keyPassword: String): CertificateAndKey { + val keyPass = keyPassword.toCharArray() + val key = getKey(alias, keyPass) as PrivateKey + val cert = getCertificate(alias) as X509Certificate + return CertificateAndKey(cert, KeyPair(cert.publicKey, key)) +} + +/** + * Extract public X509 certificate from a KeyStore file assuming storage alias is know + * @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 diff --git a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt index 39031fe6c9..8f61f1b66d 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SignatureScheme.kt @@ -1,5 +1,6 @@ package net.corda.core.crypto +import org.bouncycastle.asn1.ASN1ObjectIdentifier import java.security.Signature import java.security.spec.AlgorithmParameterSpec @@ -7,6 +8,7 @@ import java.security.spec.AlgorithmParameterSpec * This class is used to define a digital signature scheme. * @param schemeNumberID we assign a number ID for more efficient on-wire serialisation. Please ensure uniqueness between schemes. * @param schemeCodeName code name for this signature scheme (e.g. RSA_SHA256, ECDSA_SECP256K1_SHA256, ECDSA_SECP256R1_SHA256, EDDSA_ED25519_SHA512, SPHINCS-256_SHA512). + * @param signatureOID object identifier of the signature algorithm (e.g 1.3.101.112 for EdDSA) * @param providerName the provider's name (e.g. "BC"). * @param algorithmName which signature algorithm is used (e.g. RSA, ECDSA. EdDSA, SPHINCS-256). * @param signatureName a signature-scheme name as required to create [Signature] objects (e.g. "SHA256withECDSA") @@ -18,6 +20,7 @@ import java.security.spec.AlgorithmParameterSpec data class SignatureScheme( val schemeNumberID: Int, val schemeCodeName: String, + val signatureOID: ASN1ObjectIdentifier, val providerName: String, val algorithmName: String, val signatureName: String, 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 8bde11dbec..d32c2ac13d 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/X509Utilities.kt @@ -1,53 +1,32 @@ package net.corda.core.crypto -import net.corda.core.exists -import net.corda.core.random63BitValue -import net.corda.core.read -import net.corda.core.write -import org.bouncycastle.asn1.ASN1Encodable -import org.bouncycastle.asn1.ASN1EncodableVector -import org.bouncycastle.asn1.DERSequence +import net.corda.core.crypto.Crypto.generateKeyPair 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.asn1.x509.GeneralName +import org.bouncycastle.asn1.x509.KeyPurposeId +import org.bouncycastle.asn1.x509.KeyUsage 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.jce.provider.BouncyCastleProvider import org.bouncycastle.openssl.jcajce.JcaPEMWriter -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder -import org.bouncycastle.pkcs.PKCS10CertificationRequest -import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder import org.bouncycastle.util.IPAddress import org.bouncycastle.util.io.pem.PemReader import java.io.FileReader import java.io.FileWriter -import java.io.IOException import java.io.InputStream -import java.math.BigInteger import java.net.InetAddress import java.nio.file.Path -import java.security.* -import java.security.cert.Certificate +import java.security.KeyPair +import java.security.KeyStore +import java.security.PublicKey import java.security.cert.CertificateFactory import java.security.cert.X509Certificate -import java.security.spec.ECGenParameterSpec import java.time.Instant import java.time.temporal.ChronoUnit import java.util.* object X509Utilities { - - val SIGNATURE_ALGORITHM = "SHA256withECDSA" - val KEY_GENERATION_ALGORITHM = "ECDSA" - // TLS implementations only support standard SEC2 curves, although internally Corda uses newer EDDSA keys. - // Also browsers like Chrome don't seem to support the secp256k1, only the secp256r1 curve. - val ECDSA_CURVE = "secp256r1" - - val KEYSTORE_TYPE = "JKS" + val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256 // Aliases for private keys and certificates. val CORDA_ROOT_CA_PRIVATE_KEY = "cordarootcaprivatekey" @@ -57,10 +36,12 @@ object X509Utilities { val CORDA_CLIENT_CA_PRIVATE_KEY = "cordaclientcaprivatekey" val CORDA_CLIENT_CA = "cordaclientca" - init { - Security.addProvider(BouncyCastleProvider()) // register Bouncy Castle Crypto Provider required to sign certificates - } + private val CA_KEY_USAGE = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign) + private val CLIENT_KEY_USAGE = KeyUsage(KeyUsage.digitalSignature) + private val CA_KEY_PURPOSES = listOf(KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage) + private val CLIENT_KEY_PURPOSES = listOf(KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth) + private val DEFAULT_VALIDITY_WINDOW = Pair(0, 365 * 10) /** * Helper method to get a notBefore and notAfter pair from current day bounded by parent certificate validity range * @param daysBefore number of days to roll back returned start date relative to current date @@ -72,352 +53,87 @@ object X509Utilities { */ private fun getCertificateValidityWindow(daysBefore: Int, daysAfter: Int, parentNotBefore: Date? = null, parentNotAfter: Date? = null): Pair { val startOfDayUTC = Instant.now().truncatedTo(ChronoUnit.DAYS) - - var notBefore = Date.from(startOfDayUTC.minus(daysBefore.toLong(), ChronoUnit.DAYS)) - if (parentNotBefore != null) { - if (parentNotBefore.after(notBefore)) { - notBefore = parentNotBefore - } + val notBefore = Date.from(startOfDayUTC.minus(daysBefore.toLong(), ChronoUnit.DAYS)).let { notBefore -> + if (parentNotBefore != null && parentNotBefore.after(notBefore)) parentNotBefore else notBefore } - - var notAfter = Date.from(startOfDayUTC.plus(daysAfter.toLong(), ChronoUnit.DAYS)) - if (parentNotAfter != null) { - if (parentNotAfter.after(notAfter)) { - notAfter = parentNotAfter - } + val notAfter = Date.from(startOfDayUTC.plus(daysAfter.toLong(), ChronoUnit.DAYS)).let { notAfter -> + if (parentNotAfter != null && parentNotAfter.after(notAfter)) parentNotAfter else notAfter } - return Pair(notBefore, notAfter) } /** - * Encode provided public key in correct format for inclusion in certificate issuer/subject fields - */ - private fun createSubjectKeyIdentifier(key: Key): SubjectKeyIdentifier { - val info = SubjectPublicKeyInfo.getInstance(key.encoded) - return BcX509ExtensionUtils().createSubjectKeyIdentifier(info) - } - - /** - * Use bouncy castle utilities to sign completed X509 certificate with CA cert private key - */ - private fun signCertificate(certificateBuilder: X509v3CertificateBuilder, signedWithPrivateKey: PrivateKey): X509Certificate { - val signer = JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(signedWithPrivateKey) - return JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateBuilder.build(signer)) - } - - /** - * Return a bogus X509 for dev purposes. Use [getX509Name] for something more real. + * Return a bogus X509 for dev purposes. */ @Deprecated("Full legal names should be specified in all configurations") fun getDevX509Name(commonName: String): X500Name { - val nameBuilder = X500NameBuilder(BCStyle.INSTANCE) - nameBuilder.addRDN(BCStyle.CN, commonName) - nameBuilder.addRDN(BCStyle.O, "R3") - nameBuilder.addRDN(BCStyle.OU, "corda") - nameBuilder.addRDN(BCStyle.L, "London") - nameBuilder.addRDN(BCStyle.C, "UK") - return nameBuilder.build() - } - - fun getX509Name(myLegalName: String, nearestCity: String, email: String): X500Name { return X500NameBuilder(BCStyle.INSTANCE) - .addRDN(BCStyle.CN, myLegalName) - .addRDN(BCStyle.L, nearestCity) - .addRDN(BCStyle.E, email).build() + .addRDN(BCStyle.CN, commonName) + .addRDN(BCStyle.O, "R3") + .addRDN(BCStyle.OU, "corda") + .addRDN(BCStyle.L, "London") + .addRDN(BCStyle.C, "UK") + .build() } - /** - * Helper method to either open an existing keystore for modification, or create a new blank keystore. - * @param keyStoreFilePath location of KeyStore file - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened/created - */ - fun loadOrCreateKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { - val pass = storePassword.toCharArray() - val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - if (keyStoreFilePath.exists()) { - keyStoreFilePath.read { keyStore.load(it, pass) } - } else { - keyStore.load(null, pass) - keyStoreFilePath.write { keyStore.store(it, pass) } - } - return keyStore - } - - /** - * Helper method to open an existing keystore for modification/read - * @param keyStoreFilePath location of KeyStore file which must exist, or this will throw FileNotFoundException - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened - * @throws IOException if there was an error reading the key store from the file. - * @throws KeyStoreException if the password is incorrect or the key store is damaged. - */ - @Throws(KeyStoreException::class, IOException::class) - fun loadKeyStore(keyStoreFilePath: Path, storePassword: String): KeyStore { - val pass = storePassword.toCharArray() - val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - keyStoreFilePath.read { keyStore.load(it, pass) } - return keyStore - } - - /** - * Helper method to open an existing keystore for modification/read - * @param input stream containing a KeyStore e.g. loaded from a resource file - * @param storePassword password to open the store. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - * @return returns the KeyStore opened - * @throws IOException if there was an error reading the key store from the stream. - * @throws KeyStoreException if the password is incorrect or the key store is damaged. - */ - @Throws(KeyStoreException::class, IOException::class) - fun loadKeyStore(input: InputStream, storePassword: String): KeyStore { - val pass = storePassword.toCharArray() - val keyStore = KeyStore.getInstance(KEYSTORE_TYPE) - input.use { - keyStore.load(input, pass) - } - return keyStore - } - - /** - * Helper method save KeyStore to storage - * @param keyStore the KeyStore to persist - * @param keyStoreFilePath the file location to save to - * @param storePassword password to access the store in future. This does not have to be the same password as any keys stored, - * but for SSL purposes this is recommended. - */ - fun saveKeyStore(keyStore: KeyStore, keyStoreFilePath: Path, storePassword: String) { - val pass = storePassword.toCharArray() - keyStoreFilePath.write { keyStore.store(it, pass) } - } - - /** - * 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: Array) { - try { - this.deleteEntry(alias) - } catch (kse: KeyStoreException) { - // ignore as may not exist in keystore yet - } - this.setKeyEntry(alias, key, password, chain) - } - - /** - * Helper extension method to add, or overwrite any public certificate data in store - * @param alias name to record the public certificate under - * @param cert certificate to store - */ - fun KeyStore.addOrReplaceCertificate(alias: String, cert: Certificate) { - try { - this.deleteEntry(alias) - } catch (kse: KeyStoreException) { - // ignore as may not exist in keystore yet - } - this.setCertificateEntry(alias, cert) - } - - - /** - * Generate a standard curve ECDSA KeyPair suitable for TLS, although the rest of Corda uses newer curves. - * @return The generated Public/Private KeyPair - */ - fun generateECDSAKeyPairForSSL(): KeyPair { - val keyGen = KeyPairGenerator.getInstance(KEY_GENERATION_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME) - val ecSpec = ECGenParameterSpec(ECDSA_CURVE) // Force named curve, because TLS implementations don't support many curves - keyGen.initialize(ecSpec, newSecureRandom()) - return keyGen.generateKeyPair() - } - - /** - * Create certificate signing request using provided information. - * - * @param commonName The legal name of your organization. This should not be abbreviated and should include suffixes such as Inc, Corp, or LLC. - * @param nearestCity The city where your organization is located. - * @param email An email address used to contact your organization. - * @param keyPair Standard curve ECDSA KeyPair generated for TLS. - * @return The generated Certificate signing request. - */ - @Deprecated("Use [createCertificateSigningRequest(X500Name, KeyPair)] instead, specifying full legal name") - fun createCertificateSigningRequest(commonName: String, nearestCity: String, email: String, keyPair: KeyPair): PKCS10CertificationRequest = createCertificateSigningRequest(getX509Name(commonName, nearestCity, email), keyPair) - - /** - * Create certificate signing request using provided information. - * - * @param myLegalName The legal name of your organization. This should not be abbreviated and should include suffixes such as Inc, Corp, or LLC. - * @param nearestCity The city where your organization is located. - * @param email An email address used to contact your organization. - * @param keyPair Standard curve ECDSA KeyPair generated for TLS. - * @return The generated Certificate signing request. - */ - fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair): PKCS10CertificationRequest { - val signer = JcaContentSignerBuilder(SIGNATURE_ALGORITHM) - .setProvider(BouncyCastleProvider.PROVIDER_NAME) - .build(keyPair.private) - return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public).build(signer) - } - - /** - * Helper data class to pass around public certificate and [KeyPair] entities when using CA certs. - */ - data class CACertAndKey(val certificate: X509Certificate, val keyPair: KeyPair) - /** * Create a de novo root self-signed X509 v3 CA cert and [KeyPair]. * @param subject the cert Subject will be populated with the domain string + * @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided. + * @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided. * @return A data class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates. * Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates */ - fun createSelfSignedCACert(subject: X500Name): CACertAndKey { - val keyPair = generateECDSAKeyPairForSSL() - - val issuer = subject - val serial = BigInteger.valueOf(random63BitValue()) - val pubKey = keyPair.public - - // Ten year certificate validity - // TODO how do we manage certificate expiry, revocation and loss - val window = getCertificateValidityWindow(0, 365 * 10) - - val builder = JcaX509v3CertificateBuilder( - issuer, serial, window.first, window.second, subject, pubKey) - - builder.addExtension(Extension.subjectKeyIdentifier, false, - createSubjectKeyIdentifier(pubKey)) - builder.addExtension(Extension.basicConstraints, true, - BasicConstraints(2)) - - val usage = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign) - builder.addExtension(Extension.keyUsage, false, usage) - - val purposes = ASN1EncodableVector() - purposes.add(KeyPurposeId.id_kp_serverAuth) - purposes.add(KeyPurposeId.id_kp_clientAuth) - purposes.add(KeyPurposeId.anyExtendedKeyUsage) - builder.addExtension(Extension.extendedKeyUsage, false, - DERSequence(purposes)) - - val cert = signCertificate(builder, keyPair.private) - - cert.checkValidity(Date()) - cert.verify(pubKey) - - return CACertAndKey(cert, keyPair) + fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): CertificateAndKey { + val keyPair = generateKeyPair(signatureScheme) + val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second) + val cert = Crypto.createCertificate(subject, keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, signatureScheme, window, pathLength = 2) + return CertificateAndKey(cert, keyPair) } /** * Create a de novo root intermediate X509 v3 CA cert and KeyPair. * @param subject subject of the generated certificate. - * @param certificateAuthority The Public certificate and KeyPair of the root CA certificate above this used to sign it + * @param ca The Public certificate and KeyPair of the root CA certificate above this used to sign it + * @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided. + * @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided. * @return A data class is returned containing the new intermediate CA Cert and its KeyPair for signing downstream certificates. * Note the generated certificate tree is capped at max depth of 1 below this to be in line with commercially available certificates */ - fun createIntermediateCert(subject: X500Name, - certificateAuthority: CACertAndKey): CACertAndKey { - val keyPair = generateECDSAKeyPairForSSL() - - val issuer = X509CertificateHolder(certificateAuthority.certificate.encoded).subject - val serial = BigInteger.valueOf(random63BitValue()) - val pubKey = keyPair.public - - // Ten year certificate validity - // TODO how do we manage certificate expiry, revocation and loss - val window = getCertificateValidityWindow(0, 365 * 10, certificateAuthority.certificate.notBefore, certificateAuthority.certificate.notAfter) - - val builder = JcaX509v3CertificateBuilder( - issuer, serial, window.first, window.second, subject, pubKey) - - builder.addExtension(Extension.subjectKeyIdentifier, false, - createSubjectKeyIdentifier(pubKey)) - builder.addExtension(Extension.basicConstraints, true, - BasicConstraints(1)) - - val usage = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign) - builder.addExtension(Extension.keyUsage, false, usage) - - val purposes = ASN1EncodableVector() - purposes.add(KeyPurposeId.id_kp_serverAuth) - purposes.add(KeyPurposeId.id_kp_clientAuth) - purposes.add(KeyPurposeId.anyExtendedKeyUsage) - builder.addExtension(Extension.extendedKeyUsage, false, - DERSequence(purposes)) - - val cert = signCertificate(builder, certificateAuthority.keyPair.private) - - cert.checkValidity(Date()) - cert.verify(certificateAuthority.keyPair.public) - - return CACertAndKey(cert, keyPair) + fun createIntermediateCert(subject: X500Name, ca: CertificateAndKey, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): CertificateAndKey { + val keyPair = generateKeyPair(signatureScheme) + val issuer = X509CertificateHolder(ca.certificate.encoded).subject + val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate.notBefore, ca.certificate.notAfter) + val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, signatureScheme, window, pathLength = 1) + return CertificateAndKey(cert, keyPair) } /** * Create an X509v3 certificate suitable for use in TLS roles. * @param subject The contents to put in the subject field of the certificate * @param publicKey The PublicKey to be wrapped in the certificate - * @param certificateAuthority The Public certificate and KeyPair of the parent CA that will sign this certificate + * @param ca The Public certificate and KeyPair of the parent CA that will sign this certificate * @param subjectAlternativeNameDomains A set of alternate DNS names to be supported by the certificate during validation of the TLS handshakes * @param subjectAlternativeNameIps A set of alternate IP addresses to be supported by the certificate during validation of the TLS handshakes + * @param signatureScheme The signature scheme which will be used to generate keys and certificate. Default to [DEFAULT_TLS_SIGNATURE_SCHEME] if not provided. + * @param validityWindow The certificate's validity window. Default to [DEFAULT_VALIDITY_WINDOW] if not provided. * @return The generated X509Certificate suitable for use as a Server/Client certificate in TLS. * This certificate is not marked as a CA cert to be similar in nature to commercial certificates. */ - fun createServerCert(subject: X500Name, - publicKey: PublicKey, - certificateAuthority: CACertAndKey, + fun createServerCert(subject: X500Name, publicKey: PublicKey, + ca: CertificateAndKey, subjectAlternativeNameDomains: List, - subjectAlternativeNameIps: List): X509Certificate { + subjectAlternativeNameIps: List, + signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, + validityWindow: Pair = DEFAULT_VALIDITY_WINDOW): X509Certificate { - val issuer = X509CertificateHolder(certificateAuthority.certificate.encoded).subject - val serial = BigInteger.valueOf(random63BitValue()) - - // Ten year certificate validity - // TODO how do we manage certificate expiry, revocation and loss - val window = getCertificateValidityWindow(0, 365 * 10, certificateAuthority.certificate.notBefore, certificateAuthority.certificate.notAfter) - - val builder = JcaX509v3CertificateBuilder(issuer, serial, window.first, window.second, subject, publicKey) - builder.addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyIdentifier(publicKey)) - builder.addExtension(Extension.basicConstraints, false, BasicConstraints(false)) - - val usage = KeyUsage(KeyUsage.digitalSignature) - builder.addExtension(Extension.keyUsage, false, usage) - - val purposes = ASN1EncodableVector() - purposes.add(KeyPurposeId.id_kp_serverAuth) - purposes.add(KeyPurposeId.id_kp_clientAuth) - builder.addExtension(Extension.extendedKeyUsage, false, - DERSequence(purposes)) - - val subjectAlternativeNames = ArrayList() - - for (subjectAlternativeNameDomain in subjectAlternativeNameDomains) { - subjectAlternativeNames.add(GeneralName(GeneralName.dNSName, subjectAlternativeNameDomain)) - } - - for (subjectAlternativeNameIp in subjectAlternativeNameIps) { - if (IPAddress.isValidIPv6WithNetmask(subjectAlternativeNameIp) - || IPAddress.isValidIPv6(subjectAlternativeNameIp) - || IPAddress.isValidIPv4WithNetmask(subjectAlternativeNameIp) - || IPAddress.isValidIPv4(subjectAlternativeNameIp)) { - subjectAlternativeNames.add(GeneralName(GeneralName.iPAddress, subjectAlternativeNameIp)) - } - } - - val subjectAlternativeNamesExtension = DERSequence(subjectAlternativeNames.toTypedArray()) - builder.addExtension(Extension.subjectAlternativeName, false, subjectAlternativeNamesExtension) - - val cert = signCertificate(builder, certificateAuthority.keyPair.private) - - cert.checkValidity(Date()) - cert.verify(certificateAuthority.keyPair.public) - - return cert + val issuer = X509CertificateHolder(ca.certificate.encoded).subject + val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate.notBefore, ca.certificate.notAfter) + val dnsNames = subjectAlternativeNameDomains.map { GeneralName(GeneralName.dNSName, it) } + val ipAddresses = subjectAlternativeNameIps.filter { + IPAddress.isValidIPv6WithNetmask(it) || IPAddress.isValidIPv6(it) || IPAddress.isValidIPv4WithNetmask(it) || IPAddress.isValidIPv4(it) + }.map { GeneralName(GeneralName.iPAddress, it) } + return Crypto.createCertificate(issuer, ca.keyPair, subject, publicKey, CLIENT_KEY_USAGE, CLIENT_KEY_PURPOSES, signatureScheme, window, subjectAlternativeName = dnsNames + ipAddresses) } /** @@ -426,14 +142,10 @@ object X509Utilities { * @param filename Target filename */ fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, filename: Path) { - val fileWriter = FileWriter(filename.toFile()) - var jcaPEMWriter: JcaPEMWriter? = null - try { - jcaPEMWriter = JcaPEMWriter(fileWriter) - jcaPEMWriter.writeObject(x509Certificate) - } finally { - jcaPEMWriter?.close() - fileWriter.close() + FileWriter(filename.toFile()).use { + JcaPEMWriter(it).use { + it.writeObject(x509Certificate) + } } } @@ -450,128 +162,6 @@ object X509Utilities { } } - /** - * Extract public and private keys from a KeyStore file assuming storage alias is known. - * @param keyStoreFilePath Path to load KeyStore from - * @param storePassword Password to unlock the KeyStore - * @param keyPassword Password to unlock the private key entries - * @param alias The name to lookup the Key and Certificate chain from - * @return The KeyPair found in the KeyStore under the specified alias - */ - fun loadKeyPairFromKeyStore(keyStoreFilePath: Path, - storePassword: String, - keyPassword: String, - alias: String): KeyPair { - val keyStore = loadKeyStore(keyStoreFilePath, storePassword) - val keyEntry = keyStore.getKey(alias, keyPassword.toCharArray()) as PrivateKey - val certificate = keyStore.getCertificate(alias) as X509Certificate - return KeyPair(certificate.publicKey, keyEntry) - } - - /** - * Extract public and private keys from a KeyStore file assuming storage alias is known, or - * create a new pair of keys using the provided function if the keys not exist. - * @param keyStoreFilePath Path to load KeyStore from - * @param storePassword Password to unlock the KeyStore - * @param keyPassword Password to unlock the private key entries - * @param alias The name to lookup the Key and Certificate chain from - * @param keyGenerator Function for generating new keys - * @return The KeyPair found in the KeyStore under the specified alias - */ - fun loadOrCreateKeyPairFromKeyStore(keyStoreFilePath: Path, storePassword: String, keyPassword: String, - alias: String, keyGenerator: () -> CACertAndKey): KeyPair { - val keyStore = loadKeyStore(keyStoreFilePath, storePassword) - if (!keyStore.containsAlias(alias)) { - val selfSignCert = keyGenerator() - // Save to the key store. - keyStore.addOrReplaceKey(alias, selfSignCert.keyPair.private, keyPassword.toCharArray(), arrayOf(selfSignCert.certificate)) - saveKeyStore(keyStore, keyStoreFilePath, storePassword) - } - - val certificate = keyStore.getCertificate(alias) - val keyEntry = keyStore.getKey(alias, keyPassword.toCharArray()) - - return KeyPair(certificate.publicKey, keyEntry as PrivateKey) - } - - /** - * Extract public X509 certificate from a KeyStore file assuming storage alias is know - * @param keyStoreFilePath Path to load KeyStore from - * @param storePassword Password to unlock the KeyStore - * @param alias The name to lookup the Key and Certificate chain from - * @return The X509Certificate found in the KeyStore under the specified alias - */ - fun loadCertificateFromKeyStore(keyStoreFilePath: Path, - storePassword: String, - alias: String): X509Certificate { - val keyStore = loadKeyStore(keyStoreFilePath, storePassword) - return keyStore.getCertificate(alias) as X509Certificate - } - - /** - * All in one wrapper to manufacture a root CA cert and an Intermediate CA cert. - * Normally this would be run once and then the outputs would be re-used repeatedly to manufacture the server certs - * @param keyStoreFilePath The output KeyStore path to publish the private keys of the CA root and intermediate certs into. - * @param storePassword The storage password to protect access to the generated KeyStore and public certificates - * @param keyPassword The password that protects the CA private keys. - * Unlike the SSL libraries that tend to assume the password is the same as the keystore password. - * These CA private keys should be protected more effectively with a distinct password. - * @param trustStoreFilePath The output KeyStore to place the Root CA public certificate, which can be used as an SSL truststore - * @param trustStorePassword The password to protect the truststore - * @return The KeyStore object that was saved to file - */ - fun createCAKeyStoreAndTrustStore(keyStoreFilePath: Path, - storePassword: String, - keyPassword: String, - trustStoreFilePath: Path, - trustStorePassword: String, - // TODO: Remove these defaults - live calls should always specify these - // and tests should use [getTestX509Name] - rootCaName: X500Name = getDevX509Name("Corda Node Root CA"), - intermediateCaName: X500Name = getDevX509Name("Corda Node Intermediate CA") - ): KeyStore { - val rootCA = createSelfSignedCACert(rootCaName) - val intermediateCA = createIntermediateCert(intermediateCaName, rootCA) - - val keyPass = keyPassword.toCharArray() - val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword) - - keyStore.addOrReplaceKey(CORDA_ROOT_CA_PRIVATE_KEY, rootCA.keyPair.private, keyPass, arrayOf(rootCA.certificate)) - - keyStore.addOrReplaceKey(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, - intermediateCA.keyPair.private, - keyPass, - arrayOf(intermediateCA.certificate, rootCA.certificate)) - - saveKeyStore(keyStore, keyStoreFilePath, storePassword) - - val trustStore = loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword) - - trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, rootCA.certificate) - trustStore.addOrReplaceCertificate(CORDA_INTERMEDIATE_CA, intermediateCA.certificate) - - saveKeyStore(trustStore, trustStoreFilePath, trustStorePassword) - - return keyStore - } - - /** - * Helper method to load a Certificate and KeyPair from their KeyStore. - * The access details should match those of the createCAKeyStoreAndTrustStore call used to manufacture the keys. - * @param keyStore Source KeyStore to look in for the data - * @param keyPassword The password for the PrivateKey (not the store access password) - * @param alias The name to search for the data. Typically if generated with the methods here this will be one of - * CERT_PRIVATE_KEY_ALIAS, ROOT_CA_CERT_PRIVATE_KEY_ALIAS, INTERMEDIATE_CA_PRIVATE_KEY_ALIAS defined above - */ - fun loadCertificateAndKey(keyStore: KeyStore, - keyPassword: String, - alias: String): CACertAndKey { - val keyPass = keyPassword.toCharArray() - val key = keyStore.getKey(alias, keyPass) as PrivateKey - val cert = keyStore.getCertificate(alias) as X509Certificate - return CACertAndKey(cert, KeyPair(cert.publicKey, key)) - } - /** * An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine * @param keyStoreFilePath KeyStore path to save output to @@ -587,40 +177,30 @@ object X509Utilities { keyPassword: String, caKeyStore: KeyStore, caKeyPassword: String, - commonName: X500Name): KeyStore { - val rootCA = X509Utilities.loadCertificateAndKey( - caKeyStore, - caKeyPassword, - CORDA_ROOT_CA_PRIVATE_KEY) - val intermediateCA = X509Utilities.loadCertificateAndKey( - caKeyStore, - caKeyPassword, - CORDA_INTERMEDIATE_CA_PRIVATE_KEY) + commonName: X500Name, + signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME): KeyStore { - val serverKey = generateECDSAKeyPairForSSL() + val rootCA = caKeyStore.getCertificateAndKey(CORDA_ROOT_CA_PRIVATE_KEY, caKeyPassword) + val intermediateCA = caKeyStore.getCertificateAndKey(CORDA_INTERMEDIATE_CA_PRIVATE_KEY, caKeyPassword) + + val serverKey = generateKeyPair(signatureScheme) val host = InetAddress.getLocalHost() - val serverCert = createServerCert( - commonName, - serverKey.public, - intermediateCA, - listOf(host.hostName), - listOf(host.hostAddress)) + val serverCert = createServerCert(commonName, serverKey.public, intermediateCA, listOf(host.hostName), listOf(host.hostAddress), signatureScheme) val keyPass = keyPassword.toCharArray() - val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword) + val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword) keyStore.addOrReplaceKey( CORDA_CLIENT_CA_PRIVATE_KEY, serverKey.private, keyPass, arrayOf(serverCert, intermediateCA.certificate, rootCA.certificate)) - keyStore.addOrReplaceCertificate(CORDA_CLIENT_CA, serverCert) - - saveKeyStore(keyStore, keyStoreFilePath, storePassword) - + keyStore.save(keyStoreFilePath, storePassword) return keyStore } + + fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) = Crypto.createCertificateSigningRequest(subject, keyPair, signatureScheme) } val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString() @@ -630,4 +210,6 @@ class CertificateStream(val input: InputStream) { private val certificateFactory = CertificateFactory.getInstance("X.509") fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate -} \ No newline at end of file +} + +data class CertificateAndKey(val certificate: X509Certificate, val keyPair: KeyPair) 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 9b7be23a03..faedf36c5d 100644 --- a/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt +++ b/core/src/test/kotlin/net/corda/core/crypto/X509UtilitiesTest.kt @@ -2,6 +2,7 @@ package net.corda.core.crypto import net.corda.core.div import net.corda.testing.MEGA_CORP +import net.i2p.crypto.eddsa.EdDSAEngine import net.corda.testing.getTestX509Name import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.GeneralName @@ -14,15 +15,16 @@ import java.io.IOException 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.Signature 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 class X509UtilitiesTest { @@ -54,7 +56,7 @@ class X509UtilitiesTest { fun `create valid server certificate chain`() { val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test CA Cert")) val subjectDN = getTestX509Name("Server Cert") - val keyPair = X509Utilities.generateECDSAKeyPairForSSL() + val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val serverCert = X509Utilities.createServerCert(subjectDN, keyPair.public, caCertAndKey, listOf("alias name"), listOf("10.0.0.54")) assertTrue { serverCert.subjectDN.name.contains("CN=Server Cert") } // using our subject common name assertEquals(caCertAndKey.certificate.issuerDN, serverCert.issuerDN) // Issued by our CA cert @@ -76,17 +78,67 @@ class X509UtilitiesTest { assertTrue(foundAliasDnsName) } + @Test + fun `storing EdDSA key in java keystore`() { + val tmpKeyStore = tempFile("keystore.jks") + + val selfSignCert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test"), Crypto.EDDSA_ED25519_SHA512) + + assertEquals(selfSignCert.certificate.publicKey, selfSignCert.keyPair.public) + + // Save the EdDSA private key with self sign cert in the keystore. + val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + keyStore.setKeyEntry("Key", selfSignCert.keyPair.private, "password".toCharArray(), arrayOf(selfSignCert.certificate)) + keyStore.save(tmpKeyStore, "keystorepass") + + // Load the keystore from file and make sure keys are intact. + val keyStore2 = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val privateKey = keyStore2.getKey("Key", "password".toCharArray()) + val pubKey = keyStore2.getCertificate("Key").publicKey + + assertNotNull(pubKey) + assertNotNull(privateKey) + assertEquals(selfSignCert.keyPair.public, pubKey) + assertEquals(selfSignCert.keyPair.private, privateKey) + } + + @Test + fun `signing EdDSA key with EcDSA certificate`() { + val tmpKeyStore = tempFile("keystore.jks") + val ecDSACert = X509Utilities.createSelfSignedCACert(X500Name("CN=Test")) + val edDSAKeypair = Crypto.generateKeyPair("EDDSA_ED25519_SHA512") + val edDSACert = X509Utilities.createServerCert(X500Name("CN=TestEdDSA"), edDSAKeypair.public, ecDSACert, listOf("alias name"), listOf("10.0.0.54")) + + // Save the EdDSA private key with cert chains. + val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert.certificate, edDSACert)) + keyStore.save(tmpKeyStore, "keystorepass") + + // Load the keystore from file and make sure keys are intact. + val keyStore2 = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass") + val privateKey = keyStore2.getKey("Key", "password".toCharArray()) + val certs = keyStore2.getCertificateChain("Key") + + val pubKey = certs.last().publicKey + + assertEquals(2, certs.size) + assertNotNull(pubKey) + assertNotNull(privateKey) + assertEquals(edDSAKeypair.public, pubKey) + assertEquals(edDSAKeypair.private, privateKey) + } + @Test fun `create full CA keystore`() { val tmpKeyStore = tempFile("keystore.jks") val tmpTrustStore = tempFile("truststore.jks") // Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store - X509Utilities.createCAKeyStoreAndTrustStore(tmpKeyStore, "keystorepass", "keypass", tmpTrustStore, "trustpass") + createCAKeyStoreAndTrustStore(tmpKeyStore, "keystorepass", "keypass", tmpTrustStore, "trustpass") // Load back generated root CA Cert and private key from keystore and check against copy in truststore - val keyStore = X509Utilities.loadKeyStore(tmpKeyStore, "keystorepass") - val trustStore = X509Utilities.loadKeyStore(tmpTrustStore, "trustpass") + val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass") + val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass") val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY) as X509Certificate val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate @@ -96,14 +148,8 @@ class X509UtilitiesTest { // Now sign something with private key and verify against certificate public key val testData = "12345".toByteArray() - val caSigner = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) - caSigner.initSign(rootCaPrivateKey) - caSigner.update(testData) - val caSignature = caSigner.sign() - val caVerifier = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) - caVerifier.initVerify(rootCaCert.publicKey) - caVerifier.update(testData) - assertTrue { caVerifier.verify(caSignature) } + val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData) + assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) } // Load back generated intermediate CA Cert and private key val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) as X509Certificate @@ -112,14 +158,8 @@ class X509UtilitiesTest { intermediateCaCert.verify(rootCaCert.publicKey) // Now sign something with private key and verify against certificate public key - val intermediateSigner = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) - intermediateSigner.initSign(intermediateCaCertPrivateKey) - intermediateSigner.update(testData) - val intermediateSignature = intermediateSigner.sign() - val intermediateVerifier = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) - intermediateVerifier.initVerify(intermediateCaCert.publicKey) - intermediateVerifier.update(testData) - assertTrue { intermediateVerifier.verify(intermediateSignature) } + val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData) + assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) } } @Test @@ -129,22 +169,22 @@ class X509UtilitiesTest { val tmpServerKeyStore = tempFile("serverkeystore.jks") // Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store - X509Utilities.createCAKeyStoreAndTrustStore(tmpCAKeyStore, + createCAKeyStoreAndTrustStore(tmpCAKeyStore, "cakeystorepass", "cakeypass", tmpTrustStore, "trustpass") // Load signing intermediate CA cert - val caKeyStore = X509Utilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass") - val caCertAndKey = X509Utilities.loadCertificateAndKey(caKeyStore, "cakeypass", X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) + val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass") + val caCertAndKey = caKeyStore.getCertificateAndKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "cakeypass") // Generate server cert and private key and populate another keystore suitable for SSL X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name) // Load back server certificate - val serverKeyStore = X509Utilities.loadKeyStore(tmpServerKeyStore, "serverstorepass") - val serverCertAndKey = X509Utilities.loadCertificateAndKey(serverKeyStore, "serverkeypass", X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY) + val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass") + val serverCertAndKey = serverKeyStore.getCertificateAndKey(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY, "serverkeypass") serverCertAndKey.certificate.checkValidity(Date()) serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey) @@ -153,14 +193,8 @@ class X509UtilitiesTest { // Now sign something with private key and verify against certificate public key val testData = "123456".toByteArray() - val signer = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) - signer.initSign(serverCertAndKey.keyPair.private) - signer.update(testData) - val signature = signer.sign() - val verifier = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) - verifier.initVerify(serverCertAndKey.certificate.publicKey) - verifier.update(testData) - assertTrue { verifier.verify(signature) } + val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData) + assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) } } @Test @@ -170,7 +204,7 @@ class X509UtilitiesTest { val tmpServerKeyStore = tempFile("serverkeystore.jks") // Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store - val caKeyStore = X509Utilities.createCAKeyStoreAndTrustStore(tmpCAKeyStore, + val caKeyStore = createCAKeyStoreAndTrustStore(tmpCAKeyStore, "cakeystorepass", "cakeypass", tmpTrustStore, @@ -178,7 +212,7 @@ class X509UtilitiesTest { // Generate server cert and private key and populate another keystore suitable for SSL val keyStore = X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name) - val trustStore = X509Utilities.loadKeyStore(tmpTrustStore, "trustpass") + val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass") val context = SSLContext.getInstance("TLS") val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) @@ -272,4 +306,47 @@ class X509UtilitiesTest { } private fun tempFile(name: String): Path = tempFolder.root.toPath() / name + + /** + * All in one wrapper to manufacture a root CA cert and an Intermediate CA cert. + * Normally this would be run once and then the outputs would be re-used repeatedly to manufacture the server certs + * @param keyStoreFilePath The output KeyStore path to publish the private keys of the CA root and intermediate certs into. + * @param storePassword The storage password to protect access to the generated KeyStore and public certificates + * @param keyPassword The password that protects the CA private keys. + * Unlike the SSL libraries that tend to assume the password is the same as the keystore password. + * These CA private keys should be protected more effectively with a distinct password. + * @param trustStoreFilePath The output KeyStore to place the Root CA public certificate, which can be used as an SSL truststore + * @param trustStorePassword The password to protect the truststore + * @return The KeyStore object that was saved to file + */ + private fun createCAKeyStoreAndTrustStore(keyStoreFilePath: Path, + storePassword: String, + keyPassword: String, + trustStoreFilePath: Path, + trustStorePassword: String + ): KeyStore { + val rootCA = X509Utilities.createSelfSignedCACert(X509Utilities.getDevX509Name("Corda Node Root CA")) + val intermediateCA = X509Utilities.createIntermediateCert(X509Utilities.getDevX509Name("Corda Node Intermediate CA"), rootCA) + + val keyPass = keyPassword.toCharArray() + val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword) + + keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, rootCA.keyPair.private, keyPass, arrayOf(rootCA.certificate)) + + keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, + intermediateCA.keyPair.private, + keyPass, + arrayOf(intermediateCA.certificate, rootCA.certificate)) + + keyStore.save(keyStoreFilePath, storePassword) + + val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword) + + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCA.certificate) + trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCA.certificate) + + trustStore.save(trustStoreFilePath, trustStorePassword) + + return keyStore + } } 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 f9d35601c6..58c01cab68 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -8,6 +8,7 @@ import com.google.common.util.concurrent.SettableFuture import net.corda.core.* import net.corda.core.contracts.Amount import net.corda.core.contracts.PartyAndReference +import net.corda.core.crypto.KeyStoreUtilities import net.corda.core.crypto.Party import net.corda.core.crypto.X509Utilities import net.corda.core.flows.FlowInitiator @@ -71,7 +72,6 @@ import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.TimeUnit.SECONDS -import kotlin.collections.ArrayList import kotlin.reflect.KClass import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair @@ -351,7 +351,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration, private fun hasSSLCertificates(): Boolean { val keyStore = try { // This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect. - X509Utilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword) + KeyStoreUtilities.loadKeyStore(configuration.keyStoreFile, configuration.keyStorePassword) } catch (e: IOException) { null } catch (e: KeyStoreException) { diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 8f7616656b..8eea06c609 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -9,6 +9,7 @@ import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigRenderOptions import net.corda.core.copyTo import net.corda.core.createDirectories +import net.corda.core.crypto.KeyStoreUtilities import net.corda.core.crypto.X509Utilities import net.corda.core.div import net.corda.core.exists @@ -52,9 +53,7 @@ fun SSLConfiguration.configureDevKeyAndTrustStores(myLegalName: X500Name) { javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile) } if (!keyStoreFile.exists()) { - val caKeyStore = X509Utilities.loadKeyStore( - javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), - "cordacadevpass") + val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass") X509Utilities.createKeystoreForSSL(keyStoreFile, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) } } 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 4f6f8181d5..d8544b5e0e 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 @@ -246,8 +246,10 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, @Throws(IOException::class, KeyStoreException::class) private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager { - val ourCertificate = X509Utilities - .loadCertificateFromKeyStore(config.keyStoreFile, config.keyStorePassword, CORDA_CLIENT_CA) + val keyStore = KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword) + val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword) + val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_CA) + val ourSubjectDN = X500Name(ourCertificate.subjectDN.name) // This is a sanity check and should not fail unless things have been misconfigured require(ourSubjectDN == config.myLegalName) { @@ -258,8 +260,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, NODE_ROLE to CertificateChainCheckPolicy.LeafMustMatch, VERIFIER_ROLE to CertificateChainCheckPolicy.RootMustMatch ) - val keyStore = X509Utilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword) - val trustStore = X509Utilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword) val certChecks = defaultCertPolicies.mapValues { (role, defaultPolicy) -> val configPolicy = config.certificateChainCheckPolicies.noneOrSingle { it.role == role }?.certificateChainCheckPolicy (configPolicy ?: defaultPolicy).createCheck(keyStore, trustStore) 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 17af4b6667..aa7b7ff2c0 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 @@ -1,11 +1,9 @@ package net.corda.node.utilities.registration import net.corda.core.* -import net.corda.core.crypto.X509Utilities +import net.corda.core.crypto.* import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA -import net.corda.core.crypto.X509Utilities.addOrReplaceCertificate -import net.corda.core.crypto.X509Utilities.addOrReplaceKey import net.corda.node.services.config.NodeConfiguration import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.util.io.pem.PemObject @@ -33,7 +31,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: fun buildKeystore() { config.certificatesDirectory.createDirectories() - val caKeyStore = X509Utilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword) + val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword) if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) { // Create or load self signed keypair from the key store. // We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval. @@ -41,9 +39,9 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: val selfSignCert = X509Utilities.createSelfSignedCACert(config.myLegalName) // Save to the key store. caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, selfSignCert.keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate)) - X509Utilities.saveKeyStore(caKeyStore, config.keyStoreFile, keystorePassword) + caKeyStore.save(config.keyStoreFile, keystorePassword) } - val keyPair = X509Utilities.loadKeyPairFromKeyStore(config.keyStoreFile, keystorePassword, privateKeyPassword, SELF_SIGNED_PRIVATE_KEY) + val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword) val requestId = submitOrResumeCertificateSigningRequest(keyPair) val certificates = try { @@ -60,12 +58,12 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService: // Save private key and certificate chain to the key store. caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) - X509Utilities.saveKeyStore(caKeyStore, config.keyStoreFile, keystorePassword) + caKeyStore.save(config.keyStoreFile, keystorePassword) // Save root certificates to trust store. - val trustStore = X509Utilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) + val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword) // Assumes certificate chain always starts with client certificate and end with root certificate. trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last()) - X509Utilities.saveKeyStore(trustStore, config.trustStoreFile, config.trustStorePassword) + trustStore.save(config.trustStoreFile, config.trustStorePassword) println("Certificate and private key stored in ${config.keyStoreFile}.") // All done, clean up temp files. requestIdStore.deleteIfExists() 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 49d58203ce..d2d9a8348d 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 @@ -3,6 +3,7 @@ package net.corda.node.utilities.registration import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.mock +import net.corda.core.crypto.KeyStoreUtilities import net.corda.core.crypto.SecureHash import net.corda.core.crypto.X509Utilities import net.corda.core.exists @@ -51,7 +52,7 @@ class NetworkRegistrationHelperTest { assertTrue(config.keyStoreFile.exists()) assertTrue(config.trustStoreFile.exists()) - X509Utilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword).run { + KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword).run { assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY)) val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) assertEquals(3, certificateChain.size) @@ -62,7 +63,7 @@ class NetworkRegistrationHelperTest { assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY)) } - X509Utilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword).run { + KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword).run { assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY)) assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_CA)) diff --git a/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index aff00f26c8..8c37e92045 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -7,7 +7,6 @@ import com.google.common.net.HostAndPort import com.google.common.util.concurrent.ListenableFuture import net.corda.core.contracts.StateRef import net.corda.core.crypto.* -import net.corda.core.crypto.X509Utilities.getX509Name import net.corda.core.flows.FlowLogic import net.corda.core.node.ServiceHub import net.corda.core.node.VersionInfo