Documentation
¶
Overview ¶
Package pki provides interfaces and implementations for cryptographic signing operations. It supports multiple backends including software keys and PKCS#11 hardware security modules.
Example (TransparentKeyLoading) ¶
Example demonstrates transparent key loading with KeyConfig
// Example: Source-transparent key loading using KeyConfig
// This demonstrates how components don't need to care about key sources
package main
import (
"crypto"
"crypto/rand"
"fmt"
"github.com/SUNET/vc/pkg/pki"
)
// SigningService doesn't care whether keys come from files or HSM
type SigningService struct {
keyLoader *pki.KeyLoader
keyConfig *pki.KeyConfig
}
func (s *SigningService) Sign(data []byte) ([]byte, error) {
// Same code for both file and HSM keys!
km, err := s.keyLoader.LoadKeyMaterial(s.keyConfig)
if err != nil {
return nil, fmt.Errorf("failed to load key: %w", err)
}
// Use standard crypto.Signer interface
signer := km.PrivateKey.(crypto.Signer)
hash := crypto.SHA256.New()
hash.Write(data)
digest := hash.Sum(nil)
signature, err := signer.Sign(rand.Reader, digest, crypto.SHA256)
if err != nil {
return nil, fmt.Errorf("signing failed: %w", err)
}
fmt.Printf("Signed with algorithm: %s\n", km.SigningMethod.Alg())
return signature, nil
}
// Example demonstrates transparent key loading with KeyConfig
func main() {
data := []byte("data to sign")
keyLoader := pki.NewKeyLoader()
// Scenario 1: Development with file-based keys
fmt.Println("=== Development (File-based keys) ===")
devConfig := &pki.KeyConfig{
PrivateKeyPath: "/path/to/dev/key.pem",
ChainPath: "/path/to/dev/chain.pem",
}
devService := &SigningService{
keyLoader: keyLoader,
keyConfig: devConfig,
}
sig, err := devService.Sign(data)
if err != nil {
fmt.Printf("Dev signing error: %v\n", err)
} else {
fmt.Printf("Signature length: %d bytes\n\n", len(sig))
}
// Scenario 2: Production with HSM keys
// SAME component code, just different KeyConfig!
fmt.Println("=== Production (HSM keys) ===")
prodConfig := &pki.KeyConfig{
PrivateKeyPath: "production-signing-key", // HSM label
PKCS11: &pki.PKCS11Config{
ModulePath: "/usr/lib/softhsm/libsofthsm2.so",
SlotID: 0,
PIN: "1234",
KeyID: "prod-key-1",
},
}
prodService := &SigningService{
keyLoader: keyLoader,
keyConfig: prodConfig,
}
sig, err = prodService.Sign(data)
if err != nil {
fmt.Printf("Production signing error: %v\n", err)
} else {
fmt.Printf("Signature length: %d bytes\n", len(sig))
}
// Scenario 3: Fallback configuration (try HSM first, fall back to file)
fmt.Println("=== Fallback (HSM → File) ===")
fallbackConfig := &pki.KeyConfig{
PrivateKeyPath: "/backup/key.pem",
ChainPath: "/backup/chain.pem",
PKCS11: &pki.PKCS11Config{
ModulePath: "/usr/lib/softhsm/libsofthsm2.so",
SlotID: 0,
PIN: "1234",
KeyID: "fallback-key",
},
Priority: []pki.KeySource{pki.KeySourceHSM, pki.KeySourceFile},
}
fallbackService := &SigningService{
keyLoader: keyLoader,
keyConfig: fallbackConfig,
}
sig, err = fallbackService.Sign(data)
if err != nil {
fmt.Printf("Fallback signing error: %v\n", err)
} else {
fmt.Printf("Signature length: %d bytes\n", len(sig))
}
// Key benefits:
// 1. SigningService code is identical for all scenarios
// 2. Switch sources by changing only KeyConfig
// 3. No conditional logic based on key source
// 4. Support fallback scenarios with Priority
// 5. Enable/disable sources with flags
}
Output:
Index ¶
- func DecodeECDSASignature(signature []byte, curve elliptic.Curve) (*big.Int, *big.Int, error)
- func EncodeECDSASignature(r, s *big.Int, curve elliptic.Curve) ([]byte, error)
- func GetKeySizeForCurve(curve elliptic.Curve) int
- func ParseCertificate(der []byte, ext *cryptoutil.Extensions) (*x509.Certificate, error)
- type KeyConfig
- type KeyLoader
- func (kl *KeyLoader) GetKeyAlgorithm(key crypto.PrivateKey) (string, error)
- func (kl *KeyLoader) LoadCertificateChain(path string) ([]*x509.Certificate, []string, error)
- func (kl *KeyLoader) LoadKeyMaterial(config *KeyConfig) (*KeyMaterial, error)
- func (kl *KeyLoader) LoadPrivateKey(path string) (crypto.PrivateKey, error)
- func (kl *KeyLoader) ValidateKeyType(key crypto.PrivateKey, expectedType string) error
- type KeyMaterial
- type KeyMaterialSigner
- func (s *KeyMaterialSigner) Algorithm() string
- func (s *KeyMaterialSigner) GetCertificate() *x509.Certificate
- func (s *KeyMaterialSigner) GetCertificateChain() []string
- func (s *KeyMaterialSigner) KeyID() string
- func (s *KeyMaterialSigner) PrivateKey() crypto.PrivateKey
- func (s *KeyMaterialSigner) PublicKey() any
- func (s *KeyMaterialSigner) Sign(ctx context.Context, data []byte) ([]byte, error)
- func (s *KeyMaterialSigner) SignDigest(ctx context.Context, digest []byte) ([]byte, error)
- func (s *KeyMaterialSigner) SigningMethod() jwt.SigningMethod
- type KeySource
- type PKCS11Config
- type PKCS11ECDH
- type PKCS11PrivateKey
- type PKCS11Signer
- type RawSigner
- type Signer
- type SignerConfig
- func (sc *SignerConfig) GetCertificate() (*x509.Certificate, error)
- func (sc *SignerConfig) GetCertificateChain() ([]string, error)
- func (sc *SignerConfig) GetJWK() (*jose.JSONWebKey, error)
- func (sc *SignerConfig) GetKeyMaterial() (*KeyMaterial, error)
- func (sc *SignerConfig) GetPrivateKey() (crypto.PrivateKey, error)
- func (sc *SignerConfig) RawSigner() (RawSigner, error)
- func (sc *SignerConfig) Sign(data []byte) ([]byte, error)
- func (sc *SignerConfig) SignJWT(claims jwt.Claims) (string, error)
- type SoftwareSigner
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DecodeECDSASignature ¶
DecodeECDSASignature decodes an IEEE P1363 format ECDSA signature to (r, s) big integers. This is the inverse of EncodeECDSASignature.
func EncodeECDSASignature ¶
EncodeECDSASignature converts ECDSA signature components (r, s) to IEEE P1363 format. This is the fixed-size R||S concatenation format required by JWT (RFC 7518 section 3.4). ASN.1 DER encoding is NOT used for JWT ECDSA signatures.
func GetKeySizeForCurve ¶
GetKeySizeForCurve returns the key size in bytes for a given curve.
func ParseCertificate ¶ added in v0.5.4
func ParseCertificate(der []byte, ext *cryptoutil.Extensions) (*x509.Certificate, error)
ParseCertificate parses a DER-encoded certificate using extension-aware parsing if ext is non-nil, falling back to standard x509.ParseCertificate.
Types ¶
type KeyConfig ¶
type KeyConfig struct {
// File-based configuration
PrivateKeyPath string `yaml:"private_key_path" validate:"required_without=PKCS11"` // Path to PEM key file
ChainPath string `yaml:"chain_path"` // Path to certificate chain (optional). Should contain the signing certificate followed by intermediates, and the root certificate if it is not in the system trust store
// HSM-based configuration
PKCS11 *PKCS11Config `yaml:"pkcs11" validate:"required_without=PrivateKeyPath"` // PKCS#11 HSM config (optional)
// Source selection (determines which config to use)
// If empty, tries in order: File (if FilePath set), then HSM (if HSM set)
Source KeySource `yaml:"source"`
// EnableFile enables file-based key loading (default: true if FilePath set)
EnableFile bool `yaml:"enable_file"`
// EnableHSM enables HSM-based key loading (default: true if HSM set)
EnableHSM bool `yaml:"enable_hsm"`
// Priority defines fallback order when both are enabled
// If nil, uses Source field or auto-detects based on what's configured
Priority []KeySource `yaml:"priority" doc_example:"[\"hsm\", \"file\"]"`
}
KeyConfig holds configuration for loading keys from various sources. Supports both file-based and HSM-based keys with explicit control.
type KeyLoader ¶
type KeyLoader struct {
// CryptoExt provides extended algorithm and certificate support
// (e.g. brainpool curves). Must be set before concurrent use and
// not mutated afterwards.
CryptoExt *cryptoutil.Extensions
}
KeyLoader provides centralized key and certificate loading functionality. Methods are safe for concurrent use after initialization.
func NewKeyLoader ¶
func NewKeyLoader() *KeyLoader
NewKeyLoader creates a new KeyLoader instance.
Example ¶
package main
import (
"fmt"
"github.com/SUNET/vc/pkg/pki"
)
func main() {
kl := pki.NewKeyLoader()
fmt.Printf("%T\n", kl)
}
Output: *pki.KeyLoader
func NewKeyLoaderWithExtensions ¶ added in v0.5.4
func NewKeyLoaderWithExtensions(ext *cryptoutil.Extensions) *KeyLoader
NewKeyLoaderWithExtensions creates a new KeyLoader with crypto extensions.
func (*KeyLoader) GetKeyAlgorithm ¶
func (kl *KeyLoader) GetKeyAlgorithm(key crypto.PrivateKey) (string, error)
GetKeyAlgorithm returns the algorithm name for a given key
func (*KeyLoader) LoadCertificateChain ¶
LoadCertificateChain loads a certificate chain from a PEM file. Returns the certificates and their base64-encoded DER format for x5c.
func (*KeyLoader) LoadKeyMaterial ¶
func (kl *KeyLoader) LoadKeyMaterial(config *KeyConfig) (*KeyMaterial, error)
LoadKeyMaterial loads a private key based on the provided configuration. The config determines whether to load from file, HSM, or try multiple sources.
func (*KeyLoader) LoadPrivateKey ¶
func (kl *KeyLoader) LoadPrivateKey(path string) (crypto.PrivateKey, error)
LoadPrivateKey loads a private key from a PEM file. Supports PKCS#8, PKCS#1 (RSA), and SEC1 (EC) formats.
func (*KeyLoader) ValidateKeyType ¶
func (kl *KeyLoader) ValidateKeyType(key crypto.PrivateKey, expectedType string) error
ValidateKeyType checks if the private key is of the expected type
type KeyMaterial ¶
type KeyMaterial struct {
PrivateKey crypto.PrivateKey
Cert *x509.Certificate
Chain []string // Base64-encoded DER certificates for x5c
SigningMethod jwt.SigningMethod // JWT signing method determined from key type
}
KeyMaterial holds a private key with its optional certificate and chain
type KeyMaterialSigner ¶
type KeyMaterialSigner struct {
// contains filtered or unexported fields
}
KeyMaterialSigner implements the Signer interface using KeyMaterial. It provides a concrete implementation for services that need signing capabilities.
Example ¶
ExampleKeyMaterialSigner demonstrates using the Signer interface implementation
package main
import (
"context"
"fmt"
"log"
"github.com/SUNET/vc/pkg/pki"
)
func main() {
// Load key material
keyLoader := pki.NewKeyLoader()
km, err := keyLoader.LoadKeyMaterial(&pki.KeyConfig{
PrivateKeyPath: "/path/to/private-key.pem",
ChainPath: "/path/to/certificate-chain.pem",
})
if err != nil {
log.Fatalf("failed to load key material: %v", err)
}
// Create a signer from the key material
signer := pki.NewKeyMaterialSigner(km)
// Sign data using the Signer interface
ctx := context.Background()
data := []byte("data to sign")
signature, err := signer.Sign(ctx, data)
if err != nil {
log.Fatalf("failed to sign: %v", err)
}
fmt.Printf("Algorithm: %s\n", signer.Algorithm())
fmt.Printf("Key ID: %s\n", signer.KeyID())
fmt.Printf("Signature: %d bytes\n", len(signature))
}
Output:
Example (WithCredentials) ¶
ExampleKeyMaterialSigner_withCredentials demonstrates using KeyMaterialSigner with credential issuance
package main
import (
"context"
"fmt"
"log"
"github.com/SUNET/vc/pkg/pki"
)
func main() {
// Load signing key
keyLoader := pki.NewKeyLoader()
km, err := keyLoader.LoadKeyMaterial(&pki.KeyConfig{
PrivateKeyPath: "/etc/issuer/signing-key.pem",
ChainPath: "/etc/issuer/chain.pem",
})
if err != nil {
log.Fatalf("failed to load key: %v", err)
}
// Create signer implementing the Signer interface
signer := pki.NewKeyMaterialSigner(km)
// Function that requires a Signer interface (like credential issuance libraries)
issueCredential := func(s pki.Signer, claims map[string]any) error {
ctx := context.Background()
// Credential issuance would use:
// - s.Sign(ctx, data) for signing
// - s.Algorithm() for JWT header
// - s.KeyID() for kid claim
// - s.PublicKey() for verification
fmt.Printf("Issuing credential with key: %s\n", s.KeyID())
fmt.Printf("Using algorithm: %s\n", s.Algorithm())
// Example signing
data := []byte("credential data")
_, err := s.Sign(ctx, data)
return err
}
// Pass the signer to credential issuance
err = issueCredential(signer, map[string]any{
"sub": "did:example:123",
"vc": map[string]any{
"type": []string{"VerifiableCredential"},
},
})
if err != nil {
log.Fatalf("failed to issue credential: %v", err)
}
}
Output:
func NewKeyMaterialSigner ¶
func NewKeyMaterialSigner(km *KeyMaterial) *KeyMaterialSigner
NewKeyMaterialSigner creates a new Signer from KeyMaterial. The keyID is automatically determined from the certificate if available, or generated from the public key hash.
func (*KeyMaterialSigner) Algorithm ¶
func (s *KeyMaterialSigner) Algorithm() string
Algorithm returns the JWT algorithm name based on the key type.
func (*KeyMaterialSigner) GetCertificate ¶
func (s *KeyMaterialSigner) GetCertificate() *x509.Certificate
GetCertificate returns the certificate if available.
func (*KeyMaterialSigner) GetCertificateChain ¶
func (s *KeyMaterialSigner) GetCertificateChain() []string
GetCertificateChain returns the certificate chain if available.
func (*KeyMaterialSigner) KeyID ¶
func (s *KeyMaterialSigner) KeyID() string
KeyID returns the key identifier for JWT headers.
func (*KeyMaterialSigner) PrivateKey ¶
func (s *KeyMaterialSigner) PrivateKey() crypto.PrivateKey
PrivateKey returns the underlying private key. This is useful when integrating with libraries that need the raw crypto.PrivateKey.
func (*KeyMaterialSigner) PublicKey ¶
func (s *KeyMaterialSigner) PublicKey() any
PublicKey returns the public key for verification.
func (*KeyMaterialSigner) SignDigest ¶
SignDigest signs a pre-computed digest without additional hashing. This is useful for protocols like W3C Data Integrity that control the hashing process.
func (*KeyMaterialSigner) SigningMethod ¶
func (s *KeyMaterialSigner) SigningMethod() jwt.SigningMethod
SigningMethod returns the JWT signing method for this key material.
type PKCS11Config ¶
type PKCS11Config struct {
// ModulePath is the path to the PKCS#11 library
ModulePath string `yaml:"module_path" doc_example:"\"/usr/lib/softhsm/libsofthsm2.so\""`
// SlotID is the HSM slot ID
SlotID uint `yaml:"slot_id" doc_example:"0"`
// PIN is the user PIN for the slot
PIN string `yaml:"pin" doc_example:"\"1234\""`
// KeyLabel is the label of the key to use
KeyLabel string `yaml:"key_label" doc_example:"\"my-signing-key\""`
// KeyID is the identifier for the JWT kid header
KeyID string `yaml:"key_id" doc_example:"\"key-1\""`
}
PKCS11Config holds configuration for PKCS#11 HSM connection.
type PKCS11ECDH ¶ added in v0.7.23
type PKCS11ECDH struct {
// contains filtered or unexported fields
}
PKCS11ECDH performs ECDH key agreement with an EC private key that stays inside a PKCS#11 token.
SECURITY: the key never leaves the token, but the shared secret does. The derivation runs CKM_ECDH1_DERIVE with CKD_NULL, which makes the agreed secret Z itself the derived object, and Z is then read out of the token with CKA_VALUE so that the Concat KDF of RFC 7518 §4.6.2 can be applied to it here. The alternative - asking the token to run the KDF and the AES key unwrap too - needs CKM_ECDH1_DERIVE with CKD_SHA256_KDF_CONCATENATE and an agreement on how the module encodes the JWE OtherInfo, which PKCS#11 v2.40 does not standardize and most modules do not implement. So Z crosses the boundary: an attacker with code execution on this host can read one request's Z out of process memory, and with it that request's content encryption key. They cannot obtain the long-term private key, which is the property an HSM is bought for, and they cannot decrypt any other request without deriving its Z through the token.
The token must allow the derivation at all: the private key needs CKA_DERIVE, and the module must let the derived generic secret be created with CKA_EXTRACTABLE true and CKA_SENSITIVE false. A module that refuses to produce an extractable derived key cannot be used this way - the refusal surfaces as an error from ECDH, not as a wrong answer.
Safe for concurrent use: one PKCS#11 session is shared and serialized, since a session is not a concurrency primitive.
func NewPKCS11ECDH ¶ added in v0.7.23
func NewPKCS11ECDH(config *PKCS11Config) (*PKCS11ECDH, error)
NewPKCS11ECDH opens a session on the configured token, finds the EC private key by label, reads its public half, and proves that the token will actually perform the agreement.
Done once at startup rather than per request so that a token that is missing, locked, holding the wrong kind of key or unwilling to derive is a configuration failure instead of a 500 on a wallet's credential request.
func (*PKCS11ECDH) Close ¶ added in v0.7.23
func (k *PKCS11ECDH) Close() error
Close logs out and releases the session and the module.
func (*PKCS11ECDH) ECDH ¶ added in v0.7.23
func (k *PKCS11ECDH) ECDH(peer *ecdh.PublicKey) ([]byte, error)
ECDH returns the raw shared secret Z for peer, derived inside the token.
See the type comment: Z leaves the token, the private key does not.
func (*PKCS11ECDH) Public ¶ added in v0.7.23
func (k *PKCS11ECDH) Public() any
Public implements crypto.Signer's half of the usual key interface, so this can be passed anywhere a crypto.PrivateKey is expected.
func (*PKCS11ECDH) PublicKey ¶ added in v0.7.23
func (k *PKCS11ECDH) PublicKey() *ecdsa.PublicKey
PublicKey returns the public half of the token-held key.
type PKCS11PrivateKey ¶ added in v0.7.19
type PKCS11PrivateKey struct {
Config *PKCS11Config
KeyLabel string
PublicKey crypto.PublicKey
KeyType uint
}
PKCS11PrivateKey wraps HSM key information for use with standard crypto interfaces
func (*PKCS11PrivateKey) Public ¶ added in v0.7.19
func (k *PKCS11PrivateKey) Public() crypto.PublicKey
Public returns the public key associated with this private key
func (*PKCS11PrivateKey) Sign ¶ added in v0.7.19
func (k *PKCS11PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error)
Sign implements crypto.Signer interface for PKCS11 keys Note: This requires opening a new HSM session each time
type PKCS11Signer ¶
type PKCS11Signer struct {
// contains filtered or unexported fields
}
PKCS11Signer implements Signer using a PKCS#11 HSM.
func NewPKCS11Signer ¶
func NewPKCS11Signer(config *PKCS11Config) (*PKCS11Signer, error)
NewPKCS11Signer creates a new PKCS11Signer from HSM configuration.
func (*PKCS11Signer) Algorithm ¶
func (s *PKCS11Signer) Algorithm() string
Algorithm returns the JWT algorithm name.
func (*PKCS11Signer) KeyID ¶
func (s *PKCS11Signer) KeyID() string
KeyID returns the key identifier.
func (*PKCS11Signer) PublicKey ¶
func (s *PKCS11Signer) PublicKey() any
PublicKey returns the public key.
type RawSigner ¶
type RawSigner interface {
Signer
// SignDigest signs a pre-computed digest without additional hashing.
//
// For ECDSA, the digest should be the appropriate size for the curve:
// - P-256: SHA-256 (32 bytes)
// - P-384: SHA-384 (48 bytes)
// - P-521: SHA-512 (64 bytes)
//
// For EdDSA (Ed25519), note that Ed25519 does not use pre-hashing in standard mode.
// The implementation may need to use Ed25519ph (pre-hashed) or handle this specially.
//
// The returned signature is in IEEE P1363 format for ECDSA (R||S concatenation).
SignDigest(ctx context.Context, digest []byte) ([]byte, error)
}
RawSigner extends Signer with direct signature operations for advanced use cases like W3C Data Integrity proofs where the caller controls hashing.
This interface is particularly useful for: - VC 2.0 Data Integrity cryptosuites (ecdsa-rdfc-2019, eddsa-rdfc-2022, etc.) - Any protocol that requires signing pre-computed digests - Fine-grained control over the hash-then-sign process
type Signer ¶
type Signer interface {
// Sign signs the provided data and returns the signature.
//
// For ECDSA algorithms (ES256, ES384, ES512), the returned signature SHOULD be
// in IEEE P1363 format (fixed-size R||S concatenation) as required by JWS (RFC 7518 §3.4).
// If the implementation returns ASN.1 DER-encoded ECDSA signatures instead (as is
// common with crypto.Signer and some HSM backends), the jose.MakeJWT function will
// automatically convert them to JWS format. However, returning IEEE P1363 directly
// avoids the conversion overhead.
//
// For RSA algorithms (RS256, RS384, RS512), the signature is PKCS#1 v1.5 encoded
// and requires no format conversion.
Sign(ctx context.Context, data []byte) ([]byte, error)
// Algorithm returns the JWT algorithm name (e.g., "RS256", "ES256").
Algorithm() string
// KeyID returns the key identifier for the JWT kid header.
KeyID() string
// PublicKey returns the public key for verification purposes.
PublicKey() any
}
Signer defines the interface for cryptographic signing operations. Implementations can use software keys, HSMs via PKCS#11, cloud KMS, etc.
func LoadSigner ¶
LoadSigner loads key material and creates a pki.Signer from the configuration. Supports both software keys and HSM-backed keys. Returns the signer, signing certificate, and certificate chain.
type SignerConfig ¶
type SignerConfig struct {
// KeyConfig contains the underlying key configuration
KeyConfig
// contains filtered or unexported fields
}
SignerConfig provides a high-level interface for cryptographic operations without requiring services to directly handle KeyMaterial. It supports both file-based keys and HSM-based keys through PKCS#11.
Example (Fallback) ¶
ExampleSignerConfig_fallback demonstrates fallback from HSM to file
package main
import (
"fmt"
"log"
"github.com/SUNET/vc/pkg/pki"
)
func main() {
// Configure with both HSM and file-based keys, preferring HSM
config := pki.NewSignerConfig(&pki.KeyConfig{
PrivateKeyPath: "/path/to/backup-key.pem",
ChainPath: "/path/to/backup-chain.pem",
PKCS11: &pki.PKCS11Config{
ModulePath: "/usr/lib/softhsm/libsofthsm2.so",
PIN: "1234",
KeyLabel: "signing-key",
},
Priority: []pki.KeySource{pki.KeySourceHSM, pki.KeySourceFile},
EnableHSM: true,
EnableFile: true,
})
// Sign - will try HSM first, fall back to file if HSM fails
data := []byte("data to sign")
signature, err := config.Sign(data)
if err != nil {
log.Fatalf("failed to sign data: %v", err)
}
fmt.Printf("Signed with fallback: %d bytes\n", len(signature))
}
Output:
Example (FileBasedSigning) ¶
ExampleSignerConfig_fileBasedSigning demonstrates using SignerConfig with file-based keys
package main
import (
"fmt"
"log"
"github.com/SUNET/vc/pkg/pki"
"github.com/golang-jwt/jwt/v5"
)
func main() {
// Create a signer config with file-based keys
config := pki.NewSignerConfig(&pki.KeyConfig{
PrivateKeyPath: "/path/to/private-key.pem",
ChainPath: "/path/to/certificate-chain.pem",
})
// Sign arbitrary data
data := []byte("important data to sign")
signature, err := config.Sign(data)
if err != nil {
log.Fatalf("failed to sign data: %v", err)
}
fmt.Printf("Signature length: %d bytes\n", len(signature))
// Sign a JWT
claims := jwt.MapClaims{
"sub": "user123",
"iss": "my-service",
"exp": 1234567890,
}
token, err := config.SignJWT(claims)
if err != nil {
log.Fatalf("failed to sign JWT: %v", err)
}
fmt.Printf("JWT: %s\n", token)
// Get public key as JWK
jwk, err := config.GetJWK()
if err != nil {
log.Fatalf("failed to get JWK: %v", err)
}
fmt.Printf("JWK Algorithm: %s\n", jwk.Algorithm)
}
Output:
Example (HsmSigning) ¶
ExampleSignerConfig_hsmSigning demonstrates using SignerConfig with HSM
package main
import (
"fmt"
"log"
"github.com/SUNET/vc/pkg/pki"
"github.com/golang-jwt/jwt/v5"
)
func main() {
// Create a signer config with HSM
config := pki.NewSignerConfig(&pki.KeyConfig{
PKCS11: &pki.PKCS11Config{
ModulePath: "/usr/lib/softhsm/libsofthsm2.so",
PIN: "1234",
KeyLabel: "signing-key",
},
})
// Use the same interface for HSM-based signing
claims := jwt.MapClaims{
"sub": "user456",
"iss": "secure-service",
}
token, err := config.SignJWT(claims)
if err != nil {
log.Fatalf("failed to sign JWT with HSM: %v", err)
}
fmt.Printf("HSM-signed JWT: %s\n", token)
// Get certificate from HSM
cert, err := config.GetCertificate()
if err != nil {
log.Fatalf("failed to get certificate: %v", err)
}
fmt.Printf("Certificate subject: %s\n", cert.Subject)
}
Output:
Example (ServiceUsage) ¶
ExampleSignerConfig_serviceUsage demonstrates typical service usage
package main
import (
"fmt"
"log"
"github.com/SUNET/vc/pkg/pki"
"github.com/golang-jwt/jwt/v5"
)
func main() {
// In a real service, this config would come from application config
signerConfig := pki.NewSignerConfig(&pki.KeyConfig{
PrivateKeyPath: "/etc/myservice/signing-key.pem",
ChainPath: "/etc/myservice/signing-chain.pem",
})
// Service method that needs to sign something
generateAccessToken := func(userID string) (string, error) {
claims := jwt.MapClaims{
"sub": userID,
"iss": "my-service",
"iat": 1234567890,
"exp": 1234571490,
}
return signerConfig.SignJWT(claims)
}
// Use in service
token, err := generateAccessToken("user789")
if err != nil {
log.Fatalf("failed to generate token: %v", err)
}
fmt.Printf("Access token: %s\n", token)
// Get public key for verification endpoint
jwk, err := signerConfig.GetJWK()
if err != nil {
log.Fatalf("failed to get public key: %v", err)
}
fmt.Printf("Public key algorithm: %s\n", jwk.Algorithm)
}
Output:
func NewSignerConfig ¶
func NewSignerConfig(config *KeyConfig) *SignerConfig
NewSignerConfig creates a new SignerConfig with the specified configuration. Keys are loaded lazily on first use.
Example ¶
package main
import (
"fmt"
"github.com/SUNET/vc/pkg/pki"
)
func main() {
config := pki.NewSignerConfig(&pki.KeyConfig{
PrivateKeyPath: "/path/to/key.pem",
ChainPath: "/path/to/chain.pem",
})
fmt.Printf("%T\n", config)
}
Output: *pki.SignerConfig
func (*SignerConfig) GetCertificate ¶
func (sc *SignerConfig) GetCertificate() (*x509.Certificate, error)
GetCertificate returns the X.509 certificate associated with the key.
func (*SignerConfig) GetCertificateChain ¶
func (sc *SignerConfig) GetCertificateChain() ([]string, error)
GetCertificateChain returns the full certificate chain as base64-encoded DER.
func (*SignerConfig) GetJWK ¶
func (sc *SignerConfig) GetJWK() (*jose.JSONWebKey, error)
GetJWK returns the public key as a JSON Web Key (JWK). The kid is derived using the same logic as KeyMaterialSigner.KeyID() to ensure consistency between JWT headers and JWKS endpoints.
func (*SignerConfig) GetKeyMaterial ¶
func (sc *SignerConfig) GetKeyMaterial() (*KeyMaterial, error)
GetKeyMaterial returns the underlying KeyMaterial. This method should be used sparingly - prefer using the specific methods instead.
func (*SignerConfig) GetPrivateKey ¶
func (sc *SignerConfig) GetPrivateKey() (crypto.PrivateKey, error)
GetPrivateKey returns the underlying private key. This method should be used sparingly - prefer using Sign() or SignJWT() instead.
func (*SignerConfig) RawSigner ¶
func (sc *SignerConfig) RawSigner() (RawSigner, error)
RawSigner returns a RawSigner interface for advanced signing operations. This is useful for protocols like W3C Data Integrity that need to sign pre-computed digests.
type SoftwareSigner ¶
type SoftwareSigner struct {
// contains filtered or unexported fields
}
SoftwareSigner implements Signer using software-based keys.
func NewSoftwareSigner ¶
func NewSoftwareSigner(privateKey crypto.PrivateKey, keyID string) (*SoftwareSigner, error)
NewSoftwareSigner creates a new SoftwareSigner from a private key.
func (*SoftwareSigner) Algorithm ¶
func (s *SoftwareSigner) Algorithm() string
Algorithm returns the JWT algorithm name.
func (*SoftwareSigner) KeyID ¶
func (s *SoftwareSigner) KeyID() string
KeyID returns the key identifier.
func (*SoftwareSigner) PublicKey ¶
func (s *SoftwareSigner) PublicKey() any
PublicKey returns the public key.
func (*SoftwareSigner) SignDigest ¶
SignDigest signs a pre-computed digest without additional hashing. This is useful for protocols like W3C Data Integrity that control the hashing process.