]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/asn1: make sure implicit fields roundtrip
authorRoland Shoemaker <roland@golang.org>
Mon, 3 Mar 2025 17:04:35 +0000 (09:04 -0800)
committerRoland Shoemaker <roland@golang.org>
Fri, 14 Mar 2025 18:40:43 +0000 (11:40 -0700)
Make sure Marshal and Unmarshal support the same field tags for implicit
encoding choices. In particular this adds support for Unmarshalling
implicitly tagged GeneralizedTime fields. Also add tests and update the
docs.

Fixes #72078

Change-Id: I21465ee4bcd73a7db0d0c36b2df53cabfc480185
Reviewed-on: https://go-review.googlesource.com/c/go/+/654275
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
src/encoding/asn1/asn1.go
src/encoding/asn1/asn1_test.go
src/encoding/asn1/marshal.go

index 4e3f85de1335a39b8b05017e63e163896ffbd7c8..0b64f06d368b53b22ef4573e735d367755ac6e3a 100644 (file)
@@ -828,9 +828,18 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
        }
 
        // Special case for time: UTCTime and GeneralizedTime both map to the
-       // Go type time.Time.
-       if universalTag == TagUTCTime && t.tag == TagGeneralizedTime && t.class == ClassUniversal {
-               universalTag = TagGeneralizedTime
+       // Go type time.Time. getUniversalType returns the tag for UTCTime when
+       // it sees a time.Time, so if we see a different time type on the wire,
+       // or the field is tagged with a different type, we change the universal
+       // type to match.
+       if universalTag == TagUTCTime {
+               if t.class == ClassUniversal {
+                       if t.tag == TagGeneralizedTime {
+                               universalTag = t.tag
+                       }
+               } else if params.timeType != 0 {
+                       universalTag = params.timeType
+               }
        }
 
        if params.set {
@@ -1103,6 +1112,13 @@ func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) {
 //     numeric causes strings to be unmarshaled as ASN.1 NumericString values
 //     utf8    causes strings to be unmarshaled as ASN.1 UTF8String values
 //
+// When decoding an ASN.1 value with an IMPLICIT tag into a time.Time field,
+// Unmarshal will default to a UTCTime, which doesn't support time zones or
+// fractional seconds. To force usage of GeneralizedTime, use the following
+// tag:
+//
+//     generalized causes time.Times to be unmarshaled as ASN.1 GeneralizedTime values
+//
 // 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.
 //
index 60dae71df471c4fa7de27446f05aa88f9671355a..0597740bd5e5ecf66f1184180ba35fef6db458ed 100644 (file)
@@ -1185,3 +1185,34 @@ func BenchmarkObjectIdentifierString(b *testing.B) {
                _ = oidPublicKeyRSA.String()
        }
 }
+
+func TestImplicitTypeRoundtrip(t *testing.T) {
+       type tagged struct {
+               IA5         string    `asn1:"tag:1,ia5"`
+               Printable   string    `asn1:"tag:2,printable"`
+               UTF8        string    `asn1:"tag:3,utf8"`
+               Numeric     string    `asn1:"tag:4,numeric"`
+               UTC         time.Time `asn1:"tag:5,utc"`
+               Generalized time.Time `asn1:"tag:6,generalized"`
+       }
+       a := tagged{
+               IA5:         "ia5",
+               Printable:   "printable",
+               UTF8:        "utf8",
+               Numeric:     "123 456",
+               UTC:         time.Now().UTC().Truncate(time.Second),
+               Generalized: time.Now().UTC().Truncate(time.Second),
+       }
+       enc, err := Marshal(a)
+       if err != nil {
+               t.Fatalf("Marshal failed: %s", err)
+       }
+       var b tagged
+       if _, err := Unmarshal(enc, &b); err != nil {
+               t.Fatalf("Unmarshal failed: %s", err)
+       }
+
+       if !reflect.DeepEqual(a, b) {
+               t.Fatalf("Unexpected diff after roundtripping struct\na: %#v\nb: %#v", a, b)
+       }
+}
index b9c0b8bce054f5b2f3875419d8a1299d0d891fdb..70e4fafc123ce199975ee93a66942200a54fca06 100644 (file)
@@ -725,6 +725,7 @@ func makeField(v reflect.Value, params fieldParameters) (e encoder, err error) {
 //     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
+//     numeric:     causes strings to be marshaled as ASN.1, NumericString 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 any) ([]byte, error) {