Repository: go
Issue: x/crypto/cryptobyte: support reading implicitly-tagged values
Labels: Proposal,Proposal-Accepted,NeedsInvestigation

### Go version

go version go1.21.5 linux/amd64

### What operating system and processor architecture are you using (`go env`)?

```shell
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/aaron/.cache/go-build'
GOENV='/home/aaron/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/aaron/.local/share/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/aaron/.local/share/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.5'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3470374486=/tmp/go-build -gno-record-gcc-switches'
```


### What did you do?

Attempted to use [`ReadOptionalASN1Boolean`](https://pkg.go.dev/golang.org/x/crypto/cryptobyte#String.ReadOptionalASN1Boolean) to read the `onlyContainsCACerts` and `onlyContainsUserCerts` fields of a CRL's `IssuingDistributionPoint` extension.

You can see my attempt, failure, and resulting investigation [here](https://github.com/letsencrypt/boulder/pull/7225). I also have a [Go playground](https://go.dev/play/p/TEVXkYVLVsg) which minimally reproduces the situation:

```go
func example(extBytes []byte) error {
	idpExt := cryptobyte.String(extBytes)
	if !idpExt.ReadASN1(&idpExt, asn1.SEQUENCE) {
		return errors.New("Failed to read IssuingDistributionPoint distributionPoint")
	}

	var onlyContainsCACerts bool
	onlyContainsCACertsTag := asn1.Tag(2).ContextSpecific()
	if !idpExt.ReadOptionalASN1Boolean(&onlyContainsCACerts, onlyContainsCACertsTag, false) {
		return errors.New("Failed to read IssuingDistributionPoint onlyContainsCACerts")
	}

	return nil
}

func main() {
	// IMPLICIT encoding
	// Sequence of length 3, containing...
	// Context-specific tag 2 of length 1, containing...
	// A byte of all ones (the representation of the boolean TRUE).
	idpExtImplicit := []byte{0x30, 0x03, 0x82, 0x01, 0xFF}
	log.Println(example(idpExtImplicit))

	// EXPLICIT encoding
	// Sequence of length 5, containing...
	// Context-specific tag 2 of length 3, containing...
	// Boolean of length 1, containing...
	// A byte of all ones (the representation of the boolean TRUE).
	idpExtExplicit := []byte{0x30, 0x05, 0x82, 0x03, 0x01, 0x01, 0xFF}
	log.Println(example(idpExtExplicit))
}
```

### What did you expect to see?

I expected ReadOptionalASN1Boolean to succeed when reading the onlyContainsCACerts field. That field is an optional boolean, so ReadOptionalASN1Boolean should successfully read it.

### What did you see instead?

ReadOptionalASN1Boolean fails when attempting to read the onlyContainsCACerts field. This is because ReadOptionalASN1Boolean _only works for explicitly-tagged fields_. It's expecting to get a five-byte sequence, as in my second example above: a context-specific tag and length, wrapping a normal boolean tag, length, and value.

But the IssuingDistributionPoint extension is defined (in both X.509 and RFC 5280) in [an ASN.1 module that sets DEFINITIONS IMPLICIT TAGS](https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.2), so these fields are implicitly-tagged, not explicitly-tagged. They're only correctly encoded as a three bytes: a context-specific tag and length followed immediately by the single-byte boolean itself.

There are lots of IMPLICIT fields in x509, so it's somewhat concerning that the cryptobyte parser only has facilities for parsing EXPLICIT fields.

_Note: Perhaps I should have expected the behavior I saw, because the documentation of the methods in question does say (emphasis added) "ReadOptionalASN1Boolean attempts to read an optional ASN.1 BOOLEAN **explicitly** tagged with tag...". But I think this single word is easy to miss, especially when it's being used as a term of art rather than as plain english. And regardless of the behavior of the existing methods, I still think it would be valuable to have methods which can parse implicitly-tagged fields._