mirror of
https://github.com/corda/corda.git
synced 2025-02-18 16:40:55 +00:00
legal name validator for doorman node registration (#532)
This commit is contained in:
parent
9425b7c927
commit
87dd99d968
@ -0,0 +1,101 @@
|
||||
@file:JvmName("LegalNameValidator")
|
||||
|
||||
package net.corda.core.utilities
|
||||
|
||||
import java.lang.Character.UnicodeScript.*
|
||||
import java.text.Normalizer
|
||||
import java.util.regex.Pattern
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
/**
|
||||
* The validation function will validate the input string using 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.
|
||||
* - Should start with a capital letter.
|
||||
* - 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.
|
||||
*/
|
||||
fun validateLegalName(normalizedLegalName: String) {
|
||||
rules.forEach { it.validate(normalizedLegalName) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun normaliseLegalName(legalName: String): String {
|
||||
val trimmedLegalName = legalName.trim().replace(Regex("\\s+"), " ")
|
||||
return Normalizer.normalize(trimmedLegalName, Normalizer.Form.NFKC)
|
||||
}
|
||||
|
||||
private val rules: List<Rule<String>> = listOf(
|
||||
UnicodeNormalizationRule(),
|
||||
CharacterRule(',', '=', '$', '"', '\'', '\\'),
|
||||
WordRule("node", "server"),
|
||||
LengthRule(maxLength = 255),
|
||||
// TODO: Implement confusable character detection if we add more scripts.
|
||||
UnicodeRangeRule(LATIN, COMMON, INHERITED),
|
||||
CapitalLetterRule(),
|
||||
X500NameRule()
|
||||
)
|
||||
|
||||
private class UnicodeNormalizationRule : Rule<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." }
|
||||
}
|
||||
}
|
||||
|
||||
private class UnicodeRangeRule(vararg supportScripts: Character.UnicodeScript) : Rule<String> {
|
||||
private val pattern = supportScripts.map { "\\p{Is$it}" }.joinToString(separator = "", prefix = "[", postfix = "]*").let { Pattern.compile(it) }
|
||||
|
||||
override fun validate(legalName: String) {
|
||||
require(pattern.matcher(legalName).matches()) {
|
||||
val illegalChars = legalName.replace(pattern.toRegex(), "").toSet()
|
||||
if (illegalChars.size > 1) {
|
||||
"Illegal characters $illegalChars in \"$legalName\"."
|
||||
} else {
|
||||
"Illegal character $illegalChars in \"$legalName\"."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CharacterRule(vararg val bannedChars: Char) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
bannedChars.forEach {
|
||||
require(!legalName.contains(it, true)) { "Illegal character: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class WordRule(vararg val bannedWords: String) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
bannedWords.forEach {
|
||||
require(!legalName.contains(it, ignoreCase = true)) { "Illegal word: $it" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class LengthRule(val maxLength: Int) : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
require(legalName.length <= maxLength) { "Legal name longer then $maxLength characters." }
|
||||
}
|
||||
}
|
||||
|
||||
private class CapitalLetterRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
val capitalizedLegalName = legalName.split(" ").map(String::capitalize).joinToString(" ")
|
||||
require(legalName == capitalizedLegalName) { "Legal name should be capitalized. i.e. '$capitalizedLegalName'" }
|
||||
}
|
||||
}
|
||||
|
||||
private class X500NameRule : Rule<String> {
|
||||
override fun validate(legalName: String) {
|
||||
// This will throw IllegalArgumentException if the name does not comply with X500 name format.
|
||||
X500Principal("CN=$legalName")
|
||||
}
|
||||
}
|
||||
|
||||
private interface Rule<in T> {
|
||||
fun validate(legalName: T)
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package net.corda.core.utilities
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class LegalNameValidatorTest {
|
||||
@Test
|
||||
fun `no double spaces`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("Test Legal Name")
|
||||
}
|
||||
validateLegalName(normaliseLegalName("Test Legal Name"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no trailing white space`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("Test ")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no prefixed white space`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName(" Test")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `blacklisted words`() {
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("Test Server")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `blacklisted characters`() {
|
||||
validateLegalName("Test")
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("\$Test")
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("\"Test")
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("\'Test")
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("=Test")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unicode range`() {
|
||||
validateLegalName("Test A")
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
// Greek letter A.
|
||||
validateLegalName("Test Α")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `legal name length less then 256 characters`() {
|
||||
val longLegalName = StringBuilder()
|
||||
while (longLegalName.length < 255) {
|
||||
longLegalName.append("A")
|
||||
}
|
||||
validateLegalName(longLegalName.toString())
|
||||
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName(longLegalName.append("A").toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `legal name should be capitalized`() {
|
||||
validateLegalName("Good Legal Name")
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("bad name")
|
||||
}
|
||||
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("Bad name")
|
||||
}
|
||||
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("bad Name")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `correctly handle whitespaces`() {
|
||||
assertEquals("Legal Name With Tab", normaliseLegalName("Legal Name With\tTab"))
|
||||
assertEquals("Legal Name With Unicode Whitespaces", normaliseLegalName("Legal Name\u2004With\u0009Unicode\u0020Whitespaces"))
|
||||
assertEquals("Legal Name With Line Breaks", normaliseLegalName("Legal Name With\n\rLine\nBreaks"))
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("Legal Name With\tTab")
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("Legal Name\u2004With\u0009Unicode\u0020Whitespaces")
|
||||
}
|
||||
assertFailsWith(IllegalArgumentException::class) {
|
||||
validateLegalName("Legal Name With\n\rLine\nBreaks")
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ The following information from the node configuration file is needed to generate
|
||||
|
||||
:myLegalName: Your company's legal name. e.g. "Mega Corp LLC". This needs to be unique on the network. If another node
|
||||
has already been permissioned with this name then the permissioning server will automatically reject the request. The
|
||||
request will also be rejected if the name contains a ``=`` or ``,``.
|
||||
request will also be rejected if it violates legal name rules, see `Legal Name Constraints`_ for more information.
|
||||
|
||||
.. note:: In a future version the uniqueness requirement will be relaxed to a X.500 name. This will allow differentiation
|
||||
between entities with the same name.
|
||||
@ -38,6 +38,32 @@ Once the request has been approved and the certificates downloaded from the serv
|
||||
|
||||
This process only is needed when the node connects to the network for the first time, or when the certificate expires.
|
||||
|
||||
Legal Name Constraints
|
||||
----------------------
|
||||
The legal name is the unique identifier in the Corda network, so constraints have been set out to prevent encoding attacks and visual spoofing.
|
||||
|
||||
The legal name validator (see ``LegalNameValidator.kt``) is used to enforce rules on Corda's legal names, it is intended to be used by the network operator and Corda node during the node registration process.
|
||||
It has two functions, a function to normalize legal names, and a function to validate legal names.
|
||||
|
||||
The normalize function performs the following transformations:
|
||||
|
||||
* Remove leading and trailing whitespaces.
|
||||
|
||||
* Replace multiple whitespaces with a single space.
|
||||
|
||||
* Normalize the string according to `NFKC normalization form <https://en.wikipedia.org/wiki/Unicode_equivalence#Normalization>`_.
|
||||
|
||||
The validation function will validate the input string using 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.
|
||||
|
||||
* Should start with a capital letter.
|
||||
|
||||
* No commas or equals signs.
|
||||
|
||||
* No dollars or quote marks, although we may relax the quote mark constraint in future to handle Irish company names.
|
||||
|
||||
Starting the Registration
|
||||
-------------------------
|
||||
|
Loading…
x
Reference in New Issue
Block a user