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 {