cms

package
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2025 License: Apache-2.0 Imports: 14 Imported by: 3

README ¶

pkg/cms

CMS/PKCS#7 signature implementation with Ed25519 support.

Status: 🧪 Experimental (Working!)

First Go library to support Ed25519 in CMS/PKCS#7 format!

What It Does

Creates OpenSSL-compatible CMS signatures using Ed25519 keys. The key innovation is dual encoding of SignedAttributes:

graph TB
    subgraph "The Trick That Makes It Work"
        Attrs[SignedAttributes]
        Attrs --> SET["SET (0x31)<br/>For Signing"]
        Attrs --> IMPLICIT["IMPLICIT [0] (0xA0)<br/>For Storage"]

        SET --> Sign[Ed25519.Sign]
        Sign --> Signature
        IMPLICIT --> Store[Store in CMS]
        Signature --> Store

        Store --> OpenSSL[OpenSSL Compatible!]
    end

    style SET fill:#99ff99
    style IMPLICIT fill:#99ff99

Files

  • signer.go - Core CMS implementation
  • signer_test.go - Tests with RFC 8032 test vectors

How It Works

// The magic happens here:
signedAttrs := createSignedAttributes(digest, signingTime)

// For signing: Encode as SET (what OpenSSL expects)
toSign := encodeAttributesAsSet(signedAttrs)  // Tag: 0x31
signature := ed25519.Sign(privateKey, toSign)

// For storage: Encode as IMPLICIT [0]
toStore := encodeAttributesAsImplicit(signedAttrs)  // Tag: 0xA0

// Both go into the final CMS structure

OpenSSL Verification

# Our signatures verify with OpenSSL!
openssl cms -verify -in signature.pem -inform PEM \
  -noverify -content message.txt -binary

Note: The -binary flag is required for detached signatures.

Technical Details

  • Implements RFC 5652 (CMS) with RFC 8419 (Ed25519 in CMS)
  • Uses deterministic ASN.1 DER encoding
  • Includes signing time and message digest attributes
  • Self-signed certificates with 5-minute validity
  • Certificates are emitted using [0] IMPLICIT tagging with the raw DER certificate bytes (no nested SET), matching OpenSSL's CMS_CertificateChoices expectations

Test Vectors

Uses RFC 8032 Section 7.1 test vectors (allowlisted in .gitleaks.toml):

  • Secret key: 9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60
  • Public key: d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a

These are public test vectors, not secrets!

Documentation ¶

Overview ¶

Package cms provides CMS/PKCS#7 signature generation and verification with Ed25519 support.

Package cms implements CMS/PKCS#7 signature generation with Ed25519 support.

This package fills a gap in the Go ecosystem as existing CMS libraries (mozilla/pkcs7, cloudflare/cfssl) do not support Ed25519 signatures.

Security Considerations ¶

Time Security: The signing time attribute is included in the CMS signature and is cryptographically protected. However, the accuracy depends on the security of the time source. For production use, provide a TimeSource synchronized with a trusted NTP server or use a hardware-based secure time source via SignDataWithOptions.

Private Key Memory: Go's garbage collector does not guarantee that memory containing sensitive data will be zeroed before being reused or released. Private keys passed to SignData will remain in memory until garbage collected, and there is no way to force immediate clearing of this memory. For high-security environments requiring guaranteed key material erasure from memory, consider:

  • Using hardware security modules (HSMs) that keep keys in secure hardware
  • Using platform-specific memory protection mechanisms
  • Minimizing the time private keys are held in memory
  • Using short-lived signing keys

Package cms implements CMS/PKCS#7 signature verification with Ed25519 support.

This package provides RFC 5652 compliant verification of CMS/PKCS#7 signatures using Ed25519 keys, which is unique among Go CMS implementations.

Security Considerations ¶

Memory Security: For memory security considerations when handling private keys in verification contexts (e.g., when using HSMs or secure enclaves for signing), see the signer package documentation.

Example usage:

// Read CMS signature and data
cmsData, _ := os.ReadFile("signature.p7s")
originalData, _ := os.ReadFile("document.txt")

// Setup verification options
opts := cms.VerifyOptions{
	Roots: systemRootPool, // Optional: uses system roots if nil
}

// Verify the signature
chain, err := cms.Verify(cmsData, originalData, opts)
if err != nil {
	log.Fatal("Verification failed:", err)
}

// The first certificate is the signer
signerCert := chain[0]
fmt.Printf("Signed by: %s\n", signerCert.Subject)

Index ¶

Constants ¶

