diff --git a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt index 486ff93429..dfe6b2fc43 100644 --- a/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt +++ b/core/src/main/kotlin/net/corda/core/identity/CordaX500Name.kt @@ -1,8 +1,8 @@ package net.corda.core.identity import net.corda.core.internal.LegalNameValidator +import net.corda.core.internal.countryCodes import net.corda.core.serialization.CordaSerializable -import net.corda.core.utilities.countryCodes import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1ObjectIdentifier import org.bouncycastle.asn1.x500.AttributeTypeAndValue @@ -28,27 +28,11 @@ import org.bouncycastle.asn1.x500.style.BCStyle */ @CordaSerializable data class CordaX500Name(val commonName: String?, - val organisationUnit: String?, - val organisation: String, - val locality: String, - val state: String?, - val country: String) { - init { - // Legal name checks. - LegalNameValidator.validateLegalName(organisation) - - // Attribute data width checks. - require(country.length == LENGTH_COUNTRY) { "Invalid country '$country' Country code must be 2 letters ISO code " } - require(country.toUpperCase() == country) { "Country code should be in upper case." } - require(countryCodes.contains(country)) { "Invalid country code '${country}'" } - - require(organisation.length < MAX_LENGTH_ORGANISATION) { "Organisation attribute (O) must contain less then $MAX_LENGTH_ORGANISATION characters." } - require(locality.length < MAX_LENGTH_LOCALITY) { "Locality attribute (L) must contain less then $MAX_LENGTH_LOCALITY characters." } - - state?.let { require(it.length < MAX_LENGTH_STATE) { "State attribute (ST) must contain less then $MAX_LENGTH_STATE characters." } } - organisationUnit?.let { require(it.length < MAX_LENGTH_ORGANISATION_UNIT) { "Organisation Unit attribute (OU) must contain less then $MAX_LENGTH_ORGANISATION_UNIT characters." } } - commonName?.let { require(it.length < MAX_LENGTH_COMMON_NAME) { "Common Name attribute (CN) must contain less then $MAX_LENGTH_COMMON_NAME characters." } } - } + val organisationUnit: String?, + val organisation: String, + val locality: String, + val state: String?, + val country: String) { constructor(commonName: String, organisation: String, locality: String, country: String) : this(null, commonName, organisation, locality, null, country) /** * @param organisation name of the organisation. @@ -57,6 +41,29 @@ data class CordaX500Name(val commonName: String?, */ constructor(organisation: String, locality: String, country: String) : this(null, null, organisation, locality, null, country) + init { + // Legal name checks. + LegalNameValidator.validateLegalName(organisation) + + // Attribute data width checks. + require(country.length == LENGTH_COUNTRY) { "Invalid country '$country' Country code must be $LENGTH_COUNTRY letters ISO code " } + require(country.toUpperCase() == country) { "Country code should be in upper case." } + require(country in countryCodes) { "Invalid country code $country" } + + require(organisation.length < MAX_LENGTH_ORGANISATION) { + "Organisation attribute (O) must contain less then $MAX_LENGTH_ORGANISATION characters." + } + require(locality.length < MAX_LENGTH_LOCALITY) { "Locality attribute (L) must contain less then $MAX_LENGTH_LOCALITY characters." } + + state?.let { require(it.length < MAX_LENGTH_STATE) { "State attribute (ST) must contain less then $MAX_LENGTH_STATE characters." } } + organisationUnit?.let { require(it.length < MAX_LENGTH_ORGANISATION_UNIT) { + "Organisation Unit attribute (OU) must contain less then $MAX_LENGTH_ORGANISATION_UNIT characters." } + } + commonName?.let { require(it.length < MAX_LENGTH_COMMON_NAME) { + "Common Name attribute (CN) must contain less then $MAX_LENGTH_COMMON_NAME characters." } + } + } + companion object { const val LENGTH_COUNTRY = 2 const val MAX_LENGTH_ORGANISATION = 128 @@ -64,28 +71,24 @@ data class CordaX500Name(val commonName: String?, const val MAX_LENGTH_STATE = 64 const val MAX_LENGTH_ORGANISATION_UNIT = 64 const val MAX_LENGTH_COMMON_NAME = 64 - private val mandatoryAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L) - private val supportedAttributes = mandatoryAttributes + setOf(BCStyle.CN, BCStyle.ST, BCStyle.OU) + private val supportedAttributes = setOf(BCStyle.O, BCStyle.C, BCStyle.L, BCStyle.CN, BCStyle.ST, BCStyle.OU) @JvmStatic fun build(x500Name: X500Name) : CordaX500Name { - val rDNs = x500Name.rdNs.flatMap { it.typesAndValues.toList() } - val attrsMap: Map = rDNs.associateBy(AttributeTypeAndValue::getType, AttributeTypeAndValue::getValue) - val attributes = attrsMap.keys - - // Duplicate attribute value checks. - require(attributes.size == attributes.toSet().size) { "X500Name contain duplicate attribute." } - - // Mandatory attribute checks. - require(attributes.containsAll(mandatoryAttributes)) { - val missingAttributes = mandatoryAttributes.subtract(attributes).map { BCStyle.INSTANCE.oidToDisplayName(it) } - "The following attribute${if (missingAttributes.size > 1) "s are" else " is"} missing from the legal name : $missingAttributes" - } + val attrsMap: Map = x500Name.rdNs + .flatMap { it.typesAndValues.asList() } + .groupBy(AttributeTypeAndValue::getType, AttributeTypeAndValue::getValue) + .mapValues { + require(it.value.size == 1) { "Duplicate attribute ${it.key}" } + it.value[0] + } // Supported attribute checks. - require(attributes.subtract(supportedAttributes).isEmpty()) { - val unsupportedAttributes = attributes.subtract(supportedAttributes).map { BCStyle.INSTANCE.oidToDisplayName(it) } - "The following attribute${if (unsupportedAttributes.size > 1) "s are" else " is"} not supported in Corda :$unsupportedAttributes" + (attrsMap.keys - supportedAttributes).let { unsupported -> + require(unsupported.isEmpty()) { + "The following attribute${if (unsupported.size > 1) "s are" else " is"} not supported in Corda: " + + unsupported.map { BCStyle.INSTANCE.oidToDisplayName(it) } + } } val CN = attrsMap[BCStyle.CN]?.toString() diff --git a/core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt b/core/src/main/kotlin/net/corda/core/internal/CountryCode.kt similarity index 96% rename from core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt rename to core/src/main/kotlin/net/corda/core/internal/CountryCode.kt index 94f0770279..7e5f1ae54f 100644 --- a/core/src/main/kotlin/net/corda/core/utilities/CountryCode.kt +++ b/core/src/main/kotlin/net/corda/core/internal/CountryCode.kt @@ -1,6 +1,8 @@ -package net.corda.core.utilities +package net.corda.core.internal -val countryCodes = hashSetOf( +import com.google.common.collect.ImmutableSet + +val countryCodes: Set = ImmutableSet.of( "AF", "AX", "AL", diff --git a/core/src/test/kotlin/net/corda/core/identity/CordaX500NameTest.kt b/core/src/test/kotlin/net/corda/core/identity/CordaX500NameTest.kt index e67ecd82cf..766fa202a4 100644 --- a/core/src/test/kotlin/net/corda/core/identity/CordaX500NameTest.kt +++ b/core/src/test/kotlin/net/corda/core/identity/CordaX500NameTest.kt @@ -60,4 +60,11 @@ class CordaX500NameTest { CordaX500Name.parse("O=B, L=New York, C=US, OU=Org Unit, CN=Service Name") } } + + @Test + fun `rejects name with unsupported attribute`() { + assertFailsWith(IllegalArgumentException::class) { + CordaX500Name.parse("O=Bank A, L=New York, C=US, SN=blah") + } + } }