Move certificate builder code from core to node utilities.

Address PR comments

Fixup dead reference
This commit is contained in:
Matthew Nesbit
2017-08-10 10:11:16 +01:00
parent 4602739e93
commit af13371510
24 changed files with 325 additions and 285 deletions

View File

@ -1,10 +1,8 @@
package net.corda.services.messaging
import net.corda.core.crypto.Crypto
import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories
import net.corda.core.crypto.CertificateType
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.X509Utilities
import net.corda.core.internal.exists
import net.corda.node.utilities.*
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER

View File

@ -4,9 +4,10 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRenderOptions
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories
import net.corda.core.crypto.*
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.utilities.loggerFor

View File

@ -1,10 +1,14 @@
package net.corda.node.services.keys
import net.corda.core.crypto.*
import net.corda.core.crypto.ContentSignerBuilder
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.days
import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities
import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair
import java.security.PublicKey
@ -31,7 +35,7 @@ fun freshCertificate(identityService: IdentityService,
revocationEnabled: Boolean = false): AnonymousPartyAndPath {
val issuerCertificate = issuer.certificate
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate)
val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
val certFactory = CertificateFactory.getInstance("X509")
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
val anonymisedIdentity = AnonymousPartyAndPath(subjectPublicKey, ourCertPath)

View File

@ -2,13 +2,13 @@ package net.corda.node.services.messaging
import com.google.common.util.concurrent.ListenableFuture
import io.netty.handler.ssl.SslHandler
import net.corda.core.*
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.*
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.crypto.AddressFormatException
import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.parsePublicKeyBase58
import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.ThreadBox
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div
import net.corda.core.internal.noneOrSingle
import net.corda.core.node.NodeInfo
@ -22,6 +22,9 @@ import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.RPC_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE
import net.corda.node.utilities.X509Utilities
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA
import net.corda.node.utilities.getX509Certificate
import net.corda.node.utilities.loadKeyStore
import net.corda.nodeapi.*

View File

@ -0,0 +1,249 @@
package net.corda.node.utilities
import net.corda.core.crypto.*
import net.corda.core.utilities.days
import net.corda.core.utilities.millis
import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.*
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.bouncycastle.util.io.pem.PemReader
import java.io.FileReader
import java.io.FileWriter
import java.io.InputStream
import java.math.BigInteger
import java.nio.file.Path
import java.security.KeyPair
import java.security.PublicKey
import java.security.cert.*
import java.security.cert.Certificate
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.*
object X509Utilities {
val DEFAULT_IDENTITY_SIGNATURE_SCHEME = Crypto.EDDSA_ED25519_SHA512
val DEFAULT_TLS_SIGNATURE_SCHEME = Crypto.ECDSA_SECP256R1_SHA256
// Aliases for private keys and certificates.
val CORDA_ROOT_CA = "cordarootca"
val CORDA_INTERMEDIATE_CA = "cordaintermediateca"
val CORDA_CLIENT_TLS = "cordaclienttls"
val CORDA_CLIENT_CA = "cordaclientca"
private val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days)
/**
* Helper function to return the latest out of an instant and an optional date.
*/
private fun max(first: Instant, second: Date?): Date {
return if (second != null && second.time > first.toEpochMilli())
second
else
Date(first.toEpochMilli())
}
/**
* Helper function to return the earliest out of an instant and an optional date.
*/
private fun min(first: Instant, second: Date?): Date {
return if (second != null && second.time < first.toEpochMilli())
second
else
Date(first.toEpochMilli())
}
/**
* Helper method to get a notBefore and notAfter pair from current day bounded by parent certificate validity range.
* @param before duration to roll back returned start date relative to current date.
* @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> {
val startOfDayUTC = Instant.now().truncatedTo(ChronoUnit.DAYS)
val notBefore = max(startOfDayUTC - before, parent?.notBefore)
val notAfter = min(startOfDayUTC + after, parent?.notAfter)
return Pair(notBefore, notAfter)
}
/*
* Create a de novo root self-signed X509 v3 CA cert.
*/
@JvmStatic
fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder {
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
}
/**
* Create a X509 v3 cert.
* @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)
}
fun validateCertificateChain(trustedRoot: X509CertificateHolder, vararg certificates: Certificate) {
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
val certFactory = CertificateFactory.getInstance("X509")
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot.cert, null)))
params.isRevocationEnabled = false
val certPath = certFactory.generateCertPath(certificates.toList())
val pathValidator = CertPathValidator.getInstance("PKIX")
pathValidator.validate(certPath, params)
}
/**
* 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 filename Target filename.
*/
@JvmStatic
fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, filename: Path) {
FileWriter(filename.toFile()).use {
JcaPEMWriter(it).use {
it.writeObject(x509Certificate)
}
}
}
/**
* Helper method to load back a .pem/.cer format file copy of a certificate.
* @param filename Source filename.
* @return The X509Certificate that was encoded in the file.
*/
@JvmStatic
fun loadCertificateFromPEMFile(filename: Path): X509CertificateHolder {
val reader = PemReader(FileReader(filename.toFile()))
val pemObject = reader.readPemObject()
val cert = X509CertificateHolder(pemObject.content)
return cert.apply {
isValidOn(Date())
}
}
/**
* 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.
*/
fun createCertificate(certificateType: CertificateType, issuer: X500Name,
subject: X500Name, 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 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)
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
if (nameConstraints != null) {
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
}
return builder
}
/**
* Build and sign an X.509 certificate with the given signer.
*
* @param issuer name of the issuing entity.
* @param issuerSigner content signer to sign the certificate with.
* @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.
*/
fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerSigner: ContentSigner,
subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509CertificateHolder {
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
return builder.build(issuerSigner).apply {
require(isValidOn(Date()))
}
}
/**
* Build and sign an X.509 certificate with CA cert private key.
*
* @param issuer name of the issuing entity.
* @param issuerKeyPair the public & private key to sign the certificate with.
* @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.
*/
fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair,
subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509CertificateHolder {
val signatureScheme = Crypto.findSignatureScheme(issuerKeyPair.private)
val provider = Crypto.providerMap[signatureScheme.providerName]
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
return builder.build(signer).apply {
require(isValidOn(Date()))
require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public)))
}
}
/**
* Create certificate signing request using provided information.
*/
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme): PKCS10CertificationRequest {
val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.providerMap[signatureScheme.providerName])
return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public).build(signer)
}
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair) = createCertificateSigningRequest(subject, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME)
}
class CertificateStream(val input: InputStream) {
private val certificateFactory = CertificateFactory.getInstance("X.509")
fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate
}
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
CLIENT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
TLS(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment or KeyUsage.keyAgreement), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = false),
// TODO: Identity certs should have only limited depth (i.e. 1) CA signing capability, with tight name constraints
IDENTITY(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true)
}

