mirror of
https://github.com/corda/corda.git
synced 2025-01-21 03:55:00 +00:00
Add X500 name constraints for non-organisation attributes (#2108)
Enforce X500 name constraints consistently across all attributes
This commit is contained in:
parent
228b29110e
commit
026d88a2b9
@ -9,7 +9,7 @@ import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
|||||||
import org.bouncycastle.asn1.x500.AttributeTypeAndValue
|
import org.bouncycastle.asn1.x500.AttributeTypeAndValue
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||||
import java.util.Locale
|
import java.util.*
|
||||||
import javax.security.auth.x500.X500Principal
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +45,7 @@ data class CordaX500Name(val commonName: String?,
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
// Legal name checks.
|
// Legal name checks.
|
||||||
LegalNameValidator.validateLegalName(organisation)
|
LegalNameValidator.validateOrganization(organisation)
|
||||||
|
|
||||||
// Attribute data width checks.
|
// Attribute data width checks.
|
||||||
require(country.length == LENGTH_COUNTRY) { "Invalid country '$country' Country code must be $LENGTH_COUNTRY letters ISO code " }
|
require(country.length == LENGTH_COUNTRY) { "Invalid country '$country' Country code must be $LENGTH_COUNTRY letters ISO code " }
|
||||||
|
@ -6,8 +6,27 @@ import java.util.regex.Pattern
|
|||||||
import javax.security.auth.x500.X500Principal
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
object LegalNameValidator {
|
object LegalNameValidator {
|
||||||
|
@Deprecated("Use validateOrganization instead", replaceWith = ReplaceWith("validateOrganization(normalizedLegalName)"))
|
||||||
|
fun validateLegalName(normalizedLegalName: String) = validateOrganization(normalizedLegalName)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The validation function will validate the input string using the following rules:
|
* The validation function validates a string for use as part of a legal name. It applies the following rules:
|
||||||
|
*
|
||||||
|
* - No blacklisted words like "node", "server".
|
||||||
|
* - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce
|
||||||
|
* names over the phone, and character confusability attacks.
|
||||||
|
* - No commas or equals signs.
|
||||||
|
* - No dollars or quote marks, we might need to relax the quote mark constraint in future to handle Irish company names.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
|
||||||
|
*/
|
||||||
|
fun validateNameAttribute(normalizedNameAttribute: String) {
|
||||||
|
Rule.baseNameRules.forEach { it.validate(normalizedNameAttribute) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The validation function validates a string for use as the organization attribute of a name, which includes additional
|
||||||
|
* constraints over basic name attribute checks. It applies the following rules:
|
||||||
*
|
*
|
||||||
* - No blacklisted words like "node", "server".
|
* - No blacklisted words like "node", "server".
|
||||||
* - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce
|
* - Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce
|
||||||
@ -18,16 +37,19 @@ object LegalNameValidator {
|
|||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
|
* @throws IllegalArgumentException if the name does not meet the required rules. The message indicates why not.
|
||||||
*/
|
*/
|
||||||
fun validateLegalName(normalizedLegalName: String) {
|
fun validateOrganization(normalizedOrganization: String) {
|
||||||
Rule.legalNameRules.forEach { it.validate(normalizedLegalName) }
|
Rule.legalNameRules.forEach { it.validate(normalizedOrganization) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Use normalize instead", replaceWith = ReplaceWith("normalize(legalName)"))
|
||||||
|
fun normalizeLegalName(legalName: String): String = normalize(legalName)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The normalize function will trim the input string, replace any multiple spaces with a single space,
|
* The normalize function will trim the input string, replace any multiple spaces with a single space,
|
||||||
* and normalize the string according to NFKC normalization form.
|
* and normalize the string according to NFKC normalization form.
|
||||||
*/
|
*/
|
||||||
fun normaliseLegalName(legalName: String): String {
|
fun normalize(nameAttribute: String): String {
|
||||||
val trimmedLegalName = legalName.trim().replace(WHITESPACE, " ")
|
val trimmedLegalName = nameAttribute.trim().replace(WHITESPACE, " ")
|
||||||
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
|
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,15 +57,17 @@ object LegalNameValidator {
|
|||||||
|
|
||||||
sealed class Rule<in T> {
|
sealed class Rule<in T> {
|
||||||
companion object {
|
companion object {
|
||||||
val legalNameRules: List<Rule<String>> = listOf(
|
val baseNameRules: List<Rule<String>> = listOf(
|
||||||
UnicodeNormalizationRule(),
|
UnicodeNormalizationRule(),
|
||||||
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
||||||
WordRule("node", "server"),
|
WordRule("node", "server"),
|
||||||
LengthRule(maxLength = 255),
|
LengthRule(maxLength = 255),
|
||||||
// TODO: Implement confusable character detection if we add more scripts.
|
// TODO: Implement confusable character detection if we add more scripts.
|
||||||
UnicodeRangeRule(LATIN, COMMON, INHERITED),
|
UnicodeRangeRule(LATIN, COMMON, INHERITED),
|
||||||
|
X500NameRule()
|
||||||
|
)
|
||||||
|
val legalNameRules: List<Rule<String>> = baseNameRules + listOf(
|
||||||
CapitalLetterRule(),
|
CapitalLetterRule(),
|
||||||
X500NameRule(),
|
|
||||||
MustHaveAtLeastTwoLettersRule()
|
MustHaveAtLeastTwoLettersRule()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -52,7 +76,7 @@ object LegalNameValidator {
|
|||||||
|
|
||||||
private class UnicodeNormalizationRule : Rule<String>() {
|
private class UnicodeNormalizationRule : Rule<String>() {
|
||||||
override fun validate(legalName: String) {
|
override fun validate(legalName: String) {
|
||||||
require(legalName == normaliseLegalName(legalName)) { "Legal name must be normalized. Please use 'normaliseLegalName' to normalize the legal name before validation." }
|
require(legalName == normalize(legalName)) { "Legal name must be normalized. Please use 'normalize' to normalize the legal name before validation." }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,55 +8,55 @@ class LegalNameValidatorTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `no double spaces`() {
|
fun `no double spaces`() {
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("Test Legal Name")
|
LegalNameValidator.validateOrganization("Test Legal Name")
|
||||||
}
|
}
|
||||||
LegalNameValidator.validateLegalName(LegalNameValidator.normaliseLegalName("Test Legal Name"))
|
LegalNameValidator.validateOrganization(LegalNameValidator.normalize("Test Legal Name"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `no trailing white space`() {
|
fun `no trailing white space`() {
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("Test ")
|
LegalNameValidator.validateOrganization("Test ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `no prefixed white space`() {
|
fun `no prefixed white space`() {
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName(" Test")
|
LegalNameValidator.validateOrganization(" Test")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `blacklisted words`() {
|
fun `blacklisted words`() {
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("Test Server")
|
LegalNameValidator.validateOrganization("Test Server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `blacklisted characters`() {
|
fun `blacklisted characters`() {
|
||||||
LegalNameValidator.validateLegalName("Test")
|
LegalNameValidator.validateOrganization("Test")
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("\$Test")
|
LegalNameValidator.validateOrganization("\$Test")
|
||||||
}
|
}
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("\"Test")
|
LegalNameValidator.validateOrganization("\"Test")
|
||||||
}
|
}
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("\'Test")
|
LegalNameValidator.validateOrganization("\'Test")
|
||||||
}
|
}
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("=Test")
|
LegalNameValidator.validateOrganization("=Test")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `unicode range`() {
|
fun `unicode range`() {
|
||||||
LegalNameValidator.validateLegalName("Test A")
|
LegalNameValidator.validateOrganization("Test A")
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
// Greek letter A.
|
// Greek letter A.
|
||||||
LegalNameValidator.validateLegalName("Test Α")
|
LegalNameValidator.validateOrganization("Test Α")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,37 +66,37 @@ class LegalNameValidatorTest {
|
|||||||
while (longLegalName.length < 255) {
|
while (longLegalName.length < 255) {
|
||||||
longLegalName.append("A")
|
longLegalName.append("A")
|
||||||
}
|
}
|
||||||
LegalNameValidator.validateLegalName(longLegalName.toString())
|
LegalNameValidator.validateOrganization(longLegalName.toString())
|
||||||
|
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName(longLegalName.append("A").toString())
|
LegalNameValidator.validateOrganization(longLegalName.append("A").toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `legal name should be capitalized`() {
|
fun `legal name should be capitalized`() {
|
||||||
LegalNameValidator.validateLegalName("Good legal name")
|
LegalNameValidator.validateOrganization("Good legal name")
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("bad name")
|
LegalNameValidator.validateOrganization("bad name")
|
||||||
}
|
}
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("bad Name")
|
LegalNameValidator.validateOrganization("bad Name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `correctly handle whitespaces`() {
|
fun `correctly handle whitespaces`() {
|
||||||
assertEquals("Legal Name With Tab", LegalNameValidator.normaliseLegalName("Legal Name With\tTab"))
|
assertEquals("Legal Name With Tab", LegalNameValidator.normalize("Legal Name With\tTab"))
|
||||||
assertEquals("Legal Name With Unicode Whitespaces", LegalNameValidator.normaliseLegalName("Legal Name\u2004With\u0009Unicode\u0020Whitespaces"))
|
assertEquals("Legal Name With Unicode Whitespaces", LegalNameValidator.normalize("Legal Name\u2004With\u0009Unicode\u0020Whitespaces"))
|
||||||
assertEquals("Legal Name With Line Breaks", LegalNameValidator.normaliseLegalName("Legal Name With\n\rLine\nBreaks"))
|
assertEquals("Legal Name With Line Breaks", LegalNameValidator.normalize("Legal Name With\n\rLine\nBreaks"))
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("Legal Name With\tTab")
|
LegalNameValidator.validateOrganization("Legal Name With\tTab")
|
||||||
}
|
}
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("Legal Name\u2004With\u0009Unicode\u0020Whitespaces")
|
LegalNameValidator.validateOrganization("Legal Name\u2004With\u0009Unicode\u0020Whitespaces")
|
||||||
}
|
}
|
||||||
assertFailsWith(IllegalArgumentException::class) {
|
assertFailsWith(IllegalArgumentException::class) {
|
||||||
LegalNameValidator.validateLegalName("Legal Name With\n\rLine\nBreaks")
|
LegalNameValidator.validateOrganization("Legal Name With\n\rLine\nBreaks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -49,8 +49,17 @@ the minimum supported set for X.509 certificates (specified in RFC 3280), plus t
|
|||||||
* common name (CN) - used only for service identities
|
* common name (CN) - used only for service identities
|
||||||
|
|
||||||
The organisation, locality and country attributes are required, while state, organisational-unit and common name are
|
The organisation, locality and country attributes are required, while state, organisational-unit and common name are
|
||||||
optional. Attributes cannot be be present more than once in the name. The "country" code is strictly restricted to valid
|
optional. Attributes cannot be be present more than once in the name.
|
||||||
ISO 3166-1 two letter codes.
|
|
||||||
|
All of these attributes have the following set of constraints applied for security reasons:
|
||||||
|
|
||||||
|
- No blacklisted words (currently "node" and "server").
|
||||||
|
- Restrict names to Latin scripts for now to avoid right-to-left issues, debugging issues when we can't pronounce names over the phone, and character confusability attacks.
|
||||||
|
- No commas or equals signs.
|
||||||
|
- No dollars or quote marks.
|
||||||
|
|
||||||
|
Additionally the "organisation" attribute must consist of at least three letters and starting with a capital letter,
|
||||||
|
and "country code" is strictly restricted to valid ISO 3166-1 two letter codes.
|
||||||
|
|
||||||
Certificates
|
Certificates
|
||||||
------------
|
------------
|
||||||
|
@ -207,11 +207,11 @@ class NodeTabView : Fragment() {
|
|||||||
validator {
|
validator {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
error("Node name is required")
|
error("Node name is required")
|
||||||
} else if (nodeController.nameExists(LegalNameValidator.normaliseLegalName(it))) {
|
} else if (nodeController.nameExists(LegalNameValidator.normalize(it))) {
|
||||||
error("Node with this name already exists")
|
error("Node with this name already exists")
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
LegalNameValidator.validateLegalName(LegalNameValidator.normaliseLegalName(it))
|
LegalNameValidator.validateOrganization(LegalNameValidator.normalize(it))
|
||||||
null
|
null
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
error(e.message)
|
error(e.message)
|
||||||
|
Loading…
Reference in New Issue
Block a user