This section is empty.

Variables ¶

This section is empty.

Functions ¶

func SignData ¶

func SignData(data []byte, cert *x509.Certificate, privateKey ed25519.PrivateKey) ([]byte, error)

SignData creates a detached CMS/PKCS#7 signature using Ed25519.

This function implements RFC 5652 (CMS) with RFC 8410 (Ed25519 in CMS). The signature is detached (does not include the original data).

This creates a Case 1 signature (WITH signed attributes), which includes: - Content-type attribute - Message-digest attribute - Signing-time attribute

For Case 2 signatures (WITHOUT signed attributes), use SignDataWithoutAttributes. For more control over signature generation (e.g., custom time sources), use SignDataWithOptions.

Parameters:

  • data: The data to be signed
  • cert: The X.509 certificate containing the public key
  • privateKey: The Ed25519 private key for signing

Returns:

  • DER-encoded CMS/PKCS#7 signature

func SignDataWithOptions ¶

func SignDataWithOptions(data []byte, cert *x509.Certificate, privateKey ed25519.PrivateKey, opts SignOptions) ([]byte, error)

SignDataWithOptions creates a detached CMS/PKCS#7 signature using Ed25519 with custom options.

This function implements RFC 5652 (CMS) with RFC 8410 (Ed25519 in CMS). The signature is detached (does not include the original data).

Parameters:

  • data: The data to be signed
  • cert: The X.509 certificate containing the public key
  • privateKey: The Ed25519 private key for signing
  • opts: Optional parameters for signature generation

Returns:

  • DER-encoded CMS/PKCS#7 signature

func SignDataWithSigner ¶ added in v0.0.2

func SignDataWithSigner(data []byte, cert *x509.Certificate, signer crypto.Signer) ([]byte, error)

SignDataWithSigner creates a detached CMS/PKCS#7 signature using a crypto.Signer.

