Add infrastructure for X.500 name support (#534)

This introduces new functions for fetching parties by their X.500 name, Kryo serialization support for X500Name objects, an X500Name generator and some X509 utility support in preparation for full X.500 name support.
This commit is contained in:
Ross Nicoll 2017-04-13 15:32:34 +01:00 committed by GitHub
parent 1a88ca4bee
commit 6d48667d91
10 changed files with 132 additions and 14 deletions

View File

@ -38,6 +38,7 @@ import java.security.spec.ECGenParameterSpec
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.*
import javax.security.auth.x500.X500Principal
object X509Utilities {
@ -236,6 +237,18 @@ object X509Utilities {
return keyGen.generateKeyPair()
}
/**
* Create certificate signing request using provided information.
*
* @param commonName The legal name of your organization. This should not be abbreviated and should include suffixes such as Inc, Corp, or LLC.
* @param nearestCity The city where your organization is located.
* @param email An email address used to contact your organization.
* @param keyPair Standard curve ECDSA KeyPair generated for TLS.
* @return The generated Certificate signing request.
*/
@Deprecated("Use [createCertificateSigningRequest(X500Name, KeyPair)] instead, specifying full legal name")
fun createCertificateSigningRequest(commonName: String, nearestCity: String, email: String, keyPair: KeyPair): PKCS10CertificationRequest = createCertificateSigningRequest(getX509Name(commonName, nearestCity, email), keyPair)
/**
* Create certificate signing request using provided information.
*
@ -245,8 +258,7 @@ object X509Utilities {
* @param keyPair Standard curve ECDSA KeyPair generated for TLS.
* @return The generated Certificate signing request.
*/
fun createCertificateSigningRequest(myLegalName: String, nearestCity: String, email: String, keyPair: KeyPair): PKCS10CertificationRequest {
val subject = getX509Name(myLegalName, nearestCity, email)
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair): PKCS10CertificationRequest {
val signer = JcaContentSignerBuilder(SIGNATURE_ALGORITHM)
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.build(keyPair.private)
@ -261,16 +273,24 @@ object X509Utilities {
/**
* Create a de novo root self-signed X509 v3 CA cert and [KeyPair].
* @param domain The Common (CN) field of the cert Subject will be populated with the domain string
* @param commonName The Common (CN) field of the cert Subject will be populated with the domain string
* @return A data class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates.
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates
*/
fun createSelfSignedCACert(myLegalName: String): CACertAndKey {
@Deprecated("Use [createSelfSignedCACert(X500Name)] instead, specifying full legal name")
fun createSelfSignedCACert(commonName: String): CACertAndKey = createSelfSignedCACert(getDevX509Name(commonName))
/**
* Create a de novo root self-signed X509 v3 CA cert and [KeyPair].
* @param subject the cert Subject will be populated with the domain string
* @return A data class is returned containing the new root CA Cert and its [KeyPair] for signing downstream certificates.
* Note the generated certificate tree is capped at max depth of 2 to be in line with commercially available certificates
*/
fun createSelfSignedCACert(subject: X500Name): CACertAndKey {
val keyPair = generateECDSAKeyPairForSSL()
val issuer = getDevX509Name(myLegalName)
val issuer = subject
val serial = BigInteger.valueOf(random63BitValue())
val subject = issuer
val pubKey = keyPair.public
// Ten year certificate validity
@ -305,18 +325,29 @@ object X509Utilities {
/**
* Create a de novo root intermediate X509 v3 CA cert and KeyPair.
* @param domain The Common (CN) field of the cert Subject will be populated with the domain string
* @param commonName The Common (CN) field of the cert Subject will be populated with the domain string
* @param certificateAuthority The Public certificate and KeyPair of the root CA certificate above this used to sign it
* @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
*/
fun createIntermediateCert(domain: String,
@Deprecated("Use [createIntermediateCert(X500Name, CACertAndKey)] instead, specifying full legal name")
fun createIntermediateCert(commonName: String,
certificateAuthority: CACertAndKey): CACertAndKey
= createIntermediateCert(getDevX509Name(commonName), certificateAuthority)
/**
* Create a de novo root intermediate X509 v3 CA cert and KeyPair.
* @param subject subject of the generated certificate.
* @param certificateAuthority The Public certificate and KeyPair of the root CA certificate above this used to sign it
* @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
*/
fun createIntermediateCert(subject: X500Name,
certificateAuthority: CACertAndKey): CACertAndKey {
val keyPair = generateECDSAKeyPairForSSL()
val issuer = X509CertificateHolder(certificateAuthority.certificate.encoded).subject
val serial = BigInteger.valueOf(random63BitValue())
val subject = getDevX509Name(domain)
val pubKey = keyPair.public
// Ten year certificate validity
@ -517,8 +548,8 @@ object X509Utilities {
trustStoreFilePath: Path,
trustStorePassword: String
): KeyStore {
val rootCA = createSelfSignedCACert("Corda Node Root CA")
val intermediateCA = createIntermediateCert("Corda Node Intermediate CA", rootCA)
val rootCA = createSelfSignedCACert(getDevX509Name("Corda Node Root CA"))
val intermediateCA = createIntermediateCert(getDevX509Name("Corda Node Intermediate CA"), rootCA)
val keyPass = keyPassword.toCharArray()
val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword)
@ -574,7 +605,25 @@ object X509Utilities {
keyPassword: String,
caKeyStore: KeyStore,
caKeyPassword: String,
commonName: String): KeyStore {
commonName: String): KeyStore = createKeystoreForSSL(keyStoreFilePath, storePassword, keyPassword,
caKeyStore, caKeyPassword, getDevX509Name(commonName))
/**
* An all in wrapper to manufacture a server certificate and keys all stored in a KeyStore suitable for running TLS on the local machine
* @param keyStoreFilePath KeyStore path to save output to
* @param storePassword access password for KeyStore
* @param keyPassword PrivateKey access password for the generated keys.
* It is recommended that this is the same as the storePassword as most TLS libraries assume they are the same.
* @param caKeyStore KeyStore containing CA keys generated by createCAKeyStoreAndTrustStore
* @param caKeyPassword password to unlock private keys in the CA KeyStore
* @return The KeyStore created containing a private key, certificate chain and root CA public cert for use in TLS applications
*/
fun createKeystoreForSSL(keyStoreFilePath: Path,
storePassword: String,
keyPassword: String,
caKeyStore: KeyStore,
caKeyPassword: String,
commonName: X500Name): KeyStore {
val rootCA = X509Utilities.loadCertificateAndKey(
caKeyStore,
caKeyPassword,
@ -587,7 +636,7 @@ object X509Utilities {
val serverKey = generateECDSAKeyPairForSSL()
val host = InetAddress.getLocalHost()
val serverCert = createServerCert(
getDevX509Name(commonName),
commonName,
serverKey.public,
intermediateCA,
listOf(host.hostName),

View File

@ -15,6 +15,7 @@ import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import org.bouncycastle.asn1.x500.X500Name
import rx.Observable
import java.io.InputStream
import java.security.PublicKey
@ -165,6 +166,11 @@ interface CordaRPCOps : RPCOps {
*/
fun partyFromName(name: String): Party?
/**
* Returns the [Party] with the X.500 principal as it's [Party.name]
*/
fun partyFromX500Name(x500Name: X500Name): Party?
/** Enumerates the class names of the flows that this node knows about. */
fun registeredFlows(): List<String>
}

View File

@ -4,6 +4,7 @@ import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.AnonymousParty
import net.corda.core.crypto.Party
import java.security.PublicKey
import org.bouncycastle.asn1.x500.X500Name
/**
* An identity service maintains an bidirectional map of [Party]s to their associated public keys and thus supports
@ -14,7 +15,7 @@ interface IdentityService {
fun registerIdentity(party: Party)
/**
* Get all identities known to the service. This is expensive, and [partyFromKey] or [partyFromName] should be
* Get all identities known to the service. This is expensive, and [partyFromKey] or [partyFromX500Name] should be
* used in preference where possible.
*/
fun getAllIdentities(): Iterable<Party>
@ -25,6 +26,7 @@ interface IdentityService {
fun partyFromKey(key: PublicKey): Party?
fun partyFromName(name: String): Party?
fun partyFromX500Name(principal: X500Name): Party?
fun partyFromAnonymous(party: AnonymousParty): Party?
fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)

View File

@ -16,6 +16,7 @@ import net.corda.core.utilities.NonEmptySet
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.objenesis.strategy.StdInstantiatorStrategy
import org.slf4j.Logger
import java.io.BufferedInputStream
@ -88,6 +89,8 @@ object DefaultKryoCustomizer {
// Note that return type should be specifically set to InputStream, otherwise it may not work, i.e. val aStream : InputStream = HashCheckingStream(...).
addDefaultSerializer(InputStream::class.java, InputStreamSerializer)
register(X500Name::class.java, X500NameSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
}

View File

@ -16,6 +16,9 @@ import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.bouncycastle.asn1.ASN1InputStream
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.x500.X500Name
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.*
@ -584,3 +587,17 @@ object LoggerSerializer : Serializer<Logger>() {
return LoggerFactory.getLogger(input.readString())
}
}
/**
* For serialising an [X500Name] without touching Sun internal classes.
*/
@ThreadSafe
object X500NameSerializer : Serializer<X500Name>() {
override fun read(kryo: Kryo, input: Input, type: Class<X500Name>): X500Name {
return X500Name.getInstance(ASN1InputStream(input.readBytes()).readObject())
}
override fun write(kryo: Kryo, output: Output, obj: X500Name) {
output.writeBytes(obj.encoded)
}
}

View File

@ -161,6 +161,8 @@ class ContractUpgradeFlowTest {
// Create some cash.
val result = a.services.startFlow(CashIssueFlow(Amount(1000, USD), OpaqueBytes.of(1), a.info.legalIdentity, notary)).resultFuture
mockNet.runNetwork()
val baseState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() }
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
val stateAndRef = result.getOrThrow().tx.outRef<Cash.State>(0)
// Starts contract upgrade flow.
a.services.startFlow(ContractUpgradeFlow.Instigator(stateAndRef, CashV2::class.java))

View File

@ -8,6 +8,9 @@ import com.pholser.junit.quickcheck.random.SourceOfRandomness
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.serialization.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import java.nio.ByteBuffer
import java.nio.charset.Charset
import java.security.PrivateKey
import java.security.PublicKey
import java.time.Duration
@ -119,3 +122,32 @@ class TimestampGenerator : Generator<Timestamp>(Timestamp::class.java) {
}
}
class X500NameGenerator : Generator<X500Name>(X500Name::class.java) {
companion object {
private val charset = Charset.forName("US-ASCII")
private val asciiA = charset.encode("A")[0]
private val asciia = charset.encode("a")[0]
}
/**
* Append something that looks a bit like a proper noun to the string builder.
*/
private fun appendProperNoun(builder: StringBuilder, random: SourceOfRandomness, status: GenerationStatus) : StringBuilder {
val length = random.nextByte(1, 8)
val encoded = ByteBuffer.allocate(length.toInt())
encoded.put((random.nextByte(0, 25) + asciiA).toByte())
for (charIdx in 1..length - 1) {
encoded.put((random.nextByte(0, 25) + asciia).toByte())
}
return builder.append(charset.decode(encoded))
}
override fun generate(random: SourceOfRandomness, status: GenerationStatus): X500Name {
val wordCount = random.nextByte(1, 3)
val cn = StringBuilder()
for (word in 0..wordCount) {
appendProperNoun(cn, random, status).append(" ")
}
return X509Utilities.getDevX509Name(cn.trim().toString())
}
}

View File

@ -4,6 +4,7 @@ import net.corda.core.contracts.Amount
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.UpgradedContract
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
@ -23,6 +24,7 @@ import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.utilities.AddOrRemove
import net.corda.node.utilities.transaction
import org.bouncycastle.asn1.x500.X500Name
import org.jetbrains.exposed.sql.Database
import rx.Observable
import java.io.InputStream
@ -144,6 +146,7 @@ class CordaRPCOpsImpl(
override fun waitUntilRegisteredWithNetworkMap() = services.networkMapCache.mapServiceRegistered
override fun partyFromKey(key: PublicKey) = services.identityService.partyFromKey(key)
override fun partyFromName(name: String) = services.identityService.partyFromName(name)
override fun partyFromX500Name(x500Name: X500Name)= services.identityService.partyFromX500Name(x500Name)
override fun registeredFlows(): List<String> = services.flowLogicRefFactory.flowWhitelist.keys.sorted()

View File

@ -8,6 +8,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import java.security.PublicKey
import org.bouncycastle.asn1.x500.X500Name
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import javax.annotation.concurrent.ThreadSafe
@ -35,6 +36,7 @@ class InMemoryIdentityService : SingletonSerializeAsToken(), IdentityService {
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
override fun partyFromX500Name(principal: X500Name): Party? = nameToParties[principal.toString()]
override fun partyFromAnonymous(party: AnonymousParty): Party? = partyFromKey(party.owningKey)
override fun partyFromAnonymous(partyRef: PartyAndReference) = partyFromAnonymous(partyRef.party)
}

View File

@ -22,6 +22,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
import org.bouncycastle.asn1.x500.X500Name
import rx.Observable
import rx.subjects.PublishSubject
import java.io.ByteArrayInputStream
@ -94,6 +95,7 @@ class MockIdentityService(val identities: List<Party>) : IdentityService, Single
override fun partyFromAnonymous(partyRef: PartyAndReference): Party? = partyFromAnonymous(partyRef.party)
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]
override fun partyFromName(name: String): Party? = nameToParties[name]
override fun partyFromX500Name(principal: X500Name): Party? = nameToParties[principal.toString()]
}