cryptobyte: fix ReadOptionalASN1Boolean

ReadOptionalASN1Boolean was completely broken, it would only work when
there were two BOOLEAN fields in a row, with the first being OPTIONAL
(which is itself invalid ASN.1 due to the ambiguity). This fixes it
to properly expect a BOOLEAN wrapped in a context-specific tag, as is
the case for all of the other ReadOptionalASN1* methods, and updates
its doc string.

This is a breaking change as it requires adding the tag field to
properly support context-specific tags. Given the method would
previously not work this seems like a reasonable breakage.

Fixes golang/go#43019

Change-Id: I42398256216c59988e249c90bc7aa668f64df945
Reviewed-on: https://go-review.googlesource.com/c/crypto/+/274242
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Roland Shoemaker <roland@golang.org>
diff --git a/cryptobyte/asn1.go b/cryptobyte/asn1.go
index 6fc2838..2492f79 100644
--- a/cryptobyte/asn1.go
+++ b/cryptobyte/asn1.go
@@ -733,13 +733,14 @@
 	return true
 }
 
-// ReadOptionalASN1Boolean sets *out to the value of the next ASN.1 BOOLEAN or,
-// if the next bytes are not an ASN.1 BOOLEAN, to the value of defaultValue.
-// It reports whether the operation was successful.
-func (s *String) ReadOptionalASN1Boolean(out *bool, defaultValue bool) bool {
+// ReadOptionalASN1Boolean attempts to read an optional ASN.1 BOOLEAN
+// explicitly tagged with tag into out and advances. If no element with a
+// matching tag is present, it sets "out" to defaultValue instead. It reports
+// whether the read was successful.
+func (s *String) ReadOptionalASN1Boolean(out *bool, tag asn1.Tag, defaultValue bool) bool {
 	var present bool
 	var child String
-	if !s.ReadOptionalASN1(&child, &present, asn1.BOOLEAN) {
+	if !s.ReadOptionalASN1(&child, &present, tag) {
 		return false
 	}
 
@@ -748,7 +749,7 @@
 		return true
 	}
 
-	return s.ReadASN1Boolean(out)
+	return child.ReadASN1Boolean(out)
 }
 
 func (s *String) readASN1(out *String, outTag *asn1.Tag, skipHeader bool) bool {
diff --git a/cryptobyte/asn1_test.go b/cryptobyte/asn1_test.go
index e3f53a9..93760b0 100644
--- a/cryptobyte/asn1_test.go
+++ b/cryptobyte/asn1_test.go
@@ -115,6 +115,28 @@
 	}
 }
 
+const defaultBool = false
+
+var optionalBoolTestData = []readASN1Test{
+	{"empty", []byte{}, 0xa0, true, false},
+	{"invalid", []byte{0xa1, 0x3, 0x1, 0x2, 0x7f}, 0xa1, false, defaultBool},
+	{"missing", []byte{0xa1, 0x3, 0x1, 0x1, 0x7f}, 0xa0, true, defaultBool},
+	{"present", []byte{0xa1, 0x3, 0x1, 0x1, 0xff}, 0xa1, true, true},
+}
+
+func TestReadASN1OptionalBoolean(t *testing.T) {
+	for _, test := range optionalBoolTestData {
+		t.Run(test.name, func(t *testing.T) {
+			in := String(test.in)
+			var out bool
+			ok := in.ReadOptionalASN1Boolean(&out, test.tag, defaultBool)
+			if ok != test.ok || ok && out != test.out.(bool) {
+				t.Errorf("in.ReadOptionalASN1Boolean() = %v, want %v; out = %v, want %v", ok, test.ok, out, test.out)
+			}
+		})
+	}
+}
+
 func TestReadASN1IntegerSigned(t *testing.T) {
 	testData64 := []struct {
 		in  []byte