1
1
mirror of https://github.com/wader/fq.git synced 2024-12-26 15:02:28 +03:00
fq/format/asn1/asn1_ber.go
Mattias Wadman 87b2c6c10c help,doc: Use markdown for format documentation again
Markdown is used as is in online documentation and in cli the markdown decoder
is used to decode and the some jq code massages it into something cli friendly.

Was just too much of a mess to have doc in jq.
2022-09-11 00:53:29 +02:00

422 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(decode.Format{
Name: format.ASN1_BER,
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.UToSymStr{
classUniversal: "universal",
classApplication: "application",
classContext: "context",
classPrivate: "private",
}
const (
formPrimitive = 0
formConstructed = 1
)
var constructedPrimitiveMap = scalar.UToSymStr{
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.UToSymStr{
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.UToSymStr{
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.FieldUFn("tag", decodeTagNumber, universalTypeMap, scalar.ActualHex)
default:
tag = d.FieldUFn("tag", decodeTagNumber)
}
length := d.FieldUFn("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.PeekBits(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.URangeToScalar{
{Range: [2]uint64{0, 0}, S: scalar.S{Sym: false}},
{Range: [2]uint64{0x01, 0xff1}, S: scalar.S{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, "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, "bitio.Copy")
}
}
case class == classUniversal && tag == universalTypeNull:
d.FieldValueNil("value")
case class == classUniversal && tag == universalTypeObjectIdentifier:
d.FieldArray("value", func(d *decode.D) {
// first byte is = oid0*40 + oid1
d.FieldUFn("oid", func(d *decode.D) uint64 { return d.U8() / 40 })
d.SeekRel(-8)
d.FieldUFn("oid", func(d *decode.D) uint64 { return d.U8() % 40 })
for !d.End() {
d.FieldUFn("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.FieldValueU("value", 0)
default:
switch d.FieldBool("binary_encoding") {
case true:
s := d.FieldScalarBool("sign", scalar.BoolToSymS{
true: -1,
false: 1,
}).SymS()
base := d.FieldScalarU2("base", scalar.UToSymU{
0b00: 2,
0b01: 8,
0b10: 16,
0b11: 0,
}).SymU()
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.FieldValueFloat("value", m)
case false:
switch d.FieldBool("decimal_encoding") {
case true:
n := d.FieldU6("special", scalar.UToSymStr{
decimalPlusInfinity: "plus_infinity",
decimalMinusInfinity: "minus_infinity",
decimalNan: "nan",
decimalMinusZero: "minus_zero",
})
switch n {
case decimalPlusInfinity:
d.FieldValueFloat("value", math.Inf(1))
case decimalMinusInfinity:
d.FieldValueFloat("value", math.Inf(-1))
case decimalNan:
d.FieldValueFloat("value", math.NaN())
case decimalMinusZero:
d.FieldValueFloat("value", -0)
}
case false:
d.FieldU6("representation", scalar.UToSymStr{
0b00_00_01: "nr1",
0b00_00_10: "nr2",
0b00_00_11: "nr3",
})
d.FieldFFn("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) any {
decodeASN1BERValue(d, nil, nil, formConstructed, universalTypeSequence)
return nil
}