cloudflare / cloudflare/cfssl

PKCS#7 `ParsePKCS7()` fails to decode degenerate SignedData

Open
#1,425 1 comment 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.