Skip to content

agentic-research/go-cms

Repository files navigation

🛡️ go-cms

Version

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.

Features

  • Ed25519 Signing: Create CMS SignedData structures using Ed25519 keys.
  • Standards-Compliant: Aims for strict adherence to relevant RFCs for maximum interoperability.
  • OpenSSL Compatible: Signatures generated by go-cms can 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.

Status

Current version: v0.0.1

The library is functional and the API is stable. All tests pass, including OpenSSL interoperability verification.

Installation

go get github.com/agentic-research/go-cms

Quick Start

This 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)
    }
}

Verifying with OpenSSL

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!

API Documentation

The library provides two main functions:

  • cms.SignData(data []byte, cert *x509.Certificate, privateKey ed25519.PrivateKey) ([]byte, error) - Creates a CMS signature
  • cms.Verify(cmsData, originalData []byte, opts VerifyOptions) ([]*x509.Certificate, error) - Verifies a CMS signature

For detailed API documentation, see pkg.go.dev.

Limits

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

Testing & Hardening

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 container

Bugs found and fixed by this methodology (all in this repo's git history):

  • SignedData.Version accepted any int instead of the RFC 5652 §5.1 whitelist of {1, 3, 4, 5} (#13)
  • EncapContentInfo.eContentType was unchecked when signedAttributes were absent — RFC 5652 §11.1 requires id-data (#13)
  • parseASN1Length accepted non-canonical DER length encodings, a malleability surface (#13)
  • matchesSID expected EXPLICIT [0] wrapping of SubjectKeyIdentifier instead of the RFC-canonical IMPLICIT form — meaning the verifier could not validate SKI-form CMS produced by OpenSSL or github.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.ConstantTimeCompare usage.
  • 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.

Security

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.

Contributing

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.

Packages

 
 
 

Contributors