Skip to content

encoding/asn1: allow the user to specify the time format used to unmarshal #29072

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions src/encoding/asn1/asn1.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,17 +329,26 @@ func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error)

// UTCTime

func parseUTCTime(bytes []byte) (ret time.Time, err error) {
func parseUTCTime(bytes []byte, format string) (ret time.Time, err error) {
s := string(bytes)
var formatStr string

formatStr := "0601021504Z0700"
ret, err = time.Parse(formatStr, s)
if err != nil {
formatStr = "060102150405Z0700"
if format != "" {
formatStr = format
ret, err = time.Parse(formatStr, s)
}
if err != nil {
return
if err != nil {
return
}
} else {
formatStr = "0601021504Z0700"
ret, err = time.Parse(formatStr, s)
if err != nil {
formatStr = "060102150405Z0700"
ret, err = time.Parse(formatStr, s)
}
if err != nil {
return
}
}

if serialized := ret.Format(formatStr); serialized != s {
Expand All @@ -357,8 +366,11 @@ func parseUTCTime(bytes []byte) (ret time.Time, err error) {

// parseGeneralizedTime parses the GeneralizedTime from the given byte slice
// and returns the resulting time.
func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) {
const formatStr = "20060102150405Z0700"
func parseGeneralizedTime(bytes []byte, format string) (ret time.Time, err error) {
formatStr := "20060102150405Z0700"
if format != "" {
formatStr = format
}
s := string(bytes)

if ret, err = time.Parse(formatStr, s); err != nil {
Expand Down Expand Up @@ -686,9 +698,9 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
case TagOID:
result, err = parseObjectIdentifier(innerBytes)
case TagUTCTime:
result, err = parseUTCTime(innerBytes)
result, err = parseUTCTime(innerBytes, params.timeFormat)
case TagGeneralizedTime:
result, err = parseGeneralizedTime(innerBytes)
result, err = parseGeneralizedTime(innerBytes, params.timeFormat)
case TagOctetString:
result = innerBytes
default:
Expand Down Expand Up @@ -843,9 +855,9 @@ func parseField(v reflect.Value, bytes []byte, initOffset int, params fieldParam
var time time.Time
var err1 error
if universalTag == TagUTCTime {
time, err1 = parseUTCTime(innerBytes)
time, err1 = parseUTCTime(innerBytes, params.timeFormat)
} else {
time, err1 = parseGeneralizedTime(innerBytes)
time, err1 = parseGeneralizedTime(innerBytes, params.timeFormat)
}
if err1 == nil {
v.Set(reflect.ValueOf(time))
Expand Down
115 changes: 60 additions & 55 deletions src/encoding/asn1/asn1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,51 +258,54 @@ func TestObjectIdentifier(t *testing.T) {
}

type timeTest struct {
in string
ok bool
out time.Time
in string
ok bool
out time.Time
format string
}

var utcTestData = []timeTest{
{"910506164540-0700", true, time.Date(1991, 05, 06, 16, 45, 40, 0, time.FixedZone("", -7*60*60))},
{"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{}},
{"910506a34540Z", false, time.Time{}},
{"910506334a40Z", false, time.Time{}},
{"91050633444aZ", false, time.Time{}},
{"910506334461Z", false, time.Time{}},
{"910506334400Za", false, time.Time{}},
{"910506164540-0700", true, time.Date(1991, 05, 06, 16, 45, 40, 0, time.FixedZone("", -7*60*60)), ""},
{"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), ""},
// Test the use of specified custom formats that can be supplied using the timeFormat tag
{"19510506234500.123456Z", true, time.Date(1951, 05, 06, 23, 45, 0, 123456000, time.UTC), "20060102150405.999999Z0700"},
{"a10506234540Z", false, time.Time{}, ""},
{"91a506234540Z", false, time.Time{}, ""},
{"9105a6234540Z", false, time.Time{}, ""},
{"910506a34540Z", false, time.Time{}, ""},
{"910506334a40Z", false, time.Time{}, ""},
{"91050633444aZ", false, time.Time{}, ""},
{"910506334461Z", false, time.Time{}, ""},
{"910506334400Za", false, time.Time{}, ""},
/* These are invalid times. However, the time package normalises times
* and they were accepted in some versions. See #11134. */
{"000100000000Z", false, time.Time{}},
{"101302030405Z", false, time.Time{}},
{"100002030405Z", false, time.Time{}},
{"100100030405Z", false, time.Time{}},
{"100132030405Z", false, time.Time{}},
{"100231030405Z", false, time.Time{}},
{"100102240405Z", false, time.Time{}},
{"100102036005Z", false, time.Time{}},
{"100102030460Z", false, time.Time{}},
{"-100102030410Z", false, time.Time{}},
{"10-0102030410Z", false, time.Time{}},
{"10-0002030410Z", false, time.Time{}},
{"1001-02030410Z", false, time.Time{}},
{"100102-030410Z", false, time.Time{}},
{"10010203-0410Z", false, time.Time{}},
{"1001020304-10Z", false, time.Time{}},
{"000100000000Z", false, time.Time{}, ""},
{"101302030405Z", false, time.Time{}, ""},
{"100002030405Z", false, time.Time{}, ""},
{"100100030405Z", false, time.Time{}, ""},
{"100132030405Z", false, time.Time{}, ""},
{"100231030405Z", false, time.Time{}, ""},
{"100102240405Z", false, time.Time{}, ""},
{"100102036005Z", false, time.Time{}, ""},
{"100102030460Z", false, time.Time{}, ""},
{"-100102030410Z", false, time.Time{}, ""},
{"10-0102030410Z", false, time.Time{}, ""},
{"10-0002030410Z", false, time.Time{}, ""},
{"1001-02030410Z", false, time.Time{}, ""},
{"100102-030410Z", false, time.Time{}, ""},
{"10010203-0410Z", false, time.Time{}, ""},
{"1001020304-10Z", false, time.Time{}, ""},
}

func TestUTCTime(t *testing.T) {
for i, test := range utcTestData {
ret, err := parseUTCTime([]byte(test.in))
ret, err := parseUTCTime([]byte(test.in), test.format)
if err != nil {
if test.ok {
t.Errorf("#%d: parseUTCTime(%q) = error %v", i, test.in, err)
t.Errorf("#%d: parseUTCTime(%q, %q) = error %v", i, test.in, test.format, err)
}
continue
}
Expand All @@ -320,33 +323,35 @@ func TestUTCTime(t *testing.T) {
}

var generalizedTimeTestData = []timeTest{
{"20100102030405Z", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.UTC)},
{"20100102030405", false, time.Time{}},
{"20100102030405+0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", 6*60*60+7*60))},
{"20100102030405-0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", -6*60*60-7*60))},
{"20100102030405Z", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.UTC), ""},
{"20100102030405", false, time.Time{}, ""},
{"20100102030405+0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", 6*60*60+7*60)), ""},
{"20100102030405-0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", -6*60*60-7*60)), ""},
// Test the use of specified custom formats that can be supplied using the timeFormat tag
{"19510506234500.123456Z", true, time.Date(1951, 05, 06, 23, 45, 0, 123456000, time.UTC), "20060102150405.999999Z0700"},
/* These are invalid times. However, the time package normalises times
* and they were accepted in some versions. See #11134. */
{"00000100000000Z", false, time.Time{}},
{"20101302030405Z", false, time.Time{}},
{"20100002030405Z", false, time.Time{}},
{"20100100030405Z", false, time.Time{}},
{"20100132030405Z", false, time.Time{}},
{"20100231030405Z", false, time.Time{}},
{"20100102240405Z", false, time.Time{}},
{"20100102036005Z", false, time.Time{}},
{"20100102030460Z", false, time.Time{}},
{"-20100102030410Z", false, time.Time{}},
{"2010-0102030410Z", false, time.Time{}},
{"2010-0002030410Z", false, time.Time{}},
{"201001-02030410Z", false, time.Time{}},
{"20100102-030410Z", false, time.Time{}},
{"2010010203-0410Z", false, time.Time{}},
{"201001020304-10Z", false, time.Time{}},
{"00000100000000Z", false, time.Time{}, ""},
{"20101302030405Z", false, time.Time{}, ""},
{"20100002030405Z", false, time.Time{}, ""},
{"20100100030405Z", false, time.Time{}, ""},
{"20100132030405Z", false, time.Time{}, ""},
{"20100231030405Z", false, time.Time{}, ""},
{"20100102240405Z", false, time.Time{}, ""},
{"20100102036005Z", false, time.Time{}, ""},
{"20100102030460Z", false, time.Time{}, ""},
{"-20100102030410Z", false, time.Time{}, ""},
{"2010-0102030410Z", false, time.Time{}, ""},
{"2010-0002030410Z", false, time.Time{}, ""},
{"201001-02030410Z", false, time.Time{}, ""},
{"20100102-030410Z", false, time.Time{}, ""},
{"2010010203-0410Z", false, time.Time{}, ""},
{"201001020304-10Z", false, time.Time{}, ""},
}

func TestGeneralizedTime(t *testing.T) {
for i, test := range generalizedTimeTestData {
ret, err := parseGeneralizedTime([]byte(test.in))
ret, err := parseGeneralizedTime([]byte(test.in), test.format)
if (err == nil) != test.ok {
t.Errorf("#%d: Incorrect error result (did fail? %v, expected: %v)", i, err == nil, test.ok)
}
Expand Down
3 changes: 3 additions & 0 deletions src/encoding/asn1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type fieldParameters struct {
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.
timeFormat string // the time format to use when unmarshalling. Format appropriate for time.Parse()
set bool // true iff this should be encoded as a SET
omitEmpty bool // true iff this should be omitted if empty when marshaling.

Expand All @@ -102,6 +103,8 @@ func parseFieldParameters(str string) (ret fieldParameters) {
}
case part == "generalized":
ret.timeType = TagGeneralizedTime
case strings.HasPrefix(part, "timeFormat:"):
ret.timeFormat = part[11:]
case part == "utc":
ret.timeType = TagUTCTime
case part == "ia5":
Expand Down