1
1
mirror of https://github.com/wader/fq.git synced 2024-11-25 05:55:43 +03:00
fq/format/asn1/asn1_ber.go
2024-04-12 17:23:03 +02:00

421 lines
12 KiB
Go

package asn1
// T-REC-X.690-200811 (BER, DER, CER)
// https://www.itu.int/ITU-T/studygroups/com10/languages/X.690_1297.pdf
// https://cdn.standards.iteh.ai/samples/12285/039296509e8b40f3b25ba025de60365d/ISO-6093-1985.pdf
// https://en.wikipedia.org/wiki/X.690
// https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/
// https://luca.ntop.org/Teaching/Appunti/asn1.html
// https://lapo.it/asn1js/
// TODO: schema
// TODO: der/cer via mode?
// TODO: better torepr
// TODO: utc time
// TODO: validate CER DER
// TODO: bigrat?
import (
"embed"
"math"
"strconv"
"strings"
"github.com/wader/fq/format"
"github.com/wader/fq/pkg/bitio"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)
//go:embed asn1_ber.jq
//go:embed asn1_ber.md
var asn1FS embed.FS
func init() {
interp.RegisterFormat(
format.ASN1_BER,
&decode.Format{
Description: "ASN1 BER (basic encoding rules, also CER and DER)",
DecodeFn: decodeASN1BER,
Functions: []string{"torepr"},
})
interp.RegisterFS(asn1FS)
}
const (
classUniversal = 0b00
classApplication = 0b01
classContext = 0b10
classPrivate = 0b11
)
var tagClassMap = scalar.UintMapSymStr{
classUniversal: "universal",
classApplication: "application",
classContext: "context",
classPrivate: "private",
}
const (
formPrimitive = 0
formConstructed = 1
)
var constructedPrimitiveMap = scalar.UintMapSymStr{
formConstructed: "constructed",
formPrimitive: "primitive",
}
const (
universalTypeEndOfContent = 0x00
universalTypeBoolean = 0x01
universalTypeInteger = 0x02
universalTypeBitString = 0x03
universalTypeOctetString = 0x04
universalTypeNull = 0x05
universalTypeObjectIdentifier = 0x06
universalTypeObjectDescriptor = 0x07 // not encoded, just documentation?
universalTypeExternal = 0x08
universalTypeReal = 0x09
universalTypeEnumerated = 0x0a
universalTypeEmbedded = 0x0b
universalTypeUTF8string = 0x0c
universalTypeSequence = 0x10
universalTypeSet = 0x11
universalTypeNumericString = 0x12
universalTypePrintableString = 0x13
universalTypeTeletexString = 0x14
universalTypeVideotexString = 0x15
universalTypeIA5String = 0x16
universalTypeUTCTime = 0x17
universalTypeGeneralizedtime = 0x18
universalTypeGraphicString = 0x19 // not encoded?
universalTypeVisibleString = 0x1a
universalTypeGeneralString = 0x1b
universalTypeUniversalString = 0x1c // not encoded?
)
var universalTypeMap = scalar.UintMapSymStr{
universalTypeEndOfContent: "end_of_content",
universalTypeBoolean: "boolean",
universalTypeInteger: "integer",
universalTypeBitString: "bit_string",
universalTypeOctetString: "octet_string",
universalTypeNull: "null",
universalTypeObjectIdentifier: "object_identifier",
universalTypeObjectDescriptor: "object_descriptor",
universalTypeExternal: "external",
universalTypeReal: "real",
universalTypeEnumerated: "enumerated",
universalTypeEmbedded: "embedded",
universalTypeUTF8string: "utf8_string",
universalTypeSequence: "sequence",
universalTypeSet: "set",
universalTypeNumericString: "numeric_string",
universalTypePrintableString: "printable_string",
universalTypeTeletexString: "teletex_string",
universalTypeVideotexString: "videotex_string",
universalTypeIA5String: "ia5_string",
universalTypeUTCTime: "utc_time",
universalTypeGeneralizedtime: "generalized_time",
universalTypeGraphicString: "graphic_string",
universalTypeVisibleString: "visible_string",
universalTypeGeneralString: "general_string",
universalTypeUniversalString: "universal_string",
}
const (
lengthIndefinite = 0
lengthEndMarker = 0x00_00
)
const (
decimalPlusInfinity = 0b00_00_00
decimalMinusInfinity = 0b00_00_01
decimalNan = 0b00_00_10
decimalMinusZero = 0b00_00_11
)
var lengthMap = scalar.UintMapSymStr{
0: "indefinite",
}
func decodeLength(d *decode.D) uint64 {
n := d.U8()
if n&0b1000_0000 != 0 {
n = n & 0b0111_1111
if n == 0 {
return lengthIndefinite
}
if n == 127 {
d.Errorf("length 127 reserved")
}
// TODO: bigint
return d.U(int(n) * 8)
}
return n & 0b0111_1111
}
// TODO: bigint?
func decodeTagNumber(d *decode.D) uint64 {
v := d.U5()
moreBytes := v == 0b11111
for moreBytes {
moreBytes = d.Bool()
v = v<<7 | d.U7()
}
return v
}
func decodeASN1BERValue(d *decode.D, bib *bitio.Buffer, sb *strings.Builder, parentForm uint64, parentTag uint64) {
class := d.FieldU2("class", tagClassMap)
form := d.FieldU1("form", constructedPrimitiveMap)
// TODO: verify
// TODO: constructed types verify
_ = parentTag
_ = parentForm
var tag uint64
switch class {
case classUniversal:
tag = d.FieldUintFn("tag", decodeTagNumber, universalTypeMap, scalar.UintHex)
default:
tag = d.FieldUintFn("tag", decodeTagNumber)
}
length := d.FieldUintFn("length", decodeLength, lengthMap)
var l int64
switch length {
case lengthIndefinite:
// null has zero length byte
if !(class == classUniversal && tag == universalTypeNull) && form == formPrimitive {
d.Fatalf("primitive with indefinite length")
}
l = d.BitsLeft()
default:
l = int64(length) * 8
}
d.LimitedFn(l, func(d *decode.D) {
switch {
case form == formConstructed || tag == universalTypeSequence || tag == universalTypeSet:
d.FieldArray("constructed", func(d *decode.D) {
for !d.End() {
if length == lengthIndefinite && d.PeekUintBits(16) == lengthEndMarker {
break
}
if form == formConstructed && bib == nil && sb == nil {
switch tag {
case universalTypeBitString:
bib = &bitio.Buffer{}
case universalTypeOctetString:
bib = &bitio.Buffer{}
case universalTypeUTF8string,
universalTypeNumericString,
universalTypePrintableString,
universalTypeTeletexString,
universalTypeVideotexString,
universalTypeIA5String,
universalTypeUTCTime,
universalTypeVisibleString, // not encoded?
universalTypeGeneralString: // not encoded?
sb = &strings.Builder{}
}
}
d.FieldStruct("object", func(d *decode.D) { decodeASN1BERValue(d, bib, sb, form, tag) })
}
})
if length == lengthIndefinite {
d.FieldU16("end_marker")
}
if form == formConstructed {
switch tag {
case universalTypeBitString:
if bib != nil {
buf, bufLen := bib.Bits()
d.FieldRootBitBuf("value", bitio.NewBitReader(buf, bufLen))
}
case universalTypeOctetString:
if bib != nil {
buf, bufLen := bib.Bits()
d.FieldRootBitBuf("value", bitio.NewBitReader(buf, bufLen))
}
case universalTypeUTF8string,
universalTypeNumericString,
universalTypePrintableString,
universalTypeTeletexString,
universalTypeVideotexString,
universalTypeIA5String,
universalTypeUTCTime,
universalTypeVisibleString, // not encoded?
universalTypeGeneralString: // not encoded?
if sb != nil {
d.FieldValueStr("value", sb.String())
}
}
}
case class == classUniversal && tag == universalTypeEndOfContent:
// nop
case class == classUniversal && tag == universalTypeBoolean:
d.FieldU8("value", scalar.UintRangeToScalar{
{Range: [2]uint64{0, 0}, S: scalar.Uint{Sym: false}},
{Range: [2]uint64{0x01, 0xff1}, S: scalar.Uint{Sym: true}},
})
case class == classUniversal && tag == universalTypeInteger:
if length > 8 {
d.FieldSBigInt("value", int(length)*8)
} else {
d.FieldS("value", int(length)*8)
}
case class == classUniversal && tag == universalTypeBitString:
unusedBitsCount := d.FieldU8("unused_bits_count")
if unusedBitsCount > 7 {
d.Fatalf("unusedBitsCount %d > 7", unusedBitsCount)
}
br := d.FieldRawLen("value", int64(length-1)*8-int64(unusedBitsCount))
if bib != nil {
// TODO: helper?
if _, err := bitio.Copy(bib, br); err != nil {
d.IOPanic(err, "value", "bitio.Copy")
}
}
if unusedBitsCount > 0 {
d.FieldRawLen("unused_bits", int64(unusedBitsCount))
}
case class == classUniversal && tag == universalTypeOctetString:
br := d.FieldRawLen("value", int64(length)*8)
if bib != nil {
// TODO: helper?
if _, err := bitio.Copy(bib, br); err != nil {
d.IOPanic(err, "value", "bitio.Copy")
}
}
case class == classUniversal && tag == universalTypeNull:
d.FieldValueAny("value", nil)
case class == classUniversal && tag == universalTypeObjectIdentifier:
d.FieldArray("value", func(d *decode.D) {
// first byte is = oid0*40 + oid1
d.FieldUintFn("oid", func(d *decode.D) uint64 { return d.U8() / 40 })
d.SeekRel(-8)
d.FieldUintFn("oid", func(d *decode.D) uint64 { return d.U8() % 40 })
for !d.End() {
d.FieldUintFn("oid", func(d *decode.D) uint64 {
more := true
var n uint64
for more {
b := d.U8()
n = n<<7 | b&0b0111_1111
more = b&0b1000_0000 != 0
}
return n
})
}
})
case class == classUniversal && tag == universalTypeObjectDescriptor: // not encoded, just documentation?
// nop
case class == classUniversal && tag == universalTypeExternal:
d.FieldRawLen("value", int64(length)*8)
case class == classUniversal && tag == universalTypeReal:
switch {
case length == 0:
d.FieldValueUint("value", 0)
default:
switch d.FieldBool("binary_encoding") {
case true:
s := d.FieldScalarBool("sign", scalar.BoolMapSymSint{
true: -1,
false: 1,
}).SymSint()
base := d.FieldScalarU2("base", scalar.UintMapSymUint{
0b00: 2,
0b01: 8,
0b10: 16,
0b11: 0,
}).SymUint()
scale := d.FieldU2("scale")
format := d.FieldU2("format")
var exp int64
switch format {
case 0b00:
exp = d.FieldS8("exp")
case 0b01:
exp = d.FieldS16("exp")
case 0b10:
exp = d.FieldS24("exp")
default:
n := d.FieldU8("exp_bytes")
// TODO: bigint?
exp = d.FieldS("exp", int(n)*8)
}
n := d.FieldU("n", int(d.BitsLeft()))
m := float64(s) * float64(n) * math.Pow(float64(base), float64(exp)) * float64(int(1)<<scale)
d.FieldValueFlt("value", m)
case false:
switch d.FieldBool("decimal_encoding") {
case true:
n := d.FieldU6("special", scalar.UintMapSymStr{
decimalPlusInfinity: "plus_infinity",
decimalMinusInfinity: "minus_infinity",
decimalNan: "nan",
decimalMinusZero: "minus_zero",
})
switch n {
case decimalPlusInfinity:
d.FieldValueFlt("value", math.Inf(1))
case decimalMinusInfinity:
d.FieldValueFlt("value", math.Inf(-1))
case decimalNan:
d.FieldValueFlt("value", math.NaN())
case decimalMinusZero:
d.FieldValueFlt("value", -0)
}
case false:
d.FieldU6("representation", scalar.UintMapSymStr{
0b00_00_01: "nr1",
0b00_00_10: "nr2",
0b00_00_11: "nr3",
})
d.FieldFltFn("value", func(d *decode.D) float64 {
// TODO: can ParseFloat do all ISO-6093 nr?
n, _ := strconv.ParseFloat(d.UTF8(int(d.BitsLeft()/8)), 64)
return n
})
}
}
}
case class == classUniversal && tag == universalTypeUTF8string,
class == classUniversal && tag == universalTypeNumericString,
class == classUniversal && tag == universalTypePrintableString,
class == classUniversal && tag == universalTypeTeletexString,
class == classUniversal && tag == universalTypeVideotexString,
class == classUniversal && tag == universalTypeIA5String,
class == classUniversal && tag == universalTypeUTCTime,
class == classUniversal && tag == universalTypeVisibleString, // not encoded?
class == classUniversal && tag == universalTypeGeneralString: // not encoded?
// TODO: restrict?
s := d.FieldUTF8("value", int(length))
if sb != nil {
sb.WriteString(s)
}
case class == classUniversal && tag == universalTypeGeneralizedtime:
d.FieldRawLen("value", int64(length)*8)
default:
d.FieldRawLen("value", l)
}
})
}
func decodeASN1BER(d *decode.D) any {
decodeASN1BERValue(d, nil, nil, formConstructed, universalTypeSequence)
return nil
}