mirror of
https://github.com/corda/corda.git
synced 2024-12-19 13:08:04 +00:00
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:
parent
1a88ca4bee
commit
6d48667d91
@ -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),
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()]
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user