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 <filippo@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
diff --git a/cryptobyte/asn1.go b/cryptobyte/asn1.go
index a64c1d7..401414d 100644
--- a/cryptobyte/asn1.go
+++ b/cryptobyte/asn1.go
@@ -264,36 +264,35 @@
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 @@
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 @@
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 1187c71..be04bb4 100644
--- a/cryptobyte/asn1_test.go
+++ b/cryptobyte/asn1_test.go
@@ -154,6 +154,32 @@
}
})
+ // 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 {