Creating a PartyAndCertificate only requires a CertPath

This commit is contained in:
Shams Asari 2017-08-15 18:37:08 +01:00
parent 3860b22339
commit dc8d232480
22 changed files with 172 additions and 168 deletions

View File

@ -1,6 +1,7 @@
@file:JvmName("X500NameUtils")
package net.corda.core.crypto
import net.corda.core.internal.toX509CertHolder
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
@ -57,7 +58,7 @@ val X500Name.locationOrNull: String? get() = try {
} catch (e: Exception) {
null
}
val X509Certificate.subject: X500Name get() = X509CertificateHolder(encoded).subject
val X509Certificate.subject: X500Name get() = toX509CertHolder().subject
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
/**

View File

@ -13,8 +13,7 @@ import java.security.PublicKey
@CordaSerializable
abstract class AbstractParty(val owningKey: PublicKey) {
/** Anonymised parties do not include any detail apart from owning key, so equality is dependent solely on the key */
override fun equals(other: Any?): Boolean = other is AbstractParty && this.owningKey == other.owningKey
override fun equals(other: Any?): Boolean = other === this || other is AbstractParty && other.owningKey == owningKey
override fun hashCode(): Int = owningKey.hashCode()
abstract fun nameOrNull(): X500Name?

View File

@ -1,7 +1,6 @@
package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.toBase58String
import net.corda.core.crypto.toStringShort
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
@ -12,11 +11,7 @@ import java.security.PublicKey
* information such as name. It is intended to represent a party on the distributed ledger.
*/
class AnonymousParty(owningKey: PublicKey) : AbstractParty(owningKey) {
// Use the key as the bulk of the toString(), but include a human readable identifier as well, so that [Party]
// can put in the key and actual name
override fun toString() = "${owningKey.toStringShort()} <Anonymous>"
override fun nameOrNull(): X500Name? = null
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
override fun toString() = "Anonymous(${owningKey.toStringShort()})"
}

View File

@ -1,9 +1,11 @@
package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.CertificateAndKeyPair
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import java.security.PublicKey
/**
@ -26,10 +28,9 @@ import java.security.PublicKey
* @see CompositeKey
*/
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
constructor(certificate: X509CertificateHolder) : this(certificate.subject, Crypto.toSupportedPublicKey(certificate.subjectPublicKeyInfo))
override fun nameOrNull(): X500Name = name
fun anonymise(): AnonymousParty = AnonymousParty(owningKey)
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes)
override fun toString() = name.toString()
}

View File

@ -1,49 +1,42 @@
package net.corda.core.identity
import net.corda.core.serialization.CordaSerializable
import net.corda.core.internal.toX509CertHolder
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import java.security.PublicKey
import java.security.cert.*
import java.util.*
/**
* A full party plus the X.509 certificate and path linking the party back to a trust root. Equality of
* [PartyAndCertificate] instances is based on the party only, as certificate and path are data associated with the party,
* not part of the identifier themselves. While party and certificate can both be derived from the certificate path,
* this class exists in order to ensure the implementation classes of certificates and party public keys are kept stable.
* not part of the identifier themselves.
*/
@CordaSerializable
data class PartyAndCertificate(val party: Party,
val certificate: X509CertificateHolder,
val certPath: CertPath) {
constructor(name: X500Name, owningKey: PublicKey, certificate: X509CertificateHolder, certPath: CertPath) : this(Party(name, owningKey), certificate, certPath)
val name: X500Name
get() = party.name
val owningKey: PublicKey
get() = party.owningKey
override fun equals(other: Any?): Boolean {
return if (other is PartyAndCertificate)
party == other.party
else
false
//TODO Is VerifiableIdentity a better name?
class PartyAndCertificate(val certPath: CertPath) {
@Transient val certificate: X509CertificateHolder
init {
require(certPath.type == "X.509") { "Only X.509 certificates supported" }
val certs = certPath.certificates
require(certs.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
certificate = certs[0].toX509CertHolder()
}
@Transient val party: Party = Party(certificate)
val owningKey: PublicKey get() = party.owningKey
val name: X500Name get() = party.name
operator fun component1(): Party = party
operator fun component2(): X509CertificateHolder = certificate
override fun equals(other: Any?): Boolean = other === this || other is PartyAndCertificate && other.party == party
override fun hashCode(): Int = party.hashCode()
override fun toString(): String = party.toString()
/**
* Verify that the given certificate path is valid and leads to the owning key of the party.
*/
/** Verify the certificate path is valid. */
fun verify(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult {
require(certPath.certificates.first() is X509Certificate) { "Subject certificate must be an X.509 certificate" }
require(Arrays.equals(party.owningKey.encoded, certificate.subjectPublicKeyInfo.encoded)) { "Certificate public key must match party owning key" }
require(Arrays.equals(certPath.certificates.first().encoded, certificate.encoded)) { "Certificate path must link to certificate" }
val validatorParameters = PKIXParameters(setOf(trustAnchor))
val parameters = PKIXParameters(setOf(trustAnchor)).apply { isRevocationEnabled = false }
val validator = CertPathValidator.getInstance("PKIX")
validatorParameters.isRevocationEnabled = false
return validator.validate(certPath, validatorParameters) as PKIXCertPathValidatorResult
return validator.validate(certPath, parameters) as PKIXCertPathValidatorResult
}
}

View File

@ -2,6 +2,7 @@ package net.corda.core.internal
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.Logger
import rx.Observable
import rx.Observer
@ -165,6 +166,9 @@ fun <T> logElapsedTime(label: String, logger: Logger? = null, body: () -> T): T
}
}
fun java.security.cert.Certificate.toX509CertHolder() = X509CertificateHolder(encoded)
fun javax.security.cert.Certificate.toX509CertHolder() = X509CertificateHolder(encoded)
/** Convert a [ByteArrayOutputStream] to [InputStreamAndHash]. */
fun ByteArrayOutputStream.toInputStreamAndHash(): InputStreamAndHash {
val bytes = toByteArray()

View File

@ -24,16 +24,17 @@ data class NodeInfo(val addresses: List<NetworkHostAndPort>,
val legalIdentityAndCert: PartyAndCertificate, //TODO This field will be removed in future PR which gets rid of services.
val legalIdentitiesAndCerts: NonEmptySet<PartyAndCertificate>,
val platformVersion: Int,
var advertisedServices: List<ServiceEntry> = emptyList(),
val advertisedServices: List<ServiceEntry> = emptyList(),
val worldMapLocation: WorldMapLocation? = null) {
init {
require(advertisedServices.none { it.identity == legalIdentityAndCert }) { "Service identities must be different from node legal identity" }
require(advertisedServices.none { it.identity == legalIdentityAndCert }) {
"Service identities must be different from node legal identity"
}
val legalIdentity: Party
get() = legalIdentityAndCert.party
val notaryIdentity: Party
get() = advertisedServices.single { it.info.type.isNotary() }.identity.party
}
val legalIdentity: Party get() = legalIdentityAndCert.party
val notaryIdentity: Party get() = advertisedServices.single { it.info.type.isNotary() }.identity.party
fun serviceIdentities(type: ServiceType): List<Party> {
return advertisedServices.filter { it.info.type.isSubTypeOf(type) }.map { it.identity.party }
return advertisedServices.mapNotNull { if (it.info.type.isSubTypeOf(type)) it.identity.party else null }
}
}

View File

@ -1,7 +1,10 @@
package net.corda.core.node.services
import net.corda.core.contracts.PartyAndReference
import net.corda.core.identity.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import java.security.InvalidAlgorithmParameterException
@ -114,6 +117,6 @@ interface IdentityService {
* @param exactMatch If true, a case sensitive match is done against each component of each X.500 name.
*/
fun partiesFromName(query: String, exactMatch: Boolean): Set<Party>
class UnknownAnonymousPartyException(msg: String) : Exception(msg)
}
class UnknownAnonymousPartyException(msg: String) : Exception(msg)

View File

@ -0,0 +1,26 @@
package net.corda.core.identity
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.testing.getTestPartyAndCertificate
import net.corda.testing.withTestSerialization
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x500.X500Name
import org.junit.Test
import java.math.BigInteger
class PartyAndCertificateTest {
@Test
fun `kryo serialisation`() {
withTestSerialization {
val original = getTestPartyAndCertificate(Party(
X500Name("CN=Test Corp,O=Test Corp,L=Madrid,C=ES"),
entropyToKeyPair(BigInteger.valueOf(83)).public))
val copy = original.serialize().deserialize()
assertThat(copy).isEqualTo(original).isNotSameAs(original)
assertThat(copy.certPath).isEqualTo(original.certPath)
assertThat(copy.certificate).isEqualTo(original.certificate)
}
}
}

View File

@ -12,6 +12,7 @@ import de.javakaffee.kryoserializers.BitSetSerializer
import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.*
import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.CordaPluginRegistry
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializedBytes
@ -59,6 +60,12 @@ object DefaultKryoCustomizer {
instantiatorStrategy = CustomInstantiatorStrategy()
// Required for HashCheckingStream (de)serialization.
// 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)
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
addDefaultSerializer(Logger::class.java, LoggerSerializer)
// WARNING: reordering the registrations here will cause a change in the serialized form, since classes
// with custom serializers get written as registration ids. This will break backwards-compatibility.
// Please add any new registrations to the end.
@ -68,50 +75,31 @@ object DefaultKryoCustomizer {
register(SignedTransaction::class.java, SignedTransactionSerializer)
register(WireTransaction::class.java, WireTransactionSerializer)
register(SerializedBytes::class.java, SerializedBytesSerializer)
UnmodifiableCollectionsSerializer.registerSerializers(this)
ImmutableListSerializer.registerSerializers(this)
ImmutableSetSerializer.registerSerializers(this)
ImmutableSortedSetSerializer.registerSerializers(this)
ImmutableMapSerializer.registerSerializers(this)
ImmutableMultimapSerializer.registerSerializers(this)
// InputStream subclasses whitelisting, required for attachments.
register(BufferedInputStream::class.java, InputStreamSerializer)
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
noReferencesWithin<WireTransaction>()
register(ECPublicKeyImpl::class.java, ECPublicKeyImplSerializer)
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
// Using a custom serializer for compactness
register(CompositeKey::class.java, CompositeKeySerializer)
register(CompositeKey::class.java, CompositeKeySerializer) // Using a custom serializer for compactness
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
register(Array<StackTraceElement>::class, read = { _, _ -> emptyArray() }, write = { _, _, _ -> })
// This ensures a NonEmptySetSerializer is constructed with an initial value.
register(NonEmptySet::class.java, NonEmptySetSerializer)
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
register(BitSet::class.java, BitSetSerializer())
register(Class::class.java, ClassSerializer)
addDefaultSerializer(Logger::class.java, LoggerSerializer)
register(FileInputStream::class.java, InputStreamSerializer)
// Required for HashCheckingStream (de)serialization.
// 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(CertPath::class.java, CertPathSerializer)
register(X509CertPath::class.java, CertPathSerializer)
register(X500Name::class.java, X500NameSerializer)
register(X509CertificateHolder::class.java, X509CertificateSerializer)
register(BCECPrivateKey::class.java, PrivateKeySerializer)
register(BCECPublicKey::class.java, PublicKeySerializer)
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
@ -119,8 +107,8 @@ object DefaultKryoCustomizer {
register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer)
register(BCSphincs256PublicKey::class.java, PublicKeySerializer)
register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer)
register(NotaryChangeWireTransaction::class.java, NotaryChangeWireTransactionSerializer)
register(PartyAndCertificate::class.java, PartyAndCertificateSerializer)
val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) }
@ -139,6 +127,15 @@ object DefaultKryoCustomizer {
}
}
private object PartyAndCertificateSerializer : Serializer<PartyAndCertificate>() {
override fun write(kryo: Kryo, output: Output, obj: PartyAndCertificate) {
kryo.writeClassAndObject(output, obj.certPath)
}
override fun read(kryo: Kryo, input: Input, type: Class<PartyAndCertificate>): PartyAndCertificate {
return PartyAndCertificate(kryo.readClassAndObject(input) as CertPath)
}
}
private object NonEmptySetSerializer : Serializer<NonEmptySet<Any>>() {
override fun write(kryo: Kryo, output: Output, obj: NonEmptySet<Any>) {
// Write out the contents as normal

View File

@ -2,6 +2,7 @@ package net.corda.node.internal
import com.codahale.metrics.MetricRegistry
import com.google.common.annotations.VisibleForTesting
import com.google.common.collect.Lists
import com.google.common.collect.MutableClassToInstanceMap
import com.google.common.util.concurrent.MoreExecutors
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
@ -66,7 +67,6 @@ import net.corda.node.utilities.*
import net.corda.node.utilities.AddOrRemove.ADD
import org.apache.activemq.artemis.utils.ReusableLatch
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.Logger
import rx.Observable
import java.io.IOException
@ -88,6 +88,9 @@ import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit.SECONDS
import java.util.stream.Collectors.toList
import kotlin.collections.ArrayList
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.reflect.KClass
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
@ -417,8 +420,9 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
checkpointStorage = DBCheckpointStorage()
_services = ServiceHubInternalImpl()
attachments = NodeAttachmentService(configuration.dataSourceProperties, services.monitoringService.metrics, configuration.database)
network = makeMessagingService()
info = makeInfo()
val legalIdentity = obtainIdentity("identity", configuration.myLegalName)
network = makeMessagingService(legalIdentity)
info = makeInfo(legalIdentity)
val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.vaultQueryService,
services.keyManagementService, services.identityService, platformClock, services.schedulerService)
@ -486,12 +490,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
HibernateObserver(services.vaultService.rawUpdates, HibernateConfiguration(services.schemaService, configuration.database ?: Properties(), {services.identityService}))
}
private fun makeInfo(): NodeInfo {
private fun makeInfo(legalIdentity: PartyAndCertificate): NodeInfo {
val advertisedServiceEntries = makeServiceEntries()
val legalIdentity = obtainLegalIdentity()
val allIdentitiesSet = (advertisedServiceEntries.map { it.identity } + legalIdentity).toNonEmptySet()
val allIdentities = (advertisedServiceEntries.map { it.identity } + legalIdentity).toNonEmptySet()
val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet.
return NodeInfo(addresses, legalIdentity, allIdentitiesSet, platformVersion, advertisedServiceEntries, findMyLocation())
return NodeInfo(addresses, legalIdentity, allIdentities, platformVersion, advertisedServiceEntries, findMyLocation())
}
/**
@ -502,7 +505,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
return advertisedServices.map {
val serviceId = it.type.id
val serviceName = it.name ?: X500Name("${configuration.myLegalName},OU=$serviceId")
val identity = obtainKeyPair(serviceId, serviceName).first
val identity = obtainIdentity(serviceId, serviceName)
ServiceEntry(it, identity)
}
}
@ -613,8 +616,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
val instant = platformClock.instant()
val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
val reg = NodeRegistration(info, instant.toEpochMilli(), ADD, expires)
val legalIdentityKey = obtainLegalIdentityKey()
val request = RegistrationRequest(reg.toWire(services.keyManagementService, legalIdentityKey.public), network.myAddress)
val request = RegistrationRequest(reg.toWire(services.keyManagementService, info.legalIdentityAndCert.owningKey), network.myAddress)
return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapAddress)
}
@ -680,15 +682,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
runOnStop.clear()
}
protected abstract fun makeMessagingService(): MessagingService
protected abstract fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService
protected abstract fun startMessagingService(rpcOps: RPCOps)
protected fun obtainLegalIdentity(): PartyAndCertificate = identityKeyPair.first
protected fun obtainLegalIdentityKey(): KeyPair = identityKeyPair.second
private val identityKeyPair by lazy { obtainKeyPair("identity", configuration.myLegalName) }
private fun obtainKeyPair(serviceId: String, serviceName: X500Name): Pair<PartyAndCertificate, KeyPair> {
private fun obtainIdentity(id: String, name: X500Name): PartyAndCertificate {
// Load the private identity key, creating it if necessary. The identity key is a long term well known key that
// is distributed to other peers and we use it (or a key signed by it) when we need to do something
// "permissioned". The identity file is what gets distributed and contains the node's legal name along with
@ -697,50 +695,52 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
// TODO: Integrate with Key management service?
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val privateKeyAlias = "$serviceId-private-key"
val compositeKeyAlias = "$serviceId-composite-key"
val privateKeyAlias = "$id-private-key"
val compositeKeyAlias = "$id-composite-key"
if (!keyStore.containsAlias(privateKeyAlias)) {
val privKeyFile = configuration.baseDirectory / privateKeyAlias
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public"
val pubIdentityFile = configuration.baseDirectory / "$id-public"
val compositeKeyFile = configuration.baseDirectory / compositeKeyAlias
// TODO: Remove use of [ServiceIdentityGenerator.generateToDisk].
// Get keys from key file.
// TODO: this is here to smooth out the key storage transition, remove this migration in future release.
if (privKeyFile.exists()) {
migrateKeysFromFile(keyStore, serviceName, pubIdentityFile, privKeyFile, compositeKeyFile, privateKeyAlias, compositeKeyAlias)
migrateKeysFromFile(keyStore, name, pubIdentityFile, privKeyFile, compositeKeyFile, privateKeyAlias, compositeKeyAlias)
} else {
log.info("$privateKeyAlias not found in keystore ${configuration.nodeKeystore}, generating fresh key!")
keyStore.saveNewKeyPair(serviceName, privateKeyAlias, generateKeyPair())
log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!")
keyStore.saveNewKeyPair(name, privateKeyAlias, generateKeyPair())
}
}
val (cert, keys) = keyStore.certificateAndKeyPair(privateKeyAlias)
// Get keys from keystore.
val loadedServiceName = cert.subject
if (loadedServiceName != serviceName)
throw ConfigurationException("The legal name in the config file doesn't match the stored identity keystore:$serviceName vs $loadedServiceName")
val (x509Cert, keys) = keyStore.certificateAndKeyPair(privateKeyAlias)
// Use composite key instead if exists
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val (keyPair, certs) = if (keyStore.containsAlias(compositeKeyAlias)) {
val compositeKey = Crypto.toSupportedPublicKey(keyStore.getCertificate(compositeKeyAlias).publicKey)
val compositeKeyCert = keyStore.getCertificate(compositeKeyAlias)
val certificates = if (keyStore.containsAlias(compositeKeyAlias)) {
// Use composite key instead if it exists
val certificate = keyStore.getCertificate(compositeKeyAlias)
// We have to create the certificate chain for the composite key manually, this is because in order to store
// the chain in keystore we need a private key, however there are no corresponding private key for composite key.
Pair(KeyPair(compositeKey, keys.private), listOf(compositeKeyCert, *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)))
// the chain in key store we need a private key, however there is no corresponding private key for the composite key.
Lists.asList(certificate, keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA))
} else {
Pair(keys, keyStore.getCertificateChain(privateKeyAlias).toList())
keyStore.getCertificateChain(privateKeyAlias).let {
check(it[0].toX509CertHolder() == x509Cert) { "Certificates from key store do not line up!" }
it.asList()
}
val certPath = CertificateFactory.getInstance("X509").generateCertPath(certs)
}
val subject = certificates[0].toX509CertHolder().subject
if (subject != name)
throw ConfigurationException("The name for $id doesn't match what's in the key store: $name vs $subject")
partyKeys += keys
return Pair(PartyAndCertificate(loadedServiceName, keyPair.public, X509CertificateHolder(certs.first().encoded), certPath), keyPair)
return PartyAndCertificate(CertificateFactory.getInstance("X509").generateCertPath(certificates))
}
private fun migrateKeysFromFile(keyStore: KeyStoreWrapper, serviceName: X500Name,
pubKeyFile: Path, privKeyFile: Path, compositeKeyFile:Path,
privateKeyAlias: String, compositeKeyAlias: String) {
log.info("Migrating $privateKeyAlias from file to keystore...")
log.info("Migrating $privateKeyAlias from file to key store...")
// Check that the identity in the config file matches the identity file we have stored to disk.
// Load the private key.
val publicKey = Crypto.decodePublicKey(pubKeyFile.readAll())
@ -753,13 +753,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
log.info("Finish migrating $privateKeyAlias from file to keystore.")
}
private fun getTestPartyAndCertificate(party: Party, trustRoot: CertificateAndKeyPair): PartyAndCertificate {
val certFactory = CertificateFactory.getInstance("X509")
val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, party.name, party.owningKey)
val certPath = certFactory.generateCertPath(listOf(certHolder.cert, trustRoot.certificate.cert))
return PartyAndCertificate(party, certHolder, certPath)
}
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
private inner class ServiceHubInternalImpl : ServiceHubInternal, SingletonSerializeAsToken() {

View File

@ -2,6 +2,7 @@ package net.corda.node.internal
import com.codahale.metrics.JmxReporter
import net.corda.core.concurrent.CordaFuture
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.concurrent.openFuture
@ -133,7 +134,7 @@ open class Node(override val configuration: FullNodeConfiguration,
private lateinit var userService: RPCUserService
override fun makeMessagingService(): MessagingService {
override fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService {
userService = RPCUserServiceImpl(configuration.rpcUsers)
val (serverAddress, advertisedAddress) = with(configuration) {
@ -147,7 +148,7 @@ open class Node(override val configuration: FullNodeConfiguration,
printBasicNodeInfo("Incoming connection address", advertisedAddress.toString())
val myIdentityOrNullIfNetworkMapService = if (networkMapAddress != null) obtainLegalIdentity().owningKey else null
val myIdentityOrNullIfNetworkMapService = if (networkMapAddress != null) legalIdentity.owningKey else null
return NodeMessagingClient(
configuration,
versionInfo,

View File

@ -7,7 +7,9 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.toX509CertHolder
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
@ -16,16 +18,13 @@ import org.bouncycastle.cert.X509CertificateHolder
import java.security.InvalidAlgorithmParameterException
import java.security.PublicKey
import java.security.cert.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import javax.annotation.concurrent.ThreadSafe
import kotlin.collections.LinkedHashSet
/**
* Simple identity service which caches parties and provides functionality for efficient lookup.
*
* @param identities initial set of identities for the service, typically only used for unit tests.
* @param certPaths initial set of certificate paths for the service, typically only used for unit tests.
*/
@ThreadSafe
class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptySet(),
@ -43,7 +42,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
* Certificate store for certificate authority and intermediary certificates.
*/
override val caCertStore: CertStore
override val trustRootHolder = X509CertificateHolder(trustRoot.encoded)
override val trustRootHolder = trustRoot.toX509CertHolder()
override val trustAnchor: TrustAnchor = TrustAnchor(trustRoot, null)
private val keyToParties = ConcurrentHashMap<PublicKey, PartyAndCertificate>()
private val principalToParties = ConcurrentHashMap<X500Name, PartyAndCertificate>()
@ -54,7 +53,6 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
keyToParties.putAll(identities.associateBy { it.owningKey } )
principalToParties.putAll(identities.associateBy { it.name })
confidentialIdentities.forEach { identity ->
require(identity.certPath.certificates.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
principalToParties.computeIfAbsent(identity.name) { identity }
}
}
@ -66,13 +64,10 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
// TODO: Check the certificate validation logic
@Throws(CertificateExpiredException::class, CertificateNotYetValidException::class, InvalidAlgorithmParameterException::class)
override fun verifyAndRegisterIdentity(identity: PartyAndCertificate): PartyAndCertificate? {
require(identity.certPath.certificates.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
// Validate the chain first, before we do anything clever with it
identity.verify(trustAnchor)
log.trace { "Registering identity $identity" }
require(Arrays.equals(identity.certificate.subjectPublicKeyInfo.encoded, identity.owningKey.encoded)) { "Party certificate must end with party's public key" }
keyToParties[identity.owningKey] = identity
// Always keep the first party we registered, as that's the well known identity
principalToParties.computeIfAbsent(identity.name) { identity }
@ -83,7 +78,7 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
override fun certificateFromParty(party: Party): PartyAndCertificate = principalToParties[party.name] ?: throw IllegalArgumentException("Unknown identity ${party.name}")
// We give the caller a copy of the data set to avoid any locking problems
override fun getAllIdentities(): Iterable<PartyAndCertificate> = java.util.ArrayList(keyToParties.values)
override fun getAllIdentities(): Iterable<PartyAndCertificate> = ArrayList(keyToParties.values)
override fun partyFromKey(key: PublicKey): Party? = keyToParties[key]?.party
override fun partyFromX500Name(principal: X500Name): Party? = principalToParties[principal]?.party
@ -128,13 +123,13 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate> = emptyS
return results
}
@Throws(IdentityService.UnknownAnonymousPartyException::class)
@Throws(UnknownAnonymousPartyException::class)
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
val path = keyToParties[anonymousParty.owningKey]?.certPath ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}")
require(path.certificates.size > 1) { "Certificate path must contain at least two certificates" }
val actual = path.certificates[1]
require(actual is X509Certificate && actual.publicKey == party.owningKey) { "Next certificate in the path must match the party key ${party.owningKey.toStringShort()}." }
val target = path.certificates.first()
require(target is X509Certificate && target.publicKey == anonymousParty.owningKey) { "Certificate path starts with a certificate for the anonymous party" }
val anonymousIdentity = keyToParties[anonymousParty.owningKey] ?:
throw UnknownAnonymousPartyException("Unknown $anonymousParty")
val issuingCert = anonymousIdentity.certPath.certificates[1]
require(issuingCert.publicKey == party.owningKey) {
"Issuing certificate's public key must match the party key ${party.owningKey.toStringShort()}."
}
}
}

