From 2c476679df9a5c6279ec05b48165f4bed48b792e Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Thu, 17 Nov 2022 11:02:00 +0100 Subject: [PATCH] cryptobyte: add support for ReadASN1Integer into []byte This lets us extract large integers without involving math/big. While at it, drop some use of reflect where a type switch will do. Change-Id: Iebe2fb2267610bf95cf9747ba1d49b5ac9e62cda Reviewed-on: https://go-review.googlesource.com/c/crypto/+/451515 Run-TryBot: Filippo Valsorda Reviewed-by: Damien Neil Reviewed-by: Bryan Mills Reviewed-by: Roland Shoemaker TryBot-Result: Gopher Robot Auto-Submit: Filippo Valsorda --- cryptobyte/asn1.go | 73 ++++++++++++++++++++++------------------- cryptobyte/asn1_test.go | 26 +++++++++++++++ 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/cryptobyte/asn1.go b/cryptobyte/asn1.go index a64c1d7526..401414dde2 100644 --- a/cryptobyte/asn1.go +++ b/cryptobyte/asn1.go @@ -264,36 +264,35 @@ func (s *String) ReadASN1Boolean(out *bool) bool { return true } -var bigIntType = reflect.TypeOf((*big.Int)(nil)).Elem() - // ReadASN1Integer decodes an ASN.1 INTEGER into out and advances. If out does -// not point to an integer or to a big.Int, it panics. It reports whether the -// read was successful. +// not point to an integer, to a big.Int, or to a []byte it panics. Only +// positive and zero values can be decoded into []byte, and they are returned as +// big-endian binary values that share memory with s. Positive values will have +// no leading zeroes, and zero will be returned as a single zero byte. +// ReadASN1Integer reports whether the read was successful. func (s *String) ReadASN1Integer(out interface{}) bool { - if reflect.TypeOf(out).Kind() != reflect.Ptr { - panic("out is not a pointer") - } - switch reflect.ValueOf(out).Elem().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch out := out.(type) { + case *int, *int8, *int16, *int32, *int64: var i int64 if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) { return false } reflect.ValueOf(out).Elem().SetInt(i) return true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + case *uint, *uint8, *uint16, *uint32, *uint64: var u uint64 if !s.readASN1Uint64(&u) || reflect.ValueOf(out).Elem().OverflowUint(u) { return false } reflect.ValueOf(out).Elem().SetUint(u) return true - case reflect.Struct: - if reflect.TypeOf(out).Elem() == bigIntType { - return s.readASN1BigInt(out.(*big.Int)) - } + case *big.Int: + return s.readASN1BigInt(out) + case *[]byte: + return s.readASN1Bytes(out) + default: + panic("out does not point to an integer type") } - panic("out does not point to an integer type") } func checkASN1Integer(bytes []byte) bool { @@ -333,6 +332,21 @@ func (s *String) readASN1BigInt(out *big.Int) bool { return true } +func (s *String) readASN1Bytes(out *[]byte) bool { + var bytes String + if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) { + return false + } + if bytes[0]&0x80 == 0x80 { + return false + } + for len(bytes) > 1 && bytes[0] == 0 { + bytes = bytes[1:] + } + *out = bytes + return true +} + func (s *String) readASN1Int64(out *int64) bool { var bytes String if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Signed(out, bytes) { @@ -654,34 +668,27 @@ func (s *String) SkipOptionalASN1(tag asn1.Tag) bool { return s.ReadASN1(&unused, tag) } -// ReadOptionalASN1Integer attempts to read an optional ASN.1 INTEGER -// explicitly tagged with tag into out and advances. If no element with a -// matching tag is present, it writes defaultValue into out instead. If out -// does not point to an integer or to a big.Int, it panics. It reports -// whether the read was successful. +// ReadOptionalASN1Integer attempts to read an optional ASN.1 INTEGER explicitly +// tagged with tag into out and advances. If no element with a matching tag is +// present, it writes defaultValue into out instead. Otherwise, it behaves like +// ReadASN1Integer. func (s *String) ReadOptionalASN1Integer(out interface{}, tag asn1.Tag, defaultValue interface{}) bool { - if reflect.TypeOf(out).Kind() != reflect.Ptr { - panic("out is not a pointer") - } var present bool var i String if !s.ReadOptionalASN1(&i, &present, tag) { return false } if !present { - switch reflect.ValueOf(out).Elem().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + switch out.(type) { + case *int, *int8, *int16, *int32, *int64, + *uint, *uint8, *uint16, *uint32, *uint64, *[]byte: reflect.ValueOf(out).Elem().Set(reflect.ValueOf(defaultValue)) - case reflect.Struct: - if reflect.TypeOf(out).Elem() != bigIntType { - panic("invalid integer type") - } - if reflect.TypeOf(defaultValue).Kind() != reflect.Ptr || - reflect.TypeOf(defaultValue).Elem() != bigIntType { + case *big.Int: + if defaultValue, ok := defaultValue.(*big.Int); ok { + out.(*big.Int).Set(defaultValue) + } else { panic("out points to big.Int, but defaultValue does not") } - out.(*big.Int).Set(defaultValue.(*big.Int)) default: panic("invalid integer type") } diff --git a/cryptobyte/asn1_test.go b/cryptobyte/asn1_test.go index 1187c712ab..be04bb4a38 100644 --- a/cryptobyte/asn1_test.go +++ b/cryptobyte/asn1_test.go @@ -154,6 +154,32 @@ func TestReadASN1IntegerSigned(t *testing.T) { } }) + // Repeat the same cases, reading into a []byte. + t.Run("bytes", func(t *testing.T) { + for i, test := range testData64 { + in := String(test.in) + var out []byte + ok := in.ReadASN1Integer(&out) + if test.out < 0 { + if ok { + t.Errorf("#%d: in.ReadASN1Integer(%d) = %v, want false", i, test.out, ok) + } + continue + } + if !ok { + t.Errorf("#%d: in.ReadASN1Integer() = %v, want true", i, ok) + continue + } + n := new(big.Int).SetBytes(out).Int64() + if n != test.out { + t.Errorf("#%d: in.ReadASN1Integer() = %v, want true; out = %x, want %d", i, ok, out, test.out) + } + if out[0] == 0 && len(out) > 1 { + t.Errorf("#%d: in.ReadASN1Integer() = %v; out = %x, has leading zeroes", i, ok, out) + } + } + }) + // Repeat with the implicit-tagging functions t.Run("WithTag", func(t *testing.T) { for i, test := range testData64 {