diff options
Diffstat (limited to 'src/pkg/encoding/asn1')
-rw-r--r-- | src/pkg/encoding/asn1/asn1.go | 43 | ||||
-rw-r--r-- | src/pkg/encoding/asn1/asn1_test.go | 75 | ||||
-rw-r--r-- | src/pkg/encoding/asn1/marshal.go | 57 | ||||
-rw-r--r-- | src/pkg/encoding/asn1/marshal_test.go | 9 |
4 files changed, 175 insertions, 9 deletions
diff --git a/src/pkg/encoding/asn1/asn1.go b/src/pkg/encoding/asn1/asn1.go index 992356c26..ec7f91c1b 100644 --- a/src/pkg/encoding/asn1/asn1.go +++ b/src/pkg/encoding/asn1/asn1.go @@ -23,6 +23,7 @@ import ( "fmt" "math/big" "reflect" + "strconv" "time" ) @@ -197,6 +198,19 @@ func (oi ObjectIdentifier) Equal(other ObjectIdentifier) bool { return true } +func (oi ObjectIdentifier) String() string { + var s string + + for i, v := range oi { + if i > 0 { + s += "." + } + s += strconv.Itoa(v) + } + + return s +} + // parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and // returns it. An object identifier is a sequence of variable length integers // that are assigned in a hierarchy. @@ -451,11 +465,17 @@ func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type if err != nil { return } - // We pretend that GENERAL STRINGs are PRINTABLE STRINGs so - // that a sequence of them can be parsed into a []string. - if t.tag == tagGeneralString { + switch t.tag { + case tagIA5String, tagGeneralString, tagT61String, tagUTF8String: + // We pretend that various other string types are + // PRINTABLE STRINGs so that a sequence of them can be + // parsed into a []string. t.tag = tagPrintableString + case tagGeneralizedTime, tagUTCTime: + // Likewise, both time types are treated the same. + t.tag = tagUTCTime } + if t.class != classUniversal || t.isCompound != compoundType || t.tag != expectedTag { err = StructuralError{"sequence tag mismatch"} return @@ -632,6 +652,10 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam universalTag = tagGeneralizedTime } + if params.set { + universalTag = tagSet + } + expectedClass := classUniversal expectedTag := universalTag @@ -852,13 +876,20 @@ func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) { // // The following tags on struct fields have special meaning to Unmarshal: // -// optional marks the field as ASN.1 OPTIONAL -// [explicit] tag:x specifies the ASN.1 tag number; implies ASN.1 CONTEXT SPECIFIC -// default:x sets the default value for optional integer fields +// application specifies that a APPLICATION tag is used +// default:x sets the default value for optional integer fields +// explicit specifies that an additional, explicit tag wraps the implicit one +// optional marks the field as ASN.1 OPTIONAL +// set causes a SET, rather than a SEQUENCE type to be expected +// tag:x specifies the ASN.1 tag number; implies ASN.1 CONTEXT SPECIFIC // // If the type of the first field of a structure is RawContent then the raw // ASN1 contents of the struct will be stored in it. // +// If the type name of a slice element ends with "SET" then it's treated as if +// the "set" tag was set on it. This can be used with nested slices where a +// struct tag cannot be given. +// // Other ASN.1 types are not supported; if it encounters them, // Unmarshal returns a parse error. func Unmarshal(b []byte, val interface{}) (rest []byte, err error) { diff --git a/src/pkg/encoding/asn1/asn1_test.go b/src/pkg/encoding/asn1/asn1_test.go index f68804ebf..b553f78e0 100644 --- a/src/pkg/encoding/asn1/asn1_test.go +++ b/src/pkg/encoding/asn1/asn1_test.go @@ -6,6 +6,7 @@ package asn1 import ( "bytes" + "fmt" "math/big" "reflect" "testing" @@ -171,6 +172,12 @@ func TestBitStringAt(t *testing.T) { if bs.At(9) != 1 { t.Error("#4: Failed") } + if bs.At(-1) != 0 { + t.Error("#5: Failed") + } + if bs.At(17) != 0 { + t.Error("#6: Failed") + } } type bitStringRightAlignTest struct { @@ -225,6 +232,10 @@ func TestObjectIdentifier(t *testing.T) { } } } + + if s := ObjectIdentifier([]int{1, 2, 3, 4}).String(); s != "1.2.3.4" { + t.Errorf("bad ObjectIdentifier.String(). Got %s, want 1.2.3.4", s) + } } type timeTest struct { @@ -238,6 +249,7 @@ var utcTestData = []timeTest{ {"910506164540+0730", true, time.Date(1991, 05, 06, 16, 45, 40, 0, time.FixedZone("", 7*60*60+30*60))}, {"910506234540Z", true, time.Date(1991, 05, 06, 23, 45, 40, 0, time.UTC)}, {"9105062345Z", true, time.Date(1991, 05, 06, 23, 45, 0, 0, time.UTC)}, + {"5105062345Z", true, time.Date(1951, 05, 06, 23, 45, 0, 0, time.UTC)}, {"a10506234540Z", false, time.Time{}}, {"91a506234540Z", false, time.Time{}}, {"9105a6234540Z", false, time.Time{}}, @@ -389,6 +401,10 @@ type TestBigInt struct { X *big.Int } +type TestSet struct { + Ints []int `asn1:"set"` +} + var unmarshalTestData = []struct { in []byte out interface{} @@ -408,6 +424,7 @@ var unmarshalTestData = []struct { {[]byte{0x01, 0x01, 0xff}, newBool(true)}, {[]byte{0x30, 0x0b, 0x13, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x01, 0x22, 0x02, 0x01, 0x33}, &TestElementsAfterString{"foo", 0x22, 0x33}}, {[]byte{0x30, 0x05, 0x02, 0x03, 0x12, 0x34, 0x56}, &TestBigInt{big.NewInt(0x123456)}}, + {[]byte{0x30, 0x0b, 0x31, 0x09, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03}, &TestSet{Ints: []int{1, 2, 3}}}, } func TestUnmarshal(t *testing.T) { @@ -509,6 +526,38 @@ func TestRawStructs(t *testing.T) { } } +type oiEqualTest struct { + first ObjectIdentifier + second ObjectIdentifier + same bool +} + +var oiEqualTests = []oiEqualTest{ + { + ObjectIdentifier{1, 2, 3}, + ObjectIdentifier{1, 2, 3}, + true, + }, + { + ObjectIdentifier{1}, + ObjectIdentifier{1, 2, 3}, + false, + }, + { + ObjectIdentifier{1, 2, 3}, + ObjectIdentifier{10, 11, 12}, + false, + }, +} + +func TestObjectIdentifierEqual(t *testing.T) { + for _, o := range oiEqualTests { + if s := o.first.Equal(o.second); s != o.same { + t.Errorf("ObjectIdentifier.Equal: got: %t want: %t", s, o.same) + } + } +} + var derEncodedSelfSignedCert = Certificate{ TBSCertificate: TBSCertificate{ Version: 0, @@ -737,3 +786,29 @@ var derEncodedPaypalNULCertBytes = []byte{ 0xc8, 0x64, 0x8c, 0xb5, 0x50, 0x23, 0x82, 0x6f, 0xdb, 0xb8, 0x22, 0x1c, 0x43, 0x96, 0x07, 0xa8, 0xbb, } + +var stringSliceTestData = [][]string{ + {"foo", "bar"}, + {"foo", "\\bar"}, + {"foo", "\"bar\""}, + {"foo", "åäö"}, +} + +func TestStringSlice(t *testing.T) { + for _, test := range stringSliceTestData { + bs, err := Marshal(test) + if err != nil { + t.Error(err) + } + + var res []string + _, err = Unmarshal(bs, &res) + if err != nil { + t.Error(err) + } + + if fmt.Sprintf("%v", res) != fmt.Sprintf("%v", test) { + t.Errorf("incorrect marshal/unmarshal; %v != %v", res, test) + } + } +} diff --git a/src/pkg/encoding/asn1/marshal.go b/src/pkg/encoding/asn1/marshal.go index ed17e41a5..e26fe59b3 100644 --- a/src/pkg/encoding/asn1/marshal.go +++ b/src/pkg/encoding/asn1/marshal.go @@ -295,8 +295,23 @@ func marshalTwoDigits(out *forkableWriter, v int) (err error) { return out.WriteByte(byte('0' + v%10)) } +func marshalFourDigits(out *forkableWriter, v int) (err error) { + var bytes [4]byte + for i := range bytes { + bytes[3-i] = '0' + byte(v%10) + v /= 10 + } + _, err = out.Write(bytes[:]) + return +} + +func outsideUTCRange(t time.Time) bool { + year := t.Year() + return year < 1950 || year >= 2050 +} + func marshalUTCTime(out *forkableWriter, t time.Time) (err error) { - year, month, day := t.Date() + year := t.Year() switch { case 1950 <= year && year < 2000: @@ -310,6 +325,24 @@ func marshalUTCTime(out *forkableWriter, t time.Time) (err error) { return } + return marshalTimeCommon(out, t) +} + +func marshalGeneralizedTime(out *forkableWriter, t time.Time) (err error) { + year := t.Year() + if year < 0 || year > 9999 { + return StructuralError{"cannot represent time as GeneralizedTime"} + } + if err = marshalFourDigits(out, year); err != nil { + return + } + + return marshalTimeCommon(out, t) +} + +func marshalTimeCommon(out *forkableWriter, t time.Time) (err error) { + _, month, day := t.Date() + err = marshalTwoDigits(out, int(month)) if err != nil { return @@ -378,7 +411,12 @@ func stripTagAndLength(in []byte) []byte { func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameters) (err error) { switch value.Type() { case timeType: - return marshalUTCTime(out, value.Interface().(time.Time)) + t := value.Interface().(time.Time) + if outsideUTCRange(t) { + return marshalGeneralizedTime(out, t) + } else { + return marshalUTCTime(out, t) + } case bitStringType: return marshalBitString(out, value.Interface().(BitString)) case objectIdentifierType: @@ -504,7 +542,8 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters) return StructuralError{"explicit string type given to non-string member"} } - if tag == tagPrintableString { + 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 @@ -521,6 +560,10 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters) } else { tag = params.stringType } + case tagUTCTime: + if outsideUTCRange(v.Interface().(time.Time)) { + tag = tagGeneralizedTime + } } if params.set { @@ -568,6 +611,14 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters) } // 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, IA5 strings +// omitempty: causes empty slices to be skipped +// printable: causes strings to be marshaled as ASN.1, PrintableString strings. +// utf8: causes strings to be marshaled as ASN.1, UTF8 strings func Marshal(val interface{}) ([]byte, error) { var out bytes.Buffer v := reflect.ValueOf(val) diff --git a/src/pkg/encoding/asn1/marshal_test.go b/src/pkg/encoding/asn1/marshal_test.go index 763c86da2..a15acbed0 100644 --- a/src/pkg/encoding/asn1/marshal_test.go +++ b/src/pkg/encoding/asn1/marshal_test.go @@ -67,6 +67,14 @@ type marshalTest struct { 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"}, @@ -83,6 +91,7 @@ var marshalTests = []marshalTest{ {time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"}, {time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"}, {time.Unix(1258325776, 0).In(PST), "17113039313131353134353631362d30383030"}, + {farFuture(), "180f32313030303430353132303130315a"}, {BitString{[]byte{0x80}, 1}, "03020780"}, {BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"}, {ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"}, |