A personal Go library for CMS/PKCS#7 with Ed25519 support.
go-cms provides an idiomatic Go interface for creating and parsing
Cryptographic Message Syntax (CMS) messages, as specified in
RFC 5652.
Existing Go CMS libraries (mozilla/pkcs7, github.com/cloudflare/cfssl) do not support Ed25519 signatures. This library fills that gap with an implementation that passes OpenSSL verification for Ed25519-signed CMS messages.
⚠️ Important: This is an unreviewed personal project. While it passes OpenSSL interoperability tests, it has not undergone formal security review. Use at your own discretion.
- Ed25519 Signing: Create CMS
SignedDatastructures using Ed25519 keys. - Standards-Compliant: Aims for strict adherence to relevant RFCs for maximum interoperability.
- OpenSSL Compatible: Signatures generated by
go-cmscan be verified with standard OpenSSL tooling. - Clean API: A simple, Go-idiomatic interface that hides the complexity of ASN.1 encoding.
- Well-Tested: A high ratio of tests to code, including round-trip and interoperability tests.
- Zero Dependencies: Relies only on the Go standard library and
golang.org/x/crypto.
Current version: v0.0.1
The library is functional and the API is stable. All tests pass, including OpenSSL interoperability verification.
go get github.com/agentic-research/go-cmsThis example demonstrates signing a message with an Ed25519 key and generating a
detached CMS/PKCS#7 signature that can be verified with OpenSSL.
package main
import (
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"os"
"time"
"github.com/agentic-research/go-cms/pkg/cms"
)
func main() {
// 1. Generate a new Ed25519 key pair
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Fatalf("Failed to generate key: %v", err)
}
// 2. Create a self-signed certificate
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test Organization"},
Country: []string{"US"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, pubKey, privKey)
if err != nil {
log.Fatalf("Failed to create certificate: %v", err)
}
cert, err := x509.ParseCertificate(certDER)
if err != nil {
log.Fatalf("Failed to parse certificate: %v", err)
}
// 3. Sign the content
content := []byte("Hello, CMS!")
signature, err := cms.SignData(content, cert, privKey)
if err != nil {
log.Fatalf("Failed to sign content: %v", err)
}
// The output is a DER-encoded CMS message
fmt.Printf("Successfully generated CMS signature (%d bytes)\n", len(signature))
// 4. Write files for OpenSSL verification
if err := os.WriteFile("signature.der", signature, 0644); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("content.txt", content, 0644); err != nil {
log.Fatal(err)
}
// Export certificate for OpenSSL
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDER,
})
if err := os.WriteFile("cert.pem", certPEM, 0644); err != nil {
log.Fatal(err)
}
}You can verify the generated signature using the standard openssl command line
tool. After running the example above:
# Verify the signature with OpenSSL
openssl cms -verify \
-inform DER -in signature.der \
-content content.txt \
-certfile cert.pem \
-noverify \ # Use -noverify as the cert is self-signed
-binary # Important for detached signatures
# On success, the command will output:
# CMS Verification successful
# Hello, CMS!The library provides two main functions:
cms.SignData(data []byte, cert *x509.Certificate, privateKey ed25519.PrivateKey) ([]byte, error)- Creates a CMS signaturecms.Verify(cmsData, originalData []byte, opts VerifyOptions) ([]*x509.Certificate, error)- Verifies a CMS signature
For detailed API documentation, see pkg.go.dev.
The library enforces the following size limits for security:
- Maximum CMS signature size: 1MB (prevents memory exhaustion from malformed signatures)
- Maximum certificate size: 64KB (standard X.509 certificates are typically 1-4KB)
- Supported digest algorithms: SHA-256, SHA-384, SHA-512 (MD5 and SHA-1 are rejected; RFC 8419 mandates SHA-512 for Ed25519 with signedAttributes)
- Supported signature algorithm: Ed25519 only
This library has not been independently audited. What's described here is internal, audit-equivalent testing work done by the maintainer — not an external attestation.
The repository ships with a layered test methodology designed to make the work defensible without the price tag of a third-party review:
| Layer | What it does | Where |
|---|---|---|
| Unit + roundtrip tests | Sign/verify against RFC 8032 vectors, OpenSSL interop, expected-shape parsing | pkg/cms/*_test.go |
| Behavioral fuzzers | Random data + ephemeral key → sign → verify; assert roundtrip and tamper-detection invariants | pkg/cms/behavioral_fuzz_test.go, tier2_fuzz_test.go, tier3_fuzz_test.go |
| RFC-traceable tests | Named per RFC clause so you can grep TestRFC5652_5_3 and see what's covered |
pkg/cms/rfc_compliance_test.go |
| Named threat-class tests | One test per documented CMS attack (replay, key confusion, trust bypass, etc.) | pkg/cms/attack_scenarios_test.go |
| DER strictness probes | Reject non-canonical length encodings (BER) on the verifier side | pkg/cms/der_strictness_test.go |
| Byte-by-byte tamper enumeration | Verifies every byte of a Case 1 / Case 2 CMS is load-bearing | pkg/cms/tamper_enum_test.go |
Concurrent sign/verify under -race |
32 goroutines × 200 verifications | pkg/cms/concurrency_test.go |
| Length-encoding boundary tests | Byte-exact DER output at every length-form boundary (X.690 §8.1.3.4) | pkg/cms/length_boundary_test.go |
| CMS construction harness | Test-only builder for reaching verifier paths the production signer can't emit (e.g. SKI form) | pkg/cms/cms_builder_test.go |
| Mutation testing with CI gate | gremlins at ≥80% efficacy floor — see MUTATION_BASELINE.md |
.github/workflows/ci.yml |
| Static analysis | gosec (no exclusions), govulncheck, golangci-lint |
.github/workflows/ci.yml |
| Toolchain CVE tracking | go.mod pinned to a stdlib patch that clears all reachable CVEs surfaced by govulncheck |
go.mod |
Run the full audit-level suite locally:
make test # full suite under -race with coverage
make long-fuzz # every fuzzer for 10m each (override FUZZTIME=)
make overnight-fuzz # FUZZTIME=1h per fuzzer (~16h total)
make mutation-test # gremlins mutation analysis
make govulncheck # stdlib + dependency CVE check
make docker-test # OpenSSL interop in a clean containerBugs found and fixed by this methodology (all in this repo's git history):
SignedData.Versionaccepted anyintinstead of the RFC 5652 §5.1 whitelist of{1, 3, 4, 5}(#13)EncapContentInfo.eContentTypewas unchecked whensignedAttributeswere absent — RFC 5652 §11.1 requiresid-data(#13)parseASN1Lengthaccepted non-canonical DER length encodings, a malleability surface (#13)matchesSIDexpected EXPLICIT[0]wrapping of SubjectKeyIdentifier instead of the RFC-canonical IMPLICIT form — meaning the verifier could not validate SKI-form CMS produced by OpenSSL orgithub.com/github/ietf-cms(#14)
What this methodology does not cover:
- Cryptographic primitive correctness (Ed25519, SHA-2 family) — covered by Go stdlib's own test suite.
- Side-channel resistance beyond
crypto/subtle.ConstantTimeCompareusage. - Hardware Security Module integration — out of scope.
- Formal protocol-level review of CMS embedding inside higher-level protocols (S/MIME, CAdES, etc.) — out of scope.
This is a personal, open-source project. While developed with security best practices in mind, it comes with no guarantees.
For responsible disclosure of security vulnerabilities, please email me directly
at jamestexasgardner [at] gmail [dot] comand prefix the subject with[SECURITY]. I will endeavor to address all issues promptly. A formal SECURITY.md` file will be
created as the project matures. You can also reach out to me via @jamestexas on Github.
Contributions are welcome! If you are interested in helping, please see the contributing documentation for guidelines. Priority areas include adding support for parsing/verification and expanding algorithm support. Please ensure all contributions include appropriate tests.