mirror of
https://github.com/corda/corda.git
synced 2025-05-28 05:04:22 +00:00
Replace X509Certificate with X509CertificateHolder
Replace X509Certificate with X509CertificateHolder for consistency in implementation of how X.509 certificates are managed. Using the Java standard class entails the actual implementing class being one of several options depending how a certificate is built, which makes serialization/deserialization with Kryo inconsistent as some of these forms cannot be directly built from outside restricted classes.
This commit is contained in:
parent
0fd897527a
commit
c003ec0042
@ -9,6 +9,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import org.bouncycastle.asn1.ASN1Sequence
|
||||
import org.bouncycastle.asn1.DERSequence
|
||||
import org.bouncycastle.asn1.bc.BCObjectIdentifiers
|
||||
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
|
||||
@ -19,8 +20,9 @@ import org.bouncycastle.asn1.x509.Extension
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
||||
@ -29,6 +31,8 @@ import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey
|
||||
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
|
||||
import org.bouncycastle.jce.ECNamedCurveTable
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
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.pqc.jcajce.provider.BouncyCastlePQCProvider
|
||||
@ -42,7 +46,6 @@ import java.math.BigInteger
|
||||
import java.security.*
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
@ -559,20 +562,25 @@ object Crypto {
|
||||
}
|
||||
|
||||
/**
|
||||
* Use bouncy castle utilities to sign completed X509 certificate with CA cert private key.
|
||||
* 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, issuerKeyPair: KeyPair,
|
||||
fun createCertificate(certificateType: CertificateType, issuer: X500Name,
|
||||
subject: X500Name, subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Date, Date>,
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
nameConstraints: NameConstraints? = null): X509v3CertificateBuilder {
|
||||
|
||||
val signatureScheme = findSignatureScheme(issuerKeyPair.private)
|
||||
val provider = providerMap[signatureScheme.providerName]
|
||||
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.getInstance(subjectPublicKey.encoded)))
|
||||
.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)
|
||||
@ -580,11 +588,52 @@ object Crypto {
|
||||
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 = findSignatureScheme(issuerKeyPair.private)
|
||||
val provider = providerMap[signatureScheme.providerName]
|
||||
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
|
||||
|
||||
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
|
||||
return JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer)).apply {
|
||||
checkValidity(Date())
|
||||
verify(issuerKeyPair.public, provider)
|
||||
return builder.build(signer).apply {
|
||||
require(isValidOn(Date()))
|
||||
require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -671,6 +720,19 @@ object Crypto {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a public key to a supported implementation. This method is usually required to retrieve a key from an
|
||||
* [X509CertificateHolder].
|
||||
*
|
||||
* @param key a public key.
|
||||
* @return a supported implementation of the input public key.
|
||||
* @throws IllegalArgumentException on not supported scheme or if the given key specification
|
||||
* is inappropriate for a supported key factory to produce a private key.
|
||||
*/
|
||||
fun toSupportedPublicKey(key: SubjectPublicKeyInfo): PublicKey {
|
||||
return Crypto.decodePublicKey(key.encoded)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a public key to a supported implementation. This can be used to convert a SUN's EC key to an BC key.
|
||||
* This method is usually required to retrieve a key (via its corresponding cert) from JKS keystores that by default return SUN implementations.
|
||||
|
@ -22,6 +22,7 @@ val NULL_PARTY = AnonymousParty(NullPublicKey)
|
||||
|
||||
// TODO: Clean up this duplication between Null and Dummy public key
|
||||
@CordaSerializable
|
||||
@Deprecated("Has encoding format problems, consider entropyToKeyPair() instead")
|
||||
class DummyPublicKey(val s: String) : PublicKey, Comparable<PublicKey> {
|
||||
override fun getAlgorithm() = "DUMMY"
|
||||
override fun getEncoded() = s.toByteArray()
|
||||
|
@ -3,6 +3,10 @@ package net.corda.core.crypto
|
||||
import net.corda.core.exists
|
||||
import net.corda.core.read
|
||||
import net.corda.core.write
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.path.CertPath
|
||||
import org.bouncycastle.crypto.util.PublicKeyFactory
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
@ -67,6 +71,19 @@ object KeyStoreUtilities {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: CertPath) {
|
||||
val converter = JcaX509CertificateConverter()
|
||||
addOrReplaceKey(alias, key, password, chain.certificates.map { it -> converter.getCertificate(it) }.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.
|
||||
@ -122,8 +139,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 = getCertificate(alias) as X509Certificate
|
||||
return CertificateAndKeyPair(cert, KeyPair(Crypto.toSupportedPublicKey(cert.publicKey), getSupportedKey(alias, keyPassword)))
|
||||
val cert = getX509Certificate(alias)
|
||||
val publicKey = Crypto.toSupportedPublicKey(cert.subjectPublicKeyInfo)
|
||||
return CertificateAndKeyPair(cert, KeyPair(publicKey, getSupportedKey(alias, keyPassword)))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,7 +149,10 @@ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): Certi
|
||||
* @param alias The name to lookup the Key and Certificate chain from.
|
||||
* @return The X509Certificate found in the KeyStore under the specified alias.
|
||||
*/
|
||||
fun KeyStore.getX509Certificate(alias: String): X509Certificate = getCertificate(alias) as X509Certificate
|
||||
fun KeyStore.getX509Certificate(alias: String): X509CertificateHolder {
|
||||
val encoded = getCertificate(alias)?.encoded ?: throw IllegalArgumentException("No certificate under alias \"${alias}\"")
|
||||
return X509CertificateHolder(encoded)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a private key from a KeyStore file assuming storage alias is known.
|
||||
|
@ -1,16 +1,17 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.crypto.Crypto.generateKeyPair
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.mapToArray
|
||||
import org.bouncycastle.asn1.ASN1Encodable
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.X500NameBuilder
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.asn1.x509.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.util.io.pem.PemReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.InputStream
|
||||
@ -62,7 +63,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.
|
||||
*/
|
||||
private fun getCertificateValidityWindow(before: Duration, after: Duration, parent: X509Certificate? = null): Pair<Date, Date> {
|
||||
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)
|
||||
@ -104,10 +105,9 @@ object X509Utilities {
|
||||
* 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): X509Certificate {
|
||||
fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
|
||||
val cert = Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
|
||||
return cert
|
||||
return Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,13 +122,12 @@ object X509Utilities {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun createCertificate(certificateType: CertificateType,
|
||||
issuerCertificate: X509Certificate, issuerKeyPair: KeyPair,
|
||||
issuerCertificate: X509CertificateHolder, issuerKeyPair: KeyPair,
|
||||
subject: X500Name, subjectPublicKey: PublicKey,
|
||||
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
|
||||
nameConstraints: NameConstraints? = null): X509Certificate {
|
||||
nameConstraints: NameConstraints? = null): X509CertificateHolder {
|
||||
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
|
||||
val cert = Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
|
||||
return cert
|
||||
return Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,17 +138,19 @@ object X509Utilities {
|
||||
* @param certificates certificates in the path.
|
||||
* @param revocationEnabled whether revocation of certificates in the path should be checked.
|
||||
*/
|
||||
fun createCertificatePath(trustedRoot: X509Certificate, vararg certificates: X509Certificate, revocationEnabled: Boolean): CertPath {
|
||||
fun createCertificatePath(trustedRoot: X509CertificateHolder, vararg certificates: X509CertificateHolder, revocationEnabled: Boolean): CertPath {
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
val trustedRootX509 = certFactory.generateCertificate(ByteArrayInputStream(trustedRoot.encoded)) as X509Certificate
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRootX509, null)))
|
||||
params.isRevocationEnabled = revocationEnabled
|
||||
return certFactory.generateCertPath(certificates.toList())
|
||||
return certFactory.generateCertPath(certificates.map { certFactory.generateCertificate(ByteArrayInputStream(it.encoded)) }.toList())
|
||||
}
|
||||
|
||||
fun validateCertificateChain(trustedRoot: X509Certificate, vararg certificates: Certificate) {
|
||||
fun validateCertificateChain(trustedRoot: X509CertificateHolder, vararg certificates: Certificate) {
|
||||
require(certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val certFactory = CertificateFactory.getInstance("X509")
|
||||
val params = PKIXParameters(setOf(TrustAnchor(trustedRoot, null)))
|
||||
val params = PKIXParameters(setOf(TrustAnchor(converter.getCertificate(trustedRoot), null)))
|
||||
params.isRevocationEnabled = false
|
||||
val certPath = certFactory.generateCertPath(certificates.toList())
|
||||
val pathValidator = CertPathValidator.getInstance("PKIX")
|
||||
@ -162,7 +163,7 @@ object X509Utilities {
|
||||
* @param filename Target filename.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509Certificate, filename: Path) {
|
||||
fun saveCertificateAsPEMFile(x509Certificate: X509CertificateHolder, filename: Path) {
|
||||
FileWriter(filename.toFile()).use {
|
||||
JcaPEMWriter(it).use {
|
||||
it.writeObject(x509Certificate)
|
||||
@ -176,11 +177,12 @@ object X509Utilities {
|
||||
* @return The X509Certificate that was encoded in the file.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun loadCertificateFromPEMFile(filename: Path): X509Certificate {
|
||||
fun loadCertificateFromPEMFile(filename: Path): X509CertificateHolder {
|
||||
val reader = PemReader(FileReader(filename.toFile()))
|
||||
val pemObject = reader.readPemObject()
|
||||
return CertificateStream(pemObject.content.inputStream()).nextCertificate().apply {
|
||||
checkValidity()
|
||||
val cert = X509CertificateHolder(pemObject.content)
|
||||
return cert.apply {
|
||||
isValidOn(Date())
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +223,7 @@ object X509Utilities {
|
||||
CORDA_CLIENT_CA,
|
||||
clientKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientCACert, intermediateCACert, rootCACert))
|
||||
org.bouncycastle.cert.path.CertPath(arrayOf(clientCACert, intermediateCACert, rootCACert)))
|
||||
clientCAKeystore.save(clientCAKeystorePath, storePassword)
|
||||
|
||||
val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeyStorePath, storePassword)
|
||||
@ -229,7 +231,7 @@ object X509Utilities {
|
||||
CORDA_CLIENT_TLS,
|
||||
tlsKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert))
|
||||
org.bouncycastle.cert.path.CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCACert, rootCACert)))
|
||||
tlsKeystore.save(sslKeyStorePath, storePassword)
|
||||
}
|
||||
|
||||
@ -286,7 +288,7 @@ class CertificateStream(val input: InputStream) {
|
||||
fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate
|
||||
}
|
||||
|
||||
data class CertificateAndKeyPair(val certificate: X509Certificate, val keyPair: KeyPair)
|
||||
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
|
||||
|
||||
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),
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.core.utilities.unwrap
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
@ -15,7 +16,7 @@ import java.security.cert.X509Certificate
|
||||
*/
|
||||
object TxKeyFlow {
|
||||
abstract class AbstractIdentityFlow(val otherSide: Party, val revocationEnabled: Boolean): FlowLogic<Map<Party, AnonymousIdentity>>() {
|
||||
fun validateIdentity(untrustedIdentity: Pair<X509Certificate, CertPath>): AnonymousIdentity {
|
||||
fun validateIdentity(untrustedIdentity: Pair<X509CertificateHolder, CertPath>): AnonymousIdentity {
|
||||
val (wellKnownCert, certPath) = untrustedIdentity
|
||||
val theirCert = certPath.certificates.last()
|
||||
// TODO: Don't trust self-signed certificates
|
||||
@ -24,7 +25,7 @@ object TxKeyFlow {
|
||||
if (certName == otherSide.name) {
|
||||
val anonymousParty = AnonymousParty(theirCert.publicKey)
|
||||
serviceHub.identityService.registerPath(wellKnownCert, anonymousParty, certPath)
|
||||
AnonymousIdentity(certPath, theirCert, anonymousParty)
|
||||
AnonymousIdentity(certPath, X509CertificateHolder(theirCert.encoded), anonymousParty)
|
||||
} else
|
||||
throw IllegalStateException("Expected certificate subject to be ${otherSide.name} but found ${certName}")
|
||||
} else
|
||||
@ -50,7 +51,7 @@ object TxKeyFlow {
|
||||
override fun call(): Map<Party, AnonymousIdentity> {
|
||||
progressTracker.currentStep = AWAITING_KEY
|
||||
val myIdentityFragment = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentity, revocationEnabled)
|
||||
val theirIdentity = receive<Pair<X509Certificate, CertPath>>(otherSide).unwrap { validateIdentity(it) }
|
||||
val theirIdentity = receive<Pair<X509CertificateHolder, CertPath>>(otherSide).unwrap { validateIdentity(it) }
|
||||
send(otherSide, myIdentityFragment)
|
||||
return mapOf(Pair(otherSide, AnonymousIdentity(myIdentityFragment)),
|
||||
Pair(serviceHub.myInfo.legalIdentity, theirIdentity))
|
||||
@ -78,7 +79,7 @@ object TxKeyFlow {
|
||||
progressTracker.currentStep = SENDING_KEY
|
||||
val myIdentityFragment = serviceHub.keyManagementService.freshKeyAndCert(serviceHub.myInfo.legalIdentity, revocationEnabled)
|
||||
send(otherSide, myIdentityFragment)
|
||||
val theirIdentity = receive<Pair<X509Certificate, CertPath>>(otherSide).unwrap { validateIdentity(it) }
|
||||
val theirIdentity = receive<Pair<X509CertificateHolder, CertPath>>(otherSide).unwrap { validateIdentity(it) }
|
||||
return mapOf(Pair(otherSide, AnonymousIdentity(myIdentityFragment)),
|
||||
Pair(serviceHub.myInfo.legalIdentity, theirIdentity))
|
||||
}
|
||||
@ -86,9 +87,9 @@ object TxKeyFlow {
|
||||
|
||||
data class AnonymousIdentity(
|
||||
val certPath: CertPath,
|
||||
val certificate: X509Certificate,
|
||||
val certificate: X509CertificateHolder,
|
||||
val identity: AnonymousParty) {
|
||||
constructor(myIdentity: Pair<X509Certificate, CertPath>) : this(myIdentity.second,
|
||||
constructor(myIdentity: Pair<X509CertificateHolder, CertPath>) : this(myIdentity.second,
|
||||
myIdentity.first,
|
||||
AnonymousParty(myIdentity.second.certificates.last().publicKey))
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ import java.security.PublicKey
|
||||
*
|
||||
* @see CompositeKey
|
||||
*/
|
||||
class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) {
|
||||
constructor(certAndKey: CertificateAndKeyPair) : this(X500Name(certAndKey.certificate.subjectDN.name), certAndKey.keyPair.public)
|
||||
open class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) {
|
||||
constructor(certAndKey: CertificateAndKeyPair) : this(certAndKey.certificate.subject, certAndKey.keyPair.public)
|
||||
override fun toString() = name.toString()
|
||||
override fun nameOrNull(): X500Name? = name
|
||||
|
||||
|
@ -6,6 +6,7 @@ import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
@ -30,7 +31,7 @@ interface IdentityService {
|
||||
*/
|
||||
// TODO: Move this into internal identity service once available
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun registerPath(trustedRoot: X509Certificate, anonymousParty: AnonymousParty, path: CertPath)
|
||||
fun registerPath(trustedRoot: X509CertificateHolder, anonymousParty: AnonymousParty, path: CertPath)
|
||||
|
||||
/**
|
||||
* Asserts that an anonymous party maps to the given full party, by looking up the certificate chain associated with
|
||||
|
@ -3,10 +3,12 @@ package net.corda.core.node.services
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.keys
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.vault.PageSpecification
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
@ -17,6 +19,8 @@ import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import rx.Observable
|
||||
import java.io.InputStream
|
||||
import java.security.PublicKey
|
||||
@ -396,7 +400,7 @@ interface KeyManagementService {
|
||||
* @return X.509 certificate and path to the trust root.
|
||||
*/
|
||||
@Suspendable
|
||||
fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath>
|
||||
fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath>
|
||||
|
||||
/** Using the provided signing [PublicKey] internally looks up the matching [PrivateKey] and signs the data.
|
||||
* @param bytes The data to sign over using the chosen key.
|
||||
|
@ -18,6 +18,7 @@ import net.corda.core.utilities.NonEmptySetSerializer
|
||||
import net.i2p.crypto.eddsa.EdDSAPrivateKey
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey
|
||||
@ -102,11 +103,8 @@ object DefaultKryoCustomizer {
|
||||
|
||||
register(CertPath::class.java, CertPathSerializer)
|
||||
register(X509CertPath::class.java, CertPathSerializer)
|
||||
// TODO: We shouldn't need to serialize raw certificates, and if we do then we need a cleaner solution
|
||||
// than this mess.
|
||||
val x509CertObjectClazz = Class.forName("org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject")
|
||||
register(x509CertObjectClazz, X509CertificateSerializer)
|
||||
register(X500Name::class.java, X500NameSerializer)
|
||||
register(X509CertificateHolder::class.java, X509CertificateSerializer)
|
||||
|
||||
register(BCECPrivateKey::class.java, PrivateKeySerializer)
|
||||
register(BCECPublicKey::class.java, PublicKeySerializer)
|
||||
|
@ -20,6 +20,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
|
||||
import org.bouncycastle.asn1.ASN1InputStream
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayInputStream
|
||||
@ -632,16 +633,15 @@ object CertPathSerializer : Serializer<CertPath>() {
|
||||
}
|
||||
|
||||
/**
|
||||
* For serialising an [CX509Certificate] in an X.500 standard format.
|
||||
* For serialising an [CX509CertificateHolder] in an X.500 standard format.
|
||||
*/
|
||||
@ThreadSafe
|
||||
object X509CertificateSerializer : Serializer<X509Certificate>() {
|
||||
val factory = CertificateFactory.getInstance("X.509")
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<X509Certificate>): X509Certificate {
|
||||
return factory.generateCertificate(input) as X509Certificate
|
||||
object X509CertificateSerializer : Serializer<X509CertificateHolder>() {
|
||||
override fun read(kryo: Kryo, input: Input, type: Class<X509CertificateHolder>): X509CertificateHolder {
|
||||
return X509CertificateHolder(input.readBytes())
|
||||
}
|
||||
|
||||
override fun write(kryo: Kryo, output: Output, obj: X509Certificate) {
|
||||
override fun write(kryo: Kryo, output: Output, obj: X509CertificateHolder) {
|
||||
output.writeBytes(obj.encoded)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,23 @@
|
||||
package net.corda.core.crypto
|
||||
|
||||
import net.corda.core.mapToArray
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.junit.Test
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertPathValidator
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.PKIXParameters
|
||||
import java.security.cert.*
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class X509NameConstraintsTest {
|
||||
|
||||
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = X509Utilities.createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Root CA"), rootKeys)
|
||||
|
||||
@ -29,14 +30,15 @@ class X509NameConstraintsTest {
|
||||
val keyPass = "password"
|
||||
val trustStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE)
|
||||
trustStore.load(null, keyPass.toCharArray())
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, converter.getCertificate(rootCACert))
|
||||
|
||||
val tlsKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val tlsCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientCAKeyPair, subjectName, tlsKey.public)
|
||||
|
||||
val keyStore = KeyStore.getInstance(KeyStoreUtilities.KEYSTORE_TYPE)
|
||||
keyStore.load(null, keyPass.toCharArray())
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(), arrayOf(tlsCert, clientCACert, intermediateCACert, rootCACert))
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_CLIENT_TLS, tlsKey.private, keyPass.toCharArray(),
|
||||
listOf(tlsCert, clientCACert, intermediateCACert, rootCACert).mapToArray<X509CertificateHolder, Certificate>(converter::getCertificate))
|
||||
return Pair(keyStore, trustStore)
|
||||
}
|
||||
|
||||
|
@ -5,29 +5,35 @@ import net.corda.core.crypto.Crypto.generateKeyPair
|
||||
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME
|
||||
import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate
|
||||
import net.corda.core.div
|
||||
import net.corda.core.mapToArray
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.getTestX509Name
|
||||
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.cert.jcajce.JcaX509CertificateConverter
|
||||
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.math.BigInteger
|
||||
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.Certificate
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import javax.net.ssl.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.*
|
||||
|
||||
class X509UtilitiesTest {
|
||||
@Rule
|
||||
@ -38,12 +44,14 @@ class X509UtilitiesTest {
|
||||
fun `create valid self-signed CA certificate`() {
|
||||
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
|
||||
assertTrue { caCert.subjectDN.name.contains("CN=Test Cert") } // using our subject common name
|
||||
assertEquals(caCert.issuerDN, caCert.subjectDN) //self-signed
|
||||
caCert.checkValidity(Date()) // throws on verification problems
|
||||
caCert.verify(caKey.public) // throws on verification problems
|
||||
assertTrue { caCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertTrue { caCert.basicConstraints > 0 } // This returns the signing path length Would be -1 for non-CA certificate
|
||||
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
|
||||
@ -60,29 +68,33 @@ class X509UtilitiesTest {
|
||||
fun `create valid server certificate chain`() {
|
||||
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = createSelfSignedCACertificate(getTestX509Name("Test CA Cert"), caKey)
|
||||
val subjectDN = getTestX509Name("Server Cert")
|
||||
val subject = getTestX509Name("Server Cert")
|
||||
val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subjectDN, keyPair.public)
|
||||
assertTrue { serverCert.subjectDN.name.contains("CN=Server Cert") } // using our subject common name
|
||||
assertEquals(caCert.issuerDN, serverCert.issuerDN) // Issued by our CA cert
|
||||
serverCert.checkValidity(Date()) // throws on verification problems
|
||||
serverCert.verify(caKey.public) // throws on verification problems
|
||||
assertFalse { serverCert.keyUsage[5] } // Bit 5 == keyCertSign according to ASN.1 spec (see full comment on KeyUsage property)
|
||||
assertTrue { serverCert.basicConstraints == -1 } // This returns the signing path length should be -1 for non-CA certificate
|
||||
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 converter = JcaX509CertificateConverter()
|
||||
val keyPair = generateKeyPair(EDDSA_ED25519_SHA512)
|
||||
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
|
||||
|
||||
assertEquals(selfSignCert.publicKey, keyPair.public)
|
||||
assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.encoded, keyPair.public.encoded))
|
||||
|
||||
// Save the EdDSA private key with self sign cert in the keystore.
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(), arrayOf(selfSignCert))
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "password".toCharArray(),
|
||||
listOf(selfSignCert).mapToArray(converter::getCertificate))
|
||||
keyStore.save(tmpKeyStore, "keystorepass")
|
||||
|
||||
// Load the keystore from file and make sure keys are intact.
|
||||
@ -105,8 +117,10 @@ class X509UtilitiesTest {
|
||||
val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public)
|
||||
|
||||
// Save the EdDSA private key with cert chains.
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tmpKeyStore, "keystorepass")
|
||||
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(), arrayOf(ecDSACert, edDSACert))
|
||||
keyStore.setKeyEntry("Key", edDSAKeypair.private, "password".toCharArray(),
|
||||
listOf(ecDSACert, edDSACert).mapToArray(converter::getCertificate))
|
||||
keyStore.save(tmpKeyStore, "keystorepass")
|
||||
|
||||
// Load the keystore from file and make sure keys are intact.
|
||||
@ -182,23 +196,24 @@ class X509UtilitiesTest {
|
||||
val serverKeyStore = KeyStoreUtilities.loadKeyStore(tmpServerKeyStore, "serverstorepass")
|
||||
val serverCertAndKey = serverKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, "serverkeypass")
|
||||
|
||||
serverCertAndKey.certificate.checkValidity(Date())
|
||||
serverCertAndKey.certificate.verify(caCertAndKey.certificate.publicKey)
|
||||
serverCertAndKey.certificate.isValidOn(Date())
|
||||
serverCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(caCertAndKey.certificate.subjectPublicKeyInfo))
|
||||
|
||||
assertTrue { serverCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) }
|
||||
assertTrue { serverCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) }
|
||||
|
||||
// Load back server certificate
|
||||
val sslKeyStore = KeyStoreUtilities.loadKeyStore(tmpSSLKeyStore, "serverstorepass")
|
||||
val sslCertAndKey = sslKeyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, "serverkeypass")
|
||||
|
||||
sslCertAndKey.certificate.checkValidity(Date())
|
||||
sslCertAndKey.certificate.verify(serverCertAndKey.certificate.publicKey)
|
||||
sslCertAndKey.certificate.isValidOn(Date())
|
||||
sslCertAndKey.certificate.isSignatureValid(JcaContentVerifierProviderBuilder().build(serverCertAndKey.certificate.subjectPublicKeyInfo))
|
||||
|
||||
assertTrue { sslCertAndKey.certificate.subjectDN.name.contains(MEGA_CORP.name.commonName) }
|
||||
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(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
|
||||
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.certificate.publicKey, signature, testData) }
|
||||
val publicKey = Crypto.toSupportedPublicKey(serverCertAndKey.certificate.subjectPublicKeyInfo)
|
||||
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -330,6 +345,7 @@ class X509UtilitiesTest {
|
||||
trustStoreFilePath: Path,
|
||||
trustStorePassword: String
|
||||
): KeyStore {
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val rootCAKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val rootCACert = createSelfSignedCACertificate(X509Utilities.getDevX509Name("Corda Node Root CA"), rootCAKey)
|
||||
|
||||
@ -339,19 +355,19 @@ class X509UtilitiesTest {
|
||||
val keyPass = keyPassword.toCharArray()
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(keyStoreFilePath, storePassword)
|
||||
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(rootCACert))
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_ROOT_CA, rootCAKey.private, keyPass, arrayOf(converter.getCertificate(rootCACert) as Certificate))
|
||||
|
||||
keyStore.addOrReplaceKey(X509Utilities.CORDA_INTERMEDIATE_CA,
|
||||
intermediateCAKeyPair.private,
|
||||
keyPass,
|
||||
arrayOf(intermediateCACert, rootCACert))
|
||||
listOf(intermediateCACert, rootCACert).mapToArray<X509CertificateHolder, Certificate>(converter::getCertificate))
|
||||
|
||||
keyStore.save(keyStoreFilePath, storePassword)
|
||||
|
||||
val trustStore = KeyStoreUtilities.loadOrCreateKeyStore(trustStoreFilePath, trustStorePassword)
|
||||
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCACert)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, intermediateCACert)
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, converter.getCertificate(rootCACert))
|
||||
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_INTERMEDIATE_CA, converter.getCertificate(intermediateCACert))
|
||||
|
||||
trustStore.save(trustStoreFilePath, trustStorePassword)
|
||||
|
||||
@ -360,10 +376,11 @@ class X509UtilitiesTest {
|
||||
|
||||
@Test
|
||||
fun `Get correct private key type from Keystore`() {
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
|
||||
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
|
||||
val keyStore = KeyStoreUtilities.loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert))
|
||||
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(converter.getCertificate(selfSignCert)))
|
||||
|
||||
val keyFromKeystore = keyStore.getKey("Key", "keypassword".toCharArray())
|
||||
val keyFromKeystoreCasted = keyStore.getSupportedKey("Key", "keypassword")
|
||||
|
@ -10,6 +10,7 @@ import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.testing.BOB_PUBKEY
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.slf4j.LoggerFactory
|
||||
@ -142,10 +143,10 @@ class KryoTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `serialize - deserialize X509Certififcate`() {
|
||||
val expected = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
fun `serialize - deserialize X509CertififcateHolder`() {
|
||||
val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
val serialized = expected.serialize(kryo).bytes
|
||||
val actual: X509Certificate = serialized.deserialize(kryo)
|
||||
val actual: X509CertificateHolder = serialized.deserialize(kryo)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.bouncycastle.asn1.x509.GeneralName
|
||||
import org.bouncycastle.asn1.x509.GeneralSubtree
|
||||
import org.bouncycastle.asn1.x509.NameConstraints
|
||||
import org.bouncycastle.cert.path.CertPath
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
|
||||
@ -111,7 +112,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
X509Utilities.CORDA_CLIENT_CA,
|
||||
clientKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientCACert, intermediateCA.certificate, rootCACert))
|
||||
CertPath(arrayOf(clientCACert, intermediateCA.certificate, rootCACert)))
|
||||
clientCAKeystore.save(nodeKeystore, keyStorePassword)
|
||||
|
||||
val tlsKeystore = KeyStoreUtilities.loadOrCreateKeyStore(sslKeystore, keyStorePassword)
|
||||
@ -119,7 +120,7 @@ class MQSecurityAsNodeTest : MQSecurityTest() {
|
||||
X509Utilities.CORDA_CLIENT_TLS,
|
||||
tlsKey.private,
|
||||
keyPass,
|
||||
arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert))
|
||||
CertPath(arrayOf(clientTLSCert, clientCACert, intermediateCA.certificate, rootCACert)))
|
||||
tlsKeystore.save(sslKeystore, keyStorePassword)
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ import net.corda.node.utilities.transaction
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.slf4j.Logger
|
||||
import java.io.IOException
|
||||
@ -671,8 +672,10 @@ private class KeyStoreWrapper(private val storePath: Path, private val storePass
|
||||
|
||||
fun save(serviceName: X500Name, privateKeyAlias: String, keyPair: KeyPair) {
|
||||
val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, storePassword)
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val cert = X509Utilities.createCertificate(CertificateType.IDENTITY, clientCA.certificate, clientCA.keyPair, serviceName, keyPair.public)
|
||||
keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(), arrayOf(cert, *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
|
||||
keyStore.addOrReplaceKey(privateKeyAlias, keyPair.private, storePassword.toCharArray(),
|
||||
arrayOf(converter.getCertificate(cert), *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
|
||||
keyStore.save(storePath, storePassword)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.trace
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import java.security.InvalidAlgorithmParameterException
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.*
|
||||
@ -82,8 +84,9 @@ class InMemoryIdentityService(identities: Iterable<Party> = emptySet(),
|
||||
override fun pathForAnonymous(anonymousParty: AnonymousParty): CertPath? = partyToPath[anonymousParty]
|
||||
|
||||
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
|
||||
override fun registerPath(trustedRoot: X509Certificate, anonymousParty: AnonymousParty, path: CertPath) {
|
||||
val expectedTrustAnchor = TrustAnchor(trustedRoot, null)
|
||||
override fun registerPath(trustedRoot: X509CertificateHolder, anonymousParty: AnonymousParty, path: CertPath) {
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val expectedTrustAnchor = TrustAnchor(converter.getCertificate(trustedRoot), null)
|
||||
require(path.certificates.isNotEmpty()) { "Certificate path must contain at least one certificate" }
|
||||
val target = path.certificates.last() as X509Certificate
|
||||
require(target.publicKey == anonymousParty.owningKey) { "Certificate path must end with anonymous party's public key" }
|
||||
|
@ -9,11 +9,12 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import javax.annotation.concurrent.ThreadSafe
|
||||
|
||||
@ -56,7 +57,7 @@ class E2ETestKeyManagementService(val identityService: IdentityService,
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
|
||||
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
|
||||
|
||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||
return mutex.locked {
|
||||
|
@ -1,12 +1,17 @@
|
||||
package net.corda.node.services.keys
|
||||
|
||||
import net.corda.core.crypto.CertificateType
|
||||
import net.corda.core.crypto.ContentSignerBuilder
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.X509Utilities
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import java.security.KeyPair
|
||||
import java.security.Security
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
@ -23,7 +28,7 @@ import java.security.cert.X509Certificate
|
||||
fun freshKeyAndCert(keyManagementService: KeyManagementService,
|
||||
identityService: IdentityService,
|
||||
identity: Party,
|
||||
revocationEnabled: Boolean = false): Pair<X509Certificate, CertPath> {
|
||||
revocationEnabled: Boolean = false): Pair<X509CertificateHolder, CertPath> {
|
||||
val ourPublicKey = keyManagementService.freshKey()
|
||||
// FIXME: Use the actual certificate for the identity the flow is presenting themselves as
|
||||
val issuerKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_IDENTITY_SIGNATURE_SCHEME)
|
||||
|
@ -10,13 +10,14 @@ import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.node.utilities.*
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.operator.ContentSigner
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
/**
|
||||
* A persistent re-implementation of [E2ETestKeyManagementService] to support node re-start.
|
||||
@ -66,8 +67,7 @@ class PersistentKeyManagementService(val identityService: IdentityService,
|
||||
}
|
||||
return keyPair.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
|
||||
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
|
||||
|
||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||
return mutex.locked {
|
||||
|
@ -263,10 +263,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
|
||||
val trustStore = KeyStoreUtilities.loadKeyStore(config.trustStoreFile, config.trustStorePassword)
|
||||
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_TLS)
|
||||
|
||||
val ourSubjectDN = X500Name(ourCertificate.subjectDN.name)
|
||||
// This is a sanity check and should not fail unless things have been misconfigured
|
||||
require(ourSubjectDN == config.myLegalName) {
|
||||
"Legal name does not match with our subject CN: $ourSubjectDN"
|
||||
require(ourCertificate.subject == config.myLegalName) {
|
||||
"Legal name does not match with our subject CN: ${ourCertificate.subject}"
|
||||
}
|
||||
val defaultCertPolicies = mapOf(
|
||||
PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch,
|
||||
@ -510,7 +509,7 @@ private class VerifyingNettyConnector(configuration: MutableMap<String, Any>,
|
||||
"Peer has wrong subject name in the certificate - expected $expectedLegalName but got ${peerCertificate.subject}. This is either a fatal " +
|
||||
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
|
||||
}
|
||||
X509Utilities.validateCertificateChain(X509CertImpl(session.localCertificates.last().encoded), *session.peerCertificates)
|
||||
X509Utilities.validateCertificateChain(X509CertificateHolder(session.localCertificates.last().encoded), *session.peerCertificates)
|
||||
server.onTcpConnection(peerLegalName)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
connection.close()
|
||||
|
@ -2,7 +2,9 @@ package net.corda.node.services.network
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting
|
||||
import net.corda.core.ThreadBox
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.isFulfilledBy
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.messaging.MessageRecipients
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
|
@ -6,6 +6,8 @@ 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.node.services.config.NodeConfiguration
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.bouncycastle.cert.path.CertPath
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
import org.bouncycastle.util.io.pem.PemObject
|
||||
import java.io.StringWriter
|
||||
@ -40,7 +42,8 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val selfSignCert = X509Utilities.createSelfSignedCACertificate(config.myLegalName, keyPair)
|
||||
// Save to the key store.
|
||||
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(), arrayOf(selfSignCert))
|
||||
caKeyStore.addOrReplaceKey(SELF_SIGNED_PRIVATE_KEY, keyPair.private, privateKeyPassword.toCharArray(),
|
||||
CertPath(arrayOf(selfSignCert)))
|
||||
caKeyStore.save(config.nodeKeystore, keystorePassword)
|
||||
}
|
||||
val keyPair = caKeyStore.getKeyPair(SELF_SIGNED_PRIVATE_KEY, privateKeyPassword)
|
||||
@ -69,11 +72,13 @@ class NetworkRegistrationHelper(val config: NodeConfiguration, val certService:
|
||||
println("Node private key and certificate stored in ${config.nodeKeystore}.")
|
||||
|
||||
println("Generating SSL certificate for node messaging service.")
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val sslKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||
val caCert = caKeyStore.getX509Certificate(CORDA_CLIENT_CA)
|
||||
val sslCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, keyPair, caCert.subject, sslKey.public)
|
||||
val sslKeyStore = KeyStoreUtilities.loadOrCreateKeyStore(config.sslKeystore, keystorePassword)
|
||||
sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(), arrayOf(sslCert, *certificates))
|
||||
sslKeyStore.addOrReplaceKey(CORDA_CLIENT_TLS, sslKey.private, privateKeyPassword.toCharArray(),
|
||||
arrayOf(converter.getCertificate(sslCert), *certificates))
|
||||
sslKeyStore.save(config.sslKeystore, config.keyStorePassword)
|
||||
println("SSL private key and certificate stored in ${config.sslKeystore}.")
|
||||
// All done, clean up temp files.
|
||||
|
@ -5,10 +5,12 @@ import com.nhaarman.mockito_kotlin.eq
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.exists
|
||||
import net.corda.core.mapToArray
|
||||
import net.corda.core.utilities.ALICE
|
||||
import net.corda.testing.TestNodeConfiguration
|
||||
import net.corda.testing.getTestX509Name
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
@ -29,8 +31,9 @@ class NetworkRegistrationHelperTest {
|
||||
"CORDA_INTERMEDIATE_CA",
|
||||
"CORDA_ROOT_CA")
|
||||
.map { getTestX509Name(it) }
|
||||
val converter = JcaX509CertificateConverter()
|
||||
val certs = identities.map { X509Utilities.createSelfSignedCACertificate(it, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)) }
|
||||
.toTypedArray()
|
||||
.mapToArray(converter::getCertificate)
|
||||
|
||||
val certService: NetworkRegistrationService = mock {
|
||||
on { submitRequest(any()) }.then { id }
|
||||
|
@ -21,6 +21,7 @@ import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MINI_CORP
|
||||
import net.corda.testing.MOCK_VERSION_INFO
|
||||
import org.bouncycastle.cert.X509CertificateHolder
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.io.ByteArrayInputStream
|
||||
@ -32,7 +33,6 @@ import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.cert.CertPath
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Clock
|
||||
import java.util.*
|
||||
import java.util.jar.JarInputStream
|
||||
@ -91,7 +91,7 @@ class MockKeyManagementService(val identityService: IdentityService,
|
||||
return k.public
|
||||
}
|
||||
|
||||
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509Certificate, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
|
||||
override fun freshKeyAndCert(identity: Party, revocationEnabled: Boolean): Pair<X509CertificateHolder, CertPath> = freshKeyAndCert(this, identityService, identity, revocationEnabled)
|
||||
|
||||
private fun getSigningKeyPair(publicKey: PublicKey): KeyPair {
|
||||
val pk = publicKey.keys.first { keyStore.containsKey(it) }
|
||||
|
@ -3,6 +3,7 @@ package net.corda.testing.node
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import net.corda.core.crypto.CertificateAndKeyPair
|
||||
import net.corda.core.crypto.commonName
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.messaging.RPCOps
|
||||
@ -30,7 +31,9 @@ import kotlin.concurrent.thread
|
||||
* This is a bare-bones node which can only send and receive messages. It doesn't register with a network map service or
|
||||
* any other such task that would make it functional in a network and thus left to the user to do so manually.
|
||||
*/
|
||||
class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeLocalHostAndPort(), rpcAddress: HostAndPort = freeLocalHostAndPort()) : AutoCloseable {
|
||||
class SimpleNode(val config: NodeConfiguration, val address: HostAndPort = freeLocalHostAndPort(),
|
||||
rpcAddress: HostAndPort = freeLocalHostAndPort(),
|
||||
networkRoot: CertificateAndKeyPair? = null) : AutoCloseable {
|
||||
|
||||
private val databaseWithCloseable: Pair<Closeable, Database> = configureDatabase(config.dataSourceProperties)
|
||||
val database: Database get() = databaseWithCloseable.second
|
||||
|
Loading…
x
Reference in New Issue
Block a user