View File

@ -1,7 +1,7 @@
package net.corda.node.utilities.registration
import com.google.common.net.MediaType
import net.corda.core.crypto.CertificateStream
import net.corda.node.utilities.CertificateStream
import org.apache.commons.io.IOUtils
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.io.IOException

View File

@ -1,17 +1,15 @@
package net.corda.node.utilities.registration
import net.corda.core.crypto.CertificateType
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.crypto.cert
import net.corda.core.internal.*
import net.corda.core.utilities.seconds
import net.corda.core.utilities.validateX500Name
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.*
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_CA
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter

View File

@ -1,12 +1,17 @@
package net.corda.node.services.network
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.crypto.CertificateAndKeyPair
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities
import net.corda.testing.*
import org.bouncycastle.asn1.x500.X500Name
import org.junit.Test

View File

@ -0,0 +1,432 @@
package net.corda.node.utilities
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.crypto.cert
import net.corda.core.crypto.commonName
import net.corda.core.crypto.getX509Name
import net.corda.core.internal.div
import net.corda.core.internal.toTypedArray
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.config.createKeystoreForCordaNode
import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1
import net.corda.nodeapi.internal.serialization.SerializationContextImpl
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.testing.*
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
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.nio.file.Path
import java.security.KeyStore
import java.security.PrivateKey
import java.security.SecureRandom
import java.security.cert.CertPath
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.*
import java.util.stream.Stream
import javax.net.ssl.*
import kotlin.concurrent.thread
import kotlin.test.*
class X509UtilitiesTest {
@Rule
@JvmField
val tempFolder: TemporaryFolder = TemporaryFolder()
@Test
fun `create valid self-signed CA certificate`() {
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = X509Utilities.createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
assertTrue { caCert.subject.commonName == "Test Cert" } // 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
}
@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(getTestX509Name("Test Cert"), caKey)
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
assertEquals(caCert, readCertificate)
}
@Test
fun `create valid server certificate chain`() {
val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = X509Utilities.createSelfSignedCACertificate(getTestX509Name("Test CA Cert"), caKey)
val subject = getTestX509Name("Server Cert")
val keyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subject, keyPair.public)
assertTrue { serverCert.subject.toString().contains("CN=Server Cert") } // 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
}
@Test
fun `storing EdDSA key in java keystore`() {
val tmpKeyStore = tempFile("keystore.jks")
val keyPair = generateKeyPair(EDDSA_ED25519_SHA512)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.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.save(tmpKeyStore, "keystorepass")
// Load the keystore from file and make sure keys are intact.
val keyStore2 = loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
val privateKey = keyStore2.getKey("Key", "password".toCharArray())
val pubKey = keyStore2.getCertificate("Key").publicKey
assertNotNull(pubKey)
assertNotNull(privateKey)
assertEquals(keyPair.public, pubKey)
assertEquals(keyPair.private, privateKey)
}
@Test
fun `signing EdDSA key with EcDSA certificate`() {
val tmpKeyStore = tempFile("keystore.jks")
val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val ecDSACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test"), ecDSAKey)
val edDSAKeypair = generateKeyPair(EDDSA_ED25519_SHA512)
val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), 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.save(tmpKeyStore, "keystorepass")
// Load the keystore from file and make sure keys are intact.
val keyStore2 = loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
val privateKey = keyStore2.getKey("Key", "password".toCharArray())
val certs = keyStore2.getCertificateChain("Key")
val pubKey = certs.last().publicKey
assertEquals(2, certs.size)
assertNotNull(pubKey)
assertNotNull(privateKey)
assertEquals(edDSAKeypair.public, pubKey)
assertEquals(edDSAKeypair.private, privateKey)
}
@Test
fun `create full CA keystore`() {
val tmpKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.jks")
// Generate Root and Intermediate CA cert and put both into key store and root ca cert into trust store
createCAKeyStoreAndTrustStore(tmpKeyStore, "keystorepass", "keypass", tmpTrustStore, "trustpass")
// Load back generated root CA Cert and private key from keystore and check against copy in truststore
val keyStore = loadKeyStore(tmpKeyStore, "keystorepass")
val trustStore = loadKeyStore(tmpTrustStore, "trustpass")
val rootCaCert = keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
val rootCaPrivateKey = keyStore.getKey(X509Utilities.CORDA_ROOT_CA, "keypass".toCharArray()) as PrivateKey
val rootCaFromTrustStore = trustStore.getCertificate(X509Utilities.CORDA_ROOT_CA) as X509Certificate
assertEquals(rootCaCert, rootCaFromTrustStore)
rootCaCert.checkValidity(Date())
rootCaCert.verify(rootCaCert.publicKey)
// Now sign something with private key and verify against certificate public key
val testData = "12345".toByteArray()
val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
// Load back generated intermediate CA Cert and private key
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA) as X509Certificate
val intermediateCaCertPrivateKey = keyStore.getKey(X509Utilities.CORDA_INTERMEDIATE_CA, "keypass".toCharArray()) as PrivateKey
intermediateCaCert.checkValidity(Date())
intermediateCaCert.verify(rootCaCert.publicKey)
// Now sign something with private key and verify against certificate public key
val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
}
@Test
fun `create server certificate in keystore for SSL`() {
val tmpCAKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.jks")
val tmpSSLKeyStore = tempFile("sslkeystore.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
createCAKeyStoreAndTrustStore(tmpCAKeyStore,
"cakeystorepass",
"cakeypass",
tmpTrustStore,
"trustpass")
// Load signing intermediate CA cert
val caKeyStore = loadKeyStore(tmpCAKeyStore, "cakeystorepass")
val caCertAndKey = caKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_INTERMEDIATE_CA, "cakeypass")
// Generate server cert and private key and populate another keystore suitable for SSL
createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverkeypass", caKeyStore, "cakeypass", MEGA_CORP.name)
// Load back server certificate
val serverKeyStore = loadKeyStore(tmpServerKeyStore, "serverstorepass")
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass")
serverCertAndKey.certificate.isValidOn(Date())
serverCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(caCertAndKey.certificate.subjectPublicKeyInfo))
assertTrue { serverCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) }
// Load back server certificate
val sslKeyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass")
val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass")
sslCertAndKey.certificate.isValidOn(Date())
sslCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(serverCertAndKey.certificate.subjectPublicKeyInfo))
assertTrue { sslCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) }
// 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, serverCertAndKey.keyPair.private, testData)
val publicKey = Crypto.toSupportedPublicKey(serverCertAndKey.certificate.subjectPublicKeyInfo)
assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) }
}
@Test
fun `create server cert and use in SSL socket`() {
val tmpCAKeyStore = tempFile("keystore.jks")
val tmpTrustStore = tempFile("truststore.jks")
val tmpSSLKeyStore = tempFile("sslkeystore.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
val caKeyStore = createCAKeyStoreAndTrustStore(tmpCAKeyStore,
"cakeystorepass",
"cakeypass",
tmpTrustStore,
"trustpass")
// Generate server cert and private key and populate another keystore suitable for SSL
createKeystoreForCordaNode(tmpSSLKeyStore, tmpServerKeyStore, "serverstorepass", "serverstorepass", caKeyStore, "cakeypass", MEGA_CORP.name)
val keyStore = loadKeyStore(tmpSSLKeyStore, "serverstorepass")
val trustStore = loadKeyStore(tmpTrustStore, "trustpass")
val context = SSLContext.getInstance("TLS")
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
keyManagerFactory.init(keyStore, "serverstorepass".toCharArray())
val keyManagers = keyManagerFactory.keyManagers
val trustMgrFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustMgrFactory.init(trustStore)
val trustManagers = trustMgrFactory.trustManagers
context.init(keyManagers, trustManagers, SecureRandom())
val serverSocketFactory = context.serverSocketFactory
val clientSocketFactory = context.socketFactory
val serverSocket = serverSocketFactory.createServerSocket(0) as SSLServerSocket // use 0 to get first free socket
val serverParams = SSLParameters(arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"),
arrayOf("TLSv1.2"))
serverParams.wantClientAuth = true
serverParams.needClientAuth = true
serverParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
serverSocket.sslParameters = serverParams
serverSocket.useClientMode = false
val clientSocket = clientSocketFactory.createSocket() as SSLSocket
val clientParams = SSLParameters(arrayOf("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"),
arrayOf("TLSv1.2"))
clientParams.endpointIdentificationAlgorithm = null // Reconfirm default no server name indication, use our own validator.
clientSocket.sslParameters = clientParams
clientSocket.useClientMode = true
// We need to specify this explicitly because by default the client binds to 'localhost' and we want it to bind
// to whatever <hostname> resolves to(as that's what the server binds to). In particular on Debian <hostname>
// resolves to 127.0.1.1 instead of the external address of the interface, so the ssl handshake fails.
clientSocket.bind(InetSocketAddress(InetAddress.getLocalHost(), 0))
val lock = Object()
var done = false
var serverError = false
val serverThread = thread {
try {
val sslServerSocket = serverSocket.accept()
assertTrue(sslServerSocket.isConnected)
val serverInput = DataInputStream(sslServerSocket.inputStream)
val receivedString = serverInput.readUTF()
assertEquals("Hello World", receivedString)
synchronized(lock) {
done = true
lock.notifyAll()
}
sslServerSocket.close()
} catch (ex: Throwable) {
serverError = true
}
}
clientSocket.connect(InetSocketAddress(InetAddress.getLocalHost(), serverSocket.localPort))
assertTrue(clientSocket.isConnected)
// Double check hostname manually
val peerChain = clientSocket.session.peerCertificates
val peerX500Principal = (peerChain[0] as X509Certificate).subjectX500Principal
val x500name = X500Name(peerX500Principal.name)
assertEquals(MEGA_CORP.name, x500name)
X509Utilities.validateCertificateChain(trustStore.getX509Certificate(X509Utilities.CORDA_ROOT_CA), *peerChain)
val output = DataOutputStream(clientSocket.outputStream)
output.writeUTF("Hello World")
var timeout = 0
synchronized(lock) {
while (!done) {
timeout++
if (timeout > 10) throw IOException("Timed out waiting for server to complete")
lock.wait(1000)
}
}
clientSocket.close()
serverThread.join(1000)
assertFalse { serverError }
serverSocket.close()
assertTrue(done)
}
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 rootCAKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(getX509Name("Corda Node Root CA", "London", "demo@r3.com", null), rootCAKey)
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, getX509Name("Corda Node Intermediate CA", "London", "demo@r3.com", null), intermediateCAKeyPair.public)
val keyPass = keyPassword.toCharArray()
val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword)
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf<Certificate>(rootCACert.cert))
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA,
intermediateCAKeyPair.private,
keyPass,
Stream.of(intermediateCACert, rootCACert).map { it.cert }.toTypedArray<Certificate>())
keyStore.save(keyStoreFilePath, storePassword)
val trustStore = loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert.cert)
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert.cert)
trustStore.save(trustStoreFilePath, trustStorePassword)
return keyStore
}
@Test
fun `Get correct private key type from Keystore`() {
val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.cert))
val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")
assertTrue(keyFromKeystore is java.security.interfaces.ECPrivateKey) // by default JKS returns SUN EC key
assertTrue(keyFromKeystoreCasted is org.bouncycastle.jce.interfaces.ECPrivateKey)
}
@Test
fun `serialize - deserialize X509CertififcateHolder`() {
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
val context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val serialized = expected.serialize(factory, context).bytes
val actual: X509CertificateHolder = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
@Test
fun `serialize - deserialize X509CertPath`() {
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
val context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
val certFactory = CertificateFactory.getInstance("X509")
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, BOB_PUBKEY)
val expected = certFactory.generateCertPath(listOf(certificate.cert, rootCACert.cert))
val serialized = expected.serialize(factory, context).bytes
val actual: CertPath = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
}

View File

@ -3,9 +3,13 @@ package net.corda.node.utilities.registration
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.mock
import net.corda.core.crypto.*
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.cert
import net.corda.core.crypto.commonName
import net.corda.core.internal.exists
import net.corda.core.internal.toTypedArray
import net.corda.node.utilities.X509Utilities
import net.corda.node.utilities.loadKeyStore
import net.corda.testing.ALICE
import net.corda.testing.getTestX509Name