package asn1
import (
"bytes"
"errors"
"fmt"
"math/big"
"reflect"
"sort"
"time"
"unicode/utf8"
)
var (
byte00Encoder encoder = byteEncoder (0x00 )
byteFFEncoder encoder = byteEncoder (0xff )
)
type encoder interface {
Len () int
Encode (dst []byte )
}
type byteEncoder byte
func (c byteEncoder ) Len () int {
return 1
}
func (c byteEncoder ) Encode (dst []byte ) {
dst [0 ] = byte (c )
}
type bytesEncoder []byte
func (b bytesEncoder ) Len () int {
return len (b )
}
func (b bytesEncoder ) Encode (dst []byte ) {
if copy (dst , b ) != len (b ) {
panic ("internal error" )
}
}
type stringEncoder string
func (s stringEncoder ) Len () int {
return len (s )
}
func (s stringEncoder ) Encode (dst []byte ) {
if copy (dst , s ) != len (s ) {
panic ("internal error" )
}
}
type multiEncoder []encoder
func (m multiEncoder ) Len () int {
var size int
for _ , e := range m {
size += e .Len ()
}
return size
}
func (m multiEncoder ) Encode (dst []byte ) {
var off int
for _ , e := range m {
e .Encode (dst [off :])
off += e .Len ()
}
}
type setEncoder []encoder
func (s setEncoder ) Len () int {
var size int
for _ , e := range s {
size += e .Len ()
}
return size
}
func (s setEncoder ) Encode (dst []byte ) {
l := make ([][]byte , len (s ))
for i , e := range s {
l [i ] = make ([]byte , e .Len ())
e .Encode (l [i ])
}
sort .Slice (l , func (i , j int ) bool {
return bytes .Compare (l [i ], l [j ]) < 0
})
var off int
for _ , b := range l {
copy (dst [off :], b )
off += len (b )
}
}
type taggedEncoder struct {
scratch [8 ]byte
tag encoder
body encoder
}
func (t *taggedEncoder ) Len () int {
return t .tag .Len () + t .body .Len ()
}
func (t *taggedEncoder ) Encode (dst []byte ) {
t .tag .Encode (dst )
t .body .Encode (dst [t .tag .Len ():])
}
type int64Encoder int64
func (i int64Encoder ) Len () int {
n := 1
for i > 127 {
n ++
i >>= 8
}
for i < -128 {
n ++
i >>= 8
}
return n
}
func (i int64Encoder ) Encode (dst []byte ) {
n := i .Len ()
for j := 0 ; j < n ; j ++ {
dst [j ] = byte (i >> uint ((n -1 -j )*8 ))
}
}
func base128IntLength (n int64 ) int {
if n == 0 {
return 1
}
l := 0
for i := n ; i > 0 ; i >>= 7 {
l ++
}
return l
}
func appendBase128Int (dst []byte , n int64 ) []byte {
l := base128IntLength (n )
for i := l - 1 ; i >= 0 ; i -- {
o := byte (n >> uint (i *7 ))
o &= 0x7f
if i != 0 {
o |= 0x80
}
dst = append (dst , o )
}
return dst
}
func makeBigInt (n *big .Int ) (encoder , error ) {
if n == nil {
return nil , StructuralError {"empty integer" }
}
if n .Sign () < 0 {
nMinus1 := new (big .Int ).Neg (n )
nMinus1 .Sub (nMinus1 , bigOne )
bytes := nMinus1 .Bytes ()
for i := range bytes {
bytes [i ] ^= 0xff
}
if len (bytes ) == 0 || bytes [0 ]&0x80 == 0 {
return multiEncoder ([]encoder {byteFFEncoder , bytesEncoder (bytes )}), nil
}
return bytesEncoder (bytes ), nil
} else if n .Sign () == 0 {
return byte00Encoder , nil
} else {
bytes := n .Bytes ()
if len (bytes ) > 0 && bytes [0 ]&0x80 != 0 {
return multiEncoder ([]encoder {byte00Encoder , bytesEncoder (bytes )}), nil
}
return bytesEncoder (bytes ), nil
}
}
func appendLength (dst []byte , i int ) []byte {
n := lengthLength (i )
for ; n > 0 ; n -- {
dst = append (dst , byte (i >>uint ((n -1 )*8 )))
}
return dst
}
func lengthLength (i int ) (numBytes int ) {
numBytes = 1
for i > 255 {
numBytes ++
i >>= 8
}
return
}
func appendTagAndLength (dst []byte , t tagAndLength ) []byte {
b := uint8 (t .class ) << 6
if t .isCompound {
b |= 0x20
}
if t .tag >= 31 {
b |= 0x1f
dst = append (dst , b )
dst = appendBase128Int (dst , int64 (t .tag ))
} else {
b |= uint8 (t .tag )
dst = append (dst , b )
}
if t .length >= 128 {
l := lengthLength (t .length )
dst = append (dst , 0x80 |byte (l ))
dst = appendLength (dst , t .length )
} else {
dst = append (dst , byte (t .length ))
}
return dst
}
type bitStringEncoder BitString
func (b bitStringEncoder ) Len () int {
return len (b .Bytes ) + 1
}
func (b bitStringEncoder ) Encode (dst []byte ) {
dst [0 ] = byte ((8 - b .BitLength %8 ) % 8 )
if copy (dst [1 :], b .Bytes ) != len (b .Bytes ) {
panic ("internal error" )
}
}
type oidEncoder []int
func (oid oidEncoder ) Len () int {
l := base128IntLength (int64 (oid [0 ]*40 + oid [1 ]))
for i := 2 ; i < len (oid ); i ++ {
l += base128IntLength (int64 (oid [i ]))
}
return l
}
func (oid oidEncoder ) Encode (dst []byte ) {
dst = appendBase128Int (dst [:0 ], int64 (oid [0 ]*40 +oid [1 ]))
for i := 2 ; i < len (oid ); i ++ {
dst = appendBase128Int (dst , int64 (oid [i ]))
}
}
func makeObjectIdentifier (oid []int ) (e encoder , err error ) {
if len (oid ) < 2 || oid [0 ] > 2 || (oid [0 ] < 2 && oid [1 ] >= 40 ) {
return nil , StructuralError {"invalid object identifier" }
}
return oidEncoder (oid ), nil
}
func makePrintableString (s string ) (e encoder , err error ) {
for i := 0 ; i < len (s ); i ++ {
if !isPrintable (s [i ], allowAsterisk , rejectAmpersand ) {
return nil , StructuralError {"PrintableString contains invalid character" }
}
}
return stringEncoder (s ), nil
}
func makeIA5String (s string ) (e encoder , err error ) {
for i := 0 ; i < len (s ); i ++ {
if s [i ] > 127 {
return nil , StructuralError {"IA5String contains invalid character" }
}
}
return stringEncoder (s ), nil
}
func makeNumericString (s string ) (e encoder , err error ) {
for i := 0 ; i < len (s ); i ++ {
if !isNumeric (s [i ]) {
return nil , StructuralError {"NumericString contains invalid character" }
}
}
return stringEncoder (s ), nil
}
func makeUTF8String (s string ) encoder {
return stringEncoder (s )
}
func appendTwoDigits (dst []byte , v int ) []byte {
return append (dst , byte ('0' +(v /10 )%10 ), byte ('0' +v %10 ))
}
func appendFourDigits (dst []byte , v int ) []byte {
var bytes [4 ]byte
for i := range bytes {
bytes [3 -i ] = '0' + byte (v %10 )
v /= 10
}
return append (dst , bytes [:]...)
}
func outsideUTCRange (t time .Time ) bool {
year := t .Year ()
return year < 1950 || year >= 2050
}
func makeUTCTime (t time .Time ) (e encoder , err error ) {
dst := make ([]byte , 0 , 18 )
dst , err = appendUTCTime (dst , t )
if err != nil {
return nil , err
}
return bytesEncoder (dst ), nil
}
func makeGeneralizedTime (t time .Time ) (e encoder , err error ) {
dst := make ([]byte , 0 , 20 )
dst , err = appendGeneralizedTime (dst , t )
if err != nil {
return nil , err
}
return bytesEncoder (dst ), nil
}
func appendUTCTime (dst []byte , t time .Time ) (ret []byte , err error ) {
year := t .Year ()
switch {
case 1950 <= year && year < 2000 :
dst = appendTwoDigits (dst , year -1900 )
case 2000 <= year && year < 2050 :
dst = appendTwoDigits (dst , year -2000 )
default :
return nil , StructuralError {"cannot represent time as UTCTime" }
}
return appendTimeCommon (dst , t ), nil
}
func appendGeneralizedTime (dst []byte , t time .Time ) (ret []byte , err error ) {
year := t .Year ()
if year < 0 || year > 9999 {
return nil , StructuralError {"cannot represent time as GeneralizedTime" }
}
dst = appendFourDigits (dst , year )
return appendTimeCommon (dst , t ), nil
}
func appendTimeCommon (dst []byte , t time .Time ) []byte {
_ , month , day := t .Date ()
dst = appendTwoDigits (dst , int (month ))
dst = appendTwoDigits (dst , day )
hour , min , sec := t .Clock ()
dst = appendTwoDigits (dst , hour )
dst = appendTwoDigits (dst , min )
dst = appendTwoDigits (dst , sec )
_ , offset := t .Zone ()
switch {
case offset /60 == 0 :
return append (dst , 'Z' )
case offset > 0 :
dst = append (dst , '+' )
case offset < 0 :
dst = append (dst , '-' )
}
offsetMinutes := offset / 60
if offsetMinutes < 0 {
offsetMinutes = -offsetMinutes
}
dst = appendTwoDigits (dst , offsetMinutes /60 )
dst = appendTwoDigits (dst , offsetMinutes %60 )
return dst
}
func stripTagAndLength (in []byte ) []byte {
_ , offset , err := parseTagAndLength (in , 0 )
if err != nil {
return in
}
return in [offset :]
}
func makeBody (value reflect .Value , params fieldParameters ) (e encoder , err error ) {
switch value .Type () {
case flagType :
return bytesEncoder (nil ), nil
case timeType :
t := value .Interface ().(time .Time )
if params .timeType == TagGeneralizedTime || outsideUTCRange (t ) {
return makeGeneralizedTime (t )
}
return makeUTCTime (t )
case bitStringType :
return bitStringEncoder (value .Interface ().(BitString )), nil
case objectIdentifierType :
return makeObjectIdentifier (value .Interface ().(ObjectIdentifier ))
case bigIntType :
return makeBigInt (value .Interface ().(*big .Int ))
}
switch v := value ; v .Kind () {
case reflect .Bool :
if v .Bool () {
return byteFFEncoder , nil
}
return byte00Encoder , nil
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
return int64Encoder (v .Int ()), nil
case reflect .Struct :
t := v .Type ()
for i := 0 ; i < t .NumField (); i ++ {
if !t .Field (i ).IsExported () {
return nil , StructuralError {"struct contains unexported fields" }
}
}
startingField := 0
n := t .NumField ()
if n == 0 {
return bytesEncoder (nil ), nil
}
if t .Field (0 ).Type == rawContentsType {
s := v .Field (0 )
if s .Len () > 0 {
bytes := s .Bytes ()
return bytesEncoder (stripTagAndLength (bytes )), nil
}
startingField = 1
}
switch n1 := n - startingField ; n1 {
case 0 :
return bytesEncoder (nil ), nil
case 1 :
return makeField (v .Field (startingField ), parseFieldParameters (t .Field (startingField ).Tag .Get ("asn1" )))
default :
m := make ([]encoder , n1 )
for i := 0 ; i < n1 ; i ++ {
m [i ], err = makeField (v .Field (i +startingField ), parseFieldParameters (t .Field (i +startingField ).Tag .Get ("asn1" )))
if err != nil {
return nil , err
}
}
return multiEncoder (m ), nil
}
case reflect .Slice :
sliceType := v .Type ()
if sliceType .Elem ().Kind () == reflect .Uint8 {
return bytesEncoder (v .Bytes ()), nil
}
var fp fieldParameters
switch l := v .Len (); l {
case 0 :
return bytesEncoder (nil ), nil
case 1 :
return makeField (v .Index (0 ), fp )
default :
m := make ([]encoder , l )
for i := 0 ; i < l ; i ++ {
m [i ], err = makeField (v .Index (i ), fp )
if err != nil {
return nil , err
}
}
if params .set {
return setEncoder (m ), nil
}
return multiEncoder (m ), nil
}
case reflect .String :
switch params .stringType {
case TagIA5String :
return makeIA5String (v .String ())
case TagPrintableString :
return makePrintableString (v .String ())
case TagNumericString :
return makeNumericString (v .String ())
default :
return makeUTF8String (v .String ()), nil
}
}
return nil , StructuralError {"unknown Go type" }
}
func makeField (v reflect .Value , params fieldParameters ) (e encoder , err error ) {
if !v .IsValid () {
return nil , fmt .Errorf ("asn1: cannot marshal nil value" )
}
if v .Kind () == reflect .Interface && v .Type ().NumMethod () == 0 {
return makeField (v .Elem (), params )
}
if v .Kind () == reflect .Slice && v .Len () == 0 && params .omitEmpty {
return bytesEncoder (nil ), nil
}
if params .optional && params .defaultValue != nil && canHaveDefaultValue (v .Kind ()) {
defaultValue := reflect .New (v .Type ()).Elem ()
defaultValue .SetInt (*params .defaultValue )
if reflect .DeepEqual (v .Interface (), defaultValue .Interface ()) {
return bytesEncoder (nil ), nil
}
}
if params .optional && params .defaultValue == nil {
if reflect .DeepEqual (v .Interface (), reflect .Zero (v .Type ()).Interface ()) {
return bytesEncoder (nil ), nil
}
}
if v .Type () == rawValueType {
rv := v .Interface ().(RawValue )
if len (rv .FullBytes ) != 0 {
return bytesEncoder (rv .FullBytes ), nil
}
t := new (taggedEncoder )
t .tag = bytesEncoder (appendTagAndLength (t .scratch [:0 ], tagAndLength {rv .Class , rv .Tag , len (rv .Bytes ), rv .IsCompound }))
t .body = bytesEncoder (rv .Bytes )
return t , nil
}
matchAny , tag , isCompound , ok := getUniversalType (v .Type ())
if !ok || matchAny {
return nil , StructuralError {fmt .Sprintf ("unknown Go type: %v" , v .Type ())}
}
if params .timeType != 0 && tag != TagUTCTime {
return nil , StructuralError {"explicit time type given to non-time member" }
}
if params .stringType != 0 && tag != TagPrintableString {
return nil , StructuralError {"explicit string type given to non-string member" }
}
switch tag {
case TagPrintableString :
if params .stringType == 0 {
for _ , r := range v .String () {
if r >= utf8 .RuneSelf || !isPrintable (byte (r ), rejectAsterisk , rejectAmpersand ) {
if !utf8 .ValidString (v .String ()) {
return nil , errors .New ("asn1: string not valid UTF-8" )
}
tag = TagUTF8String
break
}
}
} else {
tag = params .stringType
}
case TagUTCTime :
if params .timeType == TagGeneralizedTime || outsideUTCRange (v .Interface ().(time .Time )) {
tag = TagGeneralizedTime
}
}
if params .set {
if tag != TagSequence {
return nil , StructuralError {"non sequence tagged as set" }
}
tag = TagSet
}
if tag == TagSet && !params .set {
params .set = true
}
t := new (taggedEncoder )
t .body , err = makeBody (v , params )
if err != nil {
return nil , err
}
bodyLen := t .body .Len ()
class := ClassUniversal
if params .tag != nil {
if params .application {
class = ClassApplication
} else if params .private {
class = ClassPrivate
} else {
class = ClassContextSpecific
}
if params .explicit {
t .tag = bytesEncoder (appendTagAndLength (t .scratch [:0 ], tagAndLength {ClassUniversal , tag , bodyLen , isCompound }))
tt := new (taggedEncoder )
tt .body = t
tt .tag = bytesEncoder (appendTagAndLength (tt .scratch [:0 ], tagAndLength {
class : class ,
tag : *params .tag ,
length : bodyLen + t .tag .Len (),
isCompound : true ,
}))
return tt , nil
}
tag = *params .tag
}
t .tag = bytesEncoder (appendTagAndLength (t .scratch [:0 ], tagAndLength {class , tag , bodyLen , isCompound }))
return t , nil
}
func Marshal (val any ) ([]byte , error ) {
return MarshalWithParams (val , "" )
}
func MarshalWithParams (val any , params string ) ([]byte , error ) {
e , err := makeField (reflect .ValueOf (val ), parseFieldParameters (params ))
if err != nil {
return nil , err
}
b := make ([]byte , e .Len ())
e .Encode (b )
return b , nil
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .