diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dd41fa9..05d72dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * `Iterator.decodeAsn1VarUInt()` * `Iterable.decodeAsn1VarUInt()` * `ByteArray.decodeAsn1VarUInt()` +* Revamp implicit tagging ## 3.0 diff --git a/README.md b/README.md index 9dd81d9e..495fcde6 100644 --- a/README.md +++ b/README.md @@ -412,7 +412,7 @@ allows for #### Decoding Values -Various helper functions exist to facilitate decoging the values contained in `Asn1Primitives`, such as `decodeInt()`, +Various helper functions exist to facilitate decoding the values contained in `Asn1Primitives`, such as `decodeInt()`, for example. However, anything can be decoded and tagged at will. Therefore, a generic decoding function exists, which has the following signature: @@ -432,72 +432,77 @@ DSL, which returns an `Asn1Structure`: ```kotlin Asn1.Sequence { - +Tagged(1uL) { - +Asn1Primitive(Asn1Element.Tag.BOOL, byteArrayOf(0x00)) //or +Asn1.Bool(false) - } - +Asn1.Set { - +Asn1.Sequence { - +Asn1.SetOf { - +PrintableString("World") - +PrintableString("Hello") - } - +Asn1.Set { - +PrintableString("World") - +PrintableString("Hello") - +Utf8String("!!!") - } - - } + +ExplicitlyTagged(1uL) { + +Asn1Primitive(Asn1Element.Tag.BOOL, byteArrayOf(0x00)) //or +Asn1.Bool(false) + } + +Asn1.Set { + +Asn1.Sequence { + +Asn1.SetOf { + +PrintableString("World") + +PrintableString("Hello") + } + +Asn1.Set { + +PrintableString("World") + +PrintableString("Hello") + +Utf8String("!!!") + } + } - +Asn1.Null() + } + +Asn1.Null() - +ObjectIdentifier("1.2.603.624.97") + +ObjectIdentifier("1.2.603.624.97") - +Utf8String("Foo") - +PrintableString("Bar") + +(Utf8String("Foo") withImplicitTag (0xCAFEuL withClass TagClass.PRIVATE)) + +PrintableString("Bar") - +Asn1.Set { - +Asn1.Int(3) - +Asn1.Long(-65789876543L) - +Asn1.Bool(false) - +Asn1.Bool(true) - } - +Asn1.Sequence { - +Asn1.Null() - +Asn1String.Numeric("12345") - +UtcTime(Clock.System.now()) - } -} + //fake Primitive + +(Asn1.Sequence { +Asn1.Int(42) } withImplicitTag (0x5EUL without CONSTRUCTED)) + + +Asn1.Set { + +Asn1.Int(3) + +Asn1.Int(-65789876543L) + +Asn1.Bool(false) + +Asn1.Bool(true) + } + +Asn1.Sequence { + +Asn1.Null() + +Asn1String.Numeric("12345") + +UtcTime(Clock.System.now()) + } +} withImplicitTag (1337uL withClass TagClass.APPLICATION) ``` In accordance with DER-Encoding, this produces the following ASN.1 structure: ``` -SEQUENCE (8 elem) - [1] (1 elem) - BOOLEAN false - SET (1 elem) - SEQUENCE (2 elem) - SET (2 elem) - PrintableString Hello - PrintableString World - SET (3 elem) - UTF8String !!! - PrintableString World - PrintableString Hello - NULL - OBJECT IDENTIFIER 1.2.603.624.97 - UTF8String Foo - PrintableString Bar - SET (4 elem) - BOOLEAN false - BOOLEAN true - INTEGER 3 - INTEGER (36 bit) -65789876543 - SEQUENCE (3 elem) +Application 1337 (9 elem) + + [1] (1 elem) + BOOLEAN false + SET (1 elem) + SEQUENCE (2 elem) + SET (2 elem) + PrintableString World + PrintableString Hello + SET (3 elem) + UTF8String !!! + PrintableString World + PrintableString Hello NULL - NumericString 12345 - UTCTime 2023-10-21 21:14:49 UTC + OBJECT IDENTIFIER 1.2.603.624.97 + Private 51966 (3 byte) Foo + PrintableString Bar + [94] (3 byte) 02012A + SET (4 elem) + BOOLEAN false + BOOLEAN true + INTEGER 3 + INTEGER (36 bit) -65789876543 + SEQUENCE (3 elem) + NULL + NumericString 12345 + UTCTime 2024-09-16 11:53:51 UTC ``` ## Limitations diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoPublicKey.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoPublicKey.kt index 97587062..5fff0e63 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoPublicKey.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoPublicKey.kt @@ -104,7 +104,7 @@ sealed class CryptoPublicKey : Asn1Encodable, Identifiable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence): CryptoPublicKey = runRethrowing { + override fun doDecode(src: Asn1Sequence): CryptoPublicKey = runRethrowing { if (src.children.size != 2) throw Asn1StructuralException("Invalid SPKI Structure!") val keyInfo = src.nextChild() as Asn1Sequence if (keyInfo.children.size != 2) throw Asn1StructuralException("Superfluous data in SPKI!") @@ -160,6 +160,7 @@ sealed class CryptoPublicKey : Asn1Encodable, Identifiable { (BERTags.SEQUENCE or BERTags.CONSTRUCTED) -> Rsa.fromPKCS1encoded(it) else -> throw IllegalArgumentException("Unsupported Key type") } + } /** diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt index a65513ed..6c464865 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/CryptoSignature.kt @@ -212,7 +212,7 @@ sealed interface CryptoSignature : Asn1Encodable { decodeFromDer(src.readBitString().rawBytes) } - override fun decodeFromTlv(src: Asn1Element): EC.IndefiniteLength { + override fun doDecode(src: Asn1Element): EC.IndefiniteLength { src as Asn1Sequence val r = (src.nextChild() as Asn1Primitive).readBigInteger() val s = (src.nextChild() as Asn1Primitive).readBigInteger() @@ -222,6 +222,7 @@ sealed interface CryptoSignature : Asn1Encodable { @Deprecated("use fromRawBytes", ReplaceWith("CryptoSignature.EC.fromRawBytes(input)")) operator fun invoke(input: ByteArray): DefiniteLength = fromRawBytes(input) + } } @@ -256,13 +257,14 @@ sealed interface CryptoSignature : Asn1Encodable { companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Element): CryptoSignature = runRethrowing { + override fun doDecode(src: Asn1Element): CryptoSignature = runRethrowing { when (src.tag) { Asn1Element.Tag.BIT_STRING -> RSAorHMAC((src as Asn1Primitive).decode(Asn1Element.Tag.BIT_STRING) { it }) - Asn1Element.Tag.ASN1_SEQUENCE -> EC.decodeFromTlv(src as Asn1Sequence) + Asn1Element.Tag.SEQUENCE -> EC.decodeFromTlv(src as Asn1Sequence) else -> throw Asn1Exception("Unknown Signature Format") } } + } } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt index efd96212..04fbf775 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/X509SignatureAlgorithm.kt @@ -3,7 +3,7 @@ package at.asitplus.signum.indispensable import at.asitplus.catching import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.asn1.Asn1.Null -import at.asitplus.signum.indispensable.asn1.Asn1.Tagged +import at.asitplus.signum.indispensable.asn1.Asn1.ExplicitlyTagged import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.PrimitiveKind @@ -51,13 +51,13 @@ enum class X509SignatureAlgorithm( Asn1.Sequence { +oid +Asn1.Sequence { - +Tagged(0u) { + +ExplicitlyTagged(0u) { +Asn1.Sequence { +shaOid +Null() } } - +Tagged(1u) { + +ExplicitlyTagged(1u) { +Asn1.Sequence { +KnownOIDs.pkcs1_MGF +Asn1.Sequence { @@ -66,7 +66,7 @@ enum class X509SignatureAlgorithm( } } } - +Tagged(2u) { + +ExplicitlyTagged(2u) { +Asn1.Int(bits / 8) } } @@ -111,7 +111,7 @@ enum class X509SignatureAlgorithm( } @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence): X509SignatureAlgorithm = runRethrowing { + override fun doDecode(src: Asn1Sequence): X509SignatureAlgorithm = runRethrowing { when (val oid = (src.nextChild() as Asn1Primitive).readOid()) { ES512.oid, ES384.oid, ES256.oid -> fromOid(oid) @@ -131,14 +131,14 @@ enum class X509SignatureAlgorithm( @Throws(Asn1Exception::class) private fun parsePssParams(src: Asn1Sequence): X509SignatureAlgorithm = runRethrowing { val seq = src.nextChild() as Asn1Sequence - val first = (seq.nextChild() as Asn1Tagged).verifyTag(0u).single() as Asn1Sequence + val first = (seq.nextChild() as Asn1ExplicitlyTagged).verifyTag(0u).single() as Asn1Sequence val sigAlg = (first.nextChild() as Asn1Primitive).readOid() val tag = first.nextChild().tag if (tag != Asn1Element.Tag.NULL) throw Asn1TagMismatchException(Asn1Element.Tag.NULL, tag, "PSS Params not supported yet") - val second = (seq.nextChild() as Asn1Tagged).verifyTag(1u).single() as Asn1Sequence + val second = (seq.nextChild() as Asn1ExplicitlyTagged).verifyTag(1u).single() as Asn1Sequence val mgf = (second.nextChild() as Asn1Primitive).readOid() if (mgf != KnownOIDs.pkcs1_MGF) throw IllegalArgumentException("Illegal OID: $mgf") val inner = second.nextChild() as Asn1Sequence @@ -149,7 +149,7 @@ enum class X509SignatureAlgorithm( "PSS Params not supported yet" ) - val last = (seq.nextChild() as Asn1Tagged).verifyTag(2u).single() as Asn1Primitive + val last = (seq.nextChild() as Asn1ExplicitlyTagged).verifyTag(2u).single() as Asn1Primitive val saltLen = last.readInt() return sigAlg.let { @@ -162,6 +162,7 @@ enum class X509SignatureAlgorithm( } } } + } } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1BitString.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1BitString.kt index 28c43981..93f84444 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1BitString.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1BitString.kt @@ -18,8 +18,8 @@ class Asn1BitString private constructor( * The overall [Asn1Primitive.content] resulting from [encodeToTlv] is `byteArrayOf(numPaddingBits, *rawBytes)` */ val rawBytes: ByteArray, -) : - Asn1Encodable { + +) : Asn1Encodable { /** @@ -68,7 +68,7 @@ class Asn1BitString private constructor( return bitset } - companion object : Asn1TagVerifyingDecodable { + companion object : Asn1Decodable { private fun fromBitSet(bitSet: BitSet): Pair { val rawBytes = bitSet.bytes.map { var res = 0 @@ -81,20 +81,12 @@ class Asn1BitString private constructor( } @Throws(Asn1Exception::class) - private fun decode(src: Asn1Primitive, tagOverride: Asn1Element.Tag? = null): Asn1BitString { - val expected = tagOverride ?: Asn1Element.Tag.BIT_STRING - if (src.tag != expected) - throw Asn1TagMismatchException(expected, src.tag) + override fun doDecode(src: Asn1Primitive): Asn1BitString { if (src.length == 0) return Asn1BitString(0, byteArrayOf()) if (src.content.first() > 7) throw Asn1Exception("Number of padding bits < 7") return Asn1BitString(src.content[0], src.content.sliceArray(1.. { - val explicitTag = Asn1Element.Tag(tag, constructed = true, TagClass.CONTEXT_SPECIFIC) - if (this.tag != explicitTag) throw Asn1TagMismatchException(explicitTag, this.tag) - return this.children -} - -/** - * Exception-free version of [verifyTag] - */ -fun Asn1Tagged.verifyTagOrNull(tag: ULong) = catching { verifyTag(tag) }.getOrNull() - /** * Generic decoding function. Verifies that this [Asn1Primitive]'s tag matches [tag] diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Elements.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Elements.kt index e674ab07..b86a2d8d 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Elements.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Elements.kt @@ -1,6 +1,7 @@ package at.asitplus.signum.indispensable.asn1 import at.asitplus.catching +import at.asitplus.signum.indispensable.asn1.Asn1Element.Tag.Template.Companion.withClass import at.asitplus.signum.indispensable.io.ByteArrayBase64Serializer import io.matthewnelson.encoding.base16.Base16 import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray @@ -156,11 +157,11 @@ sealed class Asn1Element( fun asSet() = thisAs() /** - * Convenience function to cast this element to an [Asn1Tagged] + * Convenience function to cast this element to an [Asn1ExplicitlyTagged] * @throws Asn1StructuralException if this element is not an explicitly tagged structure */ @Throws(Asn1StructuralException::class) - fun asTagged() = thisAs() + fun asExplicitlyTagged() = thisAs() /** * Convenience function to cast this element to an [Asn1EncapsulatingOctetString] @@ -181,6 +182,53 @@ sealed class Asn1Element( (this as? T) ?: throw Asn1StructuralException("${this::class.simpleName} cannot be reinterpreted as ${T::class.simpleName}.") + + /** + * Creates a new implicitly tagged ASN.1 Element from this ASN.1 Element. + * NOTE: The [TagClass] of the provided [tag] will be used! If you want the result to have [TagClass.CONTEXT_SPECIFIC], + * use `element withImplicitTag (tag withClass TagClass.CONTEXT_SPECIFIC)`!. If a CONSTRUCTED Tag is applied to an ASN.1 Primitive, + * the CONSTRUCTED bit is overridden and set to zero. + */ + inline infix fun withImplicitTag(tag: Tag): Asn1Element = when (this) { + is Asn1Structure -> { + if (tag.isConstructed) Asn1CustomStructure( + children, + tag.tagValue, + tag.tagClass, + sortChildren = isSorted + ) else Asn1CustomStructure.asPrimitive(children, tag.tagValue, tag.tagClass) + } + is Asn1Primitive -> Asn1Primitive(tag without CONSTRUCTED, content) + } + + /** + * Creates a new implicitly tagged ASN.1 Element from this ASN.1 Element. + * Sets the class of the resulting structure to [TagClass.CONTEXT_SPECIFIC] + */ + inline infix fun withImplicitTag(tagValue: ULong) = withImplicitTag(tagValue withClass TagClass.CONTEXT_SPECIFIC) + + + /** + * Creates a new implicitly tagged ASN.1 Element from this ASN.1 Structure. + * If the provided [template]'s tagClass is not set, the class of the resulting structure defaults to [TagClass.CONTEXT_SPECIFIC]. + * If a CONSTRUCTED Tag is applied to an ASN.1 Primitive, the CONSTRUCTED bit is overridden and set to zero. + */ + inline infix fun withImplicitTag(template: Tag.Template) = when (this) { + is Asn1Structure -> (this as Asn1Structure).withImplicitTag( + Tag( + tagValue = template.tagValue, + tagClass = template.tagClass ?: TagClass.CONTEXT_SPECIFIC, + constructed = template.constructed ?: tag.isConstructed + ) + ) + + is Asn1Primitive -> Asn1Primitive( + Tag(template.tagValue, tagClass = template.tagClass ?: TagClass.CONTEXT_SPECIFIC, constructed = false), + content + ) + } + + @Serializable @ConsistentCopyVisibility data class Tag private constructor( @@ -217,7 +265,7 @@ sealed class Asn1Element( } val SET = Tag(tagValue = BERTags.SET.toULong(), constructed = true) - val ASN1_SEQUENCE = Tag(tagValue = BERTags.SEQUENCE.toULong(), constructed = true) + val SEQUENCE = Tag(tagValue = BERTags.SEQUENCE.toULong(), constructed = true) val NULL = Tag(tagValue = BERTags.ASN1_NULL.toULong(), constructed = false) val BOOL = Tag(tagValue = BERTags.BOOLEAN.toULong(), constructed = false) @@ -239,7 +287,6 @@ sealed class Asn1Element( val TIME_GENERALIZED = Tag(tagValue = BERTags.GENERALIZED_TIME.toULong(), constructed = false) val TIME_UTC = Tag(tagValue = BERTags.UTC_TIME.toULong(), constructed = false) - } val tagClass by lazy { @@ -248,9 +295,9 @@ sealed class Asn1Element( } } - val isConstructed by lazy { encodedTag.first().toUByte().isConstructed() } + val isConstructed get() = encodedTag.first().toUByte().isConstructed() - val isExplicitlyTagged by lazy { isConstructed && tagClass == TagClass.CONTEXT_SPECIFIC } + internal val isExplicitlyTagged get() = isConstructed && tagClass == TagClass.CONTEXT_SPECIFIC override fun toString(): String = "${tagClass.let { if (it == TagClass.UNIVERSAL) "" else it.name + " " }}${tagValue}${if (isConstructed) " CONSTRUCTED" else ""}" + @@ -287,6 +334,50 @@ sealed class Asn1Element( } override fun hashCode(): Int = encodedTag.contentHashCode() + + /** + * creates a new Tag from this object, overriding the class. Useful for implicitTagging (see [Asn1Structure.withImplicitTag]) + */ + infix fun withClass(tagClass: TagClass) = + Tag(this.tagValue, tagClass = tagClass, constructed = this.isConstructed) + + /** + * creates a new Tag from this object, negating the passed property. Useful for implicitTagging (see [Asn1Structure.withImplicitTag]). + * This is a NOOP for tag that don't have this bit set. + */ + infix fun without(negated: TagProperty): Tag = when (negated) { + CONSTRUCTED -> Tag(this.tagValue, tagClass = this.tagClass, constructed = false) + } + + /** + * A tag with optional tagClass and optional constructed indicator. Used for ASN.1 builder DSL + */ + class Template(val tagValue: ULong, val tagClass: TagClass?, val constructed: Boolean?) { + + /** + * Creates a new tag template from this template, negating the passed property + */ + inline infix fun without(negated: TagProperty) = when (negated) { + is CONSTRUCTED -> Template(this.tagValue, this.tagClass, false) + } + + companion object { + /** + * Convenience function to construct a tag template from an ULong tag value and class + */ + inline infix fun ULong.withClass(tagClass: TagClass) = + Template(tagValue = this, tagClass = tagClass, constructed = null) + + /** + * Convenience function to construct a tag from an ULong tag value and property + */ + inline infix fun ULong.without(negated: TagProperty) = when (negated) { + is CONSTRUCTED -> Template(tagValue = this, tagClass = null, constructed = false) + } + + } + + } } } @@ -306,8 +397,25 @@ object Asn1EncodableSerializer : KSerializer { /** * ASN.1 structure. Contains no data itself, but holds zero or more [children] */ -sealed class Asn1Structure(tag: Tag, children: List?) : - Asn1Element(TLV(tag, byteArrayOf()), children) { +sealed class Asn1Structure( + /** + * The tag identifying this structure + */ + tag: Tag, + + /** + * This structure's child elements + */ + children: List, + /** + * Whether this structure sorts child nodes or keeps them as-is. + * This **should** be true for SET and SET OF, but is set to false for SET and SET OF elements parsed + * from DER-encoded structures, because this has a chance of altering the structure for non-conforming DER-encoded + * structures. + */ + val isSorted: Boolean = false +) : + Asn1Element(TLV(tag, byteArrayOf()), if (!isSorted) children else children.sortedBy { it.tag }) { public override val children: List @@ -337,12 +445,13 @@ sealed class Asn1Structure(tag: Tag, children: List?) : * Returns the current child (useful when iterating over this structure's children) */ fun peek() = if (!hasMoreChildren()) null else children[index] + } /** * Explicit ASN.1 Tag. Can contain any number of [children] */ -class Asn1Tagged +class Asn1ExplicitlyTagged /** * @param tag the ASN.1 Tag to be used will be properly encoded to have [BERTags.CONSTRUCTED] and * [BERTags.CONTEXT_SPECIFIC] bits set) @@ -351,6 +460,37 @@ class Asn1Tagged */ internal constructor(tag: ULong, children: List) : Asn1Structure(Tag(tag, constructed = true, tagClass = TagClass.CONTEXT_SPECIFIC), children) { + + + /** + * Returns this [Asn1ExplicitlyTagged] children, if its tag matches [tag] + * + * @throws Asn1TagMismatchException if the tag does not match + */ + @Throws(Asn1TagMismatchException::class) + fun verifyTag(explicitTag: Tag): List { + if (this.tag != explicitTag) throw Asn1TagMismatchException(explicitTag, this.tag) + return this.children + } + + /** + * Returns this [Asn1ExplicitlyTagged] children, if its tag matches [tagNumber] + * + * @throws Asn1TagMismatchException if the tag does not match + */ + @Throws(Asn1TagMismatchException::class) + fun verifyTag(tagNumber: ULong): List = verifyTag(Asn1.ExplicitTag(tagNumber)) + + /** + * Exception-free version of [verifyTag] + */ + fun verifyTagOrNull(tagNumber: ULong) = catching { verifyTag(tagNumber) }.getOrNull() + + /** + * Exception-free version of [verifyTag] + */ + fun verifyTagOrNull(explicitTag: Tag) = catching { verifyTag(explicitTag) }.getOrNull() + override fun toString() = "Tagged" + super.toString() override fun prettyPrint(indent: Int) = (" " * indent) + "Tagged" + super.prettyPrint(indent + 2) } @@ -360,7 +500,7 @@ internal constructor(tag: ULong, children: List) : * @param children the elements to put into this sequence */ class Asn1Sequence internal constructor(children: List) : - Asn1Structure(Tag.ASN1_SEQUENCE, children) { + Asn1Structure(Tag.SEQUENCE, children) { init { if (!tag.isConstructed) throw IllegalArgumentException("An ASN.1 Structure must have a CONSTRUCTED tag") @@ -375,32 +515,36 @@ class Asn1Sequence internal constructor(children: List) : * ASN1 structure (i.e. containing child nodes) with custom tag */ class Asn1CustomStructure private constructor( - tag: Tag, children: List + tag: Tag, children: List, sortChildren: Boolean ) : - Asn1Structure(tag, children) { + Asn1Structure(tag, children, sortChildren) { /** * ASN.1 CONSTRUCTED with custom tag * @param children the elements to put into this sequence * @param tag the custom tag to use * @param tagClass the tag class to use for this custom tag. defaults to [TagClass.UNIVERSAL] + * @param sortChildren whether to sort the passed child nodes. defaults to false */ constructor( children: List, tag: ULong, - tagClass: TagClass = TagClass.UNIVERSAL - ) : this(Tag(tag, constructed = true, tagClass), children) + tagClass: TagClass = TagClass.UNIVERSAL, + sortChildren: Boolean = false + ) : this(Tag(tag, constructed = true, tagClass), children, sortChildren) /** * ASN.1 CONSTRUCTED with custom tag * @param children the elements to put into this sequence * @param tag the custom tag to use * @param tagClass the tag class to use for this custom tag. defaults to [TagClass.UNIVERSAL] + * @param sortChildren whether to sort the passed child nodes. defaults to false */ constructor( children: List, tag: UByte, - tagClass: TagClass = TagClass.UNIVERSAL - ) : this(children, tag.toULong(), tagClass) + tagClass: TagClass = TagClass.UNIVERSAL, + sortChildren: Boolean = false + ) : this(children, tag.toULong(), tagClass, sortChildren) override val content: ByteArray by lazy { @@ -420,9 +564,15 @@ class Asn1CustomStructure private constructor( * @param children the elements to put into this sequence * @param tag the custom tag to use * @param tagClass the tag class to use for this custom tag. defaults to [TagClass.UNIVERSAL] + * @param sort whether to sort the passed childr nodes. defaults to false */ - fun asPrimitive(children: List, tag: ULong, tagClass: TagClass = TagClass.UNIVERSAL) = - Asn1CustomStructure(Tag(tag, constructed = false, tagClass), children) + fun asPrimitive( + children: List, + tag: ULong, + tagClass: TagClass = TagClass.UNIVERSAL, + sort: Boolean = false + ) = + Asn1CustomStructure(Tag(tag, constructed = false, tagClass), children, sort) } } @@ -468,13 +618,13 @@ class Asn1PrimitiveOctetString(content: ByteArray) : Asn1Primitive(Tag.OCTET_STR /** * ASN.1 SET 0x31 ([BERTags.SET] OR [BERTags.CONSTRUCTED]) */ -open class Asn1Set private constructor(children: List?, dontSort: Boolean) : - Asn1Structure(Tag.SET, if (dontSort) children else children?.sortedBy { it.tag }) { +open class Asn1Set private constructor(children: List, dontSort: Boolean) : + Asn1Structure(Tag.SET, children, !dontSort) { /** * @param children the elements to put into this set. will be automatically sorted by tag */ - internal constructor(children: List?) : this(children, false) + internal constructor(children: List) : this(children, false) init { if (!tag.isConstructed) throw IllegalArgumentException("An ASN.1 Structure must have a CONSTRUCTED tag") @@ -500,10 +650,9 @@ open class Asn1Set private constructor(children: List?, dontSort: B * @param children the elements to put into this set. will be automatically checked to have the same tag and sorted by value * @throws Asn1Exception if children are using different tags */ -class Asn1SetOf @Throws(Asn1Exception::class) internal constructor(children: List?) : - Asn1Set(children?.let { +class Asn1SetOf @Throws(Asn1Exception::class) internal constructor(children: List) : + Asn1Set(children.also { it -> if (it.any { elem -> elem.tag != it.first().tag }) throw Asn1Exception("SET OF must only contain elements of the same tag") - it.sortedBy { it.tag.encodedTag.encodeToString(Base16) } //TODO this is inefficient }) /** diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Encodable.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Encodable.kt index 6c09e315..f40b63c5 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Encodable.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Encodable.kt @@ -4,7 +4,7 @@ package at.asitplus.signum.indispensable.asn1 import at.asitplus.KmmResult import at.asitplus.catching -import at.asitplus.signum.indispensable.asn1.toImplicitTag +import at.asitplus.signum.indispensable.asn1.Asn1Element.Tag /** * Interface providing methods to encode to ASN.1 @@ -43,88 +43,93 @@ interface Asn1Encodable { * Safe version of [encodeToDer], wrapping the result into a [KmmResult] */ fun encodeToDerSafe() = catching { encodeToDer() } -} -/** - * Interface providing convenience methods to decode from ASN.1. - * Especially useful when companion objects of classes implementing [Asn1Encodable] implement it. - */ -interface Asn1Decodable> { /** - * Processes an [A], parsing it into an instance of [T] - * @throws Asn1Exception if invalid data is provided + * Creates a new implicitly tagged ASN.1 Element from this ASN.1 Element. + * NOTE: The [TagClass] of the provided [tag] will be used! If you want the result to have [TagClass.CONTEXT_SPECIFIC], + * also invoke `tag withClass TagClass.CONTEXT_SPECIFIC`!. If a CONSTRUCTED Tag is applied to an ASN.1 Primitive, + * the CONSTRUCTED bit is overridden and set to zero */ - @Throws(Asn1Exception::class) - fun decodeFromTlv(src: A): T + infix fun withImplicitTag(tag: Tag) = encodeToTlv().withImplicitTag(tag) /** - * Exception-free version of [decodeFromTlv] + * Creates a new implicitly tagged ASN.1 Element from this ASN.1 Element. + * Sets the class of the resulting structure to [TagClass.CONTEXT_SPECIFIC] */ - fun decodeFromTlvOrNull(src: A) = catching { decodeFromTlv(src) }.getOrNull() + infix fun withImplicitTag(tagValue: ULong) = encodeToTlv().withImplicitTag(tagValue) /** - * Safe version of [decodeFromTlv], wrapping the result into a [KmmResult] + * Creates a new implicitly tagged ASN.1 Element from this ASN.1 Structure. + * If the provided [template]'s tagClass is not set, the class of the resulting structure defaults to [TagClass.CONTEXT_SPECIFIC]. + * If a CONSTRUCTED Tag is applied to an ASN.1 Primitive, the CONSTRUCTED bit is overridden and set to zero */ - fun decodeFromTlvSafe(src: A) = catching { decodeFromTlv(src) } + infix fun withImplicitTag(template: Tag.Template) = encodeToTlv().withImplicitTag(template) +} +/** + * Interface providing convenience methods to decode from ASN.1. + * Especially useful when companion objects of classes implementing [Asn1Encodable] implement it. + */ +interface Asn1Decodable> { /** - * Convenience method, directly DER-decoding a byte array to [T] - * @throws Asn1Exception if invalid data is provided + * Processes an [A], parsing it into an instance of [T] + * @throws Asn1Exception if invalid data is provided. + * Specify [assertTag] for verifying implicitly tagged elements' tags (and better not override this function). + * @throws Asn1Exception */ @Throws(Asn1Exception::class) - fun decodeFromDer(src: ByteArray): T = decodeFromTlv(Asn1Element.parse(src) as A) + fun decodeFromTlv(src: A, assertTag: Asn1Element.Tag? = null): T { + verifyTag(src, assertTag) + return doDecode(src) + } /** - * Exception-free version of [decodeFromDer] - */ - fun decodeFromDerOrNull(src: ByteArray) = catching { decodeFromDer(src) }.getOrNull() - - /** - * Safe version of [decodeFromDer], wrapping the result into a [KmmResult] + * Actual element-specific decoding function. By default, this is invoked after [verifyTag] + * @throws Asn1Exception */ - fun decodeFromDerSafe(src: ByteArray) = catching { decodeFromDer(src) } -} - -interface Asn1TagVerifyingDecodable> : - Asn1Decodable { + @Throws(Asn1Exception::class) + fun doDecode(src: A): T /** - * Same as [Asn1Decodable.decodeFromTlv], but allows overriding the tag, should the implementing class verify it. - * Useful for implicit tagging, in which case you will want to call [at.asitplus.signum.indispensable.asn1.DERTags.toImplicitTag] on [tagOverride]. - * @throws Asn1Exception + * Specify [assertTag] for verifying implicitly tagged elements' tags (and better not override this function). + * @throws Asn1TagMismatchException */ - @Throws(Asn1Exception::class) - fun decodeFromTlv(src: Asn1Primitive, tagOverride: Asn1Element.Tag?): T + @Throws(Asn1TagMismatchException::class) + fun verifyTag(src: A, assertTag: Asn1Element.Tag?) { + val expected = assertTag ?: return + if (src.tag != expected) + throw Asn1TagMismatchException(expected, src.tag) + } /** * Exception-free version of [decodeFromTlv] */ - fun decodeFromTlvOrNull(src: Asn1Primitive, tagOverride: Asn1Element.Tag?) = - catching { decodeFromTlv(src, tagOverride) }.getOrNull() + fun decodeFromTlvOrNull(src: A, assertTag: Asn1Element.Tag? = null) = + catching { decodeFromTlv(src, assertTag) }.getOrNull() /** * Safe version of [decodeFromTlv], wrapping the result into a [KmmResult] */ - fun decodeFromTlvSafe(src: Asn1Primitive, tagOverride: Asn1Element.Tag?) = - catching { decodeFromTlv(src, tagOverride) } - + fun decodeFromTlvSafe(src: A, assertTag: Asn1Element.Tag? = null) = + catching { decodeFromTlv(src, assertTag) } /** - * Same as [Asn1Decodable.decodeFromDer], but allows overriding the tag, should the implementing class verify it. - * Useful for implicit tagging. + * Convenience method, directly DER-decoding a byte array to [T] + * @throws Asn1Exception if invalid data is provided */ @Throws(Asn1Exception::class) - fun decodeFromDer(src: ByteArray, tagOverride: Asn1Element.Tag?): T = - decodeFromTlv(Asn1Element.parse(src) as Asn1Primitive, tagOverride) + fun decodeFromDer(src: ByteArray, assertTag: Asn1Element.Tag? = null): T = + decodeFromTlv(Asn1Element.parse(src) as A, assertTag) /** * Exception-free version of [decodeFromDer] */ - fun decodeFromDerOrNull(src: ByteArray, tagOverride: Asn1Element.Tag?) = - catching { decodeFromDer(src, tagOverride) }.getOrNull() + fun decodeFromDerOrNull(src: ByteArray, assertTag: Asn1Element.Tag? = null) = + catching { decodeFromDer(src, assertTag) }.getOrNull() /** * Safe version of [decodeFromDer], wrapping the result into a [KmmResult] */ - fun decodeFromDerSafe(src: ByteArray, tagOverride: Asn1Element.Tag?) = catching { decodeFromDer(src, tagOverride) } + fun decodeFromDerSafe(src: ByteArray, assertTag: Asn1Element.Tag? = null) = + catching { decodeFromDer(src, assertTag) } } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Encoding.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Encoding.kt index c653b216..d1e9ab7e 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Encoding.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Encoding.kt @@ -12,42 +12,45 @@ import kotlin.experimental.or * Class Providing a DSL for creating arbitrary ASN.1 structures. You will almost certainly never use it directly, but rather use it as follows: * ```kotlin * Sequence { - * +Tagged(1uL) { - * +Asn1Primitive(BERTags.BOOLEAN.toUlong(), byteArrayOf(0x00)) - * } - * +Set { - * +Sequence { - * +SetOf { - * +PrintableString("World") - * +PrintableString("Hello") - * } - * +Set { - * +PrintableString("World") - * +PrintableString("Hello") - * +Utf8String("!!!") - * } + * +ExplicitlyTagged(1uL) { + * +Asn1Primitive(Asn1Element.Tag.BOOL, byteArrayOf(0x00)) //or +Asn1.Bool(false) + * } + * +Asn1.Set { + * +Asn1.Sequence { + * +Asn1.SetOf { + * +PrintableString("World") + * +PrintableString("Hello") + * } + * +Asn1.Set { + * +PrintableString("World") + * +PrintableString("Hello") + * +Utf8String("!!!") + * } * - * } * } - * +Null() + * } + * +Asn1.Null() * - * +ObjectIdentifier("1.2.603.624.97") + * +ObjectIdentifier("1.2.603.624.97") * - * +Utf8String("Foo") - * +PrintableString("Bar") + * +(Utf8String("Foo") withImplicitTag (0xCAFEuL withClass TagClass.PRIVATE)) + * +PrintableString("Bar") * - * +Set { - * +Int(3) - * +Long(-65789876543L) - * +Bool(false) - * +Bool(true) - * } - * +Sequence { - * +Null() - * +Asn1String.Numeric("12345") - * +UtcTime(instant) - * } - * } + * //fake Primitive + * +(Asn1.Sequence { +Asn1.Int(42) } withImplicitTag (0x5EUL without CONSTRUCTED)) + * + * +Asn1.Set { + * +Asn1.Int(3) + * +Asn1.Int(-65789876543L) + * +Asn1.Bool(false) + * +Asn1.Bool(true) + * } + * +Asn1.Sequence { + * +Asn1.Null() + * +Asn1String.Numeric("12345") + * +UtcTime(Clock.System.now()) + * } + * } withImplicitTag (1337uL withClass TagClass.APPLICATION) * ``` */ class Asn1TreeBuilder { @@ -172,35 +175,35 @@ object Asn1 { /** - * Creates a new EXPLICITLY TAGGED ASN.1 structure as [Asn1Tagged] using [tag]. + * Creates a new EXPLICITLY TAGGED ASN.1 structure as [Asn1ExplicitlyTagged] using [tag]. * * Use as follows: * * ```kotlin - * Tagged(2uL) { + * ExplicitlyTagged(2uL) { * +PrintableString("World World") * +Null() * +Int(1337) * } * ``` */ - fun Tagged(tag: ULong, root: Asn1TreeBuilder.() -> Unit): Asn1Tagged { + fun ExplicitlyTagged(tag: ULong, root: Asn1TreeBuilder.() -> Unit): Asn1ExplicitlyTagged { val seq = Asn1TreeBuilder() seq.root() - return Asn1Tagged(tag, seq.elements) + return Asn1ExplicitlyTagged(tag, seq.elements) } /** - * Exception-free version of [Tagged] + * Exception-free version of [ExplicitlyTagged] */ - fun TaggedOrNull(tag: ULong, root: Asn1TreeBuilder.() -> Unit) = - catching { Tagged(tag, root) }.getOrNull() + fun ExplicitlyTaggedOrNull(tag: ULong, root: Asn1TreeBuilder.() -> Unit) = + catching { ExplicitlyTagged(tag, root) }.getOrNull() /** - * Safe version on [Tagged], wrapping the result into a [KmmResult] + * Safe version on [ExplicitlyTagged], wrapping the result into a [KmmResult] */ - fun TaggedSafe(tag: ULong, root: Asn1TreeBuilder.() -> Unit) = - catching { Tagged(tag, root) } + fun ExplicitlyTaggedSafe(tag: ULong, root: Asn1TreeBuilder.() -> Unit) = + catching { ExplicitlyTagged(tag, root) } /** @@ -211,12 +214,16 @@ object Asn1 { /** Adds an INTEGER [Asn1Primitive] to this ASN.1 structure */ fun Int(value: Int) = value.encodeToTlv() + /** Adds an INTEGER [Asn1Primitive] to this ASN.1 structure */ fun Int(value: Long) = value.encodeToTlv() + /** Adds an INTEGER [Asn1Primitive] to this ASN.1 structure */ fun Int(value: UInt) = value.encodeToTlv() + /** Adds an INTEGER [Asn1Primitive] to this ASN.1 structure */ fun Int(value: ULong) = value.encodeToTlv() + /** Adds an INTEGER [Asn1Primitive] to this ASN.1 structure */ fun Int(value: BigInteger) = value.encodeToTlv() @@ -292,6 +299,20 @@ object Asn1 { return Asn1EncapsulatingOctetString(seq.elements) } + /** + * Convenience helper to easily construct implicitly tagged elements. + * Shorthand for `Tag(tagValue, constructed=false, tagClass=TagClass.CONTEXT_SPECIFIC) + */ + fun ImplicitTag(tagNum: ULong, tagClass: TagClass = TagClass.CONTEXT_SPECIFIC) = + Asn1Element.Tag(tagNum, constructed = false, tagClass = tagClass) + + /** + * Convenience helper to easily construct implicitly tagged elements. + * Shorthand for `Tag(tagValue, constructed=true, tagClass=TagClass.CONTEXT_SPECIFIC) + */ + fun ExplicitTag(tagNum: ULong) = + Asn1Element.Tag(tagNum, constructed = true, tagClass = TagClass.CONTEXT_SPECIFIC) + } /** @@ -301,12 +322,16 @@ fun Boolean.encodeToTlv() = Asn1Primitive(Asn1Element.Tag.BOOL, byteArrayOf(if ( /** Produces an INTEGER as [Asn1Primitive] */ fun Int.encodeToTlv() = Asn1Primitive(Asn1Element.Tag.INT, encodeToDer()) + /** Produces an INTEGER as [Asn1Primitive] */ fun Long.encodeToTlv() = Asn1Primitive(Asn1Element.Tag.INT, encodeToDer()) + /** Produces an INTEGER as [Asn1Primitive] */ fun UInt.encodeToTlv() = Asn1Primitive(Asn1Element.Tag.INT, encodeToDer()) + /** Produces an INTEGER as [Asn1Primitive] */ fun ULong.encodeToTlv() = Asn1Primitive(Asn1Element.Tag.INT, encodeToDer()) + /** Produces an INTEGER as [Asn1Primitive] */ fun BigInteger.encodeToTlv() = Asn1Primitive(Asn1Element.Tag.INT, encodeToDer()) @@ -401,7 +426,9 @@ fun ULong.toTwosComplementByteArray() = when { (this shr 24).toByte(), (this shr 16).toByte(), (this shr 8).toByte(), - this.toByte()) + this.toByte() + ) + else -> this.toLong().toTwosComplementByteArray() } @@ -412,29 +439,39 @@ fun UInt.toTwosComplementByteArray() = toLong().toTwosComplementByteArray() fun Long.toTwosComplementByteArray() = when { (this >= -0x80L && this <= 0x7FL) -> byteArrayOf( - this.toByte()) + this.toByte() + ) + (this >= -0x8000L && this <= 0x7FFFL) -> byteArrayOf( (this ushr 8).toByte(), - this.toByte()) + this.toByte() + ) + (this >= -0x800000L && this <= 0x7FFFFFL) -> byteArrayOf( (this ushr 16).toByte(), (this ushr 8).toByte(), - this.toByte()) + this.toByte() + ) + (this >= -0x80000000L && this <= 0x7FFFFFFFL) -> byteArrayOf( (this ushr 24).toByte(), (this ushr 16).toByte(), (this ushr 8).toByte(), - this.toByte()) + this.toByte() + ) + (this >= -0x8000000000L && this <= 0x7FFFFFFFFFL) -> byteArrayOf( (this ushr 32).toByte(), (this ushr 24).toByte(), (this ushr 16).toByte(), (this ushr 8).toByte(), - this.toByte()) + this.toByte() + ) + (this >= -0x800000000000L && this <= 0x7FFFFFFFFFFFL) -> byteArrayOf( (this ushr 40).toByte(), @@ -442,7 +479,9 @@ fun Long.toTwosComplementByteArray() = when { (this ushr 24).toByte(), (this ushr 16).toByte(), (this ushr 8).toByte(), - this.toByte()) + this.toByte() + ) + (this >= -0x80000000000000L && this <= 0x7FFFFFFFFFFFFFL) -> byteArrayOf( (this ushr 48).toByte(), @@ -451,7 +490,9 @@ fun Long.toTwosComplementByteArray() = when { (this ushr 24).toByte(), (this ushr 16).toByte(), (this ushr 8).toByte(), - this.toByte()) + this.toByte() + ) + else -> byteArrayOf( (this ushr 56).toByte(), @@ -461,14 +502,17 @@ fun Long.toTwosComplementByteArray() = when { (this ushr 24).toByte(), (this ushr 16).toByte(), (this ushr 8).toByte(), - this.toByte()) + this.toByte() + ) } /** Encodes a signed Int to a minimum-size twos-complement byte array */ fun Int.toTwosComplementByteArray() = toLong().toTwosComplementByteArray() fun Int.Companion.fromTwosComplementByteArray(it: ByteArray) = when (it.size) { - 4 -> (it[0].toInt() shl 24) or (it[1].toUByte().toInt() shl 16) or (it[2].toUByte().toInt() shl 8) or (it[3].toUByte().toInt()) + 4 -> (it[0].toInt() shl 24) or (it[1].toUByte().toInt() shl 16) or (it[2].toUByte() + .toInt() shl 8) or (it[3].toUByte().toInt()) + 3 -> (it[0].toInt() shl 16) or (it[1].toUByte().toInt() shl 8) or (it[2].toUByte().toInt()) 2 -> (it[0].toInt() shl 8) or (it[1].toUByte().toInt() shl 0) 1 -> (it[0].toInt()) @@ -477,7 +521,7 @@ fun Int.Companion.fromTwosComplementByteArray(it: ByteArray) = when (it.size) { fun UInt.Companion.fromTwosComplementByteArray(it: ByteArray) = Long.fromTwosComplementByteArray(it).let { - require ((0 <= it) && (it <= 0xFFFFFFFFL)) { "Value $it is out of bounds for UInt" } + require((0 <= it) && (it <= 0xFFFFFFFFL)) { "Value $it is out of bounds for UInt" } it.toUInt() } @@ -485,15 +529,20 @@ fun Long.Companion.fromTwosComplementByteArray(it: ByteArray) = when (it.size) { 8 -> (it[0].toLong() shl 56) or (it[1].toUByte().toLong() shl 48) or (it[2].toUByte().toLong() shl 40) or (it[3].toUByte().toLong() shl 32) or (it[4].toUByte().toLong() shl 24) or (it[5].toUByte().toLong() shl 16) or (it[6].toUByte().toLong() shl 8) or (it[7].toUByte().toLong()) + 7 -> (it[0].toLong() shl 48) or (it[1].toUByte().toLong() shl 40) or (it[2].toUByte().toLong() shl 32) or (it[3].toUByte().toLong() shl 24) or (it[4].toUByte().toLong() shl 16) or (it[5].toUByte().toLong() shl 8) or (it[6].toUByte().toLong()) + 6 -> (it[0].toLong() shl 40) or (it[1].toUByte().toLong() shl 32) or (it[2].toUByte().toLong() shl 24) or (it[3].toUByte().toLong() shl 16) or (it[4].toUByte().toLong() shl 8) or (it[5].toUByte().toLong()) + 5 -> (it[0].toLong() shl 32) or (it[1].toUByte().toLong() shl 24) or (it[2].toUByte().toLong() shl 16) or (it[3].toUByte().toLong() shl 8) or (it[4].toUByte().toLong()) + 4 -> (it[0].toLong() shl 24) or (it[1].toUByte().toLong() shl 16) or (it[2].toUByte().toLong() shl 8) or (it[3].toUByte().toLong()) + 3 -> (it[0].toLong() shl 16) or (it[1].toUByte().toLong() shl 8) or (it[2].toUByte().toLong()) 2 -> (it[0].toLong() shl 8) or (it[1].toUByte().toLong() shl 0) 1 -> (it[0].toLong()) @@ -502,12 +551,14 @@ fun Long.Companion.fromTwosComplementByteArray(it: ByteArray) = when (it.size) { fun ULong.Companion.fromTwosComplementByteArray(it: ByteArray) = when { ((it.size == 9) && (it[0] == 0.toByte())) -> - (it[1].toUByte().toULong() shl 56) or (it[2].toUByte().toULong() shl 48) or (it[3].toUByte().toULong() shl 40) or + (it[1].toUByte().toULong() shl 56) or (it[2].toUByte().toULong() shl 48) or (it[3].toUByte() + .toULong() shl 40) or (it[4].toUByte().toULong() shl 32) or (it[5].toUByte().toULong() shl 24) or (it[6].toUByte().toULong() shl 16) or (it[7].toUByte().toULong() shl 8) or (it[8].toUByte().toULong()) + else -> Long.fromTwosComplementByteArray(it).let { - require (it >= 0) { "Value $it is out of bounds for ULong" } + require(it >= 0) { "Value $it is out of bounds for ULong" } it.toULong() } } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1String.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1String.kt index 554070ed..b3e6bc2e 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1String.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1String.kt @@ -119,6 +119,6 @@ sealed class Asn1String : Asn1Encodable { companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Primitive): Asn1String = src.readString() + override fun doDecode(src: Asn1Primitive): Asn1String = src.readString() } } \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Time.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Time.kt index 1913149c..fb153d96 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Time.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/Asn1Time.kt @@ -29,7 +29,7 @@ class Asn1Time(instant: Instant, formatOverride: Format? = null) : Asn1Encodable private val THRESHOLD_GENERALIZED_TIME = Instant.parse("2050-01-01T00:00:00Z") @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Primitive) = + override fun doDecode(src: Asn1Primitive) = Asn1Time(src.readInstant(), if (src.tag == Asn1Element.Tag.TIME_UTC) Format.UTC else Format.GENERALIZED) } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/BERTags.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/BERTags.kt index 48d51866..02c98161 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/BERTags.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/BERTags.kt @@ -56,13 +56,6 @@ object BERTags { } -/** - * Convenience helper to easily construct implicitly tagged elements. Can be CONSTRUCTED or PRIMITIVE - * @param constructed whether to set the constructed bit - */ -fun ULong.toImplicitTag(constructed: Boolean = false) = - Asn1Element.Tag(this, constructed = constructed, tagClass = TagClass.CONTEXT_SPECIFIC) - internal fun UByte.isConstructed() = this and BERTags.CONSTRUCTED != 0.toUByte() @@ -91,4 +84,7 @@ enum class TagClass(val byteValue: UByte, val berTag: UByte) { catching { entries.first { it.byteValue == ((byteValue byteMask 0xC0).toUInt().toInt() ushr 6).toUByte() } } } -} \ No newline at end of file +} + +sealed interface TagProperty +object CONSTRUCTED : TagProperty \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/ObjectIdentifier.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/ObjectIdentifier.kt index 544e776e..a1f1a302 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/ObjectIdentifier.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/asn1/ObjectIdentifier.kt @@ -72,10 +72,7 @@ class ObjectIdentifier @Throws(Asn1Exception::class) constructor(@Transient vara * @throws Asn1Exception all sorts of errors on invalid input */ @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Primitive): ObjectIdentifier { - if (src.tag != Asn1Element.Tag.OID) throw Asn1TagMismatchException( - Asn1Element.Tag.OID, src.tag - ) + override fun doDecode(src: Asn1Primitive): ObjectIdentifier { if (src.length < 1) throw Asn1StructuralException("Empty OIDs are not supported") return parse(src.content) @@ -117,7 +114,6 @@ class ObjectIdentifier @Throws(Asn1Exception::class) constructor(@Transient vara return ObjectIdentifier(*collected.toUIntArray()) } } - } object ObjectIdSerializer : KSerializer { diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/AlternativeNames.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/AlternativeNames.kt index cf4f97aa..96747ee2 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/AlternativeNames.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/AlternativeNames.kt @@ -49,7 +49,7 @@ private constructor(private val extensions: List) { }.map { (it as Asn1Sequence).also { if (it.children.size != 2) throw Asn1StructuralException("Invalid otherName Alternative Name found (!=2 children): ${it.toDerHexString()}") - if (it.children.last().tag != 0uL.toImplicitTag()) throw Asn1StructuralException("Invalid otherName Alternative Name found (implicit tag != 0): ${it.toDerHexString()}") + if (it.children.last().tag != SubjectAltNameImplicitTags.otherName) throw Asn1StructuralException("Invalid otherName Alternative Name found (implicit tag != 0): ${it.toDerHexString()}") ObjectIdentifier.parse((it.children.first() as Asn1Primitive).content) //this throws if something is off } } @@ -61,7 +61,7 @@ private constructor(private val extensions: List) { }.map { (it as Asn1Sequence).also { if (it.children.size > 2) throw Asn1StructuralException("Invalid partyName Alternative Name found (>2 children): ${it.toDerHexString()}") - if (it.children.find { it.tag != 0uL.toImplicitTag() && it.tag != 1uL.toImplicitTag() } != null) throw Asn1StructuralException( + if (it.children.find { it.tag != SubjectAltNameImplicitTags.otherName && it.tag != SubjectAltNameImplicitTags.rfc822Name } != null) throw Asn1StructuralException( "Invalid partyName Alternative Name found (illegal implicit tag): ${it.toDerHexString()}" ) //TODO: strict string parsing @@ -131,13 +131,13 @@ private constructor(private val extensions: List) { * Enumeration of implicit tags used to indicate different `SubjectAltName`s */ object SubjectAltNameImplicitTags { - val otherName = 0uL.toImplicitTag() - val rfc822Name = 1uL.toImplicitTag() - val dNSName = 2uL.toImplicitTag() - val x400Address = 3uL.toImplicitTag() - val directoryName = 4uL.toImplicitTag() - val ediPartyName = 5uL.toImplicitTag() - val uniformResourceIdentifier = 6uL.toImplicitTag() - val iPAddress = 7uL.toImplicitTag() - val registeredID = 8uL.toImplicitTag() + val otherName = Asn1.ImplicitTag(0uL) + val rfc822Name = Asn1.ImplicitTag(1uL) + val dNSName = Asn1.ImplicitTag(2uL) + val x400Address = Asn1.ImplicitTag(3uL) + val directoryName = Asn1.ImplicitTag(4uL) + val ediPartyName = Asn1.ImplicitTag(5uL) + val uniformResourceIdentifier = Asn1.ImplicitTag(6uL) + val iPAddress = Asn1.ImplicitTag(7uL) + val registeredID = Asn1.ImplicitTag(8uL) } diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/Pkcs10CertificationRequest.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/Pkcs10CertificationRequest.kt index 38b927f8..eb351001 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/Pkcs10CertificationRequest.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/Pkcs10CertificationRequest.kt @@ -1,10 +1,10 @@ package at.asitplus.signum.indispensable.pki -import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.CryptoPublicKey +import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.asn1.Asn1.BitString -import at.asitplus.signum.indispensable.asn1.Asn1.Tagged +import at.asitplus.signum.indispensable.asn1.Asn1.ExplicitlyTagged import at.asitplus.signum.indispensable.io.ByteArrayBase64Serializer import kotlinx.serialization.Serializable @@ -50,9 +50,10 @@ data class TbsCertificationRequest( //subject Public Key +publicKey - +Tagged(0u) { attributes?.map { +it } } + +ExplicitlyTagged(0u) { attributes?.map { +it } } } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false @@ -77,14 +78,14 @@ data class TbsCertificationRequest( companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence) = runRethrowing { + override fun doDecode(src: Asn1Sequence) = runRethrowing { val version = (src.nextChild() as Asn1Primitive).readInt() val subject = (src.nextChild() as Asn1Sequence).children.map { RelativeDistinguishedName.decodeFromTlv(it as Asn1Set) } val cryptoPublicKey = CryptoPublicKey.decodeFromTlv(src.nextChild() as Asn1Sequence) val attributes = if (src.hasMoreChildren()) { - (src.nextChild() as Asn1Tagged).verifyTag(0u) + (src.nextChild() as Asn1ExplicitlyTagged).verifyTag(0u) .map { Pkcs10CertificationRequestAttribute.decodeFromTlv(it as Asn1Sequence) } } else null @@ -143,7 +144,7 @@ data class Pkcs10CertificationRequest( companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence): Pkcs10CertificationRequest = runRethrowing { + override fun doDecode(src: Asn1Sequence): Pkcs10CertificationRequest = runRethrowing { val tbsCsr = TbsCertificationRequest.decodeFromTlv(src.nextChild() as Asn1Sequence) val sigAlg = X509SignatureAlgorithm.decodeFromTlv(src.nextChild() as Asn1Sequence) val signature = (src.nextChild() as Asn1Primitive).readBitString() diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/Pkcs10CertificationRequestAttribute.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/Pkcs10CertificationRequestAttribute.kt index 5506a059..080101e6 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/Pkcs10CertificationRequestAttribute.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/Pkcs10CertificationRequestAttribute.kt @@ -36,7 +36,7 @@ data class Pkcs10CertificationRequestAttribute( companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence): Pkcs10CertificationRequestAttribute = runRethrowing { + override fun doDecode(src: Asn1Sequence): Pkcs10CertificationRequestAttribute = runRethrowing { val id = (src.children[0] as Asn1Primitive).readOid() val value = (src.children.last() as Asn1Set).children return Pkcs10CertificationRequestAttribute(id, value) diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/RelativeDistinguishedName.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/RelativeDistinguishedName.kt index 48faaed3..5a107f1a 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/RelativeDistinguishedName.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/RelativeDistinguishedName.kt @@ -20,7 +20,7 @@ data class RelativeDistinguishedName(val attrsAndValues: List { - override fun decodeFromTlv(src: Asn1Set): RelativeDistinguishedName = runRethrowing { + override fun doDecode(src: Asn1Set): RelativeDistinguishedName = runRethrowing { RelativeDistinguishedName(src.children.map { AttributeTypeAndValue.decodeFromTlv(it as Asn1Sequence) }) } } @@ -119,7 +119,7 @@ sealed class AttributeTypeAndValue : Asn1Encodable, Identifiable { companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence): AttributeTypeAndValue = runRethrowing { + override fun doDecode(src: Asn1Sequence): AttributeTypeAndValue = runRethrowing { val oid = (src.nextChild() as Asn1Primitive).readOid() if (oid.nodes.size >= 3 && oid.toString().startsWith("2.5.4.")) { val asn1String = src.nextChild() as Asn1Primitive @@ -142,5 +142,6 @@ sealed class AttributeTypeAndValue : Asn1Encodable, Identifiable { return Other(oid, src.nextChild()) .also { if (src.hasMoreChildren()) throw Asn1StructuralException("Superfluous elements in RDN") } } + } } \ No newline at end of file diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt index a1347a7b..f5abd8c9 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509Certificate.kt @@ -1,14 +1,17 @@ package at.asitplus.signum.indispensable.pki import at.asitplus.catching -import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.CryptoSignature +import at.asitplus.signum.indispensable.X509SignatureAlgorithm import at.asitplus.signum.indispensable.asn1.* import at.asitplus.signum.indispensable.io.BitSet import at.asitplus.signum.indispensable.io.ByteArrayBase64Serializer import at.asitplus.signum.indispensable.pki.AlternativeNames.Companion.findIssuerAltNames import at.asitplus.signum.indispensable.pki.AlternativeNames.Companion.findSubjectAltNames +import at.asitplus.signum.indispensable.pki.TbsCertificate.Companion.Tags.EXTENSIONS +import at.asitplus.signum.indispensable.pki.TbsCertificate.Companion.Tags.ISSUER_UID +import at.asitplus.signum.indispensable.pki.TbsCertificate.Companion.Tags.SUBJECT_UID import io.matthewnelson.encoding.base64.Base64 import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray import kotlinx.serialization.Serializable @@ -58,7 +61,7 @@ constructor( val issuerAlternativeNames: AlternativeNames? = extensions?.findIssuerAltNames() - private fun Asn1TreeBuilder.Version(value: Int) = Asn1.Tagged(0u) { +Asn1.Int(value) } + private fun Asn1TreeBuilder.Version(value: Int) = Asn1.ExplicitlyTagged(Tags.VERSION.tagValue) { +Asn1.Int(value) } @Throws(Asn1Exception::class) @@ -79,21 +82,12 @@ constructor( //subject public key +publicKey - issuerUniqueID?.let { - +Asn1Primitive( - 1uL.toImplicitTag(), - Asn1BitString(it).let { byteArrayOf(it.numPaddingBits, *it.rawBytes) }) - } - subjectUniqueID?.let { - +Asn1Primitive( - 1uL.toImplicitTag(), - Asn1BitString(it).let { byteArrayOf(it.numPaddingBits, *it.rawBytes) }) - - } + issuerUniqueID?.let { +(Asn1BitString(it) withImplicitTag ISSUER_UID) } + subjectUniqueID?.let { +(Asn1BitString(it) withImplicitTag SUBJECT_UID) } extensions?.let { if (it.isNotEmpty()) { - +Asn1.Tagged(3u) { + +Asn1.ExplicitlyTagged(EXTENSIONS.tagValue) { +Asn1.Sequence { it.forEach { ext -> +ext } } @@ -140,11 +134,18 @@ constructor( } companion object : Asn1Decodable { + + object Tags { + val ISSUER_UID = Asn1.ImplicitTag(1uL) + val SUBJECT_UID = Asn1.ImplicitTag(2uL) + val EXTENSIONS = Asn1.ExplicitTag(3uL) + val VERSION = Asn1.ExplicitTag(0uL) + } + @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence) = runRethrowing { - //TODO make sure to always check for superfluous data + override fun doDecode(src: Asn1Sequence) = runRethrowing { val version = src.nextChild().let { - ((it as Asn1Tagged).verifyTag(0u).single() as Asn1Primitive).readInt() + ((it as Asn1ExplicitlyTagged).verifyTag(Tags.VERSION).single() as Asn1Primitive).readInt() } val serialNumber = (src.nextChild() as Asn1Primitive).decode(Asn1Element.Tag.INT) { it } val sigAlg = X509SignatureAlgorithm.decodeFromTlv(src.nextChild() as Asn1Sequence) @@ -160,18 +161,18 @@ constructor( val cryptoPublicKey = CryptoPublicKey.decodeFromTlv(src.nextChild() as Asn1Sequence) val issuerUniqueID = src.peek()?.let { next -> - if (next.tag == 1uL.toImplicitTag()) { - (src.nextChild() as Asn1Primitive).let { Asn1BitString.decodeFromTlv(it, 1uL.toImplicitTag()) } + if (next.tag == ISSUER_UID) { + (src.nextChild() as Asn1Primitive).let { Asn1BitString.decodeFromTlv(it, ISSUER_UID) } } else null } val subjectUniqueID = src.peek()?.let { next -> - if (next.tag == 2uL.toImplicitTag()) { - (src.nextChild() as Asn1Primitive).let { Asn1BitString.decodeFromTlv(it, 2uL.toImplicitTag()) } + if (next.tag == SUBJECT_UID) { + (src.nextChild() as Asn1Primitive).let { Asn1BitString.decodeFromTlv(it, SUBJECT_UID) } } else null } val extensions = if (src.hasMoreChildren()) { - ((src.nextChild() as Asn1Tagged).verifyTag(3u).single() as Asn1Sequence).children.map { + ((src.nextChild() as Asn1ExplicitlyTagged).verifyTag(EXTENSIONS.tagValue).single() as Asn1Sequence).children.map { X509CertificateExtension.decodeFromTlv(it as Asn1Sequence) } } else null @@ -245,7 +246,7 @@ data class X509Certificate @Throws(IllegalArgumentException::class) constructor( companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence): X509Certificate = runRethrowing { + override fun doDecode(src: Asn1Sequence): X509Certificate = runRethrowing { val tbs = TbsCertificate.decodeFromTlv(src.nextChild() as Asn1Sequence) val sigAlg = X509SignatureAlgorithm.decodeFromTlv(src.nextChild() as Asn1Sequence) val signature = when { diff --git a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509CertificateExtension.kt b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509CertificateExtension.kt index cb7aeebb..34479e1e 100644 --- a/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509CertificateExtension.kt +++ b/indispensable/src/commonMain/kotlin/at/asitplus/signum/indispensable/pki/X509CertificateExtension.kt @@ -40,7 +40,7 @@ data class X509CertificateExtension @Throws(Asn1Exception::class) private constr companion object : Asn1Decodable { @Throws(Asn1Exception::class) - override fun decodeFromTlv(src: Asn1Sequence): X509CertificateExtension = runRethrowing { + override fun doDecode(src: Asn1Sequence): X509CertificateExtension = runRethrowing { val id = (src.children[0] as Asn1Primitive).readOid() val critical = diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/Asn1EncodingTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/Asn1EncodingTest.kt index a5739e5a..11243b0b 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/Asn1EncodingTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/Asn1EncodingTest.kt @@ -7,7 +7,7 @@ import at.asitplus.signum.indispensable.asn1.Asn1.Null import at.asitplus.signum.indispensable.asn1.Asn1.OctetString import at.asitplus.signum.indispensable.asn1.Asn1.OctetStringEncapsulating import at.asitplus.signum.indispensable.asn1.Asn1.PrintableString -import at.asitplus.signum.indispensable.asn1.Asn1.Tagged +import at.asitplus.signum.indispensable.asn1.Asn1.ExplicitlyTagged import at.asitplus.signum.indispensable.asn1.Asn1.UtcTime import at.asitplus.signum.indispensable.asn1.Asn1.Utf8String import at.asitplus.signum.indispensable.io.BitSet @@ -77,7 +77,7 @@ class Asn1EncodingTest : FreeSpec({ } }.derEncoded ) - +Tagged(9u) { +Clock.System.now().encodeToAsn1UtcTime() } + +ExplicitlyTagged(9u) { +Clock.System.now().encodeToAsn1UtcTime() } +OctetString(byteArrayOf(17, -43, 23, -12, 8, 65, 90)) +Bool(false) +Bool(true) @@ -206,7 +206,7 @@ class Asn1EncodingTest : FreeSpec({ val instant = Clock.System.now() val sequence = Asn1.Sequence { - +Tagged(1u) { +Asn1Primitive(BERTags.BOOLEAN, byteArrayOf(0x00)) } + +ExplicitlyTagged(1u) { +Asn1Primitive(BERTags.BOOLEAN, byteArrayOf(0x00)) } +Asn1.Set { +Asn1.Sequence { +Asn1.SetOf { diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/CastingTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/CastingTest.kt index 70c5d2e8..d9ff3147 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/CastingTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/CastingTest.kt @@ -11,7 +11,7 @@ class CastingTest : FreeSpec({ "Primitive" { shouldThrow { Asn1.Int(0).asSet() } - shouldThrow { Asn1.Int(0).asTagged() } + shouldThrow { Asn1.Int(0).asExplicitlyTagged() } shouldThrow { Asn1.Int(0).asStructure() } shouldThrow { Asn1.Int(0).asSequence() } shouldThrow { Asn1.Int(0).asPrimitiveOctetString() } @@ -21,7 +21,7 @@ class CastingTest : FreeSpec({ "Set" { shouldThrow { Asn1.Set { +Asn1.Null() }.asSequence() } - shouldThrow { Asn1.Set { +Asn1.Null() }.asTagged() } + shouldThrow { Asn1.Set { +Asn1.Null() }.asExplicitlyTagged() } shouldThrow { Asn1.Set { +Asn1.Null() }.asPrimitiveOctetString() } shouldThrow { Asn1.Set { +Asn1.Null() }.asEncapsulatingOctetString() } @@ -31,7 +31,7 @@ class CastingTest : FreeSpec({ "OctetString ENCAPSULATING" { shouldThrow { Asn1.OctetStringEncapsulating { +Asn1.Null() }.asSet() } - shouldThrow { Asn1.OctetStringEncapsulating { +Asn1.Null() }.asTagged() } + shouldThrow { Asn1.OctetStringEncapsulating { +Asn1.Null() }.asExplicitlyTagged() } shouldThrow { Asn1.OctetStringEncapsulating { +Asn1.Null() }.asPrimitiveOctetString() } shouldThrow { Asn1.OctetStringEncapsulating { +Asn1.Null() }.asSequence() } @@ -44,7 +44,7 @@ class CastingTest : FreeSpec({ shouldThrow { Asn1PrimitiveOctetString(byteArrayOf()).asSet() } shouldThrow { Asn1PrimitiveOctetString(byteArrayOf()).asStructure() } shouldThrow { Asn1PrimitiveOctetString(byteArrayOf()).asSequence() } - shouldThrow { Asn1PrimitiveOctetString(byteArrayOf()).asTagged() } + shouldThrow { Asn1PrimitiveOctetString(byteArrayOf()).asExplicitlyTagged() } shouldThrow { Asn1PrimitiveOctetString(byteArrayOf()).asEncapsulatingOctetString() } Asn1PrimitiveOctetString(byteArrayOf()).let { it.asPrimitiveOctetString() shouldBe it } Asn1PrimitiveOctetString(byteArrayOf()).let { it.asPrimitive() shouldBe it } @@ -52,13 +52,13 @@ class CastingTest : FreeSpec({ "Custom Structure" { - shouldThrow { Asn1.Tagged(19u) { +Asn1.Null() }.asSequence() } - shouldThrow { Asn1.Tagged(19u) { +Asn1.Null() }.asSet() } - shouldThrow { Asn1.Tagged(19u) { +Asn1.Null() }.asPrimitiveOctetString() } - shouldThrow { Asn1.Tagged(19u) { +Asn1.Null() }.asEncapsulatingOctetString() } + shouldThrow { Asn1.ExplicitlyTagged(19u) { +Asn1.Null() }.asSequence() } + shouldThrow { Asn1.ExplicitlyTagged(19u) { +Asn1.Null() }.asSet() } + shouldThrow { Asn1.ExplicitlyTagged(19u) { +Asn1.Null() }.asPrimitiveOctetString() } + shouldThrow { Asn1.ExplicitlyTagged(19u) { +Asn1.Null() }.asEncapsulatingOctetString() } - Asn1.Tagged(19u) { +Asn1.Null() }.let { it.asStructure() shouldBe it } - Asn1.Tagged(19u) { +Asn1.Null() }.let { it.asTagged() shouldBe it } + Asn1.ExplicitlyTagged(19u) { +Asn1.Null() }.let { it.asStructure() shouldBe it } + Asn1.ExplicitlyTagged(19u) { +Asn1.Null() }.let { it.asExplicitlyTagged() shouldBe it } } diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/ImplicitTaggingTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/ImplicitTaggingTest.kt new file mode 100644 index 00000000..209f80ce --- /dev/null +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/ImplicitTaggingTest.kt @@ -0,0 +1,154 @@ +package at.asitplus.signum.indispensable + +import at.asitplus.signum.indispensable.asn1.* +import at.asitplus.signum.indispensable.asn1.TagClass.* +import at.asitplus.signum.indispensable.asn1.Asn1Element.Tag.Template.Companion.withClass +import at.asitplus.signum.indispensable.asn1.Asn1Element.Tag.Template.Companion.without +import io.kotest.core.spec.style.FreeSpec +import io.kotest.datatest.withData +import io.kotest.matchers.booleans.shouldBeFalse +import io.kotest.matchers.booleans.shouldBeTrue +import io.kotest.matchers.shouldBe +import io.kotest.property.Arb +import io.kotest.property.arbitrary.uLong +import io.kotest.property.checkAll + +class ImplicitTaggingTest : FreeSpec({ + + "Plain" - { + checkAll(Arb.uLong()) { tagNum -> + val universalConstructed = Asn1Element.Tag(tagNum, constructed = true) + + universalConstructed.tagValue shouldBe tagNum + universalConstructed.isConstructed.shouldBeTrue() + universalConstructed.tagClass shouldBe UNIVERSAL + + val universalPrimitive = Asn1Element.Tag(tagNum, constructed = false) + + + universalPrimitive.tagValue shouldBe tagNum + universalPrimitive.isConstructed.shouldBeFalse() + universalPrimitive.tagClass shouldBe UNIVERSAL + + withData(nameFn = { "$tagNum $it" }, TagClass.entries) { tagClass -> + val classy = universalConstructed withClass tagClass + classy.tagClass shouldBe tagClass + (universalConstructed without CONSTRUCTED).isConstructed shouldBe false + (classy without CONSTRUCTED).also { + it.isConstructed shouldBe false + (universalConstructed withClass tagClass without CONSTRUCTED) shouldBe it + } + + val classyPrimitive = universalPrimitive withClass tagClass + classyPrimitive.tagClass shouldBe tagClass + (universalPrimitive without CONSTRUCTED).also { + it.isConstructed shouldBe false + it shouldBe universalPrimitive + } + (classyPrimitive without CONSTRUCTED) shouldBe classyPrimitive + + } + + + } + } + + "Primitive" - { + checkAll(Arb.uLong()) { tagNum -> + val primitive = Asn1Primitive(tagNum, byteArrayOf()) + + val universalPrimitive = primitive.tag + + + universalPrimitive.tagValue shouldBe tagNum + universalPrimitive.isConstructed.shouldBeFalse() + universalPrimitive.tagClass shouldBe UNIVERSAL + + (primitive withImplicitTag tagNum).tag.tagClass shouldBe CONTEXT_SPECIFIC + + withData(nameFn = { "$tagNum $it" }, TagClass.entries) { tagClass -> + + val newTagValue = tagNum / 2uL; + val newTagObject = Asn1Element.Tag(newTagValue, constructed = true) //test CONSTRUCTED override + val taggedElement = primitive withImplicitTag (newTagValue withClass tagClass) + val taggedElementFromTag = primitive withImplicitTag (newTagObject withClass tagClass) + taggedElementFromTag shouldBe taggedElement + + val classyPrimitive = taggedElement.tag + classyPrimitive.tagClass shouldBe tagClass + classyPrimitive.tagValue shouldBe newTagValue + classyPrimitive.isConstructed.shouldBeFalse() + (primitive withImplicitTag (newTagValue withClass tagClass without CONSTRUCTED)).also { + it.tag shouldBe classyPrimitive + } + + (primitive withImplicitTag (newTagObject withClass tagClass)).also { + it.tag shouldBe classyPrimitive + it.tag shouldBe (primitive withImplicitTag (newTagObject withClass tagClass without CONSTRUCTED)).tag + } + + val encoded = taggedElement.derEncoded + Asn1Element.parse(encoded).derEncoded shouldBe encoded + } + } + } + + + "Constructed" - { + checkAll(Arb.uLong()) { tagNum -> + val set = Asn1Set(listOf()) + + val universalConstructed = set.tag + + + universalConstructed shouldBe Asn1Element.Tag.SET + universalConstructed.isConstructed.shouldBeTrue() + universalConstructed.tagClass shouldBe UNIVERSAL + + (set withImplicitTag tagNum).tag.tagClass shouldBe CONTEXT_SPECIFIC + + withData(nameFn = { "$tagNum $it" }, TagClass.entries) { tagClass -> + + val newTagValue = tagNum / 2uL + + val taggedElement = set withImplicitTag (newTagValue withClass tagClass) + val classySet = taggedElement.tag + classySet.tagClass shouldBe tagClass + classySet.tagValue shouldBe newTagValue + classySet.isConstructed.shouldBeTrue() + (set withImplicitTag (newTagValue withClass tagClass without CONSTRUCTED)).also { + it.tag.isConstructed.shouldBeFalse() + it.tag.tagValue shouldBe newTagValue + it.tag.tagClass shouldBe tagClass + } + + val encoded = taggedElement.derEncoded + Asn1Element.parse(encoded).derEncoded shouldBe encoded + + val primitive = set withImplicitTag (newTagValue without CONSTRUCTED) + primitive.tag.tagClass shouldBe CONTEXT_SPECIFIC + primitive.tag.isConstructed.shouldBeFalse() + primitive.tag.tagValue shouldBe newTagValue + + withData(true, false) { constructed -> + val newTag = Asn1Element.Tag(newTagValue, constructed = constructed) + val taggedElement = set withImplicitTag (newTag withClass tagClass) + val classySet = taggedElement.tag + classySet.tagClass shouldBe tagClass + classySet.tagValue shouldBe newTagValue + classySet.isConstructed shouldBe newTag.isConstructed + (set withImplicitTag (newTag withClass tagClass without CONSTRUCTED)).also { + it.tag.isConstructed.shouldBeFalse() + it.tag.tagValue shouldBe newTagValue + it.tag.tagClass shouldBe tagClass + } + + } + + + } + } + } + + +}) \ No newline at end of file diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/TagEncodingTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/TagEncodingTest.kt index 80b55850..a6cb689c 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/TagEncodingTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/TagEncodingTest.kt @@ -48,7 +48,7 @@ class TagEncodingTest : FreeSpec({ tag.tagValue shouldBe it.toULong() val bc = DERTaggedObject(true, it, ASN1Integer(1337)) - val own = Asn1.Tagged(it.toULong()) { + val own = Asn1.ExplicitlyTagged(it.toULong()) { +Asn1.Int(1337) } withClue( @@ -70,7 +70,7 @@ class TagEncodingTest : FreeSpec({ tag.tagValue shouldBe it.toULong() val bc = DERTaggedObject(true, it, ASN1Integer(1337)) - val own = Asn1.Tagged(it.toULong()) { + val own = Asn1.ExplicitlyTagged(it.toULong()) { +Asn1.Int(1337) } withClue( diff --git a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/TagSortingTest.kt b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/TagSortingTest.kt index f48cec5a..9e0d74b2 100644 --- a/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/TagSortingTest.kt +++ b/indispensable/src/jvmTest/kotlin/at/asitplus/signum/indispensable/TagSortingTest.kt @@ -2,6 +2,7 @@ package at.asitplus.signum.indispensable +import at.asitplus.signum.indispensable.asn1.Asn1 import at.asitplus.signum.indispensable.asn1.Asn1Element import at.asitplus.signum.indispensable.asn1.TagClass import io.kotest.core.spec.style.FreeSpec