mirror of
https://github.com/corda/corda.git
synced 2025-06-14 21:28:14 +00:00
Using X509Certificate consistently throughout, rather than BC's X509CertificateHolder. (#2305)
The later is now only used where needed. This has reduced the amount of converting we have to do back and forth.
This commit is contained in:
@ -5,10 +5,8 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.toX509CertHolder
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
|
||||
import net.corda.nodeapi.internal.crypto.*
|
||||
@ -39,10 +37,10 @@ object DevIdentityGenerator {
|
||||
// TODO The passwords for the dev key stores are spread everywhere and should be constants in a single location
|
||||
val caKeyStore = loadKeyStore(javaClass.classLoader.getResourceAsStream("certificates/cordadevcakeys.jks"), "cordacadevpass")
|
||||
val intermediateCa = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cordacadevkeypass")
|
||||
val rootCert = caKeyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)
|
||||
val rootCert = caKeyStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA)
|
||||
|
||||
nodeSslConfig.certificatesDirectory.createDirectories()
|
||||
nodeSslConfig.createDevKeyStores(rootCert.toX509CertHolder(), intermediateCa, legalName)
|
||||
nodeSslConfig.createDevKeyStores(rootCert, intermediateCa, legalName)
|
||||
|
||||
val keyStoreWrapper = KeyStoreWrapper(nodeSslConfig.nodeKeystore, nodeSslConfig.keyStorePassword)
|
||||
val identity = keyStoreWrapper.storeLegalIdentity(legalName, "$NODE_IDENTITY_ALIAS_PREFIX-private-key", Crypto.generateKeyPair())
|
||||
@ -62,16 +60,21 @@ object DevIdentityGenerator {
|
||||
|
||||
keyPairs.zip(dirs) { keyPair, nodeDir ->
|
||||
val (serviceKeyCert, compositeKeyCert) = listOf(keyPair.public, compositeKey).map { publicKey ->
|
||||
X509Utilities.createCertificate(CertificateType.SERVICE_IDENTITY, intermediateCa.certificate, intermediateCa.keyPair, notaryName, publicKey)
|
||||
X509Utilities.createCertificate(
|
||||
CertificateType.SERVICE_IDENTITY,
|
||||
intermediateCa.certificate,
|
||||
intermediateCa.keyPair,
|
||||
notaryName.x500Principal,
|
||||
publicKey)
|
||||
}
|
||||
val distServKeyStoreFile = (nodeDir / "certificates").createDirectories() / "distributedService.jks"
|
||||
val keystore = loadOrCreateKeyStore(distServKeyStoreFile, "cordacadevpass")
|
||||
keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert.cert)
|
||||
keystore.setCertificateEntry("$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key", compositeKeyCert)
|
||||
keystore.setKeyEntry(
|
||||
"$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key",
|
||||
keyPair.private,
|
||||
"cordacadevkeypass".toCharArray(),
|
||||
arrayOf(serviceKeyCert.cert, intermediateCa.certificate.cert, rootCert))
|
||||
arrayOf(serviceKeyCert, intermediateCa.certificate, rootCert))
|
||||
keystore.save(distServKeyStoreFile, "cordacadevpass")
|
||||
}
|
||||
|
||||
|
@ -8,13 +8,13 @@ import net.corda.nodeapi.internal.crypto.*
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
/**
|
||||
* Create the node and SSL key stores needed by a node. The node key store will be populated with a node CA cert (using
|
||||
* the given legal name), and the SSL key store will store the TLS cert which is a sub-cert of the node CA.
|
||||
*/
|
||||
fun SSLConfiguration.createDevKeyStores(rootCert: X509CertificateHolder, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) {
|
||||
fun SSLConfiguration.createDevKeyStores(rootCert: X509Certificate, intermediateCa: CertificateAndKeyPair, legalName: CordaX500Name) {
|
||||
val (nodeCaCert, nodeCaKeyPair) = createDevNodeCa(intermediateCa, legalName)
|
||||
|
||||
loadOrCreateKeyStore(nodeKeystore, keyStorePassword).apply {
|
||||
@ -27,7 +27,7 @@ fun SSLConfiguration.createDevKeyStores(rootCert: X509CertificateHolder, interme
|
||||
}
|
||||
|
||||
val tlsKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName, tlsKeyPair.public)
|
||||
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, nodeCaCert, nodeCaKeyPair, legalName.x500Principal, tlsKeyPair.public)
|
||||
|
||||
loadOrCreateKeyStore(sslKeystore, keyStorePassword).apply {
|
||||
addOrReplaceKey(
|
||||
@ -50,7 +50,7 @@ fun createDevNodeCa(intermediateCa: CertificateAndKeyPair, legalName: CordaX500N
|
||||
CertificateType.NODE_CA,
|
||||
intermediateCa.certificate,
|
||||
intermediateCa.keyPair,
|
||||
legalName,
|
||||
legalName.x500Principal,
|
||||
keyPair.public,
|
||||
nameConstraints = nameConstraints)
|
||||
return CertificateAndKeyPair(cert, keyPair)
|
||||
|
@ -3,8 +3,9 @@
|
||||
package net.corda.nodeapi.internal.crypto
|
||||
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.internal.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.internal.read
|
||||
import net.corda.core.internal.write
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
@ -67,18 +68,6 @@ fun loadKeyStore(input: InputStream, storePassword: String): KeyStore {
|
||||
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<out X509CertificateHolder>) {
|
||||
addOrReplaceKey(alias, key, password, chain.map { it.cert }.toTypedArray<Certificate>())
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper extension method to add, or overwrite any key data in store.
|
||||
* @param alias name to record the private key and certificate chain under.
|
||||
@ -132,9 +121,9 @@ fun KeyStore.getKeyPair(alias: String, keyPassword: String): KeyPair = getCertif
|
||||
* @param keyPassword The password for the PrivateKey (not the store access password).
|
||||
*/
|
||||
fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): CertificateAndKeyPair {
|
||||
val cert = getX509Certificate(alias).toX509CertHolder()
|
||||
val publicKey = Crypto.toSupportedPublicKey(cert.subjectPublicKeyInfo)
|
||||
return CertificateAndKeyPair(cert, KeyPair(publicKey, getSupportedKey(alias, keyPassword)))
|
||||
val certificate = getX509Certificate(alias)
|
||||
val publicKey = Crypto.toSupportedPublicKey(certificate.publicKey)
|
||||
return CertificateAndKeyPair(certificate, KeyPair(publicKey, getSupportedKey(alias, keyPassword)))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,6 @@ package net.corda.nodeapi.internal.crypto
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.read
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
@ -15,8 +14,13 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St
|
||||
fun storeLegalIdentity(legalName: CordaX500Name, alias: String, keyPair: KeyPair): PartyAndCertificate {
|
||||
val nodeCaCertChain = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
|
||||
val nodeCa = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
|
||||
val identityCert = X509Utilities.createCertificate(CertificateType.LEGAL_IDENTITY, nodeCa.certificate, nodeCa.keyPair, legalName, keyPair.public)
|
||||
val identityCertPath = X509CertificateFactory().generateCertPath(identityCert.cert, *nodeCaCertChain)
|
||||
val identityCert = X509Utilities.createCertificate(
|
||||
CertificateType.LEGAL_IDENTITY,
|
||||
nodeCa.certificate,
|
||||
nodeCa.keyPair,
|
||||
legalName.x500Principal,
|
||||
keyPair.public)
|
||||
val identityCertPath = X509CertificateFactory().generateCertPath(identityCert, *nodeCaCertChain)
|
||||
// Assume key password = store password.
|
||||
keyStore.addOrReplaceKey(alias, keyPair.private, storePassword.toCharArray(), identityCertPath.certificates.toTypedArray())
|
||||
keyStore.save(storePath, storePassword)
|
||||
|
@ -4,12 +4,14 @@ import net.corda.core.CordaOID
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SignatureScheme
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.internal.CertRole
|
||||
import net.corda.core.internal.reader
|
||||
import net.corda.core.internal.writer
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.utilities.millis
|
||||
import org.bouncycastle.asn1.*
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
@ -34,6 +36,7 @@ import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
object X509Utilities {
|
||||
val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512
|
||||
@ -74,7 +77,7 @@ object X509Utilities {
|
||||
* @param after duration to roll forward returned end date relative to current date.
|
||||
* @param parent if provided certificate whose validity should bound the date interval returned.
|
||||
*/
|
||||
fun getCertificateValidityWindow(before: Duration, after: Duration, parent: X509CertificateHolder? = null): Pair<Date, Date> {
|
||||
fun getCertificateValidityWindow(before: Duration, after: Duration, parent: X509Certificate? = null): Pair<Date, Date> {
|
||||
val startOfDayUTC = Instant.now().truncatedTo(ChronoUnit.DAYS)
|
||||
val notBefore = max(startOfDayUTC - before, parent?.notBefore)
|
||||
val notAfter = min(startOfDayUTC + after, parent?.notAfter)
|
||||
@ -85,60 +88,11 @@ object X509Utilities {
|
||||
* Create a de novo root self-signed X509 v3 CA cert.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createSelfSignedCACertificate(subject: CordaX500Name,
|
||||
fun createSelfSignedCACertificate(subject: X500Principal,
|
||||
keyPair: KeyPair,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder {
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509Certificate {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
|
||||
return createCertificate(CertificateType.ROOT_CA, subject.x500Name, keyPair, subject.x500Name, keyPair.public, window)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a X509 v3 certificate for use as a CA or for TLS. This does not require a [CordaX500Name] because the
|
||||
* constraints are inappropriate for TLS/CA usage, however as a result this is unsuitable for Corda identity
|
||||
* certificate generation.
|
||||
*
|
||||
* @param issuerCertificate The Public certificate of the root CA above this used to sign it.
|
||||
* @param issuerKeyPair The KeyPair of the root CA above this used to sign it.
|
||||
* @param subject subject of the generated certificate.
|
||||
* @param subjectPublicKey subject's public key.
|
||||
* @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.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuerCertificate: X509CertificateHolder,
|
||||
issuerKeyPair: KeyPair,
|
||||
subject: CordaX500Name,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
|
||||
nameConstraints: NameConstraints? = null): X509CertificateHolder {
|
||||
return createCertificate(certificateType, issuerCertificate, issuerKeyPair, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a X509 v3 certificate for use as a CA or for TLS. This does not require a [CordaX500Name] because the
|
||||
* constraints are inappropriate for TLS/CA usage, however as a result this is unsuitable for Corda identity
|
||||
* certificate generation.
|
||||
*
|
||||
* @param issuerCertificate The Public certificate of the root CA above this used to sign it.
|
||||
* @param issuerKeyPair The KeyPair of the root CA above this used to sign it.
|
||||
* @param subject subject of the generated certificate.
|
||||
* @param subjectPublicKey subject's public key.
|
||||
* @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.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuerCertificate: X509CertificateHolder,
|
||||
issuerKeyPair: KeyPair,
|
||||
subject: X500Name,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
|
||||
nameConstraints: NameConstraints? = null): X509CertificateHolder {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
|
||||
return createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
|
||||
return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
|
||||
}
|
||||
|
||||
@Throws(CertPathValidatorException::class)
|
||||
@ -153,13 +107,13 @@ object X509Utilities {
|
||||
|
||||
/**
|
||||
* Helper method to store a .pem/.cer format file copy of a certificate if required for import into a PC/Mac, or for inspection.
|
||||
* @param x509Certificate certificate to save.
|
||||
* @param certificate certificate to save.
|
||||
* @param file Target file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, file: Path) {
|
||||
fun saveCertificateAsPEMFile(certificate: X509Certificate, file: Path) {
|
||||
JcaPEMWriter(file.writer()).use {
|
||||
it.writeObject(x509Certificate)
|
||||
it.writeObject(certificate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,9 +126,10 @@ object X509Utilities {
|
||||
fun loadCertificateFromPEMFile(file: Path): X509Certificate {
|
||||
return file.reader().use {
|
||||
val pemObject = PemReader(it).readPemObject()
|
||||
val certHolder = X509CertificateHolder(pemObject.content)
|
||||
certHolder.isValidOn(Date())
|
||||
certHolder.cert
|
||||
X509CertificateHolder(pemObject.content).run {
|
||||
isValidOn(Date())
|
||||
toJca()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,38 +142,18 @@ object X509Utilities {
|
||||
* @param validityWindow the time period the certificate is valid for.
|
||||
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
|
||||
*/
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuer: CordaX500Name,
|
||||
subject: CordaX500Name,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
nameConstraints: NameConstraints? = null): X509v3CertificateBuilder {
|
||||
return createCertificate(certificateType, issuer.x500Name, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a partial X.509 certificate ready for signing.
|
||||
*
|
||||
* @param issuer name of the issuing entity.
|
||||
* @param subject name of the certificate subject.
|
||||
* @param subjectPublicKey public key of the certificate subject.
|
||||
* @param validityWindow the time period the certificate is valid for.
|
||||
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
|
||||
*/
|
||||
internal fun createCertificate(certificateType: CertificateType,
|
||||
issuer: X500Name,
|
||||
subject: X500Name,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
nameConstraints: NameConstraints? = null): X509v3CertificateBuilder {
|
||||
|
||||
fun createPartialCertificate(certificateType: CertificateType,
|
||||
issuer: X500Principal,
|
||||
subject: X500Principal,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
nameConstraints: NameConstraints? = null): X509v3CertificateBuilder {
|
||||
val serial = BigInteger.valueOf(random63BitValue())
|
||||
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
|
||||
val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded))
|
||||
val role = certificateType.role
|
||||
|
||||
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second,
|
||||
subject, subjectPublicKey)
|
||||
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
|
||||
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo))
|
||||
.addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA))
|
||||
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
|
||||
@ -234,6 +169,37 @@ object X509Utilities {
|
||||
return builder
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a X509 v3 certificate using the given issuer certificate and key pair.
|
||||
*
|
||||
* @param issuerCertificate The Public certificate of the root CA above this used to sign it.
|
||||
* @param issuerKeyPair The KeyPair of the root CA above this used to sign it.
|
||||
* @param subject subject of the generated certificate.
|
||||
* @param subjectPublicKey subject's public key.
|
||||
* @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.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuerCertificate: X509Certificate,
|
||||
issuerKeyPair: KeyPair,
|
||||
subject: X500Principal,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
|
||||
return createCertificate(
|
||||
certificateType,
|
||||
issuerCertificate.subjectX500Principal,
|
||||
issuerKeyPair,
|
||||
subject,
|
||||
subjectPublicKey,
|
||||
window,
|
||||
nameConstraints
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and sign an X.509 certificate with the given signer.
|
||||
*
|
||||
@ -245,15 +211,16 @@ object X509Utilities {
|
||||
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
|
||||
*/
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuer: X500Name,
|
||||
issuer: X500Principal,
|
||||
issuerSigner: ContentSigner,
|
||||
subject: CordaX500Name,
|
||||
subject: X500Principal,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
nameConstraints: NameConstraints? = null): X509CertificateHolder {
|
||||
val builder = createCertificate(certificateType, issuer, subject.x500Name, subjectPublicKey, validityWindow, nameConstraints)
|
||||
return builder.build(issuerSigner).apply {
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
val builder = createPartialCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
|
||||
return builder.build(issuerSigner).run {
|
||||
require(isValidOn(Date()))
|
||||
toJca()
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,39 +235,47 @@ object X509Utilities {
|
||||
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
|
||||
*/
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuer: X500Name,
|
||||
issuer: X500Principal,
|
||||
issuerKeyPair: KeyPair,
|
||||
subject: X500Name,
|
||||
subject: X500Principal,
|
||||
subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
nameConstraints: NameConstraints? = null): X509CertificateHolder {
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
val signatureScheme = Crypto.findSignatureScheme(issuerKeyPair.private)
|
||||
val provider = Crypto.findProvider(signatureScheme.providerName)
|
||||
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
|
||||
|
||||
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
|
||||
return builder.build(signer).apply {
|
||||
val builder = createPartialCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
|
||||
return builder.build(signer).run {
|
||||
require(isValidOn(Date()))
|
||||
require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public)))
|
||||
toJca()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create certificate signing request using provided information.
|
||||
*/
|
||||
private fun createCertificateSigningRequest(subject: CordaX500Name,
|
||||
private fun createCertificateSigningRequest(subject: X500Principal,
|
||||
email: String,
|
||||
keyPair: KeyPair,
|
||||
signatureScheme: SignatureScheme): PKCS10CertificationRequest {
|
||||
val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.findProvider(signatureScheme.providerName))
|
||||
return JcaPKCS10CertificationRequestBuilder(subject.x500Name, keyPair.public).addAttribute(BCStyle.E, DERUTF8String(email)).build(signer)
|
||||
return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public).addAttribute(BCStyle.E, DERUTF8String(email)).build(signer)
|
||||
}
|
||||
|
||||
fun createCertificateSigningRequest(subject: CordaX500Name, email: String, keyPair: KeyPair): PKCS10CertificationRequest {
|
||||
fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair): PKCS10CertificationRequest {
|
||||
return createCertificateSigningRequest(subject, email, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a [X509Certificate] into Bouncycastle's [X509CertificateHolder].
|
||||
*
|
||||
* NOTE: To avoid unnecessary copying use [X509Certificate] where possible.
|
||||
*/
|
||||
fun X509Certificate.toBc() = X509CertificateHolder(encoded)
|
||||
fun X509CertificateHolder.toJca(): X509Certificate = X509CertificateFactory().generateCertificate(encoded.inputStream())
|
||||
|
||||
/**
|
||||
* Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best
|
||||
* so assume this class is not.
|
||||
@ -396,4 +371,4 @@ enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurpo
|
||||
)
|
||||
}
|
||||
|
||||
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
|
||||
data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair)
|
||||
|
@ -4,10 +4,7 @@ import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
|
||||
import net.corda.core.crypto.Crypto.generateKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.cert
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.toTypedArray
|
||||
import net.corda.core.internal.x500Name
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
@ -23,12 +20,9 @@ import net.corda.testing.BOB_NAME
|
||||
import net.corda.testing.TestIdentity
|
||||
import net.corda.testing.internal.createDevIntermediateCaCertPath
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints
|
||||
import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.KeyUsage
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -42,18 +36,16 @@ import java.security.SecureRandom
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.stream.Stream
|
||||
import javax.net.ssl.*
|
||||
import javax.security.auth.x500.X500Principal
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.*
|
||||
|
||||
class X509UtilitiesTest {
|
||||
private companion object {
|
||||
val ALICE = TestIdentity(ALICE_NAME, 70).party
|
||||
val bob = TestIdentity(BOB_NAME, 80)
|
||||
val BOB = TestIdentity(BOB_NAME, 80)
|
||||
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party
|
||||
val BOB get() = bob.party
|
||||
val BOB_PUBKEY get() = bob.publicKey
|
||||
val CIPHER_SUITES = arrayOf(
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
||||
@ -63,27 +55,30 @@ class X509UtilitiesTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder: TemporaryFolder = TemporaryFolder()
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun `create valid self-signed CA certificate`() {
|
||||
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey)
|
||||
assertEquals(X500Name("CN=Test Cert,O=R3 Ltd,L=London,C=GB"), caCert.subject) // using our subject common name
|
||||
assertEquals(caCert.issuer, caCert.subject) //self-signed
|
||||
caCert.isValidOn(Date()) // throws on verification problems
|
||||
caCert.isSignatureValid(JcaContentVerifierProviderBuilder().build(caKey.public)) // throws on verification problems
|
||||
val basicConstraints = BasicConstraints.getInstance(caCert.getExtension(Extension.basicConstraints).parsedValue)
|
||||
val keyUsage = KeyUsage.getInstance(caCert.getExtension(Extension.keyUsage).parsedValue)
|
||||
assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertNull(basicConstraints.pathLenConstraint) // No length constraint specified on this CA certificate
|
||||
val subject = X500Principal("CN=Test Cert,O=R3 Ltd,L=London,C=GB")
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(subject, caKey)
|
||||
assertEquals(subject, caCert.subjectX500Principal) // using our subject common name
|
||||
assertEquals(caCert.issuerX500Principal, caCert.subjectX500Principal) //self-signed
|
||||
caCert.checkValidity(Date()) // throws on verification problems
|
||||
caCert.verify(caKey.public) // throws on verification problems
|
||||
caCert.toBc().run {
|
||||
val basicConstraints = BasicConstraints.getInstance(getExtension(Extension.basicConstraints).parsedValue)
|
||||
val keyUsage = KeyUsage.getInstance(getExtension(Extension.keyUsage).parsedValue)
|
||||
assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertNull(basicConstraints.pathLenConstraint) // No length constraint specified on this CA certificate
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `load and save a PEM file certificate`() {
|
||||
val tmpCertificateFile = tempFile("cacert.pem")
|
||||
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey).cert
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(X500Principal("CN=Test Cert,O=R3 Ltd,L=London,C=GB"), caKey)
|
||||
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
|
||||
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
|
||||
assertEquals(caCert, readCertificate)
|
||||
@ -92,18 +87,20 @@ class X509UtilitiesTest {
|
||||
@Test
|
||||
fun `create valid server certificate chain`() {
|
||||
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Test CA Cert", organisation = "R3 Ltd", locality = "London", country = "GB"), caKey)
|
||||
val subject = CordaX500Name(commonName = "Server Cert", organisation = "R3 Ltd", locality = "London", country = "GB")
|
||||
val caCert = X509Utilities.createSelfSignedCACertificate(X500Principal("CN=Test CA Cert,O=R3 Ltd,L=London,C=GB"), caKey)
|
||||
val subject = X500Principal("CN=Server Cert,O=R3 Ltd,L=London,C=GB")
|
||||
val keyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subject, keyPair.public)
|
||||
assertEquals(X500Name("C=GB,L=London,O=R3 Ltd,CN=Server Cert"), serverCert.subject) // using our subject common name
|
||||
assertEquals(caCert.issuer, serverCert.issuer) // Issued by our CA cert
|
||||
serverCert.isValidOn(Date()) // throws on verification problems
|
||||
serverCert.isSignatureValid(JcaContentVerifierProviderBuilder().build(caKey.public)) // throws on verification problems
|
||||
val basicConstraints = BasicConstraints.getInstance(serverCert.getExtension(Extension.basicConstraints).parsedValue)
|
||||
val keyUsage = KeyUsage.getInstance(serverCert.getExtension(Extension.keyUsage).parsedValue)
|
||||
assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertNull(basicConstraints.pathLenConstraint) // Non-CA certificate
|
||||
assertEquals(subject, serverCert.subjectX500Principal) // using our subject common name
|
||||
assertEquals(caCert.issuerX500Principal, serverCert.issuerX500Principal) // Issued by our CA cert
|
||||
serverCert.checkValidity(Date()) // throws on verification problems
|
||||
serverCert.verify(caKey.public) // throws on verification problems
|
||||
serverCert.toBc().run {
|
||||
val basicConstraints = BasicConstraints.getInstance(getExtension(Extension.basicConstraints).parsedValue)
|
||||
val keyUsage = KeyUsage.getInstance(getExtension(Extension.keyUsage).parsedValue)
|
||||
assertFalse { keyUsage.hasUsages(5) } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertNull(basicConstraints.pathLenConstraint) // Non-CA certificate
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -111,15 +108,14 @@ class X509UtilitiesTest {
|
||||
val tmpKeyStore = tempFile("keystore.jks")
|
||||
|
||||
val keyPair = generateKeyPair(EDDSA_ED25519_SHA512)
|
||||
val testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB")
|
||||
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair)
|
||||
|
||||
assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.encoded, keyPair.public.encoded))
|
||||
assertTrue(Arrays.equals(selfSignCert.publicKey.encoded, keyPair.public.encoded))
|
||||
|
||||
// Save the EdDSA private key with self sign cert in the keystore.
|
||||
val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(),
|
||||
Stream.of(selfSignCert).map { it.cert }.toTypedArray())
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), arrayOf(selfSignCert))
|
||||
keyStore.save(tmpKeyStore, "keystorepass")
|
||||
|
||||
// Load the keystore from file and make sure keys are intact.
|
||||
@ -137,15 +133,14 @@ class X509UtilitiesTest {
|
||||
fun `signing EdDSA key with EcDSA certificate`() {
|
||||
val tmpKeyStore = tempFile("keystore.jks")
|
||||
val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB")
|
||||
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
|
||||
val ecDSACert = X509Utilities.createSelfSignedCACertificate(testName, ecDSAKey)
|
||||
val edDSAKeypair = generateKeyPair(EDDSA_ED25519_SHA512)
|
||||
val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public)
|
||||
val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, BOB.name.x500Principal, edDSAKeypair.public)
|
||||
|
||||
// Save the EdDSA private key with cert chains.
|
||||
val keyStore = loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
|
||||
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(),
|
||||
Stream.of(ecDSACert, edDSACert).map { it.cert }.toTypedArray())
|
||||
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert, edDSACert))
|
||||
keyStore.save(tmpKeyStore, "keystorepass")
|
||||
|
||||
// Load the keystore from file and make sure keys are intact.
|
||||
@ -179,23 +174,22 @@ class X509UtilitiesTest {
|
||||
val serverKeyStore = loadKeyStore(sslConfig.nodeKeystore, sslConfig.keyStorePassword)
|
||||
val (serverCert, serverKeyPair) = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, sslConfig.keyStorePassword)
|
||||
|
||||
serverCert.cert.checkValidity()
|
||||
serverCert.cert.verify(intermediateCa.certificate.cert.publicKey)
|
||||
assertThat(CordaX500Name.parse(serverCert.subject.toString())).isEqualTo(MEGA_CORP.name)
|
||||
serverCert.checkValidity()
|
||||
serverCert.verify(intermediateCa.certificate.publicKey)
|
||||
assertThat(CordaX500Name.build(serverCert.subjectX500Principal)).isEqualTo(MEGA_CORP.name)
|
||||
|
||||
// Load back SSL certificate
|
||||
val sslKeyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)
|
||||
val (sslCert) = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, sslConfig.keyStorePassword)
|
||||
|
||||
sslCert.cert.checkValidity()
|
||||
sslCert.cert.verify(serverCert.cert.publicKey)
|
||||
assertThat(CordaX500Name.parse(sslCert.subject.toString())).isEqualTo(MEGA_CORP.name)
|
||||
sslCert.checkValidity()
|
||||
sslCert.verify(serverCert.publicKey)
|
||||
assertThat(CordaX500Name.build(sslCert.subjectX500Principal)).isEqualTo(MEGA_CORP.name)
|
||||
|
||||
// Now sign something with private key and verify against certificate public key
|
||||
val testData = "123456".toByteArray()
|
||||
val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverKeyPair.private, testData)
|
||||
val publicKey = Crypto.toSupportedPublicKey(serverCert.subjectPublicKeyInfo)
|
||||
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) }
|
||||
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCert.publicKey, signature, testData) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -210,7 +204,7 @@ class X509UtilitiesTest {
|
||||
|
||||
// Generate server cert and private key and populate another keystore suitable for SSL
|
||||
sslConfig.createDevKeyStores(rootCa.certificate, intermediateCa, MEGA_CORP.name)
|
||||
sslConfig.createTrustStore(rootCa.certificate.cert)
|
||||
sslConfig.createTrustStore(rootCa.certificate)
|
||||
|
||||
val keyStore = loadKeyStore(sslConfig.sslKeystore, sslConfig.keyStorePassword)
|
||||
val trustStore = loadKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword)
|
||||
@ -303,10 +297,10 @@ class X509UtilitiesTest {
|
||||
@Test
|
||||
fun `get correct private key type from Keystore`() {
|
||||
val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val testName = CordaX500Name(commonName = "Test", organisation = "R3 Ltd", locality = "London", country = "GB")
|
||||
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, keyPair)
|
||||
val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.cert))
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert))
|
||||
|
||||
val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
|
||||
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")
|
||||
@ -316,7 +310,7 @@ class X509UtilitiesTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `serialize - deserialize X509CertififcateHolder`() {
|
||||
fun `serialize - deserialize X509Certififcate`() {
|
||||
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
|
||||
val context = SerializationContextImpl(KryoHeaderV0_1,
|
||||
javaClass.classLoader,
|
||||
@ -324,9 +318,9 @@ class X509UtilitiesTest {
|
||||
emptyMap(),
|
||||
true,
|
||||
SerializationContext.UseCase.P2P)
|
||||
val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name.x500Principal, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
val serialized = expected.serialize(factory, context).bytes
|
||||
val actual: X509CertificateHolder = serialized.deserialize(factory, context)
|
||||
val actual = serialized.deserialize<X509Certificate>(factory, context)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@ -340,9 +334,9 @@ class X509UtilitiesTest {
|
||||
true,
|
||||
SerializationContext.UseCase.P2P)
|
||||
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
|
||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name.x500Name, BOB_PUBKEY)
|
||||
val expected = X509CertificateFactory().generateCertPath(certificate.cert, rootCACert.cert)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE_NAME.x500Principal, rootCAKey)
|
||||
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB_NAME.x500Principal, BOB.publicKey)
|
||||
val expected = X509CertificateFactory().generateCertPath(certificate, rootCACert)
|
||||
val serialized = expected.serialize(factory, context).bytes
|
||||
val actual: CertPath = serialized.deserialize(factory, context)
|
||||
assertEquals(expected, actual)
|
||||
|
Reference in New Issue
Block a user