x/crypto: cryptobyte: manage integers with implicit tags

This change adds two functions to cryptobyte to encode and decode
int64 with tags supplied by the user.

This change also modifies a documentation comment which was outdated.

Fixes golang/go#24973

Change-Id: I2e3ca475891ba62df902f33085719f94e87a27cc
GitHub-Last-Rev: cd0300d9bfbd4e38f22503a046ea18f9ddd37676
GitHub-Pull-Request: golang/crypto#42
Reviewed-on: https://go-review.googlesource.com/108456
Reviewed-by: Adam Langley <agl@golang.org>
diff --git a/cryptobyte/asn1.go b/cryptobyte/asn1.go
index bb094a5..528b9bf 100644
--- a/cryptobyte/asn1.go
+++ b/cryptobyte/asn1.go
@@ -23,6 +23,12 @@
 	b.addASN1Signed(asn1.INTEGER, v)
 }
 
+// AddASN1Int64WithTag appends a DER-encoded ASN.1 INTEGER with the
+// given tag.
+func (b *Builder) AddASN1Int64WithTag(v int64, tag asn1.Tag) {
+	b.addASN1Signed(tag, v)
+}
+
 // AddASN1Enum appends a DER-encoded ASN.1 ENUMERATION.
 func (b *Builder) AddASN1Enum(v int64) {
 	b.addASN1Signed(asn1.ENUM, v)
@@ -362,6 +368,14 @@
 	return true
 }
 
+// ReadASN1Int64WithTag decodes an ASN.1 INTEGER with the given tag into out
+// and advances. It reports whether the read was successful and resulted in a
+// value that can be represented in an int64.
+func (s *String) ReadASN1Int64WithTag(out *int64, tag asn1.Tag) bool {
+	var bytes String
+	return s.ReadASN1(&bytes, tag) && checkASN1Integer(bytes) && asn1Signed(out, bytes)
+}
+
 // ReadASN1Enum decodes an ASN.1 ENUMERATION into out and advances. It reports
 // whether the read was successful.
 func (s *String) ReadASN1Enum(out *int) bool {
@@ -623,7 +637,7 @@
 
 // ReadOptionalASN1OctetString attempts to read an optional ASN.1 OCTET STRING
 // explicitly tagged with tag into out and advances. If no element with a
-// matching tag is present, it writes defaultValue into out instead. It reports
+// matching tag is present, it sets "out" to nil instead. It reports
 // whether the read was successful.
 func (s *String) ReadOptionalASN1OctetString(out *[]byte, outPresent *bool, tag asn1.Tag) bool {
 	var present bool
diff --git a/cryptobyte/asn1_test.go b/cryptobyte/asn1_test.go
index ee6674a..9f6c952 100644
--- a/cryptobyte/asn1_test.go
+++ b/cryptobyte/asn1_test.go
@@ -149,6 +149,39 @@
 			}
 		}
 	})
+
+	// Repeat with the implicit-tagging functions
+	t.Run("WithTag", func(t *testing.T) {
+		for i, test := range testData64 {
+			tag := asn1.Tag((i * 3) % 32).ContextSpecific()
+
+			testData := make([]byte, len(test.in))
+			copy(testData, test.in)
+
+			// Alter the tag of the test case.
+			testData[0] = uint8(tag)
+
+			in := String(testData)
+			var out int64
+			ok := in.ReadASN1Int64WithTag(&out, tag)
+			if !ok || out != test.out {
+				t.Errorf("#%d: in.ReadASN1Int64WithTag() = %v, want true; out = %d, want %d", i, ok, out, test.out)
+			}
+
+			var b Builder
+			b.AddASN1Int64WithTag(test.out, tag)
+			result, err := b.Bytes()
+
+			if err != nil {
+				t.Errorf("#%d: AddASN1Int64WithTag failed: %s", i, err)
+				continue
+			}
+
+			if !bytes.Equal(result, testData) {
+				t.Errorf("#%d: AddASN1Int64WithTag: got %x, want %x", i, result, testData)
+			}
+		}
+	})
 }
 
 func TestReadASN1IntegerUnsigned(t *testing.T) {