Skip to content

Commit 4f45737

Browse files
cryptobyte: add support for UTCTime
Mostly cribbed from encoding/asn1.parseUTCTime. Fixes golang/go#45411 Change-Id: I6c6ab2a2ad7c05a7f8bd81ecce4fcbb4e608e8db Reviewed-on: https://go-review.googlesource.com/c/crypto/+/273286 Trust: Roland Shoemaker <[email protected]> Trust: Katie Hockman <[email protected]> Run-TryBot: Roland Shoemaker <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Katie Hockman <[email protected]>
1 parent 0c34fe9 commit 4f45737

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

cryptobyte/asn1.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@ func (b *Builder) AddASN1GeneralizedTime(t time.Time) {
117117
})
118118
}
119119

120+
// AddASN1UTCTime appends a DER-encoded ASN.1 UTCTime.
121+
func (b *Builder) AddASN1UTCTime(t time.Time) {
122+
b.AddASN1(asn1.UTCTime, func(c *Builder) {
123+
// As utilized by the X.509 profile, UTCTime can only
124+
// represent the years 1950 through 2049.
125+
if t.Year() < 1950 || t.Year() >= 2050 {
126+
b.err = fmt.Errorf("cryptobyte: cannot represent %v as a UTCTime", t)
127+
return
128+
}
129+
c.AddBytes([]byte(t.Format(defaultUTCTimeFormatStr)))
130+
})
131+
}
132+
120133
// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not
121134
// support BIT STRINGs that are not a whole number of bytes.
122135
func (b *Builder) AddASN1BitString(data []byte) {
@@ -466,6 +479,45 @@ func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool {
466479
return true
467480
}
468481

482+
const defaultUTCTimeFormatStr = "060102150405Z0700"
483+
484+
// ReadASN1UTCTime decodes an ASN.1 UTCTime into out and advances.
485+
// It reports whether the read was successful.
486+
func (s *String) ReadASN1UTCTime(out *time.Time) bool {
487+
var bytes String
488+
if !s.ReadASN1(&bytes, asn1.UTCTime) {
489+
return false
490+
}
491+
t := string(bytes)
492+
493+
formatStr := defaultUTCTimeFormatStr
494+
var err error
495+
res, err := time.Parse(formatStr, t)
496+
if err != nil {
497+
// Fallback to minute precision if we can't parse second
498+
// precision. If we are following X.509 or X.690 we shouldn't
499+
// support this, but we do.
500+
formatStr = "0601021504Z0700"
501+
res, err = time.Parse(formatStr, t)
502+
}
503+
if err != nil {
504+
return false
505+
}
506+
507+
if serialized := res.Format(formatStr); serialized != t {
508+
return false
509+
}
510+
511+
if res.Year() >= 2050 {
512+
// UTCTime interprets the low order digits 50-99 as 1950-99.
513+
// This only applies to its use in the X.509 profile.
514+
// See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
515+
res = res.AddDate(-100, 0, 0)
516+
}
517+
*out = res
518+
return true
519+
}
520+
469521
// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances.
470522
// It reports whether the read was successful.
471523
func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool {

cryptobyte/asn1_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,37 @@ func TestReadASN1GeneralizedTime(t *testing.T) {
311311
}
312312
}
313313

314+
func TestReadASN1UTCTime(t *testing.T) {
315+
testData := []struct {
316+
in string
317+
ok bool
318+
out time.Time
319+
}{
320+
{"000102030405Z", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.UTC)},
321+
{"500102030405Z", true, time.Date(1950, 01, 02, 03, 04, 05, 0, time.UTC)},
322+
{"490102030405Z", true, time.Date(2049, 01, 02, 03, 04, 05, 0, time.UTC)},
323+
{"990102030405Z", true, time.Date(1999, 01, 02, 03, 04, 05, 0, time.UTC)},
324+
{"250102030405Z", true, time.Date(2025, 01, 02, 03, 04, 05, 0, time.UTC)},
325+
{"750102030405Z", true, time.Date(1975, 01, 02, 03, 04, 05, 0, time.UTC)},
326+
{"000102030405+0905", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.FixedZone("", 9*60*60+5*60))},
327+
{"000102030405-0905", true, time.Date(2000, 01, 02, 03, 04, 05, 0, time.FixedZone("", -9*60*60-5*60))},
328+
{"0001020304Z", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.UTC)},
329+
{"5001020304Z", true, time.Date(1950, 01, 02, 03, 04, 00, 0, time.UTC)},
330+
{"0001020304+0905", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.FixedZone("", 9*60*60+5*60))},
331+
{"0001020304-0905", true, time.Date(2000, 01, 02, 03, 04, 0, 0, time.FixedZone("", -9*60*60-5*60))},
332+
{"000102030405Z0700", false, time.Time{}},
333+
{"000102030405", false, time.Time{}},
334+
}
335+
for i, test := range testData {
336+
in := String(append([]byte{byte(asn1.UTCTime), byte(len(test.in))}, test.in...))
337+
var out time.Time
338+
ok := in.ReadASN1UTCTime(&out)
339+
if ok != test.ok || ok && !reflect.DeepEqual(out, test.out) {
340+
t.Errorf("#%d: in.ReadASN1UTCTime() = %v, want %v; out = %q, want %q", i, ok, test.ok, out, test.out)
341+
}
342+
}
343+
}
344+
314345
func TestReadASN1BitString(t *testing.T) {
315346
testData := []struct {
316347
in []byte

0 commit comments

Comments
 (0)