cryptoutil

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2026 License: BSD-2-Clause-Views Imports: 11 Imported by: 14

README

go-cryptoutil

Go Reference Go Report Card coverage Build Status License Go Version

Extensible Go crypto utilities for certificate parsing, signature verification, ECDSA encoding, algorithm mapping, and key management — with a plugin architecture for non-standard curves and future post-quantum algorithms.

Overview

Go's crypto/x509 package supports a fixed set of key types and curves. go-cryptoutil provides an extension mechanism that lets you plug in support for additional algorithms (e.g. brainpool curves, PQ signatures) without forking the standard library.

Core Module (go-cryptoutil)

Zero external dependencies. Provides:

  • x509ext.go — Extensible certificate parsing and signature verification. Falls back to crypto/x509 first, then tries registered extension parsers/verifiers.
  • ecdsa.go — ECDSA signature format conversion between IEEE P1363 (raw r‖s) and ASN.1 DER. Used by XML-DSIG, JWS, COSE, and WebAuthn.
  • algorithms.go — Cross-protocol algorithm registry mapping keys to JWS, XML-DSIG, and COSE algorithm identifiers.
  • keyutil.go — Extensible private key parsing (PKCS#8, EC, PKCS#1) plus key type inspection helpers.
Brainpool Plugin (go-cryptoutil/brainpool)

Separate Go module with the gematik brainpool dependency. Provides:

  • Register(ext) — Registers brainpool P256r1, P384r1, P512r1 certificate parsing, signature verification, key parsing, and algorithm mappings.

Installation

# Core module (zero dependencies)
go get github.com/sirosfoundation/go-cryptoutil

# Brainpool plugin (adds gematik dependency)
go get github.com/sirosfoundation/go-cryptoutil/brainpool

Usage

Basic: Extensible Certificate Parsing
import (
    "github.com/sirosfoundation/go-cryptoutil"
    "github.com/sirosfoundation/go-cryptoutil/brainpool"
)

ext := cryptoutil.New()
brainpool.Register(ext)

// Parse certificates — stdlib curves + brainpool
cert, err := ext.ParseCertificate(derBytes)

// Parse PEM bundles
certs, err := ext.ParseCertificatesPEM(pemData)
ECDSA Signature Conversion
// XML-DSIG / JWS raw r||s → ASN.1 DER (for Go's x509.CheckSignature)
derSig, err := cryptoutil.ECDSARawToASN1(rawSig)

// ASN.1 DER → raw r||s (for signing output)
rawSig, err := cryptoutil.ECDSAASN1ToRaw(derSig, 32) // 32 for P-256
Algorithm Lookup
ext := cryptoutil.New()
alg := ext.Algorithms.ForKey(publicKey)
fmt.Println(alg.JWS)     // "ES256"
fmt.Println(alg.XMLDSIG)  // "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"

Architecture

go-cryptoutil/           Core module (zero deps)
├── x509ext.go           Extensible cert parsing + sig verification
├── ecdsa.go             ECDSA raw↔ASN.1 conversion
├── algorithms.go        Cross-protocol algorithm registry
├── keyutil.go           Extensible key parsing + helpers
└── brainpool/           Plugin submodule
    └── brainpool.go     Brainpool P256r1/P384r1/P512r1 support

The extension mechanism uses function types (CertificateParser, SignatureVerifier, PrivateKeyParser) registered on an Extensions struct. The core always tries Go's standard library first and falls back to registered extensions, returning ErrNotHandled to signal the next extension should be tried.

Writing a Plugin

func Register(ext *cryptoutil.Extensions) {
    ext.Parsers = append(ext.Parsers, func(der []byte) (*x509.Certificate, error) {
        // Try to parse; return cryptoutil.ErrNotHandled if not your cert type
        return myCert, nil
    })
    ext.Verifiers = append(ext.Verifiers, func(cert *x509.Certificate, algo x509.SignatureAlgorithm, signed, sig []byte) error {
        // Verify signature; return cryptoutil.ErrNotHandled if not your algorithm
        return nil
    })
}

Development

make test          # Run all tests
make lint          # Run golangci-lint
make coverage      # Generate coverage report
make check-coverage # Check coverage thresholds
make setup         # Install tools + git hooks

License

BSD 2-Clause. See LICENSE.txt.

Documentation

Overview

Package cryptoutil provides extensible cryptographic utilities for certificate parsing, signature verification, key management, and algorithm mapping.

The core type is Extensions, which holds pluggable parsers, verifiers, and algorithm mappers. The standard library functions are always tried first; registered extensions act as fallbacks for algorithm families that Go's crypto/x509 does not support (e.g. Brainpool curves, PQ algorithms).

Index

Constants

This section is empty.

Variables

View Source
var ErrNotHandled = errors.New("cryptoutil: not handled by this extension")

ErrNotHandled signals that an extension does not handle this input. Extensions should return this (not nil) when declining to process.

Functions

func ECDSAASN1ToRaw

func ECDSAASN1ToRaw(der []byte, componentSize int) ([]byte, error)

ECDSAASN1ToRaw converts an ASN.1 DER-encoded ECDSA signature to IEEE P1363 raw (r||s) format with the given byte size per component (typically key size in bytes: 32 for P-256, 48 for P-384, 66 for P-521).

Each component is zero-padded on the left to exactly componentSize bytes.

func ECDSAComponentSize

func ECDSAComponentSize(curve elliptic.Curve) int

ECDSAComponentSize returns the expected byte size of each r/s component for the given curve. This is useful when converting between ASN.1 DER and raw r||s ECDSA signatures.

func ECDSARawToASN1

func ECDSARawToASN1(raw []byte) ([]byte, error)

ECDSARawToASN1 converts an IEEE P1363 raw (r||s) ECDSA signature to ASN.1 DER encoding. This format conversion is required by XML-DSIG (RFC 4050), JWS (RFC 7518), COSE (RFC 9053), and WebAuthn, all of which use the raw concatenation format, while Go's crypto/ecdsa expects ASN.1 DER.

The input must be an even number of bytes with r and s each occupying exactly half the total length.

func IsECDSAKey

func IsECDSAKey(pub crypto.PublicKey) bool

IsECDSAKey returns true if the public key is an ECDSA key.

func IsEdDSAKey

func IsEdDSAKey(pub crypto.PublicKey) bool

IsEdDSAKey returns true if the public key is an Ed25519 key.

func IsRSAKey

func IsRSAKey(pub crypto.PublicKey) bool

IsRSAKey returns true if the public key is an RSA key.

func NormalizeECDSASignature added in v0.4.0

func NormalizeECDSASignature(sig []byte) ([]byte, error)

NormalizeECDSASignature takes an ECDSA signature that may be BER-encoded (with unnecessary leading zeros, wrong padding, etc.) and returns a strictly DER-encoded signature. This is needed because some authenticators (notably YubiKey 5.8 firmware) produce BER-encoded signatures that Go's strict DER parser in crypto/x509 rejects.

BER (Basic Encoding Rules) is more permissive than DER (Distinguished Encoding Rules). While both are valid ASN.1, X.509 signatures should use DER. This function normalizes BER to DER by: 1. Leniently parsing the BER-encoded ASN.1 SEQUENCE 2. Extracting the raw r and s integer values 3. Re-encoding as strict DER

Returns the original signature unchanged if it's already valid DER, or an error if it cannot be parsed as an ECDSA signature at all.

func PublicKeyFromSigner

func PublicKeyFromSigner(s crypto.Signer) crypto.PublicKey

PublicKeyFromSigner extracts the public key from a crypto.Signer.

Types

type Algorithm

type Algorithm struct {
	Name    string      // Canonical name: "P-256", "RS256", etc.
	JWS     string      // JWS/JWA identifier (RFC 7518)
	XMLDSIG string      // XML-DSIG algorithm URI
	COSE    int         // COSE algorithm number (RFC 9053), 0 if not assigned
	Hash    crypto.Hash // Hash function for this algorithm
	KeyType string      // "EC", "RSA", "OKP", etc.
}

Algorithm describes a cryptographic algorithm across multiple protocol domains.

type AlgorithmRegistry

type AlgorithmRegistry struct {
	// CurveAlgorithms maps elliptic curve Params().Name to Algorithm.
	CurveAlgorithms map[string]*Algorithm

	// All is the full list of registered algorithms.
	All []*Algorithm
	// contains filtered or unexported fields
}

AlgorithmRegistry maps public keys to algorithms across protocol domains.

func NewAlgorithmRegistry

func NewAlgorithmRegistry() *AlgorithmRegistry

NewAlgorithmRegistry returns a registry pre-populated with standard NIST algorithms.

func (*AlgorithmRegistry) ByXMLDSIG added in v0.3.0

func (r *AlgorithmRegistry) ByXMLDSIG(uri string) *Algorithm

ByXMLDSIG returns the Algorithm registered for the given XML-DSIG URI, or nil if the URI is not recognized.

func (*AlgorithmRegistry) COSEAlgorithm

func (r *AlgorithmRegistry) COSEAlgorithm(pub crypto.PublicKey) (int, error)

COSEAlgorithm returns the COSE algorithm number for the given public key.

func (*AlgorithmRegistry) CurveName

func (r *AlgorithmRegistry) CurveName(pub *ecdsa.PublicKey) (string, error)

CurveName returns the JWK curve name (e.g. "P-256") for an elliptic curve key.

func (*AlgorithmRegistry) ForKey

func (r *AlgorithmRegistry) ForKey(pub crypto.PublicKey) (*Algorithm, error)

ForKey returns the Algorithm for the given public key, or an error if the key type or curve is not recognized.

func (*AlgorithmRegistry) JWSAlgorithm

func (r *AlgorithmRegistry) JWSAlgorithm(pub crypto.PublicKey) (string, error)

JWSAlgorithm returns the JWS/JWA algorithm name for the given public key.

func (*AlgorithmRegistry) Register

func (r *AlgorithmRegistry) Register(a *Algorithm)

Register adds an algorithm to the registry. If the algorithm has KeyType "EC", it is also indexed by its Name in CurveAlgorithms.

func (*AlgorithmRegistry) XMLDSIGAlgorithm

func (r *AlgorithmRegistry) XMLDSIGAlgorithm(pub crypto.PublicKey) (string, error)

XMLDSIGAlgorithm returns the XML-DSIG algorithm URI for the given public key.

type CertificateParser

type CertificateParser func(der []byte) (*x509.Certificate, error)

CertificateParser tries to parse a DER-encoded certificate that the standard library rejected. Return the certificate on success, or ErrNotHandled if the parser does not recognize this certificate type.

type Extensions

type Extensions struct {
	Parsers    []CertificateParser
	Verifiers  []SignatureVerifier
	Algorithms *AlgorithmRegistry
	KeyParsers []PrivateKeyParser
}

Extensions holds registered crypto extensions. Pass an instance through your application's configuration; use New to create one with defaults.

func New

func New() *Extensions

New returns an Extensions with an empty AlgorithmRegistry initialized.

func (*Extensions) CheckSignature

func (ext *Extensions) CheckSignature(cert *x509.Certificate, algo x509.SignatureAlgorithm, signed, signature []byte) error

CheckSignature tries the standard cert.CheckSignature first, then each registered SignatureVerifier. Returns nil on the first successful verification.

func (*Extensions) CheckSignatureXMLDSIG added in v0.3.0

func (ext *Extensions) CheckSignatureXMLDSIG(cert *x509.Certificate, xmldsigURI string, signed, signature []byte) error

CheckSignatureXMLDSIG verifies a signature using an XML-DSIG algorithm URI (e.g. "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256"). It resolves the URI via the AlgorithmRegistry, tries the standard x509.Certificate.CheckSignature if possible, and falls back to registered SignatureVerifiers. This enables verification of non-standard algorithms (e.g. brainpool) that have registered XML-DSIG URIs.

func (*Extensions) ParseCertificate

func (ext *Extensions) ParseCertificate(der []byte) (*x509.Certificate, error)

ParseCertificate tries Go's x509.ParseCertificate first, then each registered CertificateParser in order. Returns the first successful result. If stdlib parses the certificate but cannot extract the public key (PublicKey is nil), extension parsers are also tried.

func (*Extensions) ParseCertificatesPEM

func (ext *Extensions) ParseCertificatesPEM(pemData []byte) ([]*x509.Certificate, error)

ParseCertificatesPEM parses all certificates from PEM-encoded data, using ext.ParseCertificate for each block so that extended algorithms (brainpool, PQ, etc.) are supported. Non-CERTIFICATE blocks are skipped.

func (*Extensions) ParsePrivateKey

func (ext *Extensions) ParsePrivateKey(der []byte) (crypto.PrivateKey, error)

ParsePrivateKey parses a DER-encoded private key, trying Go's standard parsers (PKCS#8, EC, PKCS#1) first, then registered KeyParsers.

func (*Extensions) ParsePrivateKeyPEM

func (ext *Extensions) ParsePrivateKeyPEM(pemData []byte) (crypto.PrivateKey, error)

ParsePrivateKeyPEM parses a PEM-encoded private key. Supports RSA PRIVATE KEY, EC PRIVATE KEY, and PRIVATE KEY block types, plus registered extension parsers.

type PrivateKeyParser

type PrivateKeyParser func(der []byte) (crypto.PrivateKey, error)

PrivateKeyParser tries to parse a DER-encoded private key that the standard library cannot handle. Return the key on success, or ErrNotHandled if the parser does not recognize this key type.

type SignatureVerifier

type SignatureVerifier func(cert *x509.Certificate, algo x509.SignatureAlgorithm, signed, signature []byte) error

SignatureVerifier verifies a signature on signed data using a certificate whose key type the standard library cannot handle. Return nil on success, ErrNotHandled if not applicable, or an error describing the failure.

func BERTolerantECDSAVerifier added in v0.4.0

func BERTolerantECDSAVerifier() SignatureVerifier

BERTolerantECDSAVerifier returns a SignatureVerifier that handles ECDSA signatures with BER encoding. It normalizes the signature to DER before verification. This is useful as a fallback when the standard x509 CheckSignature fails due to BER-encoded signatures.

Usage:

ext := cryptoutil.New()
ext.Verifiers = append(ext.Verifiers, cryptoutil.BERTolerantECDSAVerifier())

Directories

Path Synopsis
brainpool module

Jump to

Keyboard shortcuts

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