This function enables hardware-backed signing (e.g., PKCS#11, HSMs) by accepting the standard crypto.Signer interface instead of requiring direct access to the private key material.

Parameters:

  • data: The data to be signed
  • cert: The X.509 certificate containing the public key
  • signer: A crypto.Signer implementation (e.g., ed25519.PrivateKey)

Returns:

  • DER-encoded CMS/PKCS#7 signature

func SignDataWithoutAttributes ¶ added in v0.0.3

func SignDataWithoutAttributes(data []byte, cert *x509.Certificate, privateKey ed25519.PrivateKey) ([]byte, error)

SignDataWithoutAttributes creates a Case 2 CMS/PKCS#7 signature (without signed attributes).

This function implements RFC 5652 (CMS) with RFC 8419 (Ed25519 in CMS) for Case 2: - No signed attributes are included - For Ed25519 (PureEdDSA), the signature is directly over the raw data - The signature is detached (does not include the original data)

Case 2 signatures are simpler but provide less metadata: - No signing time - No content-type protection - Message digest is implicit (not included as an attribute)

Use this when: - You need minimal signature size - Compatibility with systems that don't support signed attributes - The signing time is not important

Parameters:

  • data: The data to be signed
  • cert: The X.509 certificate containing the public key
  • privateKey: The Ed25519 private key for signing

Returns:

  • DER-encoded CMS/PKCS#7 signature without signed attributes

func Verify ¶

func Verify(cmsSignature, detachedData []byte, opts VerifyOptions) ([]*x509.Certificate, error)

Verify parses and validates a detached CMS/PKCS#7 signature

This function implements RFC 5652 (CMS) verification for Ed25519 signatures. It validates the signature structure, certificate chain, message digest, and cryptographic signature.

Parameters:

  • cmsSignature: DER-encoded CMS/PKCS#7 signature
  • detachedData: The original data that was signed
  • opts: Verification options including trusted roots

Returns:

  • The validated certificate chain (signer cert first, then intermediates)
  • An error if verification fails at any step

Types ¶

type KeyError ¶

type KeyError struct {
	Operation string // Operation that failed (generate, load, verify, etc.)
	KeyType   string // Type of key (master, ephemeral, etc.)
	Wrapped   error  // Underlying error
}

KeyError provides detailed information about key operation failures

func NewKeyError ¶

func NewKeyError(operation, keyType string, wrapped error) *KeyError

NewKeyError creates a new KeyError

func (*KeyError) Error ¶

func (e *KeyError) Error() string

func (*KeyError) Unwrap ¶

func (e *KeyError) Unwrap() error

type RevocationChecker ¶

type RevocationChecker interface {
	// CheckRevocation checks if a certificate has been revoked.
	// Returns an error if the certificate is revoked or if the check fails.
	// Returns nil if the certificate is valid and not revoked.
	CheckRevocation(cert *x509.Certificate) error
}

RevocationChecker provides an interface for checking certificate revocation status. Implementations can use CRL, OCSP, or other revocation mechanisms.

Example implementation:

type MyRevocationChecker struct{}

func (c *MyRevocationChecker) CheckRevocation(cert *x509.Certificate) error {
    // Check CRL or OCSP
    if isRevoked(cert) {
        return fmt.Errorf("certificate %s is revoked", cert.SerialNumber)
    }
    return nil
}

type SignOptions ¶

type SignOptions struct {
	// TimeSource provides the time to use for the signing time attribute.
	// If nil, time.Now() is used. For production use, consider using a
	// secure time source synchronized with a trusted time server.
	TimeSource TimeSource

	// DigestAlgorithm specifies the hash algorithm to use for message digest.
	//
	// IMPORTANT: For Ed25519 signatures, this field is IGNORED.
	// RFC 8419 Section 3 mandates that Ed25519 with signed attributes MUST use SHA-512.
	// This requirement is enforced regardless of the value specified here.
	//
	// For future support of other signature algorithms (RSA, ECDSA), this field
	// would allow selection of the digest algorithm.
	DigestAlgorithm crypto.Hash

	// IntermediateCerts are additional certificates to include in the CMS structure.
	// These should be intermediate CA certificates in the chain from the signer
	// certificate to the root CA. The signer certificate (passed to SignData) is
	// always included; these are additional certificates to help verifiers build
	// the certificate chain.
	IntermediateCerts []*x509.Certificate
}

SignOptions provides optional parameters for signature generation.

type SignatureError ¶

type SignatureError struct {
	Type    string // Type of signature (binding, request, CMS, etc.)
	Reason  string // Human-readable reason for failure
	Wrapped error  // Underlying error
}

SignatureError provides detailed information about signature verification failures

func NewSignatureError ¶

func NewSignatureError(sigType, reason string, wrapped error) *SignatureError

NewSignatureError creates a new SignatureError

func (*SignatureError) Error ¶

func (e *SignatureError) Error() string

func (*SignatureError) Unwrap ¶

func (e *SignatureError) Unwrap() error

type TimeSource ¶

type TimeSource interface {
	// Now returns the current time to be used for signature generation.
	Now() time.Time
}

TimeSource provides the current time for signature generation. Implementations can provide secure time sources (NTP, trusted time servers) or fixed times for testing.

Security Note: The signing time is included in the signed attributes and is cryptographically protected by the signature. However, the accuracy of this time depends on the security of the time source. For high-security applications, use a trusted time source synchronized with a reliable NTP server or a hardware-based secure time source.

Example implementation:

type SecureTimeSource struct{}

func (s *SecureTimeSource) Now() time.Time {
    // Query trusted NTP server or hardware time source
    return getSecureTime()
}

type ValidationError ¶

type ValidationError struct {
	Field   string // Field that failed validation
	Value   string // Value that was invalid (if safe to include)
	Reason  string // Why it's invalid
	Wrapped error  // Underlying error
}

ValidationError represents validation failures

func NewValidationError ¶

func NewValidationError(field, value, reason string, wrapped error) *ValidationError

NewValidationError creates a new ValidationError

func (*ValidationError) Error ¶

func (e *ValidationError) Error() string

func (*ValidationError) Unwrap ¶

func (e *ValidationError) Unwrap() error

type VerifyOptions ¶

type VerifyOptions struct {
	Roots              *x509.CertPool     // Trusted root certificates
	Intermediates      *x509.CertPool     // Intermediate certificates
	CurrentTime        time.Time          // Time for validation (default: time.Now())
	TimeFunc           func() time.Time   // Optional time source for testing (overrides CurrentTime)
	SkipTimeValidation bool               // Skip certificate expiry validation (useful for ephemeral certificate scenarios, e.g., short-lived certs in automation or Git commits)
	KeyUsages          []x509.ExtKeyUsage // Required key usages
	RevocationChecker  RevocationChecker  // Optional revocation checker (CRL/OCSP)
	MaxSignatureSize   int64              // Maximum signature size in bytes (default: 1MB, prevents DoS)
}

VerifyOptions allows specifying verification parameters

Directories ¶

Path Synopsis
Package internal provides shared internal utilities for the CMS package.
Package internal provides shared internal utilities for the CMS package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL