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

View File

@ -13,8 +13,7 @@ import java.security.PublicKey
@CordaSerializable @CordaSerializable
abstract class AbstractParty(val owningKey: PublicKey) { 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 */ /** 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() override fun hashCode(): Int = owningKey.hashCode()
abstract fun nameOrNull(): X500Name? abstract fun nameOrNull(): X500Name?

View File

@ -1,7 +1,6 @@
package net.corda.core.identity package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference import net.corda.core.contracts.PartyAndReference
import net.corda.core.crypto.toBase58String
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name 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. * information such as name. It is intended to represent a party on the distributed ledger.
*/ */
class AnonymousParty(owningKey: PublicKey) : AbstractParty(owningKey) { 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 nameOrNull(): X500Name? = null
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes) 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 package net.corda.core.identity
import net.corda.core.contracts.PartyAndReference 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 net.corda.core.utilities.OpaqueBytes
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import java.security.PublicKey import java.security.PublicKey
/** /**
@ -26,10 +28,9 @@ import java.security.PublicKey
* @see CompositeKey * @see CompositeKey
*/ */
class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) { class Party(val name: X500Name, owningKey: PublicKey) : AbstractParty(owningKey) {
constructor(certAndKey: CertificateAndKeyPair) : this(certAndKey.certificate.subject, certAndKey.keyPair.public) constructor(certificate: X509CertificateHolder) : this(certificate.subject, Crypto.toSupportedPublicKey(certificate.subjectPublicKeyInfo))
override fun toString() = name.toString() override fun nameOrNull(): X500Name = name
override fun nameOrNull(): X500Name? = name
fun anonymise(): AnonymousParty = AnonymousParty(owningKey) fun anonymise(): AnonymousParty = AnonymousParty(owningKey)
override fun ref(bytes: OpaqueBytes): PartyAndReference = PartyAndReference(this, bytes) 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 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.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.* 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 * 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, * [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, * not part of the identifier themselves.
* this class exists in order to ensure the implementation classes of certificates and party public keys are kept stable.
*/ */
@CordaSerializable //TODO Is VerifiableIdentity a better name?
data class PartyAndCertificate(val party: Party, class PartyAndCertificate(val certPath: CertPath) {
val certificate: X509CertificateHolder, @Transient val certificate: X509CertificateHolder
val certPath: CertPath) { init {
constructor(name: X500Name, owningKey: PublicKey, certificate: X509CertificateHolder, certPath: CertPath) : this(Party(name, owningKey), certificate, certPath) require(certPath.type == "X.509") { "Only X.509 certificates supported" }
val name: X500Name val certs = certPath.certificates
get() = party.name require(certs.size >= 2) { "Certificate path must at least include subject and issuing certificates" }
val owningKey: PublicKey certificate = certs[0].toX509CertHolder()
get() = party.owningKey
override fun equals(other: Any?): Boolean {
return if (other is PartyAndCertificate)
party == other.party
else
false
} }
@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 hashCode(): Int = party.hashCode()
override fun toString(): String = party.toString() override fun toString(): String = party.toString()
/** /** Verify the certificate path is valid. */
* Verify that the given certificate path is valid and leads to the owning key of the party.
*/
fun verify(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult { fun verify(trustAnchor: TrustAnchor): PKIXCertPathValidatorResult {
require(certPath.certificates.first() is X509Certificate) { "Subject certificate must be an X.509 certificate" } val parameters = PKIXParameters(setOf(trustAnchor)).apply { isRevocationEnabled = false }
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 validator = CertPathValidator.getInstance("PKIX") val validator = CertPathValidator.getInstance("PKIX")
validatorParameters.isRevocationEnabled = false return validator.validate(certPath, parameters) as PKIXCertPathValidatorResult
return validator.validate(certPath, validatorParameters) as PKIXCertPathValidatorResult
} }
} }

View File

@ -2,6 +2,7 @@ package net.corda.core.internal
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.Logger import org.slf4j.Logger
import rx.Observable import rx.Observable
import rx.Observer 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]. */ /** Convert a [ByteArrayOutputStream] to [InputStreamAndHash]. */
fun ByteArrayOutputStream.toInputStreamAndHash(): InputStreamAndHash { fun ByteArrayOutputStream.toInputStreamAndHash(): InputStreamAndHash {
val bytes = toByteArray() 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 legalIdentityAndCert: PartyAndCertificate, //TODO This field will be removed in future PR which gets rid of services.
val legalIdentitiesAndCerts: NonEmptySet<PartyAndCertificate>, val legalIdentitiesAndCerts: NonEmptySet<PartyAndCertificate>,
val platformVersion: Int, val platformVersion: Int,
var advertisedServices: List<ServiceEntry> = emptyList(), val advertisedServices: List<ServiceEntry> = emptyList(),
val worldMapLocation: WorldMapLocation? = null) { val worldMapLocation: WorldMapLocation? = null) {
init { 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 val legalIdentity: Party get() = legalIdentityAndCert.party
get() = advertisedServices.single { it.info.type.isNotary() }.identity.party val notaryIdentity: Party get() = advertisedServices.single { it.info.type.isNotary() }.identity.party
fun serviceIdentities(type: ServiceType): List<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 package net.corda.core.node.services
import net.corda.core.contracts.PartyAndReference 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.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import java.security.InvalidAlgorithmParameterException 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. * @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> 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.UnmodifiableCollectionsSerializer
import de.javakaffee.kryoserializers.guava.* import de.javakaffee.kryoserializers.guava.*
import net.corda.core.crypto.composite.CompositeKey import net.corda.core.crypto.composite.CompositeKey
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.CordaPluginRegistry import net.corda.core.node.CordaPluginRegistry
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
@ -59,6 +60,12 @@ object DefaultKryoCustomizer {
instantiatorStrategy = CustomInstantiatorStrategy() 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 // 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. // with custom serializers get written as registration ids. This will break backwards-compatibility.
// Please add any new registrations to the end. // Please add any new registrations to the end.
@ -68,50 +75,31 @@ object DefaultKryoCustomizer {
register(SignedTransaction::class.java, SignedTransactionSerializer) register(SignedTransaction::class.java, SignedTransactionSerializer)
register(WireTransaction::class.java, WireTransactionSerializer) register(WireTransaction::class.java, WireTransactionSerializer)
register(SerializedBytes::class.java, SerializedBytesSerializer) register(SerializedBytes::class.java, SerializedBytesSerializer)
UnmodifiableCollectionsSerializer.registerSerializers(this) UnmodifiableCollectionsSerializer.registerSerializers(this)
ImmutableListSerializer.registerSerializers(this) ImmutableListSerializer.registerSerializers(this)
ImmutableSetSerializer.registerSerializers(this) ImmutableSetSerializer.registerSerializers(this)
ImmutableSortedSetSerializer.registerSerializers(this) ImmutableSortedSetSerializer.registerSerializers(this)
ImmutableMapSerializer.registerSerializers(this) ImmutableMapSerializer.registerSerializers(this)
ImmutableMultimapSerializer.registerSerializers(this) ImmutableMultimapSerializer.registerSerializers(this)
// InputStream subclasses whitelisting, required for attachments. // InputStream subclasses whitelisting, required for attachments.
register(BufferedInputStream::class.java, InputStreamSerializer) register(BufferedInputStream::class.java, InputStreamSerializer)
register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer) register(Class.forName("sun.net.www.protocol.jar.JarURLConnection\$JarURLInputStream"), InputStreamSerializer)
noReferencesWithin<WireTransaction>() noReferencesWithin<WireTransaction>()
register(ECPublicKeyImpl::class.java, ECPublicKeyImplSerializer) register(ECPublicKeyImpl::class.java, ECPublicKeyImplSerializer)
register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer) register(EdDSAPublicKey::class.java, Ed25519PublicKeySerializer)
register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer) register(EdDSAPrivateKey::class.java, Ed25519PrivateKeySerializer)
register(CompositeKey::class.java, CompositeKeySerializer) // Using a custom serializer for compactness
// Using a custom serializer for compactness
register(CompositeKey::class.java, CompositeKeySerializer)
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway. // 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 = { _, _, _ -> }) register(Array<StackTraceElement>::class, read = { _, _ -> emptyArray() }, write = { _, _, _ -> })
// This ensures a NonEmptySetSerializer is constructed with an initial value. // This ensures a NonEmptySetSerializer is constructed with an initial value.
register(NonEmptySet::class.java, NonEmptySetSerializer) register(NonEmptySet::class.java, NonEmptySetSerializer)
addDefaultSerializer(SerializeAsToken::class.java, SerializeAsTokenSerializer<SerializeAsToken>())
register(BitSet::class.java, BitSetSerializer()) register(BitSet::class.java, BitSetSerializer())
register(Class::class.java, ClassSerializer) register(Class::class.java, ClassSerializer)
addDefaultSerializer(Logger::class.java, LoggerSerializer)
register(FileInputStream::class.java, InputStreamSerializer) 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(CertPath::class.java, CertPathSerializer)
register(X509CertPath::class.java, CertPathSerializer) register(X509CertPath::class.java, CertPathSerializer)
register(X500Name::class.java, X500NameSerializer) register(X500Name::class.java, X500NameSerializer)
register(X509CertificateHolder::class.java, X509CertificateSerializer) register(X509CertificateHolder::class.java, X509CertificateSerializer)
register(BCECPrivateKey::class.java, PrivateKeySerializer) register(BCECPrivateKey::class.java, PrivateKeySerializer)
register(BCECPublicKey::class.java, PublicKeySerializer) register(BCECPublicKey::class.java, PublicKeySerializer)
register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer) register(BCRSAPrivateCrtKey::class.java, PrivateKeySerializer)
@ -119,8 +107,8 @@ object DefaultKryoCustomizer {
register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer) register(BCSphincs256PrivateKey::class.java, PrivateKeySerializer)
register(BCSphincs256PublicKey::class.java, PublicKeySerializer) register(BCSphincs256PublicKey::class.java, PublicKeySerializer)
register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer) register(sun.security.ec.ECPublicKeyImpl::class.java, PublicKeySerializer)
register(NotaryChangeWireTransaction::class.java, NotaryChangeWireTransactionSerializer) register(NotaryChangeWireTransaction::class.java, NotaryChangeWireTransactionSerializer)
register(PartyAndCertificate::class.java, PartyAndCertificateSerializer)
val customization = KryoSerializationCustomization(this) val customization = KryoSerializationCustomization(this)
pluginRegistries.forEach { it.customizeSerialization(customization) } 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>>() { private object NonEmptySetSerializer : Serializer<NonEmptySet<Any>>() {
override fun write(kryo: Kryo, output: Output, obj: NonEmptySet<Any>) { override fun write(kryo: Kryo, output: Output, obj: NonEmptySet<Any>) {
// Write out the contents as normal // Write out the contents as normal

View File

@ -2,6 +2,7 @@ package net.corda.node.internal
import com.codahale.metrics.MetricRegistry import com.codahale.metrics.MetricRegistry
import com.google.common.annotations.VisibleForTesting import com.google.common.annotations.VisibleForTesting
import com.google.common.collect.Lists
import com.google.common.collect.MutableClassToInstanceMap import com.google.common.collect.MutableClassToInstanceMap
import com.google.common.util.concurrent.MoreExecutors import com.google.common.util.concurrent.MoreExecutors
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
@ -66,7 +67,6 @@ import net.corda.node.utilities.*
import net.corda.node.utilities.AddOrRemove.ADD import net.corda.node.utilities.AddOrRemove.ADD
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.slf4j.Logger import org.slf4j.Logger
import rx.Observable import rx.Observable
import java.io.IOException import java.io.IOException
@ -88,6 +88,9 @@ import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeUnit.SECONDS
import java.util.stream.Collectors.toList import java.util.stream.Collectors.toList
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.reflect.KClass import kotlin.reflect.KClass
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
@ -417,8 +420,9 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
checkpointStorage = DBCheckpointStorage() checkpointStorage = DBCheckpointStorage()
_services = ServiceHubInternalImpl() _services = ServiceHubInternalImpl()
attachments = NodeAttachmentService(configuration.dataSourceProperties, services.monitoringService.metrics, configuration.database) attachments = NodeAttachmentService(configuration.dataSourceProperties, services.monitoringService.metrics, configuration.database)
network = makeMessagingService() val legalIdentity = obtainIdentity("identity", configuration.myLegalName)
info = makeInfo() network = makeMessagingService(legalIdentity)
info = makeInfo(legalIdentity)
val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.vaultQueryService, val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.vaultQueryService,
services.keyManagementService, services.identityService, platformClock, services.schedulerService) 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})) 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 advertisedServiceEntries = makeServiceEntries()
val legalIdentity = obtainLegalIdentity() val allIdentities = (advertisedServiceEntries.map { it.identity } + legalIdentity).toNonEmptySet()
val allIdentitiesSet = (advertisedServiceEntries.map { it.identity } + legalIdentity).toNonEmptySet()
val addresses = myAddresses() // TODO There is no support for multiple IP addresses yet. 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 { return advertisedServices.map {
val serviceId = it.type.id val serviceId = it.type.id
val serviceName = it.name ?: X500Name("${configuration.myLegalName},OU=$serviceId") val serviceName = it.name ?: X500Name("${configuration.myLegalName},OU=$serviceId")
val identity = obtainKeyPair(serviceId, serviceName).first val identity = obtainIdentity(serviceId, serviceName)
ServiceEntry(it, identity) ServiceEntry(it, identity)
} }
} }
@ -613,8 +616,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
val instant = platformClock.instant() val instant = platformClock.instant()
val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
val reg = NodeRegistration(info, instant.toEpochMilli(), ADD, expires) val reg = NodeRegistration(info, instant.toEpochMilli(), ADD, expires)
val legalIdentityKey = obtainLegalIdentityKey() val request = RegistrationRequest(reg.toWire(services.keyManagementService, info.legalIdentityAndCert.owningKey), network.myAddress)
val request = RegistrationRequest(reg.toWire(services.keyManagementService, legalIdentityKey.public), network.myAddress)
return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapAddress) return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapAddress)
} }
@ -680,15 +682,11 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
runOnStop.clear() runOnStop.clear()
} }
protected abstract fun makeMessagingService(): MessagingService protected abstract fun makeMessagingService(legalIdentity: PartyAndCertificate): MessagingService
protected abstract fun startMessagingService(rpcOps: RPCOps) protected abstract fun startMessagingService(rpcOps: RPCOps)
protected fun obtainLegalIdentity(): PartyAndCertificate = identityKeyPair.first private fun obtainIdentity(id: String, name: X500Name): PartyAndCertificate {
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> {
// Load the private identity key, creating it if necessary. The identity key is a long term well known key that // 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 // 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 // "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? // TODO: Integrate with Key management service?
val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword) val keyStore = KeyStoreWrapper(configuration.nodeKeystore, configuration.keyStorePassword)
val privateKeyAlias = "$serviceId-private-key" val privateKeyAlias = "$id-private-key"
val compositeKeyAlias = "$serviceId-composite-key" val compositeKeyAlias = "$id-composite-key"
if (!keyStore.containsAlias(privateKeyAlias)) { if (!keyStore.containsAlias(privateKeyAlias)) {
val privKeyFile = configuration.baseDirectory / privateKeyAlias val privKeyFile = configuration.baseDirectory / privateKeyAlias
val pubIdentityFile = configuration.baseDirectory / "$serviceId-public" val pubIdentityFile = configuration.baseDirectory / "$id-public"
val compositeKeyFile = configuration.baseDirectory / compositeKeyAlias val compositeKeyFile = configuration.baseDirectory / compositeKeyAlias
// TODO: Remove use of [ServiceIdentityGenerator.generateToDisk]. // TODO: Remove use of [ServiceIdentityGenerator.generateToDisk].
// Get keys from key file. // Get keys from key file.
// TODO: this is here to smooth out the key storage transition, remove this migration in future release. // TODO: this is here to smooth out the key storage transition, remove this migration in future release.
if (privKeyFile.exists()) { if (privKeyFile.exists()) {
migrateKeysFromFile(keyStore, serviceName, pubIdentityFile, privKeyFile, compositeKeyFile, privateKeyAlias, compositeKeyAlias) migrateKeysFromFile(keyStore, name, pubIdentityFile, privKeyFile, compositeKeyFile, privateKeyAlias, compositeKeyAlias)
} else { } else {
log.info("$privateKeyAlias not found in keystore ${configuration.nodeKeystore}, generating fresh key!") log.info("$privateKeyAlias not found in key store ${configuration.nodeKeystore}, generating fresh key!")
keyStore.saveNewKeyPair(serviceName, privateKeyAlias, generateKeyPair()) keyStore.saveNewKeyPair(name, privateKeyAlias, generateKeyPair())
} }
} }
val (cert, keys) = keyStore.certificateAndKeyPair(privateKeyAlias) val (x509Cert, 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")
// Use composite key instead if exists
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity. // 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 certificates = if (keyStore.containsAlias(compositeKeyAlias)) {
val compositeKey = Crypto.toSupportedPublicKey(keyStore.getCertificate(compositeKeyAlias).publicKey) // Use composite key instead if it exists
val compositeKeyCert = keyStore.getCertificate(compositeKeyAlias) val certificate = keyStore.getCertificate(compositeKeyAlias)
// We have to create the certificate chain for the composite key manually, this is because in order to store // 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. // the chain in key store we need a private key, however there is no corresponding private key for the composite key.
Pair(KeyPair(compositeKey, keys.private), listOf(compositeKeyCert, *keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA))) Lists.asList(certificate, keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA))
} else { } 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 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, private fun migrateKeysFromFile(keyStore: KeyStoreWrapper, serviceName: X500Name,
pubKeyFile: Path, privKeyFile: Path, compositeKeyFile:Path, pubKeyFile: Path, privKeyFile: Path, compositeKeyFile:Path,
privateKeyAlias: String, compositeKeyAlias: String) { 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. // Check that the identity in the config file matches the identity file we have stored to disk.
// Load the private key. // Load the private key.
val publicKey = Crypto.decodePublicKey(pubKeyFile.readAll()) 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.") 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() protected open fun generateKeyPair() = cryptoGenerateKeyPair()
private inner class ServiceHubInternalImpl : ServiceHubInternal, SingletonSerializeAsToken() { private inner class ServiceHubInternalImpl : ServiceHubInternal, SingletonSerializeAsToken() {

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package net.corda.node.services.keys
import net.corda.core.crypto.ContentSignerBuilder import net.corda.core.crypto.ContentSignerBuilder
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert import net.corda.core.crypto.cert
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.days import net.corda.core.utilities.days
@ -35,10 +34,11 @@ fun freshCertificate(identityService: IdentityService,
revocationEnabled: Boolean = false): PartyAndCertificate { revocationEnabled: Boolean = false): PartyAndCertificate {
val issuerCertificate = issuer.certificate val issuerCertificate = issuer.certificate
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate) 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 certFactory = CertificateFactory.getInstance("X509")
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates) 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) identityService.verifyAndRegisterIdentity(anonymisedIdentity)
return 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.concurrent.openFuture
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.noneOrSingle import net.corda.core.internal.noneOrSingle
import net.corda.core.internal.toX509CertHolder
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.NetworkMapCache.MapChange 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
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA
import net.corda.node.utilities.getX509Certificate
import net.corda.node.utilities.loadKeyStore import net.corda.node.utilities.loadKeyStore
import net.corda.nodeapi.* import net.corda.nodeapi.*
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER 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.spi.core.security.jaas.UserPrincipal
import org.apache.activemq.artemis.utils.ConfigurationHelper import org.apache.activemq.artemis.utils.ConfigurationHelper
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import rx.Subscription import rx.Subscription
import java.io.IOException import java.io.IOException
import java.math.BigInteger import java.math.BigInteger
@ -273,12 +272,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration,
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager { private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword) val keyStore = loadKeyStore(config.sslKeystore, config.keyStorePassword)
val trustStore = loadKeyStore(config.trustStoreFile, config.trustStorePassword) 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( val defaultCertPolicies = mapOf(
PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch, PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch,
NODE_ROLE to CertificateChainCheckPolicy.LeafMustMatch, 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!" "misconfiguration by the remote peer or an SSL man-in-the-middle attack!"
} }
// Make sure certificate has the same name. // Make sure certificate has the same name.
val peerCertificate = X509CertificateHolder(session.peerCertificateChain.first().encoded) val peerCertificate = session.peerCertificateChain[0].toX509CertHolder()
require(peerCertificate.subject == expectedLegalName) { require(peerCertificate.subject == expectedLegalName) {
"Peer has wrong subject name in the certificate - expected $expectedLegalName but got ${peerCertificate.subject}. This is either a fatal " + "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!" "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) server.onTcpConnection(peerLegalName)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
connection.close() connection.close()

View File

@ -21,6 +21,7 @@ import java.util.Collections.synchronizedMap
class PersistentNetworkMapService(services: ServiceHubInternal, minimumPlatformVersion: Int) class PersistentNetworkMapService(services: ServiceHubInternal, minimumPlatformVersion: Int)
: AbstractNetworkMapService(services, minimumPlatformVersion) { : 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") { 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 nodeParty = partyAndCertificate("node_party_name", "node_party_key", "node_party_certificate", "node_party_path")
val registrationInfo = blob("node_registration_info") 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) { 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 // 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], override fun keyFromRow(row: ResultRow): PartyAndCertificate = PartyAndCertificate(row[table.nodeParty.certPath])
row[table.nodeParty.certificate], row[table.nodeParty.certPath])
override fun valueFromRow(row: ResultRow): NodeRegistrationInfo = deserializeFromBlob(row[table.registrationInfo]) override fun valueFromRow(row: ResultRow): NodeRegistrationInfo = deserializeFromBlob(row[table.registrationInfo])
override fun addKeyToInsert(insert: InsertStatement, entry: Map.Entry<PartyAndCertificate, NodeRegistrationInfo>, finalizables: MutableList<() -> Unit>) { override fun addKeyToInsert(insert: InsertStatement, entry: Map.Entry<PartyAndCertificate, NodeRegistrationInfo>, finalizables: MutableList<() -> Unit>) {
insert[table.nodeParty.name] = entry.key.name.toString() insert[table.nodeParty.name] = entry.key.name.toString()
insert[table.nodeParty.owningKey] = entry.key.owningKey insert[table.nodeParty.owningKey] = entry.key.owningKey
insert[table.nodeParty.certPath] = entry.key.certPath
insert[table.nodeParty.certificate] = entry.key.certificate 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>) { override fun addValueToInsert(insert: InsertStatement, entry: Map.Entry<PartyAndCertificate, NodeRegistrationInfo>, finalizables: MutableList<() -> Unit>) {

View File

@ -1,8 +1,11 @@
package net.corda.node.utilities 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.exists
import net.corda.core.internal.read import net.corda.core.internal.read
import net.corda.core.internal.toX509CertHolder
import net.corda.core.internal.write import net.corda.core.internal.write
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder 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. * @return The X509Certificate found in the KeyStore under the specified alias.
*/ */
fun KeyStore.getX509Certificate(alias: String): X509CertificateHolder { fun KeyStore.getX509Certificate(alias: String): X509CertificateHolder {
val encoded = getCertificate(alias)?.encoded ?: throw IllegalArgumentException("No certificate under alias \"$alias\"") val certificate = getCertificate(alias) ?: throw IllegalArgumentException("No certificate under alias \"$alias\"")
return X509CertificateHolder(encoded) 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 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.AnonymousParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate 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.services.identity.InMemoryIdentityService
import net.corda.node.utilities.CertificateType import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities import net.corda.node.utilities.X509Utilities
@ -90,10 +90,10 @@ class InMemoryIdentityServiceTests {
val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val txKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate) val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate)
// TODO: Generate certificate with an EdDSA key rather than ECDSA // 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) val txIdentity = AnonymousParty(txKey.public)
assertFailsWith<IdentityService.UnknownAnonymousPartyException> { assertFailsWith<UnknownAnonymousPartyException> {
service.assertOwnership(identity, txIdentity) service.assertOwnership(identity, txIdentity)
} }
} }
@ -107,7 +107,7 @@ class InMemoryIdentityServiceTests {
fun `get anonymous identity by key`() { fun `get anonymous identity by key`() {
val trustRoot = DUMMY_CA val trustRoot = DUMMY_CA
val (alice, aliceTxIdentity) = createParty(ALICE.name, trustRoot) 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 // Now we have identities, construct the service and let it know about both
val service = InMemoryIdentityService(setOf(alice), emptySet(), trustRoot.certificate.cert) val service = InMemoryIdentityService(setOf(alice), emptySet(), trustRoot.certificate.cert)
@ -163,7 +163,7 @@ class InMemoryIdentityServiceTests {
val txKey = Crypto.generateKeyPair() val txKey = Crypto.generateKeyPair()
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public) val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, issuer.certificate, issuerKeyPair, x500Name, txKey.public)
val txCertPath = certFactory.generateCertPath(listOf(txCert.cert) + issuer.certPath.certificates) 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.crypto.commonName
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.core.internal.toTypedArray import net.corda.core.internal.toTypedArray
import net.corda.core.internal.toX509CertHolder
import net.corda.node.utilities.X509Utilities import net.corda.node.utilities.X509Utilities
import net.corda.node.utilities.loadKeyStore import net.corda.node.utilities.loadKeyStore
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.getTestX509Name import net.corda.testing.getTestX509Name
import net.corda.testing.testNodeConfiguration import net.corda.testing.testNodeConfiguration
import org.bouncycastle.cert.X509CertificateHolder
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
@ -68,7 +68,7 @@ class NetworkRegistrationHelperTest {
assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) assertFalse(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA) val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
assertEquals(3, certificateChain.size) 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 { sslKeystore.run {
@ -78,7 +78,7 @@ class NetworkRegistrationHelperTest {
assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS)) assertTrue(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) val certificateChain = getCertificateChain(X509Utilities.CORDA_CLIENT_TLS)
assertEquals(4, certificateChain.size) 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 { trustStore.run {

View File

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

View File

@ -207,7 +207,7 @@ fun getTestPartyAndCertificate(party: Party, trustRoot: CertificateAndKeyPair =
val certFactory = CertificateFactory.getInstance("X509") val certFactory = CertificateFactory.getInstance("X509")
val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, party.name, party.owningKey) val certHolder = X509Utilities.createCertificate(CertificateType.IDENTITY, trustRoot.certificate, trustRoot.keyPair, party.name, party.owningKey)
val certPath = certFactory.generateCertPath(listOf(certHolder.cert, trustRoot.certificate.cert)) 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 // 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. // 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 } require(id >= 0) { "Node ID must be zero or positive, was passed: " + id }
return mockNet.messagingNetwork.createNodeWithID( return mockNet.messagingNetwork.createNodeWithID(
!mockNet.threadPerNode, !mockNet.threadPerNode,