mirror of
https://github.com/corda/corda.git
synced 2025-06-17 06:38:21 +00:00
Move certificate builder code from core to node utilities.
Address PR comments Fixup dead reference
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.*
|
||||
|
249
node/src/main/kotlin/net/corda/node/utilities/X509Utilities.kt
Normal file
249
node/src/main/kotlin/net/corda/node/utilities/X509Utilities.kt
Normal 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)
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user