View File

@ -3,7 +3,6 @@ package net.corda.node.services.keys
import net.corda.core.crypto.ContentSignerBuilder
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.days
@ -35,10 +34,11 @@ fun freshCertificate(identityService: IdentityService,
revocationEnabled: Boolean = false): PartyAndCertificate {
val issuerCertificate = issuer.certificate
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate)
val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject,
issuerSigner, issuer.name, subjectPublicKey, window)
val certFactory = CertificateFactory.getInstance("X509")
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
val anonymisedIdentity = PartyAndCertificate(Party(issuer.name, subjectPublicKey), ourCertificate, ourCertPath)
val anonymisedIdentity = PartyAndCertificate(ourCertPath)
identityService.verifyAndRegisterIdentity(anonymisedIdentity)
return anonymisedIdentity
}

View File

@ -11,6 +11,7 @@ import net.corda.core.internal.ThreadBox
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div
import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.toX509CertHolder
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkMapCache.MapChange
@ -25,7 +26,6 @@ import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE
import net.corda.node.utilities.X509Utilities
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA
import net.corda.node.utilities.getX509Certificate
import net.corda.node.utilities.loadKeyStore
import net.corda.nodeapi.*
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
@ -52,7 +52,6 @@ import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
import org.apache.activemq.artemis.utils.ConfigurationHelper
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import rx.Subscription
import java.io.IOException
import java.math.BigInteger
@ -273,12 +272,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword)
val ourCertificate = keyStore.getX509Certificate(CORDA_CLIENT_TLS)
// This is a sanity check and should not fail unless things have been misconfigured
require(ourCertificate.subject == config.myLegalName) {
"Legal name does not match with our subject CN: ${ourCertificate.subject}"
}
val defaultCertPolicies = mapOf(
PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch,
NODE_ROLE to CertificateChainCheckPolicy.LeafMustMatch,
@ -512,12 +506,12 @@ private class VerifyingNettyConnector(configuration: MutableMap<String, Any>,
"misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
}
// Make sure certificate has the same name.
val peerCertificate = X509CertificateHolder(session.peerCertificateChain.first().encoded)
val peerCertificate = session.peerCertificateChain[0].toX509CertHolder()
require(peerCertificate.subject == expectedLegalName) {
"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(X509CertificateHolder(session.localCertificates.last().encoded), *session.peerCertificates)
X509Utilities.validateCertificateChain(session.localCertificates.last().toX509CertHolder(), *session.peerCertificates)
server.onTcpConnection(peerLegalName)
} catch (e: IllegalArgumentException) {
connection.close()

View File

@ -21,6 +21,7 @@ import java.util.Collections.synchronizedMap
class PersistentNetworkMapService(services: ServiceHubInternal, minimumPlatformVersion: Int)
: AbstractNetworkMapService(services, minimumPlatformVersion) {
// Only the node_party_path column is needed to reconstruct a PartyAndCertificate but we have the others for human readability
private object Table : JDBCHashedTable("${NODE_DATABASE_PREFIX}network_map_nodes") {
val nodeParty = partyAndCertificate("node_party_name", "node_party_key", "node_party_certificate", "node_party_path")
val registrationInfo = blob("node_registration_info")
@ -28,16 +29,15 @@ class PersistentNetworkMapService(services: ServiceHubInternal, minimumPlatformV
override val nodeRegistrations: MutableMap<PartyAndCertificate, NodeRegistrationInfo> = synchronizedMap(object : AbstractJDBCHashMap<PartyAndCertificate, NodeRegistrationInfo, Table>(Table, loadOnInit = true) {
// TODO: We should understand an X500Name database field type, rather than manually doing the conversion ourselves
override fun keyFromRow(row: ResultRow): PartyAndCertificate = PartyAndCertificate(X500Name(row[table.nodeParty.name]), row[table.nodeParty.owningKey],
row[table.nodeParty.certificate], row[table.nodeParty.certPath])
override fun keyFromRow(row: ResultRow): PartyAndCertificate = PartyAndCertificate(row[table.nodeParty.certPath])
override fun valueFromRow(row: ResultRow): NodeRegistrationInfo = deserializeFromBlob(row[table.registrationInfo])
override fun addKeyToInsert(insert: InsertStatement, entry: Map.Entry<PartyAndCertificate, NodeRegistrationInfo>, finalizables: MutableList<() -> Unit>) {
insert[table.nodeParty.name] = entry.key.name.toString()
insert[table.nodeParty.owningKey] = entry.key.owningKey
insert[table.nodeParty.certPath] = entry.key.certPath
insert[table.nodeParty.certificate] = entry.key.certificate
insert[table.nodeParty.certPath] = entry.key.certPath
}
override fun addValueToInsert(insert: InsertStatement, entry: Map.Entry<PartyAndCertificate, NodeRegistrationInfo>, finalizables: MutableList<() -> Unit>) {

View File

@ -1,8 +1,11 @@
package net.corda.node.utilities
import net.corda.core.crypto.*
import net.corda.core.crypto.CertificateAndKeyPair
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert
import net.corda.core.internal.exists
import net.corda.core.internal.read
import net.corda.core.internal.toX509CertHolder
import net.corda.core.internal.write
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
@ -145,8 +148,8 @@ fun KeyStore.getCertificateAndKeyPair(alias: String, keyPassword: String): Certi
* @return The X509Certificate found in the KeyStore under the specified alias.
*/
fun KeyStore.getX509Certificate(alias: String): X509CertificateHolder {
val encoded = getCertificate(alias)?.encoded ?: throw IllegalArgumentException("No certificate under alias \"$alias\"")
return X509CertificateHolder(encoded)
val certificate = getCertificate(alias) ?: throw IllegalArgumentException("No certificate under alias \"$alias\"")
return certificate.toX509CertHolder()
}
/**
@ -206,5 +209,5 @@ class KeyStoreWrapper(private val storePath: Path, private val storePassword: St
fun getCertificate(alias: String): Certificate = keyStore.getCertificate(alias)
fun certificateAndKeyPair(alias: String) = keyStore.getCertificateAndKeyPair(alias, storePassword)
fun certificateAndKeyPair(alias: String): CertificateAndKeyPair = keyStore.getCertificateAndKeyPair(alias, storePassword)
}

View File

@ -7,7 +7,7 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities
@ -90,10 +90,10 @@ class InMemoryIdentityServiceTests {
val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate)
// TODO: Generate certificate with an EdDSA key rather than ECDSA
val identity = Party(CertificateAndKeyPair(rootCert, rootKey))
val identity = Party(rootCert)
val txIdentity = AnonymousParty(txKey.public)
assertFailsWith<IdentityService.UnknownAnonymousPartyException> {
assertFailsWith<UnknownAnonymousPartyException> {
service.assertOwnership(identity, txIdentity)
}
}
@ -107,7 +107,7 @@ class InMemoryIdentityServiceTests {
fun `get anonymous identity by key`() {
val trustRoot = DUMMY_CA
val (alice, aliceTxIdentity) = createParty(ALICE.name, trustRoot)
val (bob, bobTxIdentity) = createParty(ALICE.name, trustRoot)
val (_, bobTxIdentity) = createParty(ALICE.name, trustRoot)
// Now we have identities, construct the service and let it know about both
val service = InMemoryIdentityService(setOf(alice), emptySet(), trustRoot.certificate.cert)
@ -163,7 +163,7 @@ class InMemoryIdentityServiceTests {
val txKey = Crypto.generateKeyPair()
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public)
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates)
return Pair(issuer, PartyAndCertificate(Party(x500Name, txKey.public), txCert, txCertPath))
return Pair(issuer, PartyAndCertificate(txCertPath))
}
/**

View File

@ -9,12 +9,12 @@ import net.corda.core.crypto.cert
import net.corda.core.crypto.commonName
import net.corda.core.internal.exists
import net.corda.core.internal.toTypedArray
import net.corda.core.internal.toX509CertHolder
import net.corda.node.utilities.X509Utilities
import net.corda.node.utilities.loadKeyStore
import net.corda.testing.ALICE
import net.corda.testing.getTestX509Name
import net.corda.testing.testNodeConfiguration
import org.bouncycastle.cert.X509CertificateHolder
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
@ -68,7 +68,7 @@ class NetworkRegistrationHelperTest {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
assertEquals(3, certificateChain.size)
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { X509CertificateHolder(it.encoded).subject.commonName })
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { it.toX509CertHolder().subject.commonName })
}
sslKeystore.run {
@ -78,7 +78,7 @@ class NetworkRegistrationHelperTest {
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)
assertEquals(4, certificateChain.size)
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { X509CertificateHolder(it.encoded).subject.commonName })
assertEquals(listOf("CORDA_CLIENT_CA", "CORDA_CLIENT_CA", "CORDA_INTERMEDIATE_CA", "CORDA_ROOT_CA"), certificateChain.map { it.toX509CertHolder().subject.commonName })
}
trustStore.run {

View File

@ -3,11 +3,9 @@ package net.corda.traderdemo.flow
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.CommercialPaper
import net.corda.core.contracts.Amount
import net.corda.finance.`issued by`
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
@ -16,13 +14,13 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.days
import net.corda.core.utilities.seconds
import net.corda.finance.`issued by`
import java.time.Instant
import java.util.*
/**
* Flow for the Bank of Corda node to issue some commercial paper to the seller's node, to sell to the buyer.
*/
@InitiatingFlow
@StartableByRPC
class CommercialPaperIssueFlow(val amount: Amount<Currency>,
val issueRef: OpaqueBytes,

View File

@ -207,7 +207,7 @@ fun getTestPartyAndCertificate(party: Party, trustRoot: CertificateAndKeyPair =
val certFactory = CertificateFactory.getInstance("X509")
val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, party.name, party.owningKey)
val certPath = certFactory.generateCertPath(listOf(certHolder.cert, trustRoot.certificate.cert))
return PartyAndCertificate(party, certHolder, certPath)
return PartyAndCertificate(certPath)
}
/**

View File

@ -149,7 +149,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
// We only need to override the messaging service here, as currently everything that hits disk does so
// through the java.nio API which we are already mocking via Jimfs.
override fun makeMessagingService(): MessagingService {
override fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService {
require(id >= 0) { "Node ID must be zero or positive, was passed: " + id }
return mockNet.messagingNetwork.createNodeWithID(
!mockNet.threadPerNode,