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 package net.corda.core.crypto
import net.corda.core.random63BitValue
import net.i2p.crypto.eddsa.EdDSAEngine import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAKey import net.i2p.crypto.eddsa.EdDSAKey
import net.i2p.crypto.eddsa.EdDSASecurityProvider import net.i2p.crypto.eddsa.EdDSASecurityProvider
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable 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.ECNamedCurveTable
import org.bouncycastle.jce.interfaces.ECKey import org.bouncycastle.jce.interfaces.ECKey
import org.bouncycastle.jce.provider.BouncyCastleProvider 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.provider.BouncyCastlePQCProvider
import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec
import java.math.BigInteger
import java.security.* import java.security.*
import java.security.cert.X509Certificate
import java.security.spec.InvalidKeySpecException import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
import java.util.*
/** /**
* This object controls and provides the available and supported signature schemes for Corda. * This object controls and provides the available and supported signature schemes for Corda.
@ -28,15 +47,6 @@ import java.security.spec.X509EncodedKeySpec
* </ul> * </ul>
*/ */
object Crypto { 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. * RSA_SHA256 signature scheme using SHA256 as hash algorithm and MGF1 (with SHA256) as mask generation function.
* Note: Recommended key size >= 3072 bits. * Note: Recommended key size >= 3072 bits.
@ -44,6 +54,7 @@ object Crypto {
val RSA_SHA256 = SignatureScheme( val RSA_SHA256 = SignatureScheme(
1, 1,
"RSA_SHA256", "RSA_SHA256",
PKCSObjectIdentifiers.id_RSASSA_PSS,
BouncyCastleProvider.PROVIDER_NAME, BouncyCastleProvider.PROVIDER_NAME,
"RSA", "RSA",
"SHA256WITHRSAANDMGF1", "SHA256WITHRSAANDMGF1",
@ -56,6 +67,7 @@ object Crypto {
val ECDSA_SECP256K1_SHA256 = SignatureScheme( val ECDSA_SECP256K1_SHA256 = SignatureScheme(
2, 2,
"ECDSA_SECP256K1_SHA256", "ECDSA_SECP256K1_SHA256",
X9ObjectIdentifiers.ecdsa_with_SHA256,
BouncyCastleProvider.PROVIDER_NAME, BouncyCastleProvider.PROVIDER_NAME,
"ECDSA", "ECDSA",
"SHA256withECDSA", "SHA256withECDSA",
@ -68,6 +80,7 @@ object Crypto {
val ECDSA_SECP256R1_SHA256 = SignatureScheme( val ECDSA_SECP256R1_SHA256 = SignatureScheme(
3, 3,
"ECDSA_SECP256R1_SHA256", "ECDSA_SECP256R1_SHA256",
X9ObjectIdentifiers.ecdsa_with_SHA256,
BouncyCastleProvider.PROVIDER_NAME, BouncyCastleProvider.PROVIDER_NAME,
"ECDSA", "ECDSA",
"SHA256withECDSA", "SHA256withECDSA",
@ -80,7 +93,9 @@ object Crypto {
val EDDSA_ED25519_SHA512 = SignatureScheme( val EDDSA_ED25519_SHA512 = SignatureScheme(
4, 4,
"EDDSA_ED25519_SHA512", "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, EdDSAKey.KEY_ALGORITHM,
EdDSAEngine.SIGNATURE_ALGORITHM, EdDSAEngine.SIGNATURE_ALGORITHM,
EdDSANamedCurveTable.getByName("ED25519"), EdDSANamedCurveTable.getByName("ED25519"),
@ -95,6 +110,7 @@ object Crypto {
val SPHINCS256_SHA256 = SignatureScheme( val SPHINCS256_SHA256 = SignatureScheme(
5, 5,
"SPHINCS-256_SHA512", "SPHINCS-256_SHA512",
BCObjectIdentifiers.sphincs256_with_SHA512,
"BCPQC", "BCPQC",
"SPHINCS256", "SPHINCS256",
"SHA512WITHSPHINCS256", "SHA512WITHSPHINCS256",
@ -119,6 +135,25 @@ object Crypto {
SPHINCS256_SHA256 SPHINCS256_SHA256
).associateBy { it.schemeCodeName } ).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. * 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. * This function is usually called by key generators and verify signature functions.
@ -170,7 +205,7 @@ object Crypto {
*/ */
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun decodePrivateKey(encodedKey: ByteArray): PrivateKey { fun decodePrivateKey(encodedKey: ByteArray): PrivateKey {
for ((_, _, providerName, algorithmName) in supportedSignatureSchemes.values) { for ((_, _, _, providerName, algorithmName) in supportedSignatureSchemes.values) {
try { try {
return KeyFactory.getInstance(algorithmName, providerMap[providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey)) return KeyFactory.getInstance(algorithmName, providerMap[providerName]).generatePrivate(PKCS8EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) { } catch (ikse: InvalidKeySpecException) {
@ -217,7 +252,7 @@ object Crypto {
*/ */
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun decodePublicKey(encodedKey: ByteArray): PublicKey { fun decodePublicKey(encodedKey: ByteArray): PublicKey {
for ((_, _, providerName, algorithmName) in supportedSignatureSchemes.values) { for ((_, _, _, providerName, algorithmName) in supportedSignatureSchemes.values) {
try { try {
return KeyFactory.getInstance(algorithmName, providerMap[providerName]).generatePublic(X509EncodedKeySpec(encodedKey)) return KeyFactory.getInstance(algorithmName, providerMap[providerName]).generatePublic(X509EncodedKeySpec(encodedKey))
} catch (ikse: InvalidKeySpecException) { } catch (ikse: InvalidKeySpecException) {
@ -459,12 +494,13 @@ object Crypto {
/** /**
* Generate a [KeyPair] for the selected [SignatureScheme]. * Generate a [KeyPair] for the selected [SignatureScheme].
* Note that RSA is the sole algorithm initialized specifically by its supported keySize. * 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]. * @return a new [KeyPair] for the requested [SignatureScheme].
* @throws IllegalArgumentException if the requested signature scheme is not supported. * @throws IllegalArgumentException if the requested signature scheme is not supported.
*/ */
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
fun generateKeyPair(signatureScheme: SignatureScheme): KeyPair { @JvmOverloads
fun generateKeyPair(signatureScheme: SignatureScheme = DEFAULT_SIGNATURE_SCHEME): KeyPair {
if (!supportedSignatureSchemes.containsKey(signatureScheme.schemeCodeName)) if (!supportedSignatureSchemes.containsKey(signatureScheme.schemeCodeName))
throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName") throw IllegalArgumentException("Unsupported key/algorithm for schemeCodeName: $signatureScheme.schemeCodeName")
val keyPairGenerator = KeyPairGenerator.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName]) val keyPairGenerator = KeyPairGenerator.getInstance(signatureScheme.algorithmName, providerMap[signatureScheme.providerName])
@ -475,13 +511,50 @@ object Crypto {
return keyPairGenerator.generateKeyPair() 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. */ /** Check if the requested signature scheme is supported by the system. */
fun isSupportedSignatureScheme(schemeCodeName: String): Boolean = schemeCodeName in supportedSignatureSchemes fun isSupportedSignatureScheme(schemeCodeName: String): Boolean = schemeCodeName in supportedSignatureSchemes
fun isSupportedSignatureScheme(signatureScheme: SignatureScheme): Boolean = signatureScheme.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 package net.corda.core.crypto
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import java.security.Signature import java.security.Signature
import java.security.spec.AlgorithmParameterSpec import java.security.spec.AlgorithmParameterSpec
@ -7,6 +8,7 @@ import java.security.spec.AlgorithmParameterSpec
* This class is used to define a digital signature scheme. * 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 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 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 providerName the provider's name (e.g. "BC").
* @param algorithmName which signature algorithm is used (e.g. RSA, ECDSA. EdDSA, SPHINCS-256). * @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") * @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( data class SignatureScheme(
val schemeNumberID: Int, val schemeNumberID: Int,
val schemeCodeName: String, val schemeCodeName: String,
val signatureOID: ASN1ObjectIdentifier,
val providerName: String, val providerName: String,
val algorithmName: String, val algorithmName: String,
val signatureName: String, val signatureName: String,

View File

@ -1,53 +1,32 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.exists import net.corda.core.crypto.Crypto.generateKeyPair
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 org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle 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.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.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.IPAddress
import org.bouncycastle.util.io.pem.PemReader import org.bouncycastle.util.io.pem.PemReader
import java.io.FileReader import java.io.FileReader
import java.io.FileWriter import java.io.FileWriter
import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.math.BigInteger
import java.net.InetAddress import java.net.InetAddress
import java.nio.file.Path import java.nio.file.Path
import java.security.* import java.security.KeyPair
import java.security.cert.Certificate import java.security.KeyStore
import java.security.PublicKey
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.security.spec.ECGenParameterSpec
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
object X509Utilities { object X509Utilities {
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
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"
// Aliases for private keys and certificates. // Aliases for private keys and certificates.
val CORDA_ROOT_CA_PRIVATE_KEY = "cordarootcaprivatekey" val CORDA_ROOT_CA_PRIVATE_KEY = "cordarootcaprivatekey"
@ -57,10 +36,12 @@ object X509Utilities {
val CORDA_CLIENT_CA_PRIVATE_KEY = "cordaclientcaprivatekey" val CORDA_CLIENT_CA_PRIVATE_KEY = "cordaclientcaprivatekey"
val CORDA_CLIENT_CA = "cordaclientca" val CORDA_CLIENT_CA = "cordaclientca"
init { private val CA_KEY_USAGE = KeyUsage(KeyUsage.keyCertSign or KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.dataEncipherment or KeyUsage.cRLSign)
Security.addProvider(BouncyCastleProvider()) // register Bouncy Castle Crypto Provider required to sign certificates 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 * 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 * @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> { private fun getCertificateValidityWindow(daysBefore: Int, daysAfter: Int, parentNotBefore: Date? = null, parentNotAfter: Date? = null): Pair<Date, Date> {
val startOfDayUTC = Instant.now().truncatedTo(ChronoUnit.DAYS) val startOfDayUTC = Instant.now().truncatedTo(ChronoUnit.DAYS)
val notBefore = Date.from(startOfDayUTC.minus(daysBefore.toLong(), ChronoUnit.DAYS)).let { notBefore ->
var notBefore = Date.from(startOfDayUTC.minus(daysBefore.toLong(), ChronoUnit.DAYS)) if (parentNotBefore != null && parentNotBefore.after(notBefore)) parentNotBefore else notBefore
if (parentNotBefore != null) {
if (parentNotBefore.after(notBefore)) {
notBefore = parentNotBefore
}
} }
val notAfter = Date.from(startOfDayUTC.plus(daysAfter.toLong(), ChronoUnit.DAYS)).let { notAfter ->
var notAfter = Date.from(startOfDayUTC.plus(daysAfter.toLong(), ChronoUnit.DAYS)) if (parentNotAfter != null && parentNotAfter.after(notAfter)) parentNotAfter else notAfter
if (parentNotAfter != null) {
if (parentNotAfter.after(notAfter)) {
notAfter = parentNotAfter
}
} }
return Pair(notBefore, notAfter) return Pair(notBefore, notAfter)
} }
/** /**
* Encode provided public key in correct format for inclusion in certificate issuer/subject fields * Return a bogus X509 for dev purposes.
*/
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.
*/ */
@Deprecated("Full legal names should be specified in all configurations") @Deprecated("Full legal names should be specified in all configurations")
fun getDevX509Name(commonName: String): X500Name { 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) return X500NameBuilder(BCStyle.INSTANCE)
.addRDN(BCStyle.CN, myLegalName) .addRDN(BCStyle.CN, commonName)
.addRDN(BCStyle.L, nearestCity) .addRDN(BCStyle.O, "R3")
.addRDN(BCStyle.E, email).build() .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]. * 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 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. * @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 * 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 { fun createSelfSignedCACert(subject: X500Name, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKey {
val keyPair = generateECDSAKeyPairForSSL() val keyPair = generateKeyPair(signatureScheme)
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
val issuer = subject val cert = Crypto.createCertificate(subject, keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, signatureScheme, window, pathLength = 2)
val serial = BigInteger.valueOf(random63BitValue()) return CertificateAndKey(cert, keyPair)
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)
} }
/** /**
* Create a de novo root intermediate X509 v3 CA cert and KeyPair. * Create a de novo root intermediate X509 v3 CA cert and KeyPair.
* @param subject subject of the generated certificate. * @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. * @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 * 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, fun createIntermediateCert(subject: X500Name, ca: CertificateAndKey, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME, validityWindow: Pair<Int, Int> = DEFAULT_VALIDITY_WINDOW): CertificateAndKey {
certificateAuthority: CACertAndKey): CACertAndKey { val keyPair = generateKeyPair(signatureScheme)
val keyPair = generateECDSAKeyPairForSSL() val issuer = X509CertificateHolder(ca.certificate.encoded).subject
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate.notBefore, ca.certificate.notAfter)
val issuer = X509CertificateHolder(certificateAuthority.certificate.encoded).subject val cert = Crypto.createCertificate(issuer, ca.keyPair, subject, keyPair.public, CA_KEY_USAGE, CA_KEY_PURPOSES, signatureScheme, window, pathLength = 1)
val serial = BigInteger.valueOf(random63BitValue()) return CertificateAndKey(cert, keyPair)
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)
} }
/** /**
* Create an X509v3 certificate suitable for use in TLS roles. * Create an X509v3 certificate suitable for use in TLS roles.
* @param subject The contents to put in the subject field of the certificate * @param subject The contents to put in the subject field of the certificate
* @param publicKey The PublicKey to be wrapped in 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 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 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. * @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. * This certificate is not marked as a CA cert to be similar in nature to commercial certificates.
*/ */
fun createServerCert(subject: X500Name, fun createServerCert(subject: X500Name, publicKey: PublicKey,
publicKey: PublicKey, ca: CertificateAndKey,
certificateAuthority: CACertAndKey,
subjectAlternativeNameDomains: List<String>, 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 issuer = X509CertificateHolder(ca.certificate.encoded).subject
val serial = BigInteger.valueOf(random63BitValue()) val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, ca.certificate.notBefore, ca.certificate.notAfter)
val dnsNames = subjectAlternativeNameDomains.map { GeneralName(GeneralName.dNSName, it) }
// Ten year certificate validity val ipAddresses = subjectAlternativeNameIps.filter {
// TODO how do we manage certificate expiry, revocation and loss IPAddress.isValidIPv6WithNetmask(it) || IPAddress.isValidIPv6(it) || IPAddress.isValidIPv4WithNetmask(it) || IPAddress.isValidIPv4(it)
val window = getCertificateValidityWindow(0, 365 * 10, certificateAuthority.certificate.notBefore, certificateAuthority.certificate.notAfter) }.map { GeneralName(GeneralName.iPAddress, it) }
return Crypto.createCertificate(issuer, ca.keyPair, subject, publicKey, CLIENT_KEY_USAGE, CLIENT_KEY_PURPOSES, signatureScheme, window, subjectAlternativeName = dnsNames + ipAddresses)
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
} }
/** /**
@ -426,14 +142,10 @@ object X509Utilities {
* @param filename Target filename * @param filename Target filename
*/ */
fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, filename: Path) { fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, filename: Path) {
val fileWriter = FileWriter(filename.toFile()) FileWriter(filename.toFile()).use {
var jcaPEMWriter: JcaPEMWriter? = null JcaPEMWriter(it).use {
try { it.writeObject(x509Certificate)
jcaPEMWriter = JcaPEMWriter(fileWriter) }
jcaPEMWriter.writeObject(x509Certificate)
} finally {
jcaPEMWriter?.close()
fileWriter.close()
} }
} }
@ -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 * 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 * @param keyStoreFilePath KeyStore path to save output to
@ -587,40 +177,30 @@ object X509Utilities {
keyPassword: String, keyPassword: String,
caKeyStore: KeyStore, caKeyStore: KeyStore,
caKeyPassword: String, caKeyPassword: String,
commonName: X500Name): KeyStore { commonName: X500Name,
val rootCA = X509Utilities.loadCertificateAndKey( signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME): KeyStore {
caKeyStore,
caKeyPassword,
CORDA_ROOT_CA_PRIVATE_KEY)
val intermediateCA = X509Utilities.loadCertificateAndKey(
caKeyStore,
caKeyPassword,
CORDA_INTERMEDIATE_CA_PRIVATE_KEY)
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 host = InetAddress.getLocalHost()
val serverCert = createServerCert( val serverCert = createServerCert(commonName, serverKey.public, intermediateCA, listOf(host.hostName), listOf(host.hostAddress), signatureScheme)
commonName,
serverKey.public,
intermediateCA,
listOf(host.hostName),
listOf(host.hostAddress))
val keyPass = keyPassword.toCharArray() val keyPass = keyPassword.toCharArray()
val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword) val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
keyStore.addOrReplaceKey( keyStore.addOrReplaceKey(
CORDA_CLIENT_CA_PRIVATE_KEY, CORDA_CLIENT_CA_PRIVATE_KEY,
serverKey.private, serverKey.private,
keyPass, keyPass,
arrayOf(serverCert, intermediateCA.certificate, rootCA.certificate)) arrayOf(serverCert, intermediateCA.certificate, rootCA.certificate))
keyStore.addOrReplaceCertificate(CORDA_CLIENT_CA, serverCert) keyStore.addOrReplaceCertificate(CORDA_CLIENT_CA, serverCert)
keyStore.save(keyStoreFilePath, storePassword)
saveKeyStore(keyStore, keyStoreFilePath, storePassword)
return keyStore 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() val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
@ -631,3 +211,5 @@ class CertificateStream(val input: InputStream) {
fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate 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.core.div
import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP
import net.i2p.crypto.eddsa.EdDSAEngine
import net.corda.testing.getTestX509Name import net.corda.testing.getTestX509Name
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralName
@ -14,15 +15,16 @@ import java.io.IOException
import java.net.InetAddress import java.net.InetAddress
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyStore
import java.security.PrivateKey import java.security.PrivateKey
import java.security.SecureRandom import java.security.SecureRandom
import java.security.Signature
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.* import java.util.*
import javax.net.ssl.* import javax.net.ssl.*
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
class X509UtilitiesTest { class X509UtilitiesTest {
@ -54,7 +56,7 @@ class X509UtilitiesTest {
fun `create valid server certificate chain`() { fun `create valid server certificate chain`() {
val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test CA Cert")) val caCertAndKey = X509Utilities.createSelfSignedCACert(getTestX509Name("Test CA Cert"))
val subjectDN = getTestX509Name("Server 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")) 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 assertTrue { serverCert.subjectDN.name.contains("CN=Server Cert") } // using our subject common name
assertEquals(caCertAndKey.certificate.issuerDN, serverCert.issuerDN) // Issued by our CA cert assertEquals(caCertAndKey.certificate.issuerDN, serverCert.issuerDN) // Issued by our CA cert
@ -76,17 +78,67 @@ class X509UtilitiesTest {
assertTrue(foundAliasDnsName) 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 @Test
fun `create full CA keystore`() { fun `create full CA keystore`() {
val tmpKeyStore = tempFile("keystore.jks") val tmpKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.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 // 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 // Load back generated root CA Cert and private key from keystore and check against copy in truststore
val keyStore = X509Utilities.loadKeyStore(tmpKeyStore, "keystorepass") val keyStore = KeyStoreUtilities.loadKeyStore(tmpKeyStore, "keystorepass")
val trustStore = X509Utilities.loadKeyStore(tmpTrustStore, "trustpass") val trustStore = KeyStoreUtilities.loadKeyStore(tmpTrustStore, "trustpass")
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY) as X509Certificate 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 rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY, "keypass".toCharArray()) as PrivateKey
val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate 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 // Now sign something with private key and verify against certificate public key
val testData = "12345".toByteArray() val testData = "12345".toByteArray()
val caSigner = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
caSigner.initSign(rootCaPrivateKey) assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
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) }
// Load back generated intermediate CA Cert and private key // Load back generated intermediate CA Cert and private key
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) as X509Certificate val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) as X509Certificate
@ -112,14 +158,8 @@ class X509UtilitiesTest {
intermediateCaCert.verify(rootCaCert.publicKey) intermediateCaCert.verify(rootCaCert.publicKey)
// Now sign something with private key and verify against certificate public key // Now sign something with private key and verify against certificate public key
val intermediateSigner = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
intermediateSigner.initSign(intermediateCaCertPrivateKey) assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
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) }
} }
@Test @Test
@ -129,22 +169,22 @@ class X509UtilitiesTest {
val tmpServerKeyStore = tempFile("serverkeystore.jks") val tmpServerKeyStore = tempFile("serverkeystore.jks")
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store // 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", "cakeystorepass",
"cakeypass", "cakeypass",
tmpTrustStore, tmpTrustStore,
"trustpass") "trustpass")
// Load signing intermediate CA cert // Load signing intermediate CA cert
val caKeyStore = X509Utilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass") val caKeyStore = KeyStoreUtilities.loadKeyStore(tmpCAKeyStore, "cakeystorepass")
val caCertAndKey = X509Utilities.loadCertificateAndKey(caKeyStore, "cakeypass", X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY) val caCertAndKey = caKeyStore.getCertificateAndKey(X509Utilities.CORDA_INTERMEDIATE_CA_PRIVATE_KEY, "cakeypass")
// Generate server cert and private key and populate another keystore suitable for SSL // Generate server cert and private key and populate another keystore suitable for SSL
X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name) X509Utilities.createKeystoreForSSL(tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
// Load back server certificate // Load back server certificate
val serverKeyStore = X509Utilities.loadKeyStore(tmpServerKeyStore, "serverstorepass") val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass")
val serverCertAndKey = X509Utilities.loadCertificateAndKey(serverKeyStore, "serverkeypass", X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY) val serverCertAndKey = serverKeyStore.getCertificateAndKey(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY, "serverkeypass")
serverCertAndKey.certificate.checkValidity(Date()) serverCertAndKey.certificate.checkValidity(Date())
serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey) serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey)
@ -153,14 +193,8 @@ class X509UtilitiesTest {
// Now sign something with private key and verify against certificate public key // Now sign something with private key and verify against certificate public key
val testData = "123456".toByteArray() val testData = "123456".toByteArray()
val signer = Signature.getInstance(X509Utilities.SIGNATURE_ALGORITHM) val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
signer.initSign(serverCertAndKey.keyPair.private) assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
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) }
} }
@Test @Test
@ -170,7 +204,7 @@ class X509UtilitiesTest {
val tmpServerKeyStore = tempFile("serverkeystore.jks") val tmpServerKeyStore = tempFile("serverkeystore.jks")
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store // 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", "cakeystorepass",
"cakeypass", "cakeypass",
tmpTrustStore, tmpTrustStore,
@ -178,7 +212,7 @@ class X509UtilitiesTest {
// Generate server cert and private key and populate another keystore suitable for SSL // 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 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 context = SSLContext.getInstance("TLS")
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
@ -272,4 +306,47 @@ class X509UtilitiesTest {
} }
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name 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.*
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.KeyStoreUtilities
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities
import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowInitiator
@ -71,7 +72,6 @@ import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeUnit.SECONDS
import kotlin.collections.ArrayList
import kotlin.reflect.KClass import kotlin.reflect.KClass
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
@ -351,7 +351,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
private fun hasSSLCertificates(): Boolean { private fun hasSSLCertificates(): Boolean {
val keyStore = try { val keyStore = try {
// This will throw IOException if key file not found or KeyStoreException if keystore password is incorrect. // 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) { } catch (e: IOException) {
null null
} catch (e: KeyStoreException) { } catch (e: KeyStoreException) {

View File

@ -9,6 +9,7 @@ import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigRenderOptions
import net.corda.core.copyTo import net.corda.core.copyTo
import net.corda.core.createDirectories import net.corda.core.createDirectories
import net.corda.core.crypto.KeyStoreUtilities
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities
import net.corda.core.div import net.corda.core.div
import net.corda.core.exists 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) javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordatruststore.jks").copyTo(trustStoreFile)
} }
if (!keyStoreFile.exists()) { if (!keyStoreFile.exists()) {
val caKeyStore = X509Utilities.loadKeyStore( val caKeyStore = KeyStoreUtilities.loadKeyStore(javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"), "cordacadevpass")
javaClass.classLoader.getResourceAsStream("net/corda/node/internal/certificates/cordadevcakeys.jks"),
"cordacadevpass")
X509Utilities.createKeystoreForSSL(keyStoreFile, keyStorePassword, keyStorePassword, caKeyStore, "cordacadevkeypass", myLegalName) 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) @Throws(IOException::class, KeyStoreException::class)
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager { private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
val ourCertificate = X509Utilities val keyStore = KeyStoreUtilities.loadKeyStore(config.keyStoreFile, config.keyStorePassword)
.loadCertificateFromKeyStore(config.keyStoreFile, config.keyStorePassword, CORDA_CLIENT_CA) val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_CA)
val ourSubjectDN = X500Name(ourCertificate.subjectDN.name) val ourSubjectDN = X500Name(ourCertificate.subjectDN.name)
// This is a sanity check and should not fail unless things have been misconfigured // This is a sanity check and should not fail unless things have been misconfigured
require(ourSubjectDN == config.myLegalName) { require(ourSubjectDN == config.myLegalName) {
@ -258,8 +260,6 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
NODE_ROLE to CertificateChainCheckPolicy.LeafMustMatch, NODE_ROLE to CertificateChainCheckPolicy.LeafMustMatch,
VERIFIER_ROLE to CertificateChainCheckPolicy.RootMustMatch 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 certChecks = defaultCertPolicies.mapValues { (role, defaultPolicy) ->
val configPolicy = config.certificateChainCheckPolicies.noneOrSingle { it.role == role }?.certificateChainCheckPolicy val configPolicy = config.certificateChainCheckPolicies.noneOrSingle { it.role == role }?.certificateChainCheckPolicy
(configPolicy ?: defaultPolicy).createCheck(keyStore, trustStore) (configPolicy ?: defaultPolicy).createCheck(keyStore, trustStore)

View File

@ -1,11 +1,9 @@
package net.corda.node.utilities.registration package net.corda.node.utilities.registration
import net.corda.core.* 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_CLIENT_CA
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_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 net.corda.node.services.config.NodeConfiguration
import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemObject import org.bouncycastle.util.io.pem.PemObject
@ -33,7 +31,7 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
fun buildKeystore() { fun buildKeystore() {
config.certificatesDirectory.createDirectories() config.certificatesDirectory.createDirectories()
val caKeyStore = X509Utilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword) val caKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.keyStoreFile, keystorePassword)
if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) { if (!caKeyStore.containsAlias(CORDA_CLIENT_CA)) {
// Create or load self signed keypair from the key store. // 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. // 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) val selfSignCert = X509Utilities.createSelfSignedCACert(config.myLegalName)
// Save to the key store. // Save to the key store.
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, selfSignCert.keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert.certificate)) 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 requestId = submitOrResumeCertificateSigningRequest(keyPair)
val certificates = try { val certificates = try {
@ -60,12 +58,12 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
// Save private key and certificate chain to the key store. // Save private key and certificate chain to the key store.
caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates) caKeyStore.addOrReplaceKey(CORDA_CLIENT_CA, keyPair.private, privateKeyPassword.toCharArray(), certificates)
caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY) caKeyStore.deleteEntry(SELF_SIGNED_PRIVATE_KEY)
X509Utilities.saveKeyStore(caKeyStore, config.keyStoreFile, keystorePassword) caKeyStore.save(config.keyStoreFile, keystorePassword)
// Save root certificates to trust store. // 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. // Assumes certificate chain always starts with client certificate and end with root certificate.
trustStore.addOrReplaceCertificate(CORDA_ROOT_CA, certificates.last()) 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}.") println("Certificate and private key stored in ${config.keyStoreFile}.")
// All done, clean up temp files. // All done, clean up temp files.
requestIdStore.deleteIfExists() 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.any
import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.mock
import net.corda.core.crypto.KeyStoreUtilities
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.X509Utilities import net.corda.core.crypto.X509Utilities
import net.corda.core.exists import net.corda.core.exists
@ -51,7 +52,7 @@ class NetworkRegistrationHelperTest {
assertTrue(config.keyStoreFile.exists()) assertTrue(config.keyStoreFile.exists())
assertTrue(config.trustStoreFile.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)) assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA_PRIVATE_KEY))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
assertEquals(3, certificateChain.size) assertEquals(3, certificateChain.size)
@ -62,7 +63,7 @@ class NetworkRegistrationHelperTest {
assertFalse(containsAlias(X509Utilities.CORDA_ROOT_CA_PRIVATE_KEY)) 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_PRIVATE_KEY))
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA)) assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_CA))
assertFalse(containsAlias(X509Utilities.CORDA_INTERMEDIATE_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 com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.crypto.X509Utilities.getX509Name
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.VersionInfo import net.corda.core.node.VersionInfo