Skip to content

Commit d676787

Browse files
add validatedCopy method in GeneralName interface for introducing validation for generalNames without one
1 parent 09eecc5 commit d676787

File tree

10 files changed

+193
-190
lines changed

10 files changed

+193
-190
lines changed

indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/generalNames/DNSName.kt

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -91,38 +91,39 @@ data class DNSName internal constructor(
9191
}
9292

9393
override fun constrains(input: GeneralNameOption?): GeneralNameOption.ConstraintResult {
94-
if (!isValid || input?.isValid == false) throw Asn1Exception("Invalid DNSName")
95-
if (input !is DNSName) {
96-
return GeneralNameOption.ConstraintResult.DIFF_TYPE
97-
}
98-
99-
val thisName = value.value.lowercase()
100-
val inputName = input.value.value.lowercase()
101-
102-
if (thisName == inputName) {
103-
return GeneralNameOption.ConstraintResult.MATCH
104-
}
94+
return try {
95+
super.constrains(input)
96+
} catch (_: UnsupportedOperationException) {
97+
val thisName = value.value.lowercase()
98+
val inputName = (input as DNSName).value.value.lowercase()
99+
100+
when {
101+
thisName == inputName -> GeneralNameOption.ConstraintResult.MATCH
102+
103+
thisName.endsWith(inputName) -> {
104+
val index = thisName.lastIndexOf(inputName)
105+
val charBefore = thisName.getOrNull(index - 1)
106+
val inputStartsWithDot = inputName.startsWith('.')
107+
if ((charBefore == '.' && !inputStartsWithDot) || (charBefore != '.' && inputStartsWithDot)) {
108+
GeneralNameOption.ConstraintResult.NARROWS
109+
} else {
110+
GeneralNameOption.ConstraintResult.SAME_TYPE
111+
}
112+
}
105113

106-
if (thisName.endsWith(inputName)) {
107-
val index = thisName.lastIndexOf(inputName)
108-
val charBefore = thisName.getOrNull(index - 1)
109-
val inputStartsWithDot = inputName.startsWith('.')
110-
if ((charBefore == '.' && !inputStartsWithDot) || (charBefore != '.' && inputStartsWithDot)) {
111-
return GeneralNameOption.ConstraintResult.NARROWS
112-
}
113-
return GeneralNameOption.ConstraintResult.SAME_TYPE
114-
}
114+
inputName.endsWith(thisName) -> {
115+
val index = inputName.lastIndexOf(thisName)
116+
val charBefore = inputName.getOrNull(index - 1)
117+
val thisStartsWithDot = thisName.startsWith('.')
118+
if ((charBefore == '.' && !thisStartsWithDot) || (charBefore != '.' && thisStartsWithDot)) {
119+
GeneralNameOption.ConstraintResult.WIDENS
120+
} else {
121+
GeneralNameOption.ConstraintResult.SAME_TYPE
122+
}
123+
}
115124

116-
if (inputName.endsWith(thisName)) {
117-
val index = inputName.lastIndexOf(thisName)
118-
val charBefore = inputName.getOrNull(index - 1)
119-
val thisStartsWithDot = thisName.startsWith('.')
120-
if ((charBefore == '.' && !thisStartsWithDot) || (charBefore != '.' && thisStartsWithDot)) {
121-
return GeneralNameOption.ConstraintResult.WIDENS
125+
else -> GeneralNameOption.ConstraintResult.SAME_TYPE
122126
}
123-
return GeneralNameOption.ConstraintResult.SAME_TYPE
124127
}
125-
126-
return GeneralNameOption.ConstraintResult.SAME_TYPE
127128
}
128129
}

indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/generalNames/EDIPartyName.kt

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,22 @@ import at.asitplus.signum.indispensable.asn1.Asn1Decodable
44
import at.asitplus.signum.indispensable.asn1.Asn1Element
55
import at.asitplus.signum.indispensable.asn1.Asn1Encodable
66
import at.asitplus.signum.indispensable.asn1.Asn1ExplicitlyTagged
7+
import at.asitplus.signum.indispensable.asn1.Asn1String
78
import at.asitplus.signum.indispensable.asn1.Asn1StructuralException
89

9-
data class EDIPartyName (
10+
data class EDIPartyName internal constructor(
1011
val value: Asn1ExplicitlyTagged,
1112
override val performValidation: Boolean = false,
13+
override val isValid: Boolean? = null,
1214
override val type: GeneralNameOption.NameType = GeneralNameOption.NameType.OTHER
1315
): GeneralNameOption, Asn1Encodable<Asn1Element> {
1416

15-
/**
16-
* Always `null`, since no validation logic is implemented
17-
*/
18-
override val isValid: Boolean? = null
17+
constructor(value: Asn1ExplicitlyTagged) : this(value, false)
1918

2019
override fun encodeToTlv() = value
2120

22-
companion object : Asn1Decodable<Asn1Element, OtherName> {
23-
override fun doDecode(src: Asn1Element): OtherName {
21+
companion object : Asn1Decodable<Asn1Element, EDIPartyName> {
22+
override fun doDecode(src: Asn1Element): EDIPartyName {
2423
if (src !is Asn1ExplicitlyTagged) throw Asn1StructuralException("Invalid ediPartyName Alternative Name found: ${src.toDerHexString()}")
2524

2625
src.also { it ->
@@ -29,20 +28,15 @@ data class EDIPartyName (
2928
"Invalid partyName Alternative Name found (illegal implicit tag): ${it.toDerHexString()}"
3029
)
3130
}
32-
return OtherName(src)
31+
return EDIPartyName(src)
3332
}
3433
}
3534

3635
override fun toString(): String {
3736
return value.prettyPrint()
3837
}
3938

40-
41-
override fun constrains(input: GeneralNameOption?): GeneralNameOption.ConstraintResult {
42-
if (input !is EDIPartyName) {
43-
return GeneralNameOption.ConstraintResult.DIFF_TYPE
44-
} else {
45-
throw UnsupportedOperationException("Narrows, widens and match are not yet implemented for EDIPartyName.")
46-
}
39+
override fun validatedCopy(checkIsValid: (GeneralNameOption) -> Boolean): EDIPartyName {
40+
return EDIPartyName(value, true, checkIsValid(this))
4741
}
4842
}

indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/generalNames/GeneralName.kt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,36 @@ sealed interface GeneralNameOption {
4040

4141
val type: NameType
4242

43-
fun constrains(input: GeneralNameOption?): ConstraintResult
43+
fun constrains(input: GeneralNameOption?): ConstraintResult {
44+
when {
45+
input == null || this::class != input::class -> return ConstraintResult.DIFF_TYPE
46+
47+
isValid == null || input.isValid == null ->
48+
throw IllegalArgumentException(
49+
"Validation for ${this::class.simpleName} has not been performed. " +
50+
"Use ${this::class.simpleName}.validatedCopy { /* validation lambda */ } to set isValid before calling constrains."
51+
)
52+
53+
!isValid!! || !input.isValid!! -> {
54+
throw Asn1Exception("Invalid ${this::class.simpleName}")
55+
}
56+
57+
else -> throw UnsupportedOperationException(
58+
"Narrows, widens and match are not yet implemented for ${this::class.simpleName}."
59+
)
60+
}
61+
}
62+
63+
/**
64+
* Returns a copy of this GeneralNameOption with the `isValid` property set
65+
* according to the [checkIsValid] lambda.
66+
*
67+
* Intended for subclasses that do not implement validation (`isValid == null`)
68+
* and allows marking them as valid or invalid before performing constraint checks.
69+
* */
70+
fun validatedCopy(checkIsValid: (GeneralNameOption) -> Boolean) : GeneralNameOption {
71+
throw IllegalArgumentException()
72+
}
4473
}
4574

4675
data class GeneralName(
@@ -76,6 +105,7 @@ data class GeneralName(
76105
src.asExplicitlyTagged().children.first().asSequence()
77106
)
78107
)
108+
79109
else -> throw Asn1Exception("Unsupported GeneralName tag")
80110
}
81111
}

indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/generalNames/IPAddressName.kt

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -112,42 +112,43 @@ data class IPAddressName internal constructor(
112112
}
113113

114114
override fun constrains(input: GeneralNameOption?): GeneralNameOption.ConstraintResult {
115-
if (!isValid || input?.isValid == false) throw Asn1Exception("Invalid IpAddressName")
116-
if (input !is IPAddressName) return GeneralNameOption.ConstraintResult.DIFF_TYPE
117-
if (this == input) return GeneralNameOption.ConstraintResult.MATCH
115+
return try {
116+
super.constrains(input)
117+
} catch (_: UnsupportedOperationException) {
118+
when {
119+
this == input as IPAddressName -> GeneralNameOption.ConstraintResult.MATCH
120+
121+
network == null && input.network == null &&
122+
((address!!.isV4() && input.address!!.isV4()) || (address!!.isV6() && input.address!!.isV6())) ->
123+
GeneralNameOption.ConstraintResult.SAME_TYPE
124+
125+
network != null && input.network != null -> {
126+
val thisNet = network as IpNetwork<Number, Any>
127+
val otherNet = input.network as IpNetwork<Number, Any>
128+
when {
129+
thisNet == otherNet -> GeneralNameOption.ConstraintResult.MATCH
130+
thisNet.contains(otherNet) -> GeneralNameOption.ConstraintResult.WIDENS
131+
otherNet.contains(thisNet) -> GeneralNameOption.ConstraintResult.NARROWS
132+
else -> GeneralNameOption.ConstraintResult.SAME_TYPE
133+
}
134+
}
118135

119-
if (network == null && input.network == null && ((address!!.isV4() && input.address!!.isV4()) || (address.isV6() && input.address!!.isV6()))) {
120-
return GeneralNameOption.ConstraintResult.SAME_TYPE
121-
}
136+
network != null -> {
137+
val thisNet = network as IpNetwork<Number, Any>
138+
val otherAddress = input.address as IpAddress<Number, Any>
139+
if (thisNet.contains(otherAddress)) GeneralNameOption.ConstraintResult.WIDENS
140+
else GeneralNameOption.ConstraintResult.SAME_TYPE
141+
}
142+
143+
input.network != null -> {
144+
val thisAddress = address as IpAddress<Number, Any>
145+
val otherNet = input.network as IpNetwork<Number, Any>
146+
if (otherNet.contains(thisAddress)) GeneralNameOption.ConstraintResult.NARROWS
147+
else GeneralNameOption.ConstraintResult.SAME_TYPE
148+
}
122149

123-
// Subnet vs Subnet
124-
if (network != null && input.network != null) {
125-
val thisNet = network as IpNetwork<Number, Any>
126-
val otherNet = input.network as IpNetwork<Number, Any>
127-
when {
128-
thisNet == otherNet -> GeneralNameOption.ConstraintResult.MATCH
129-
thisNet.contains(otherNet) -> GeneralNameOption.ConstraintResult.WIDENS
130-
otherNet.contains(thisNet) -> GeneralNameOption.ConstraintResult.NARROWS
131150
else -> GeneralNameOption.ConstraintResult.SAME_TYPE
132151
}
133152
}
134-
135-
// Other is subnet, this is host
136-
if (network != null) {
137-
val thisNet = network as IpNetwork<Number, Any>
138-
val otherAddress = input.address as IpAddress<Number, Any>
139-
return if (thisNet.contains(otherAddress)) GeneralNameOption.ConstraintResult.WIDENS
140-
else GeneralNameOption.ConstraintResult.SAME_TYPE
141-
}
142-
143-
// Other is subnet, this is host
144-
if (input.network != null) {
145-
val thisAddress = address as IpAddress<Number, Any>
146-
val otherNet = input.network as IpNetwork<Number, Any>
147-
return if (otherNet.contains(thisAddress)) GeneralNameOption.ConstraintResult.NARROWS
148-
else GeneralNameOption.ConstraintResult.SAME_TYPE
149-
}
150-
151-
return GeneralNameOption.ConstraintResult.SAME_TYPE
152153
}
153154
}

indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/generalNames/OtherName.kt

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@ import at.asitplus.signum.indispensable.asn1.Asn1Encodable
66
import at.asitplus.signum.indispensable.asn1.Asn1ExplicitlyTagged
77
import at.asitplus.signum.indispensable.asn1.Asn1StructuralException
88

9-
data class OtherName (
9+
data class OtherName internal constructor(
1010
val value: Asn1ExplicitlyTagged,
1111
override val performValidation: Boolean = false,
12+
override val isValid: Boolean? = null,
1213
override val type: GeneralNameOption.NameType = GeneralNameOption.NameType.OTHER
1314
): GeneralNameOption, Asn1Encodable<Asn1Element> {
1415

15-
/**
16-
* Always `null`, since no validation logic is implemented
17-
*/
18-
override val isValid: Boolean? = null
16+
constructor(value: Asn1ExplicitlyTagged) : this(value, false)
1917

2018
override fun encodeToTlv() = value
2119

@@ -32,11 +30,7 @@ data class OtherName (
3230
return value.prettyPrint()
3331
}
3432

35-
override fun constrains(input: GeneralNameOption?): GeneralNameOption.ConstraintResult {
36-
if (input !is X400AddressName) {
37-
return GeneralNameOption.ConstraintResult.DIFF_TYPE
38-
} else {
39-
throw UnsupportedOperationException("Narrows, widens and match are not yet implemented for OtherName.")
40-
}
33+
override fun validatedCopy(checkIsValid: (GeneralNameOption) -> Boolean): OtherName {
34+
return OtherName(value, true, checkIsValid(this))
4135
}
4236
}

indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/generalNames/RFC822Name.kt

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,39 +44,37 @@ data class RFC822Name internal constructor(
4444
}
4545

4646
override fun constrains(input: GeneralNameOption?): GeneralNameOption.ConstraintResult {
47-
if (!isValid || input?.isValid == false) throw Asn1Exception("Invalid RFC822Name")
48-
if (input !is RFC822Name) {
49-
return GeneralNameOption.ConstraintResult.DIFF_TYPE
50-
}
51-
52-
val thisName = value.value.lowercase()
53-
val inputName = input.value.value.lowercase()
54-
55-
if (thisName == inputName) {
56-
return GeneralNameOption.ConstraintResult.MATCH
57-
}
58-
59-
fun isEmailLike(name: String) = '@' in name
60-
fun hasDomainPrefix(name: String) = name.startsWith(".")
47+
return try {
48+
super.constrains(input)
49+
} catch (_: UnsupportedOperationException) {
50+
val thisName = value.value.lowercase()
51+
val inputName = (input as RFC822Name).value.value.lowercase()
52+
fun isEmailLike(name: String) = '@' in name
53+
fun hasDomainPrefix(name: String) = name.startsWith(".")
54+
55+
when {
56+
thisName == inputName -> GeneralNameOption.ConstraintResult.MATCH
57+
58+
thisName.endsWith(inputName) -> {
59+
when {
60+
isEmailLike(inputName) -> GeneralNameOption.ConstraintResult.SAME_TYPE
61+
hasDomainPrefix(inputName) -> GeneralNameOption.ConstraintResult.NARROWS
62+
thisName.getOrNull(thisName.lastIndexOf(inputName) - 1) == '@' -> GeneralNameOption.ConstraintResult.NARROWS
63+
else -> GeneralNameOption.ConstraintResult.SAME_TYPE
64+
}
65+
}
66+
67+
inputName.endsWith(thisName) -> {
68+
when {
69+
isEmailLike(thisName) -> GeneralNameOption.ConstraintResult.SAME_TYPE
70+
hasDomainPrefix(thisName) -> GeneralNameOption.ConstraintResult.WIDENS
71+
inputName.getOrNull(inputName.lastIndexOf(thisName) - 1) == '@' -> GeneralNameOption.ConstraintResult.WIDENS
72+
else -> GeneralNameOption.ConstraintResult.SAME_TYPE
73+
}
74+
}
6175

62-
if (thisName.endsWith(inputName)) {
63-
return when {
64-
isEmailLike(inputName) -> GeneralNameOption.ConstraintResult.SAME_TYPE
65-
hasDomainPrefix(inputName) -> GeneralNameOption.ConstraintResult.NARROWS
66-
thisName.getOrNull(thisName.lastIndexOf(inputName) - 1) == '@' -> GeneralNameOption.ConstraintResult.NARROWS
6776
else -> GeneralNameOption.ConstraintResult.SAME_TYPE
6877
}
6978
}
70-
71-
if (inputName.endsWith(thisName)) {
72-
return when {
73-
isEmailLike(thisName) -> GeneralNameOption.ConstraintResult.SAME_TYPE
74-
hasDomainPrefix(thisName) -> GeneralNameOption.ConstraintResult.WIDENS
75-
inputName.getOrNull(inputName.lastIndexOf(thisName) - 1) == '@' -> GeneralNameOption.ConstraintResult.WIDENS
76-
else -> GeneralNameOption.ConstraintResult.SAME_TYPE
77-
}
78-
}
79-
80-
return GeneralNameOption.ConstraintResult.SAME_TYPE
8179
}
8280
}

indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/generalNames/RegisteredIDName.kt

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import at.asitplus.signum.indispensable.asn1.Asn1Encodable
55
import at.asitplus.signum.indispensable.asn1.Asn1Primitive
66
import at.asitplus.signum.indispensable.asn1.ObjectIdentifier
77

8-
data class RegisteredIDName (
8+
data class RegisteredIDName internal constructor(
99
val value: ObjectIdentifier,
1010
override val performValidation: Boolean = false,
1111
override val type: GeneralNameOption.NameType = GeneralNameOption.NameType.OID
1212
): GeneralNameOption, Asn1Encodable<Asn1Primitive> {
13+
14+
constructor(value: ObjectIdentifier) : this(value, false)
15+
1316
override fun encodeToTlv() = value.encodeToTlv()
1417

1518
/**
@@ -27,12 +30,4 @@ data class RegisteredIDName (
2730
override fun toString(): String {
2831
return value.toString()
2932
}
30-
31-
override fun constrains(input: GeneralNameOption?): GeneralNameOption.ConstraintResult {
32-
if (input !is RegisteredIDName) {
33-
return GeneralNameOption.ConstraintResult.DIFF_TYPE
34-
} else {
35-
throw UnsupportedOperationException("Narrows, widens and match are not yet implemented for RegisteredIDName.")
36-
}
37-
}
3833
}

0 commit comments

Comments
 (0)