Support signing and storing EdDSA key and certificate in java keystore. (#601)

This commit is contained in:
Patrick Kuo 2017-05-08 17:38:59 +01:00 committed by GitHub
parent c3557e0a68
commit 1f4535bc2e
12 changed files with 475 additions and 568 deletions

View File

@ -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())
}
}

View File

@ -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) }
}
}

View File

@ -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

View File

@ -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,

View File

@ -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
}
}
data class CertificateAndKey(val certificate: X509Certificate, val keyPair: KeyPair)

View File

@ -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
}
}

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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()

View File

@ -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))

View File

@ -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