Skip to content

Commit

Permalink
Fix/implicit tagging (#126)
Browse files Browse the repository at this point in the history
revamp and clean up implicit tagging. this closes #13
  • Loading branch information
JesusMcCloud authored Sep 16, 2024
1 parent 9464eb5 commit b8cc4a2
Show file tree
Hide file tree
Showing 25 changed files with 639 additions and 299 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* `Iterator<Byte>.decodeAsn1VarUInt()`
* `Iterable<Byte>.decodeAsn1VarUInt()`
* `ByteArray.decodeAsn1VarUInt()`
* Revamp implicit tagging

## 3.0

Expand Down
119 changes: 62 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ sealed class CryptoPublicKey : Asn1Encodable<Asn1Sequence>, 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!")
Expand Down Expand Up @@ -160,6 +160,7 @@ sealed class CryptoPublicKey : Asn1Encodable<Asn1Sequence>, Identifiable {
(BERTags.SEQUENCE or BERTags.CONSTRUCTED) -> Rsa.fromPKCS1encoded(it)
else -> throw IllegalArgumentException("Unsupported Key type")
}

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ sealed interface CryptoSignature : Asn1Encodable<Asn1Element> {
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()
Expand All @@ -222,6 +222,7 @@ sealed interface CryptoSignature : Asn1Encodable<Asn1Element> {

@Deprecated("use fromRawBytes", ReplaceWith("CryptoSignature.EC.fromRawBytes(input)"))
operator fun invoke(input: ByteArray): DefiniteLength = fromRawBytes(input)

}

}
Expand Down Expand Up @@ -256,13 +257,14 @@ sealed interface CryptoSignature : Asn1Encodable<Asn1Element> {

companion object : Asn1Decodable<Asn1Element, CryptoSignature> {
@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")
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -66,7 +66,7 @@ enum class X509SignatureAlgorithm(
}
}
}
+Tagged(2u) {
+ExplicitlyTagged(2u) {
+Asn1.Int(bits / 8)
}
}
Expand Down Expand Up @@ -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)

Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -162,6 +162,7 @@ enum class X509SignatureAlgorithm(
}
}
}

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class Asn1BitString private constructor(
* The overall [Asn1Primitive.content] resulting from [encodeToTlv] is `byteArrayOf(numPaddingBits, *rawBytes)`
*/
val rawBytes: ByteArray,
) :
Asn1Encodable<Asn1Primitive> {

) : Asn1Encodable<Asn1Primitive> {


/**
Expand Down Expand Up @@ -68,7 +68,7 @@ class Asn1BitString private constructor(
return bitset
}

companion object : Asn1TagVerifyingDecodable<Asn1BitString> {
companion object : Asn1Decodable<Asn1Primitive, Asn1BitString> {
private fun fromBitSet(bitSet: BitSet): Pair<Byte, ByteArray> {
val rawBytes = bitSet.bytes.map {
var res = 0
Expand All @@ -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..<src.content.size))
}

@Throws(Asn1Exception::class)
override fun decodeFromTlv(src: Asn1Primitive) = decodeFromTlv(src, null)

@Throws(Asn1Exception::class)
override fun decodeFromTlv(src: Asn1Primitive, tagOverride: Asn1Element.Tag?) = decode(src, tagOverride)
}

override fun encodeToTlv() = Asn1Primitive(Asn1Element.Tag.BIT_STRING, byteArrayOf(numPaddingBits, *rawBytes))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private class Asn1Reader(input: ByteArray) {
if (tlv.isSequence()) result.add(Asn1Sequence(Asn1Reader(tlv.content).doParse()))
else if (tlv.isSet()) result.add(Asn1Set.fromPresorted(Asn1Reader(tlv.content).doParse()))
else if (tlv.isExplicitlyTagged()) result.add(
Asn1Tagged(
Asn1ExplicitlyTagged(
tlv.tag.tagValue,
Asn1Reader(tlv.content).doParse()
)
Expand All @@ -60,7 +60,7 @@ private class Asn1Reader(input: ByteArray) {
}

private fun TLV.isSet() = tag == Asn1Element.Tag.SET
private fun TLV.isSequence() = (tag == Asn1Element.Tag.ASN1_SEQUENCE)
private fun TLV.isSequence() = (tag == Asn1Element.Tag.SEQUENCE)
private fun TLV.isExplicitlyTagged() = tag.isExplicitlyTagged

@Throws(Asn1Exception::class)
Expand Down Expand Up @@ -187,7 +187,7 @@ fun Asn1Primitive.readInstantOrNull() = catching { readInstant() }.getOrNull()


/**
* decodes this [Asn1Primitive]'s content into an [ByteArray], assuming it was encoded as BIT STRING
* decodes this [Asn1Primitive]'s content into a [ByteArray], assuming it was encoded as BIT STRING
*
* @throws Asn1Exception on invalid input
*/
Expand All @@ -214,23 +214,6 @@ fun Asn1Primitive.readNull() = decode(Asn1Element.Tag.NULL) {}
fun Asn1Primitive.readNullOrNull() = catching { readNull() }.getOrNull()


/**
* Returns this [Asn1Tagged] children, if its tag matches [tag]
*
* @throws Asn1TagMismatchException if the tag does not match
*/
@Throws(Asn1TagMismatchException::class)
fun Asn1Tagged.verifyTag(tag: ULong): List<Asn1Element> {
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]
Expand Down
Loading

0 comments on commit b8cc4a2

Please sign in to comment.