Fork of "encoding/asn1" from go standard library to add ASN.1 "choice" and indefinite length encodings.
This commit is contained in:
commit
5ca2e7da6f
1066
asn1/asn1.go
Normal file
1066
asn1/asn1.go
Normal file
File diff suppressed because it is too large
Load Diff
1098
asn1/asn1_test.go
Normal file
1098
asn1/asn1_test.go
Normal file
File diff suppressed because it is too large
Load Diff
182
asn1/common.go
Normal file
182
asn1/common.go
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package asn1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ASN.1 objects have metadata preceding them:
|
||||||
|
// the tag: the type of the object
|
||||||
|
// a flag denoting if this object is compound or not
|
||||||
|
// the class type: the namespace of the tag
|
||||||
|
// the length of the object, in bytes
|
||||||
|
|
||||||
|
// Here are some standard tags and classes
|
||||||
|
|
||||||
|
// ASN.1 tags represent the type of the following object.
|
||||||
|
const (
|
||||||
|
TagBoolean = 1
|
||||||
|
TagInteger = 2
|
||||||
|
TagBitString = 3
|
||||||
|
TagOctetString = 4
|
||||||
|
TagNull = 5
|
||||||
|
TagOID = 6
|
||||||
|
TagEnum = 10
|
||||||
|
TagUTF8String = 12
|
||||||
|
TagSequence = 16
|
||||||
|
TagSet = 17
|
||||||
|
TagNumericString = 18
|
||||||
|
TagPrintableString = 19
|
||||||
|
TagT61String = 20
|
||||||
|
TagIA5String = 22
|
||||||
|
TagUTCTime = 23
|
||||||
|
TagGeneralizedTime = 24
|
||||||
|
TagGeneralString = 27
|
||||||
|
)
|
||||||
|
|
||||||
|
// ASN.1 class types represent the namespace of the tag.
|
||||||
|
const (
|
||||||
|
ClassUniversal = 0
|
||||||
|
ClassApplication = 1
|
||||||
|
ClassContextSpecific = 2
|
||||||
|
ClassPrivate = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type tagAndLength struct {
|
||||||
|
class, tag, length int
|
||||||
|
isCompound bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASN.1 has IMPLICIT and EXPLICIT tags, which can be translated as "instead
|
||||||
|
// of" and "in addition to". When not specified, every primitive type has a
|
||||||
|
// default tag in the UNIVERSAL class.
|
||||||
|
//
|
||||||
|
// For example: a BIT STRING is tagged [UNIVERSAL 3] by default (although ASN.1
|
||||||
|
// doesn't actually have a UNIVERSAL keyword). However, by saying [IMPLICIT
|
||||||
|
// CONTEXT-SPECIFIC 42], that means that the tag is replaced by another.
|
||||||
|
//
|
||||||
|
// On the other hand, if it said [EXPLICIT CONTEXT-SPECIFIC 10], then an
|
||||||
|
// /additional/ tag would wrap the default tag. This explicit tag will have the
|
||||||
|
// compound flag set.
|
||||||
|
//
|
||||||
|
// (This is used in order to remove ambiguity with optional elements.)
|
||||||
|
//
|
||||||
|
// You can layer EXPLICIT and IMPLICIT tags to an arbitrary depth, however we
|
||||||
|
// don't support that here. We support a single layer of EXPLICIT or IMPLICIT
|
||||||
|
// tagging with tag strings on the fields of a structure.
|
||||||
|
|
||||||
|
// fieldParameters is the parsed representation of tag string from a structure field.
|
||||||
|
type fieldParameters struct {
|
||||||
|
optional bool // true iff the field is OPTIONAL
|
||||||
|
explicit bool // true iff an EXPLICIT tag is in use.
|
||||||
|
application bool // true iff an APPLICATION tag is in use.
|
||||||
|
private bool // true iff a PRIVATE tag is in use.
|
||||||
|
defaultValue *int64 // a default value for INTEGER typed fields (maybe nil).
|
||||||
|
tag *int // the EXPLICIT or IMPLICIT tag (maybe nil).
|
||||||
|
stringType int // the string tag to use when marshaling.
|
||||||
|
timeType int // the time tag to use when marshaling.
|
||||||
|
set bool // true iff this should be encoded as a SET
|
||||||
|
omitEmpty bool // true iff this should be omitted if empty when marshaling.
|
||||||
|
|
||||||
|
// Invariants:
|
||||||
|
// if explicit is set, tag is non-nil.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a tag string with the format specified in the package comment,
|
||||||
|
// parseFieldParameters will parse it into a fieldParameters structure,
|
||||||
|
// ignoring unknown parts of the string.
|
||||||
|
func parseFieldParameters(str string) (ret fieldParameters) {
|
||||||
|
for _, part := range strings.Split(str, ",") {
|
||||||
|
switch {
|
||||||
|
case part == "optional":
|
||||||
|
ret.optional = true
|
||||||
|
case part == "explicit":
|
||||||
|
ret.explicit = true
|
||||||
|
if ret.tag == nil {
|
||||||
|
ret.tag = new(int)
|
||||||
|
}
|
||||||
|
case part == "generalized":
|
||||||
|
ret.timeType = TagGeneralizedTime
|
||||||
|
case part == "utc":
|
||||||
|
ret.timeType = TagUTCTime
|
||||||
|
case part == "ia5":
|
||||||
|
ret.stringType = TagIA5String
|
||||||
|
case part == "printable":
|
||||||
|
ret.stringType = TagPrintableString
|
||||||
|
case part == "numeric":
|
||||||
|
ret.stringType = TagNumericString
|
||||||
|
case part == "utf8":
|
||||||
|
ret.stringType = TagUTF8String
|
||||||
|
case strings.HasPrefix(part, "default:"):
|
||||||
|
i, err := strconv.ParseInt(part[8:], 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
ret.defaultValue = new(int64)
|
||||||
|
*ret.defaultValue = i
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(part, "tag:"):
|
||||||
|
i, err := strconv.Atoi(part[4:])
|
||||||
|
if err == nil {
|
||||||
|
ret.tag = new(int)
|
||||||
|
*ret.tag = i
|
||||||
|
}
|
||||||
|
case part == "set":
|
||||||
|
ret.set = true
|
||||||
|
case part == "application":
|
||||||
|
ret.application = true
|
||||||
|
if ret.tag == nil {
|
||||||
|
ret.tag = new(int)
|
||||||
|
}
|
||||||
|
case part == "private":
|
||||||
|
ret.private = true
|
||||||
|
if ret.tag == nil {
|
||||||
|
ret.tag = new(int)
|
||||||
|
}
|
||||||
|
case part == "omitempty":
|
||||||
|
ret.omitEmpty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a reflected Go type, getUniversalType returns the default tag number
|
||||||
|
// and expected compound flag.
|
||||||
|
func getUniversalType(t reflect.Type) (matchAny bool, tagNumber int, isCompound, ok bool) {
|
||||||
|
switch t {
|
||||||
|
case rawValueType:
|
||||||
|
return true, -1, false, true
|
||||||
|
case objectIdentifierType:
|
||||||
|
return false, TagOID, false, true
|
||||||
|
case bitStringType:
|
||||||
|
return false, TagBitString, false, true
|
||||||
|
case timeType:
|
||||||
|
return false, TagUTCTime, false, true
|
||||||
|
case enumeratedType:
|
||||||
|
return false, TagEnum, false, true
|
||||||
|
case bigIntType:
|
||||||
|
return false, TagInteger, false, true
|
||||||
|
}
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return false, TagBoolean, false, true
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return false, TagInteger, false, true
|
||||||
|
case reflect.Struct:
|
||||||
|
return false, TagSequence, true, true
|
||||||
|
case reflect.Slice:
|
||||||
|
if t.Elem().Kind() == reflect.Uint8 {
|
||||||
|
return false, TagOctetString, false, true
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(t.Name(), "SET") {
|
||||||
|
return false, TagSet, true, true
|
||||||
|
}
|
||||||
|
return false, TagSequence, true, true
|
||||||
|
case reflect.String:
|
||||||
|
return false, TagPrintableString, false, true
|
||||||
|
}
|
||||||
|
return false, 0, false, false
|
||||||
|
}
|
691
asn1/marshal.go
Normal file
691
asn1/marshal.go
Normal file
@ -0,0 +1,691 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package asn1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
byte00Encoder encoder = byteEncoder(0x00)
|
||||||
|
byteFFEncoder encoder = byteEncoder(0xff)
|
||||||
|
)
|
||||||
|
|
||||||
|
// encoder represents an ASN.1 element that is waiting to be marshaled.
|
||||||
|
type encoder interface {
|
||||||
|
// Len returns the number of bytes needed to marshal this element.
|
||||||
|
Len() int
|
||||||
|
// Encode encodes this element by writing Len() bytes to dst.
|
||||||
|
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 taggedEncoder struct {
|
||||||
|
// scratch contains temporary space for encoding the tag and length of
|
||||||
|
// an element in order to avoid extra allocations.
|
||||||
|
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 {
|
||||||
|
// A negative number has to be converted to two's-complement
|
||||||
|
// form. So we'll invert and subtract 1. If the
|
||||||
|
// most-significant-bit isn't set then we'll need to pad the
|
||||||
|
// beginning with 0xff in order to keep the number negative.
|
||||||
|
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 {
|
||||||
|
// Zero is written as a single 0 zero rather than no bytes.
|
||||||
|
return byte00Encoder, nil
|
||||||
|
} else {
|
||||||
|
bytes := n.Bytes()
|
||||||
|
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
|
||||||
|
// We'll have to pad this with 0x00 in order to stop it
|
||||||
|
// looking like a negative number.
|
||||||
|
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++ {
|
||||||
|
// The asterisk is often used in PrintableString, even though
|
||||||
|
// it is invalid. If a PrintableString was specifically
|
||||||
|
// requested then the asterisk is permitted by this code.
|
||||||
|
// Ampersand is allowed in parsing due a handful of CA
|
||||||
|
// certificates, however when making new certificates
|
||||||
|
// it is rejected.
|
||||||
|
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).PkgPath != "" {
|
||||||
|
return nil, StructuralError{"struct contains unexported fields"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startingField := 0
|
||||||
|
|
||||||
|
n := t.NumField()
|
||||||
|
if n == 0 {
|
||||||
|
return bytesEncoder(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the first element of the structure is a non-empty
|
||||||
|
// RawContents, then we don't bother serializing the rest.
|
||||||
|
if t.Field(0).Type == rawContentsType {
|
||||||
|
s := v.Field(0)
|
||||||
|
if s.Len() > 0 {
|
||||||
|
bytes := s.Bytes()
|
||||||
|
/* The RawContents will contain the tag and
|
||||||
|
* length fields but we'll also be writing
|
||||||
|
* those ourselves, so we strip them out of
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 the field is an interface{} then recurse into it.
|
||||||
|
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 no default value is given then the zero value for the type is
|
||||||
|
// assumed to be the default value. This isn't obviously the correct
|
||||||
|
// behavior, but it's what Go has traditionally done.
|
||||||
|
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 {
|
||||||
|
// This is a string without an explicit string type. We'll use
|
||||||
|
// a PrintableString if the character set in the string is
|
||||||
|
// sufficiently limited, otherwise we'll use a UTF8String.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// implicit tag.
|
||||||
|
tag = *params.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
t.tag = bytesEncoder(appendTagAndLength(t.scratch[:0], tagAndLength{class, tag, bodyLen, isCompound}))
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal returns the ASN.1 encoding of val.
|
||||||
|
//
|
||||||
|
// In addition to the struct tags recognised by Unmarshal, the following can be
|
||||||
|
// used:
|
||||||
|
//
|
||||||
|
// ia5: causes strings to be marshaled as ASN.1, IA5String values
|
||||||
|
// omitempty: causes empty slices to be skipped
|
||||||
|
// printable: causes strings to be marshaled as ASN.1, PrintableString values
|
||||||
|
// utf8: causes strings to be marshaled as ASN.1, UTF8String values
|
||||||
|
// utc: causes time.Time to be marshaled as ASN.1, UTCTime values
|
||||||
|
// generalized: causes time.Time to be marshaled as ASN.1, GeneralizedTime values
|
||||||
|
func Marshal(val interface{}) ([]byte, error) {
|
||||||
|
return MarshalWithParams(val, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalWithParams allows field parameters to be specified for the
|
||||||
|
// top-level element. The form of the params is the same as the field tags.
|
||||||
|
func MarshalWithParams(val interface{}, 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
|
||||||
|
}
|
321
asn1/marshal_test.go
Normal file
321
asn1/marshal_test.go
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package asn1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type intStruct struct {
|
||||||
|
A int
|
||||||
|
}
|
||||||
|
|
||||||
|
type twoIntStruct struct {
|
||||||
|
A int
|
||||||
|
B int
|
||||||
|
}
|
||||||
|
|
||||||
|
type bigIntStruct struct {
|
||||||
|
A *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type nestedStruct struct {
|
||||||
|
A intStruct
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawContentsStruct struct {
|
||||||
|
Raw RawContent
|
||||||
|
A int
|
||||||
|
}
|
||||||
|
|
||||||
|
type implicitTagTest struct {
|
||||||
|
A int `asn1:"implicit,tag:5"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type explicitTagTest struct {
|
||||||
|
A int `asn1:"explicit,tag:5"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type flagTest struct {
|
||||||
|
A Flag `asn1:"tag:0,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type generalizedTimeTest struct {
|
||||||
|
A time.Time `asn1:"generalized"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ia5StringTest struct {
|
||||||
|
A string `asn1:"ia5"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type printableStringTest struct {
|
||||||
|
A string `asn1:"printable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type genericStringTest struct {
|
||||||
|
A string
|
||||||
|
}
|
||||||
|
|
||||||
|
type optionalRawValueTest struct {
|
||||||
|
A RawValue `asn1:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type omitEmptyTest struct {
|
||||||
|
A []string `asn1:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultTest struct {
|
||||||
|
A int `asn1:"optional,default:1"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type applicationTest struct {
|
||||||
|
A int `asn1:"application,tag:0"`
|
||||||
|
B int `asn1:"application,tag:1,explicit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type privateTest struct {
|
||||||
|
A int `asn1:"private,tag:0"`
|
||||||
|
B int `asn1:"private,tag:1,explicit"`
|
||||||
|
C int `asn1:"private,tag:31"` // tag size should be 2 octet
|
||||||
|
D int `asn1:"private,tag:128"` // tag size should be 3 octet
|
||||||
|
}
|
||||||
|
|
||||||
|
type numericStringTest struct {
|
||||||
|
A string `asn1:"numeric"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testSET []int
|
||||||
|
|
||||||
|
var PST = time.FixedZone("PST", -8*60*60)
|
||||||
|
|
||||||
|
type marshalTest struct {
|
||||||
|
in interface{}
|
||||||
|
out string // hex encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
func farFuture() time.Time {
|
||||||
|
t, err := time.Parse(time.RFC3339, "2100-04-05T12:01:01Z")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalTests = []marshalTest{
|
||||||
|
{10, "02010a"},
|
||||||
|
{127, "02017f"},
|
||||||
|
{128, "02020080"},
|
||||||
|
{-128, "020180"},
|
||||||
|
{-129, "0202ff7f"},
|
||||||
|
{intStruct{64}, "3003020140"},
|
||||||
|
{bigIntStruct{big.NewInt(0x123456)}, "30050203123456"},
|
||||||
|
{twoIntStruct{64, 65}, "3006020140020141"},
|
||||||
|
{nestedStruct{intStruct{127}}, "3005300302017f"},
|
||||||
|
{[]byte{1, 2, 3}, "0403010203"},
|
||||||
|
{implicitTagTest{64}, "3003850140"},
|
||||||
|
{explicitTagTest{64}, "3005a503020140"},
|
||||||
|
{flagTest{true}, "30028000"},
|
||||||
|
{flagTest{false}, "3000"},
|
||||||
|
{time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"},
|
||||||
|
{time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"},
|
||||||
|
{time.Unix(1258325776, 0).In(PST), "17113039313131353134353631362d30383030"},
|
||||||
|
{farFuture(), "180f32313030303430353132303130315a"},
|
||||||
|
{generalizedTimeTest{time.Unix(1258325776, 0).UTC()}, "3011180f32303039313131353232353631365a"},
|
||||||
|
{BitString{[]byte{0x80}, 1}, "03020780"},
|
||||||
|
{BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"},
|
||||||
|
{ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"},
|
||||||
|
{ObjectIdentifier([]int{1, 2, 840, 133549, 1, 1, 5}), "06092a864888932d010105"},
|
||||||
|
{ObjectIdentifier([]int{2, 100, 3}), "0603813403"},
|
||||||
|
{"test", "130474657374"},
|
||||||
|
{
|
||||||
|
"" +
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 127 times 'x'
|
||||||
|
"137f" +
|
||||||
|
"7878787878787878787878787878787878787878787878787878787878787878" +
|
||||||
|
"7878787878787878787878787878787878787878787878787878787878787878" +
|
||||||
|
"7878787878787878787878787878787878787878787878787878787878787878" +
|
||||||
|
"78787878787878787878787878787878787878787878787878787878787878",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"" +
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
|
||||||
|
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 128 times 'x'
|
||||||
|
"138180" +
|
||||||
|
"7878787878787878787878787878787878787878787878787878787878787878" +
|
||||||
|
"7878787878787878787878787878787878787878787878787878787878787878" +
|
||||||
|
"7878787878787878787878787878787878787878787878787878787878787878" +
|
||||||
|
"7878787878787878787878787878787878787878787878787878787878787878",
|
||||||
|
},
|
||||||
|
{ia5StringTest{"test"}, "3006160474657374"},
|
||||||
|
{optionalRawValueTest{}, "3000"},
|
||||||
|
{printableStringTest{"test"}, "3006130474657374"},
|
||||||
|
{printableStringTest{"test*"}, "30071305746573742a"},
|
||||||
|
{genericStringTest{"test"}, "3006130474657374"},
|
||||||
|
{genericStringTest{"test*"}, "30070c05746573742a"},
|
||||||
|
{genericStringTest{"test&"}, "30070c057465737426"},
|
||||||
|
{rawContentsStruct{nil, 64}, "3003020140"},
|
||||||
|
{rawContentsStruct{[]byte{0x30, 3, 1, 2, 3}, 64}, "3003010203"},
|
||||||
|
{RawValue{Tag: 1, Class: 2, IsCompound: false, Bytes: []byte{1, 2, 3}}, "8103010203"},
|
||||||
|
{testSET([]int{10}), "310302010a"},
|
||||||
|
{omitEmptyTest{[]string{}}, "3000"},
|
||||||
|
{omitEmptyTest{[]string{"1"}}, "30053003130131"},
|
||||||
|
{"Σ", "0c02cea3"},
|
||||||
|
{defaultTest{0}, "3003020100"},
|
||||||
|
{defaultTest{1}, "3000"},
|
||||||
|
{defaultTest{2}, "3003020102"},
|
||||||
|
{applicationTest{1, 2}, "30084001016103020102"},
|
||||||
|
{privateTest{1, 2, 3, 4}, "3011c00101e103020102df1f0103df81000104"},
|
||||||
|
{numericStringTest{"1 9"}, "30051203312039"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshal(t *testing.T) {
|
||||||
|
for i, test := range marshalTests {
|
||||||
|
data, err := Marshal(test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d failed: %s", i, err)
|
||||||
|
}
|
||||||
|
out, _ := hex.DecodeString(test.out)
|
||||||
|
if !bytes.Equal(out, data) {
|
||||||
|
t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type marshalWithParamsTest struct {
|
||||||
|
in interface{}
|
||||||
|
params string
|
||||||
|
out string // hex encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalWithParamsTests = []marshalWithParamsTest{
|
||||||
|
{intStruct{10}, "set", "310302010a"},
|
||||||
|
{intStruct{10}, "application", "600302010a"},
|
||||||
|
{intStruct{10}, "private", "e00302010a"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalWithParams(t *testing.T) {
|
||||||
|
for i, test := range marshalWithParamsTests {
|
||||||
|
data, err := MarshalWithParams(test.in, test.params)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d failed: %s", i, err)
|
||||||
|
}
|
||||||
|
out, _ := hex.DecodeString(test.out)
|
||||||
|
if !bytes.Equal(out, data) {
|
||||||
|
t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type marshalErrTest struct {
|
||||||
|
in interface{}
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalErrTests = []marshalErrTest{
|
||||||
|
{bigIntStruct{nil}, "empty integer"},
|
||||||
|
{numericStringTest{"a"}, "invalid character"},
|
||||||
|
{ia5StringTest{"\xb0"}, "invalid character"},
|
||||||
|
{printableStringTest{"!"}, "invalid character"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalError(t *testing.T) {
|
||||||
|
for i, test := range marshalErrTests {
|
||||||
|
_, err := Marshal(test.in)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("#%d should fail, but success", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), test.err) {
|
||||||
|
t.Errorf("#%d got: %v want %v", i, err, test.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidUTF8(t *testing.T) {
|
||||||
|
_, err := Marshal(string([]byte{0xff, 0xff}))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("invalid UTF8 string was accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalOID(t *testing.T) {
|
||||||
|
var marshalTestsOID = []marshalTest{
|
||||||
|
{[]byte("\x06\x01\x30"), "0403060130"}, // bytes format returns a byte sequence \x04
|
||||||
|
// {ObjectIdentifier([]int{0}), "060100"}, // returns an error as OID 0.0 has the same encoding
|
||||||
|
{[]byte("\x06\x010"), "0403060130"}, // same as above "\x06\x010" = "\x06\x01" + "0"
|
||||||
|
{ObjectIdentifier([]int{2, 999, 3}), "0603883703"}, // Example of ITU-T X.690
|
||||||
|
{ObjectIdentifier([]int{0, 0}), "060100"}, // zero OID
|
||||||
|
}
|
||||||
|
for i, test := range marshalTestsOID {
|
||||||
|
data, err := Marshal(test.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d failed: %s", i, err)
|
||||||
|
}
|
||||||
|
out, _ := hex.DecodeString(test.out)
|
||||||
|
if !bytes.Equal(out, data) {
|
||||||
|
t.Errorf("#%d got: %x want %x\n\t%q\n\t%q", i, data, out, data, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIssue11130(t *testing.T) {
|
||||||
|
data := []byte("\x06\x010") // == \x06\x01\x30 == OID = 0 (the figure)
|
||||||
|
var v interface{}
|
||||||
|
// v has Zero value here and Elem() would panic
|
||||||
|
_, err := Unmarshal(data, &v)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(v).String() != reflect.TypeOf(ObjectIdentifier{}).String() {
|
||||||
|
t.Errorf("marshal OID returned an invalid type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data1, err := Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(data, data1) {
|
||||||
|
t.Errorf("got: %q, want: %q \n", data1, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var v1 interface{}
|
||||||
|
_, err = Unmarshal(data1, &v1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(v, v1) {
|
||||||
|
t.Errorf("got: %#v data=%q , want : %#v data=%q\n ", v1, data1, v, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshal(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, test := range marshalTests {
|
||||||
|
Marshal(test.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user