From 1f4535bc2e9fed196f7462cd99731e6657a1940f Mon Sep 17 00:00:00 2001
From: Patrick Kuo <patrick.kuo@r3.com>
Date: Mon, 8 May 2017 17:38:59 +0100
Subject: [PATCH] Support signing and storing EdDSA key and certificate in java
 keystore. (#601)

---
 .../corda/core/crypto/ContentSignerBuilder.kt |  39 ++
 .../kotlin/net/corda/core/crypto/Crypto.kt    | 113 +++-
 .../corda/core/crypto/KeyStoreUtilities.kt    | 136 +++++
 .../net/corda/core/crypto/SignatureScheme.kt  |   3 +
 .../net/corda/core/crypto/X509Utilities.kt    | 564 +++---------------
 .../corda/core/crypto/X509UtilitiesTest.kt    | 149 +++--
 .../net/corda/node/internal/AbstractNode.kt   |   4 +-
 .../node/services/config/ConfigUtilities.kt   |   5 +-
 .../messaging/ArtemisMessagingServer.kt       |   8 +-
 .../registration/NetworkRegistrationHelper.kt |  16 +-
 .../NetworkisRegistrationHelperTest.kt        |   5 +-
 .../kotlin/net/corda/testing/CoreTestUtils.kt |   1 -
 12 files changed, 475 insertions(+), 568 deletions(-)
 create mode 100644 core/src/main/kotlin/net/corda/core/crypto/ContentSignerBuilder.kt
 create mode 100644 core/src/main/kotlin/net/corda/core/crypto/KeyStoreUtilities.kt

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
  * </ul>
  */
 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<String, Provider> = 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<KeyPurposeId>,
+                          signatureScheme: SignatureScheme, validityWindow: Pair<Date, Date>,
+                          pathLength: Int? = null, subjectAlternativeName: List<GeneralName>? = 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<Certificate>) {
+    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<Date, Date> {
         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<Certificate>) {
-        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<Int, Int> = 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<Int, Int> = 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<String>,
-                         subjectAlternativeNameIps: List<String>): X509Certificate {
+                         subjectAlternativeNameIps: List<String>,
+                         signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME,
+                         validityWindow: Pair<Int, Int> = 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<ASN1Encodable>()
-
-        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