PKCS#7 `ParsePKCS7()` fails to decode degenerate SignedData
- Dominant language
- Go
- Stars
- 9.5k
- Forks
- 1.2k
- PR merge metrics
- No merged PRs in 30d
Description
### Problem
CFSSL's PKCS#7 parser fails to decode valid degenerate PKCS#7 structures (e.g., certs-only bundles created via OpenSSL) with the error:
```
asn1: syntax error: sequence truncated
```
This is due to a broken struct tag in [crypto/pkcs7/pkcs7.go#L63](https://github.com/cloudflare/cfssl/blob/master/crypto/pkcs7/pkcs7.go#L63):
```go
Certificates asn1.RawValue `asn1:"optional" asn1:"tag:0"`
```
This syntax is **invalid in Go**: Go only uses **one tag string per struct field**. The `asn1` key appears **twice**, so **only the last one is parsed**.
#### Go Struct Tag Behavior
From Go's official reflection documentation:
> By convention, tag strings are a concatenation of key:"value" pairs, separated by spaces. To retrieve the value for a key, use the Get method.
> — [reflect.StructTag docs](https://pkg.go.dev/reflect#StructTag)
From the Go source, the tag parser in `encoding/asn1` uses:
```go
t.Field(i).Tag.Get("asn1")
```
[marshal.go line 961 (Go 1.21.0)](https://github.com/golang/go/blob/go1.21.0/src/encoding/asn1/marshal.go#L527)
This means the line in CFSSL:
```go
Certificates asn1.RawValue `asn1:"optional" asn1:"tag:0"`
```
is treated equivalently to:
```go
Certificates asn1.RawValue `asn1:"tag:0"`
```
The `optional` modifier is **ignored** entirely.
---
### ASN.1 Semantics
The CMS / PKCS#7 specification ([RFC 5652 §5.1](https://datatracker.ietf.org/doc/html/rfc5652#section-5.1)) defines the certificates field as:
```asn1
certificates [0] IMPLICIT CertificateSet OPTIONAL
```
Where `CertificateSet ::= SET OF CertificateChoices`. This means the DER encoding is:
```
A0 ... 31 ... # context-specific tag 0 (IMPLICIT), followed by SET (correct!)
```
But Go (with `tag:0`) expects:
```
A0 ... 30 ... # context-specific tag 0 (IMPLICIT), followed by SEQUENCE (wrong!)
```
Because **Go defaults to SEQUENCE** unless `set` is specified.
---
### Fix in Go
To parse a `[0] IMPLICIT SET OF` structure, the correct tag is:
```go
asn1:"optional,set,tag:0"
```
This informs Go to expect:
* Context-specific tag 0
* IMPLICIT tag replacement (default behavior)
* A SET (not a SEQUENCE)
* Optionality
---
### Proposed Fix
In [crypto/pkcs7/pkcs7.go](https://github.com/cloudflare/cfssl/blob/master/crypto/pkcs7/pkcs7.go#L63), change:
```go
Certificates asn1.RawValue `asn1:"optional" asn1:"tag:0"`
```
to:
```go
Certificates asn1.RawValue `asn1:"optional,set,tag:0"`
```
**Or**, better yet — parse the certs directly:
```go
Certificates []*x509.Certificate `asn1:"optional,set,tag:0"`
```
This will fix compatibility with all OpenSSL-generated `.p7b` bundles and bring CFSSL into alignment with the CMS standard. Caveat: I did not verify if this works.
---
### Repro
Create a degenerate PKCS#7 structure:
```bash
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 1 -subj "/CN=example.com"
openssl crl2pkcs7 -nocrl -certfile cert.pem -outform DER -out certs-only.p7b
```
An example PKCS7 file is attached below (example.p7b). For the purpose of posting it on this issue, it has been base64 encoded. (To feed it into ParsePKCS7 you need to execute `base64.StdEncoding.DecodeString` on it first, or use the command line statements above to get a binary DER file which isn't base64 encoded).
```
MIIBygYJKoZIhvcNAQcCoIIBuzCCAbcCAQExADALBgkqhkiG9w0BBwGgggGfMIIB
mzCCAUGgAwIBAgIUcYmQ/QL987d0R3vNzzKPsahNt7kwCgYIKoZIzj0EAwIwIzEU
MBIGA1UEAwwLZXhhbXBsZS5jb20xCzAJBgNVBAYTAlVTMB4XDTI1MDgwNDEwMzEy
NloXDTI1MDgwNTEwMzEyNlowIzEUMBIGA1UEAwwLZXhhbXBsZS5jb20xCzAJBgNV
BAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9ik4neGlQRDogJDhgFQ/
kD2myoojA6i78FE4U0oL6Pe8CyRvkC2Vvr422NW9gXFhLMcdQbzHdCmb71CsWv3K
/aNTMFEwHQYDVR0OBBYEFCl6EWnoXJjjN7t8TRaXHfSvC6jBMB8GA1UdIwQYMBaA
FCl6EWnoXJjjN7t8TRaXHfSvC6jBMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0E
AwIDSAAwRQIhAKiWWGvqZeIZej69JpEH5DU9cOFYgCdgIoqSRGn2ZA8UAiBPixlT
BXsBRGajsRPBHX19SetTmQ4m8GhykPptwYcoEDEA
```
Then call `ParsePKCS7()` on the result. It fails unless the struct tag is corrected.
---
### Request
Please update the tag on `Certificates` in `signedData` to include `set`. This will fix a standards-compliant decoding failure and make `ParsePKCS7()` work on widely used PKCS#7 formats.
Thanks!
### Context
Degenerate PKCS#7 is used for example in the EST protocol's cacerts endpoint [RFC 7030](https://datatracker.ietf.org/doc/html/rfc7030). Tools such as OpenSSL or Windows Certificate Manager can open the above example.p7b file without any issues.
Contributor guide
Assessment
This issue has not been assessed yet.