0
1
mirror of https://github.com/golang/go synced 2025-05-29 15:20:52 +00:00

encoding/asn1: make sure implicit fields roundtrip

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 

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>
This commit is contained in:
Roland Shoemaker
2025-03-03 09:04:35 -08:00
parent a1ddbdd3ef
commit 7e3d2aa69f
3 changed files with 51 additions and 3 deletions

@ -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.
//

@ -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)
}
}

@ -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) {