gobottle

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Dec 21, 2025 License: MIT Imports: 31 Imported by: 0

README

GoDoc CI

gobottle

A comprehensive Go library for the Bottle RFC, providing layered message containers with encryption and signatures. Supports both classical (ECDSA, Ed25519, RSA) and post-quantum (ML-KEM, ML-DSA, SLH-DSA) cryptography.

Installation

go get github.com/BottleFmt/gobottle

Requires Go 1.24 or later.

Features

  • Bottle: Layered message containers with encryption and signatures
  • ML-KEM: Post-quantum encryption (ML-KEM-768, ML-KEM-1024) with optional X25519 hybrid mode
  • ML-DSA: Post-quantum signatures (ML-DSA-44, ML-DSA-65, ML-DSA-87)
  • SLH-DSA: Stateless hash-based post-quantum signatures (12 parameter sets)
  • ECDH Encryption: Simple message encryption to ECDSA/ECDH keys
  • IDCard: Identity management with sub-keys and key purposes
  • Keychain: Secure key storage and management
  • Membership: Cryptographically signed group memberships

Bottle

Bottles are versatile containers for arbitrary data that support multiple layers of encryption and signatures. They can be serialized as CBOR or JSON.

Creating and Opening Bottles
import (
    "crypto/rand"
    "github.com/BottleFmt/gobottle"
)

// Create a bottle with a message
bottle := gobottle.NewBottle([]byte("secret message"))

// Encrypt for one or more recipients (any recipient can decrypt)
bottle.Encrypt(rand.Reader, bobPublicKey, alicePublicKey)

// Wrap in another layer to include encryption metadata in signature
bottle.BottleUp()

// Sign the bottle
bottle.Sign(rand.Reader, senderPrivateKey)

// Open the bottle (Bob decrypts)
opener, err := gobottle.NewOpener(bobPrivateKey)
message, info, err := opener.Open(bottle)

// Check who signed it
if info.SignedBy(senderPublicKey) {
    fmt.Println("Verified signature from sender")
}
fmt.Printf("Decryption layers: %d\n", info.Decryption)
Bottle with Structured Data
// Marshal Go structs directly into bottles
type MyData struct {
    Name  string `json:"name"`
    Value int    `json:"value"`
}

// CBOR encoding (compact, binary)
bottle, err := gobottle.Marshal(MyData{Name: "test", Value: 42})

// JSON encoding
bottle, err := gobottle.MarshalJson(MyData{Name: "test", Value: 42})

// Unmarshal from bottle
var data MyData
opener := gobottle.MustOpener(privateKey)
info, err := opener.Unmarshal(bottle, &data)
Opening Encoded Bottles
// Open CBOR-encoded bottle directly
message, info, err := opener.OpenCbor(cborBytes)

// Open from HTTP request (handles Content-Type)
message, info, err := opener.OpenHttp(httpRequest)

ML-KEM Post-Quantum Encryption

ML-KEM (formerly CRYSTALS-Kyber) provides quantum-resistant key encapsulation. The library supports hybrid mode combining ML-KEM with X25519 for defense-in-depth.

Key Generation
// Generate ML-KEM-768 hybrid key (recommended)
privateKey, err := gobottle.GenerateMLKEMKey(rand.Reader, true)

// Generate ML-KEM-768 pure (no X25519)
privateKey, err := gobottle.GenerateMLKEMKey(rand.Reader, false)

// Generate ML-KEM-1024 for higher security
privateKey, err := gobottle.GenerateMLKEMKey1024(rand.Reader, true)
Encryption and Decryption
// Hybrid encryption (X25519 + ML-KEM)
ciphertext, err := gobottle.HybridEncrypt(rand.Reader, plaintext, publicKey)

// Pure ML-KEM encryption
ciphertext, err := gobottle.MLKEMEncrypt(rand.Reader, plaintext, publicKey)

// Decryption (auto-detects hybrid vs pure)
plaintext, err := gobottle.MLKEMDecrypt(ciphertext, privateKey)
Using ML-KEM with Bottles
// ML-KEM keys work seamlessly with bottles
mlkemKey, _ := gobottle.GenerateMLKEMKey(rand.Reader, true)

bottle := gobottle.NewBottle([]byte("quantum-safe message"))
bottle.Encrypt(rand.Reader, mlkemKey.Public())

// Mixed recipients (classical + post-quantum)
bottle.Encrypt(rand.Reader, ecdsaKey.Public(), mlkemKey.Public())
Key Serialization
// Marshal to PKCS#8 (private) / PKIX (public)
privDER, err := privateKey.MarshalPKCS8PrivateKey()
pubDER, err := publicKey.MarshalPKIXPublicKey()

// Parse from DER
privateKey, err := gobottle.ParseMLKEMPrivateKey(privDER)
publicKey, err := gobottle.ParseMLKEMPublicKey(pubDER)

ML-DSA Post-Quantum Signatures

ML-DSA (Module-Lattice Digital Signature Algorithm, formerly CRYSTALS-Dilithium) provides quantum-resistant digital signatures. Three security levels are supported:

  • ML-DSA-44: NIST security level 2 (comparable to AES-128)
  • ML-DSA-65: NIST security level 3 (comparable to AES-192)
  • ML-DSA-87: NIST security level 5 (comparable to AES-256)
Key Generation
import "github.com/KarpelesLab/mldsa"

// Generate ML-DSA-65 key (recommended)
key, err := mldsa.GenerateKey65(rand.Reader)

// Other variants
key44, err := mldsa.GenerateKey44(rand.Reader)  // Level 2
key87, err := mldsa.GenerateKey87(rand.Reader)  // Level 5
Signing and Verification
// Sign a message (ML-DSA signs messages directly, no pre-hashing)
signature, err := gobottle.Sign(rand.Reader, key, message)

// Verify signature
err = gobottle.Verify(key.PublicKey(), message, signature)

// Sign with context for domain separation
opts := &mldsa.SignerOpts{Context: []byte("my-application")}
signature, err := gobottle.Sign(rand.Reader, key, message, opts)
err = gobottle.Verify(key.PublicKey(), message, signature, opts)
Using ML-DSA with Bottles
// ML-DSA keys work seamlessly with bottles
key, _ := mldsa.GenerateKey65(rand.Reader)

bottle := gobottle.NewBottle([]byte("quantum-safe signed message"))
bottle.Sign(rand.Reader, key)

// Verify on open
opener := gobottle.MustOpener()
msg, info, err := opener.Open(bottle)
if info.SignedBy(key.PublicKey()) {
    fmt.Println("Verified ML-DSA signature")
}
Key Serialization
// Marshal to PKCS#8 (private) / PKIX (public)
privDER, err := gobottle.MarshalMLDSAPrivateKey(key)
pubDER, err := gobottle.MarshalPKIXPublicKey(key.PublicKey())

// Parse from DER
privateKey, err := gobottle.ParseMLDSAPrivateKey(privDER)
publicKey, err := gobottle.ParsePKIXPublicKey(pubDER)

SLH-DSA Post-Quantum Signatures

SLH-DSA (Stateless Hash-Based Digital Signature Algorithm, also known as SPHINCS+) provides quantum-resistant digital signatures based on hash functions. It offers strong security guarantees without relying on lattice assumptions. Twelve parameter sets are supported:

SHA2-based:

  • SLH-DSA-SHA2-128s/128f: NIST security level 1
  • SLH-DSA-SHA2-192s/192f: NIST security level 3
  • SLH-DSA-SHA2-256s/256f: NIST security level 5

SHAKE-based:

  • SLH-DSA-SHAKE-128s/128f: NIST security level 1
  • SLH-DSA-SHAKE-192s/192f: NIST security level 3
  • SLH-DSA-SHAKE-256s/256f: NIST security level 5

The "s" variants are optimized for smaller signatures, while "f" variants are optimized for faster signing.

Key Generation
import "github.com/KarpelesLab/slhdsa"

// Generate SLH-DSA-SHA2-128s key (small signatures)
key, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128s)

// Generate SLH-DSA-SHA2-128f key (fast signing)
key, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128f)

// Higher security levels
key192, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_192s)
key256, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_256s)

// SHAKE-based variants
keyShake, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHAKE_128s)
Signing and Verification
// Sign a message (SLH-DSA signs messages directly, no pre-hashing)
signature, err := gobottle.Sign(rand.Reader, key, message)

// Verify signature
err = gobottle.Verify(key.Public(), message, signature)

// Sign with context for domain separation
opts := &slhdsa.Options{Context: []byte("my-application")}
signature, err := gobottle.Sign(rand.Reader, key, message, opts)
err = gobottle.Verify(key.Public(), message, signature, opts)
Using SLH-DSA with Bottles
// SLH-DSA keys work seamlessly with bottles
key, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128s)

bottle := gobottle.NewBottle([]byte("hash-based signed message"))
bottle.Sign(rand.Reader, key)

// Verify on open
opener := gobottle.MustOpener()
msg, info, err := opener.Open(bottle)
if info.SignedBy(key.Public()) {
    fmt.Println("Verified SLH-DSA signature")
}
Key Serialization
// Marshal to PKCS#8 (private) / PKIX (public)
privDER, err := gobottle.MarshalSLHDSAPrivateKey(key)
pubDER, err := gobottle.MarshalPKIXPublicKey(key.Public())

// Parse from DER
privateKey, err := gobottle.ParseSLHDSAPrivateKey(privDER)
publicKey, err := gobottle.ParsePKIXPublicKey(pubDER)

ECDH Message Encryption

Simple encryption to ECDSA/ECDH keys, supporting TPM and HSM backends through the ECDHHandler interface.

// Encrypt to an ECDH public key
ciphertext, err := gobottle.ECDHEncrypt(rand.Reader, plaintext, ecdhPublicKey)

// Decrypt with private key (or any ECDHHandler)
plaintext, err := gobottle.ECDHDecrypt(ciphertext, ecdhPrivateKey)

IDCard

IDCards allow entities to declare sub-keys with specific purposes (signing, decryption) and manage key lifecycles.

// Create an IDCard for a signing key
idcard, err := gobottle.NewIDCard(signingKey.Public())

// Add metadata
idcard.Meta = map[string]string{"name": "Alice", "email": "alice@example.com"}

// Configure key purposes
idcard.SetKeyPurposes(signingKey.Public(), "sign", "decrypt")

// Add a dedicated encryption key
idcard.SetKeyPurposes(encryptionKey.Public(), "decrypt")
idcard.SetKeyDuration(encryptionKey.Public(), 365*24*time.Hour) // 1 year expiry

// Sign and serialize the IDCard
signedIDCard, err := idcard.Sign(rand.Reader, signingKey)

// Load and verify an IDCard
var loaded gobottle.IDCard
err = loaded.UnmarshalBinary(signedIDCard)

// Check key purposes
err = loaded.TestKeyPurpose(someKey, "sign")
if err != nil {
    fmt.Println("Key not authorized for signing")
}

// Get all keys for a purpose
decryptKeys := loaded.GetKeys("decrypt")

Keychain

Keychain provides secure storage for private keys, indexed by their public key.

// Create a keychain
kc := gobottle.NewKeychain()

// Add keys (supports ECDSA, Ed25519, RSA, ML-KEM)
kc.AddKey(ecdsaPrivateKey)
kc.AddKey(ed25519PrivateKey)
kc.AddKey(mlkemPrivateKey)

// Add multiple keys at once
kc.AddKeys(key1, key2, key3)

// Retrieve keys by public key
privateKey, err := kc.GetKey(publicKey)
signer, err := kc.GetSigner(publicKey)

// Sign with a specific key
signature, err := kc.Sign(rand.Reader, publicKey, message)

// Iterate over keys
for signer := range kc.Signers {
    fmt.Printf("Signer: %T\n", signer.Public())
}

// Use keychain with Opener
opener, err := gobottle.NewOpener(kc)

Membership

Memberships provide cryptographically signed group affiliations.

// Create a membership
membership := gobottle.NewMembership(memberIDCard, groupPublicKey)
membership.Info["role"] = "admin"

// Sign with group owner's key
err = membership.Sign(rand.Reader, groupOwnerKey)

// Verify membership
err = membership.Verify(groupIDCard)

// Add to IDCard
idcard.UpdateGroups([][]byte{membershipBytes})

Signing and Verification

Low-level signing utilities that handle algorithm-specific requirements automatically.

// Sign a message (hashing handled automatically)
signature, err := gobottle.Sign(rand.Reader, privateKey, message)

// Verify a signature
err = gobottle.Verify(publicKey, message, signature)
if err != nil {
    fmt.Println("Signature verification failed")
}

Utility Functions

PKIX Key Marshaling

Extended PKIX support including ML-KEM and ML-DSA keys:

// Marshal any public key to PKIX format
der, err := gobottle.MarshalPKIXPublicKey(publicKey)

// Parse PKIX public key (supports ML-KEM, ML-DSA)
publicKey, err := gobottle.ParsePKIXPublicKey(der)
Short Buffer Encryption

Encrypt small buffers (like AES keys) to various key types:

// Encrypt a short buffer to any supported public key
encrypted, err := gobottle.EncryptShortBuffer(rand.Reader, aesKey, recipientPublicKey)

// Decrypt
decrypted, err := gobottle.DecryptShortBuffer(encrypted, recipientPrivateKey)
Memory Clearing

Securely clear sensitive data from memory:

privateKeyBytes := make([]byte, 32)
defer gobottle.MemClr(privateKeyBytes)
Hashing

Helper for single or multi-level hashing:

// Single hash
digest := gobottle.Hash(data, sha256.New)

// Multi-level hash (hash of hash)
digest := gobottle.Hash(data, sha256.New, sha256.New)

Supported Key Types

Type Signing Encryption Post-Quantum
ECDSA (P-256, P-384, P-521) ✓ (via ECDH)
Ed25519 ✓ (via X25519)
RSA
ML-KEM-768
ML-KEM-1024
ML-KEM + X25519 (Hybrid)
ML-DSA-44/65/87
SLH-DSA (12 variants)

Error Handling

var (
    ErrNoAppropriateKey   // No key available to decrypt
    ErrVerifyFailed       // Signature verification failed
    ErrKeyNotFound        // Key not found in keychain/IDCard
    ErrGroupNotFound      // Group not found in IDCard
    ErrKeyUnfit           // Key not authorized for the operation
    ErrEncryptNoRecipient // No valid recipient for encryption
)

License

See LICENSE file.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoAppropriateKey   = errors.New("no appropriate key available to open bottle")
	ErrVerifyFailed       = errors.New("signature verification failed")
	ErrKeyNotFound        = wraperr("the key was not found", fs.ErrNotExist)
	ErrGroupNotFound      = wraperr("the group was not found", fs.ErrNotExist)
	ErrKeyUnfit           = errors.New("the provided key was not fit")
	ErrEncryptNoRecipient = errors.New("cannot encrypt a message without at least one valid recipient")
)
View Source
var EmptyOpener = &Opener{}

EmptyOpener is an opener without any keys that can open bottles, but can't check keys

Functions

func DecryptShortBuffer

func DecryptShortBuffer(k []byte, rcvd any) ([]byte, error)

DecryptShortBuffer decrypts a given buffer

func ECDHDecrypt

func ECDHDecrypt(data []byte, privateKey ECDHHandler) ([]byte, error)

ECDHDecrypt decrypts data received for us, using the private key passed (can be a tpm, etc)

func ECDHEncrypt

func ECDHEncrypt(rnd io.Reader, data []byte, remote *ecdh.PublicKey) ([]byte, error)

ECDHEncrypt encrypts data for receiving by remote

func EncryptShortBuffer

func EncryptShortBuffer(rand io.Reader, k []byte, rcvd crypto.PublicKey) ([]byte, error)

EncryptShortBuffer performs a simple encryption of a buffer

func Hash

func Hash(b []byte, alg ...func() hash.Hash) []byte

Hash is a helper function to perform hashes on buffers, including multi-level hashing

func HybridEncrypt

func HybridEncrypt(rnd io.Reader, data []byte, remote *MLKEMPublicKey) ([]byte, error)

HybridEncrypt encrypts data using hybrid X25519 + ML-KEM mode. Format: <version=1><x25519 pubkey len><x25519 ephemeral pubkey><mlkem ciphertext len><mlkem ciphertext><nonce><encrypted data>

func MLKEMDecrypt

func MLKEMDecrypt(data []byte, privateKey *MLKEMPrivateKey) ([]byte, error)

MLKEMDecrypt decrypts data encrypted with HybridEncrypt or MLKEMEncrypt.

func MLKEMEncrypt

func MLKEMEncrypt(rnd io.Reader, data []byte, remote *MLKEMPublicKey) ([]byte, error)

MLKEMEncrypt encrypts data using pure ML-KEM mode (no hybrid). Format: <version=2><mlkem ciphertext len><mlkem ciphertext><nonce><encrypted data>

func MarshalMLDSAPrivateKey

func MarshalMLDSAPrivateKey(key crypto.Signer) ([]byte, error)

MarshalMLDSAPrivateKey marshals an ML-DSA private key to PKCS#8/ASN.1 DER format.

func MarshalMLDSAPublicKey

func MarshalMLDSAPublicKey(pub crypto.PublicKey) ([]byte, error)

MarshalMLDSAPublicKey marshals an ML-DSA public key to PKIX/ASN.1 DER format.

func MarshalMLKEMPrivateKey

func MarshalMLKEMPrivateKey(k *MLKEMPrivateKey) []byte

MarshalMLKEMPrivateKey marshals an MLKEMPrivateKey to a simple binary format. Deprecated: Use MarshalPKCS8PrivateKey for standard PKCS#8 encoding.

func MarshalMLKEMPublicKey

func MarshalMLKEMPublicKey(k *MLKEMPublicKey) []byte

MarshalMLKEMPublicKey marshals an MLKEMPublicKey to a simple binary format. Deprecated: Use MarshalPKIXPublicKey for standard PKIX encoding.

func MarshalPKIXPublicKey

func MarshalPKIXPublicKey(pub crypto.PublicKey) ([]byte, error)

MarshalPKIXPublicKey marshals a public key to PKIX/ASN.1 DER format. It supports all key types supported by crypto/x509.MarshalPKIXPublicKey as well as ML-KEM, ML-DSA, and SLH-DSA keys.

func MarshalSLHDSAPrivateKey

func MarshalSLHDSAPrivateKey(key *slhdsa.PrivateKey) ([]byte, error)

MarshalSLHDSAPrivateKey marshals an SLH-DSA private key to PKCS#8/ASN.1 DER format.

func MarshalSLHDSAPublicKey

func MarshalSLHDSAPublicKey(pub crypto.PublicKey) ([]byte, error)

MarshalSLHDSAPublicKey marshals an SLH-DSA public key to PKIX/ASN.1 DER format.

func MemClr

func MemClr(b []byte)

MemClr is a simple function that will clear a buffer in order to make it easier to reset memory storing private keys on defer.

func ParseMLDSAPrivateKey

func ParseMLDSAPrivateKey(der []byte) (crypto.Signer, error)

ParseMLDSAPrivateKey parses a PKCS#8-encoded ML-DSA private key.

func ParseMLDSAPublicKey

func ParseMLDSAPublicKey(der []byte) (crypto.PublicKey, error)

ParseMLDSAPublicKey parses a PKIX-encoded ML-DSA public key.

func ParseSLHDSAPrivateKey

func ParseSLHDSAPrivateKey(der []byte) (*slhdsa.PrivateKey, error)

ParseSLHDSAPrivateKey parses a PKCS#8-encoded SLH-DSA private key.

func ParseSLHDSAPublicKey

func ParseSLHDSAPublicKey(der []byte) (*slhdsa.PublicKey, error)

ParseSLHDSAPublicKey parses a PKIX-encoded SLH-DSA public key.

func Sign

func Sign(rand io.Reader, key crypto.Signer, buf []byte, opts ...crypto.SignerOpts) ([]byte, error)

Sign generates a signature for the given buffer. Hash will be performed as needed

func Verify

func Verify(key crypto.PublicKey, buf, sig []byte, opts ...crypto.SignerOpts) error

Verify will verify the given buffer against the signature, depending on the key type. If the key is a RSA key and PSS options are given, then the signature will be handled as a PSS signature.

Unlike Verify methods found in most packages, this one takes in the actual buffer to be signed and will perform the hash if it needs to be done.

Types

type Bottle

type Bottle struct {
	Header     map[string]any      `json:"hdr,omitempty"` // extra values to be stored, will not be signed/encrypted unless the message is bottled
	Message    []byte              `json:"msg"`
	Format     MessageFormat       `json:"fmt"`
	Recipients []*MessageRecipient `json:"dst,omitempty"` // if Format != ClearText
	Signatures []*MessageSignature `json:"sig,omitempty"` // signature
	// contains filtered or unexported fields
}

Bottle is a signed, encrypted message container. Any Format other than ClearText means the Message contains a Bottle that has been encrypted.

func AsCborBottle

func AsCborBottle(data []byte) *Bottle

AsCborBottle considers data to be a cbor-encoded Bottle, and will return a Bottle container matching this assumption

func AsJsonBottle

func AsJsonBottle(data []byte) *Bottle

AsJsonBottle considers data to be a json-encoded Bottle, and will return a Bottle container matching this assumption

func Marshal

func Marshal(data any) (*Bottle, error)

Marshal will use cbor to marshal data into a bottle

func MarshalJson

func MarshalJson(data any) (*Bottle, error)

MarshalJson will use json to marshal data into a bottle

func NewBottle

func NewBottle(data []byte) *Bottle

NewBottle will return a new clean bottle only containing the provided data

func (*Bottle) BottleUp

func (m *Bottle) BottleUp() error

BottleUp encodes the current message into itself, allowing application of extra layers

func (*Bottle) Child

func (b *Bottle) Child() (*Bottle, error)

Child is the reverse operation as BottleUp and will return the bottle's child. This will fail if the bottle is encrypted or does not contain another bottle.

func (*Bottle) Encrypt

func (m *Bottle) Encrypt(rand io.Reader, recipients ...crypto.PublicKey) error

Encrypt encrypts the message so only recipients can decrypt it

func (*Bottle) IsCleanBottle

func (m *Bottle) IsCleanBottle() bool

IsCleanBottle returns true if the Bottle is clean (ie. so signature has been scribbed on top) and contains another Bottle.

func (*Bottle) Sign

func (m *Bottle) Sign(rand io.Reader, key crypto.Signer, opts ...crypto.SignerOpts) error

Sign signs the message, and can be called multiple times. Any message can be signed, including a raw message. It is however recommanded to bottle up an encrypted message before signing in order to ensure the encryption information is signed too.

Attempting to apply encryption to a message with a signature will always cause it to be bottled up

type ECDHHandler

type ECDHHandler interface {
	ECDH(remote *ecdh.PublicKey) ([]byte, error)
}

type IDCard

type IDCard struct {
	Self    []byte            `json:"self" cbor:"1,keyasint"` // our own public key (PKIX)
	Issued  time.Time         `json:"iss" cbor:"2,keyasint"`  // issuance date. If two IDCard exist for the same public key, the most recent one will be taken into account
	SubKeys []*SubKey         `json:"sub" cbor:"3,keyasint"`  // known sub keys
	Revoke  []*SubKey         `json:"rev" cbor:"4,keyasint"`  // any key into the revoke list will be strongly rejected
	Groups  []*Membership     `json:"grp" cbor:"5,keyasint"`  // groups this key is member of
	Meta    map[string]string `json:"meta" cbor:"6,keyasint"` // self-defined metadata
}

IDCard is a basic ID for a given signature key that allows it to specify keys that can be used for encryption/etc

func NewIDCard

func NewIDCard(k crypto.PublicKey) (*IDCard, error)

NewIDCard generates a new ID card for the given public key

func (*IDCard) AddKeyPurpose

func (id *IDCard) AddKeyPurpose(k crypto.PublicKey, purposes ...string) error

AddKeyPurpose adds the given purpose(s) to the given key

func (*IDCard) AddKeychain

func (id *IDCard) AddKeychain(kc *Keychain)

AddKeychain adds the keys found in Keychain to the IDCard.

func (*IDCard) FindGroup

func (id *IDCard) FindGroup(k any) (*Membership, error)

FindGroup locates the Membership matching the given key

func (*IDCard) FindKey

func (id *IDCard) FindKey(k any, create bool) (*SubKey, error)

FindKey locates the SubKey matching the given key, and optionally creates one if create is set to true

func (*IDCard) GetKeys

func (id *IDCard) GetKeys(purpose string) []crypto.PublicKey

GetKeys returns all the keys of an IDCard that fit a given purpose

func (*IDCard) SetKeyDuration

func (id *IDCard) SetKeyDuration(k crypto.PublicKey, t time.Duration) error

SetKeyDuration specifies the duration for the given key

func (*IDCard) SetKeyPurposes

func (id *IDCard) SetKeyPurposes(k crypto.PublicKey, purposes ...string) error

SetKeyPurposes specifies the purpose of a given key (sign, decrypt, etc)

func (*IDCard) Sign

func (id *IDCard) Sign(rand io.Reader, k crypto.Signer) ([]byte, error)

Sign will return a signed bottle containing this ID Card

func (*IDCard) TestKeyPurpose

func (id *IDCard) TestKeyPurpose(k any, purpose string) error

TestKeyPurpose return nil if the provided key is fit for the given purpose, a not found error if the key couldn't be found, or a ErrKeyUnfit

func (*IDCard) UnmarshalBinary

func (id *IDCard) UnmarshalBinary(b []byte) error

UnmarshalBinary will read a signed ID card, returning an error if it wasn't signed

func (*IDCard) UpdateGroups

func (id *IDCard) UpdateGroups(data [][]byte) error

UpdateGroups update the attached memberships based on the provided data

type Keychain

type Keychain struct {
	// contains filtered or unexported fields
}

Keychain is an object storing private keys that can be used to sign or decrypt things.

func NewKeychain

func NewKeychain() *Keychain

NewKeychain returns a new, empty keychain

func (*Keychain) AddKey

func (kc *Keychain) AddKey(k any) error

AddKey adds a key to the keychain. The value passed must be a PrivateKey whose Public() method returns a public key object that can be marshalled by crypto/x509.MarshalPKIXPublicKey, or an MLKEMPrivateKey. If another Keychain is passed all its keys will be added.

func (*Keychain) AddKeys

func (kc *Keychain) AddKeys(keys ...any) error

AddKeys adds a number of keys to the keychain, and stops at the first error found.

func (*Keychain) All

func (kc *Keychain) All(yield func(PrivateKey) bool)

All returns all the keys in the Keychain

func (*Keychain) FirstSigner

func (kc *Keychain) FirstSigner() crypto.Signer

FirstSigner returns the first crypto.Signer that was added to this Keychain.

func (*Keychain) GetKey

func (kc *Keychain) GetKey(public any) (PrivateKey, error)

GetKey returns the private key matching the passed public key, if known. A []byte of the PKIX marshalled public key, a public key object, or an MLKEMPublicKey can be passed.

func (*Keychain) GetSigner

func (kc *Keychain) GetSigner(public any) (crypto.Signer, error)

GetSigner will return the signer matching the public key

func (*Keychain) Sign

func (kc *Keychain) Sign(rand io.Reader, publicKey any, buf []byte, opts ...crypto.SignerOpts) ([]byte, error)

Sign will use the specified key from the keychain to sign the given buffer. Unlike Go's standard sign method, the whole buffer should be passed and will be signed as needed.

func (*Keychain) Signers

func (kc *Keychain) Signers(yield func(crypto.Signer) bool)

Signers returns all the signing-capable keys in the Keychain

type MLDSAVariant

type MLDSAVariant byte

MLDSAVariant specifies the ML-DSA parameter set.

const (
	MLDSA44 MLDSAVariant = 0 // ML-DSA-44 (NIST security level 2)
	MLDSA65 MLDSAVariant = 1 // ML-DSA-65 (NIST security level 3)
	MLDSA87 MLDSAVariant = 2 // ML-DSA-87 (NIST security level 5)
)

type MLKEMPrivateKey

type MLKEMPrivateKey struct {
	X25519 *ecdh.PrivateKey // optional, for hybrid mode
	// contains filtered or unexported fields
}

MLKEMPrivateKey wraps an ML-KEM decapsulation key with an optional X25519 key for hybrid mode.

func GenerateMLKEMKey

func GenerateMLKEMKey(rand io.Reader, hybrid bool) (*MLKEMPrivateKey, error)

GenerateMLKEMKey generates a new ML-KEM-768 key pair. If hybrid is true, also generates an X25519 key pair. For ML-KEM-1024, use GenerateMLKEMKey1024.

func GenerateMLKEMKey1024

func GenerateMLKEMKey1024(rand io.Reader, hybrid bool) (*MLKEMPrivateKey, error)

GenerateMLKEMKey1024 generates a new ML-KEM-1024 key pair. If hybrid is true, also generates an X25519 key pair.

func GenerateMLKEMKey768

func GenerateMLKEMKey768(rand io.Reader, hybrid bool) (*MLKEMPrivateKey, error)

GenerateMLKEMKey768 generates a new ML-KEM-768 key pair. If hybrid is true, also generates an X25519 key pair.

func ParseMLKEMPrivateKey

func ParseMLKEMPrivateKey(der []byte) (*MLKEMPrivateKey, error)

ParseMLKEMPrivateKey parses a PKCS#8-encoded ML-KEM private key.

func UnmarshalMLKEMPrivateKey

func UnmarshalMLKEMPrivateKey(data []byte) (*MLKEMPrivateKey, error)

UnmarshalMLKEMPrivateKey unmarshals an MLKEMPrivateKey from simple binary format. Deprecated: Use ParseMLKEMPrivateKey for standard PKCS#8 encoding.

func (*MLKEMPrivateKey) IsHybrid

func (k *MLKEMPrivateKey) IsHybrid() bool

IsHybrid returns true if this is a hybrid key (X25519 + ML-KEM).

func (*MLKEMPrivateKey) MLKEMPublic

func (k *MLKEMPrivateKey) MLKEMPublic() *MLKEMPublicKey

MLKEMPublic returns the typed ML-KEM public key for this private key.

func (*MLKEMPrivateKey) MarshalPKCS8PrivateKey

func (k *MLKEMPrivateKey) MarshalPKCS8PrivateKey() ([]byte, error)

MarshalPKCS8PrivateKey marshals an MLKEMPrivateKey to PKCS#8/ASN.1 DER format.

func (*MLKEMPrivateKey) Public

func (k *MLKEMPrivateKey) Public() crypto.PublicKey

Public returns the public key for this private key as crypto.PublicKey. This implements the PrivateKey interface.

func (*MLKEMPrivateKey) Variant

func (k *MLKEMPrivateKey) Variant() MLKEMVariant

Variant returns the ML-KEM variant (768 or 1024) for this key.

type MLKEMPublicKey

type MLKEMPublicKey struct {
	X25519 *ecdh.PublicKey // optional, for hybrid mode
	// contains filtered or unexported fields
}

MLKEMPublicKey wraps an ML-KEM encapsulation key with an optional X25519 key for hybrid mode. When X25519 is set, encryption uses hybrid mode (X25519 + ML-KEM) for defense-in-depth.

func ParseMLKEMPublicKey

func ParseMLKEMPublicKey(der []byte) (*MLKEMPublicKey, error)

ParseMLKEMPublicKey parses a PKIX-encoded ML-KEM public key.

func UnmarshalMLKEMPublicKey

func UnmarshalMLKEMPublicKey(data []byte) (*MLKEMPublicKey, error)

UnmarshalMLKEMPublicKey unmarshals an MLKEMPublicKey from simple binary format. Deprecated: Use ParseMLKEMPublicKey for standard PKIX encoding.

func (*MLKEMPublicKey) Equal

func (k *MLKEMPublicKey) Equal(other crypto.PublicKey) bool

Equal reports whether k and other have the same value.

func (*MLKEMPublicKey) IsHybrid

func (k *MLKEMPublicKey) IsHybrid() bool

IsHybrid returns true if this is a hybrid key (X25519 + ML-KEM).

func (*MLKEMPublicKey) MarshalPKIXPublicKey

func (k *MLKEMPublicKey) MarshalPKIXPublicKey() ([]byte, error)

MarshalPKIXPublicKey marshals an MLKEMPublicKey to PKIX/ASN.1 DER format. For hybrid keys, it uses a composite key format.

func (*MLKEMPublicKey) Variant

func (k *MLKEMPublicKey) Variant() MLKEMVariant

Variant returns the ML-KEM variant (768 or 1024) for this key.

type MLKEMVariant

type MLKEMVariant byte

MLKEMVariant specifies the ML-KEM parameter set.

const (
	MLKEM768  MLKEMVariant = 0 // ML-KEM-768 (recommended for most applications)
	MLKEM1024 MLKEMVariant = 1 // ML-KEM-1024 (higher security level)
)

type Membership

type Membership struct {
	Subject   []byte            `json:"sub" cbor:"1,keyasint"` // must be == parent.Self (if empty, fill with parent.Self before sig)
	Key       []byte            `json:"key" cbor:"2,keyasint"` // group key (group identification)
	Status    string            `json:"sta" cbor:"3,keyasint"` // status of membership (valid|suspended)
	Issued    time.Time         `json:"iss" cbor:"4,keyasint"` // update time of membership info
	Info      map[string]string `json:"nfo" cbor:"5,keyasint"` // subject information (name, etc)
	SignKey   []byte            `json:"sky" cbor:"6,keyasint"` // signature generating key (must be listed as sign key for the Key's IDCard)
	Signature []byte            `json:"sig" cbor:"7,keyasint"` // signature of structure with sign=nil by group key
}

Membership is a membership in a group.

func NewMembership

func NewMembership(member *IDCard, key []byte) *Membership

func (*Membership) Sign

func (m *Membership) Sign(rand io.Reader, key crypto.Signer, opts ...crypto.SignerOpts) error

Sign signs the membership using the provided key

func (*Membership) SignatureBytes

func (m *Membership) SignatureBytes() ([]byte, error)

SignatureBytes returns a representation of Membership that can be used to sign or verify the structure

func (*Membership) Verify

func (m *Membership) Verify(groupId *IDCard) error

Verify ensures the signature is correct. If the group ID is known, it must be passed.

type MessageFormat

type MessageFormat int
const (
	ClearText  MessageFormat = iota
	CborBottle               // bottle in a bottle
	AES                      // AES+AEAD encrypted cbor bottle
	JsonBottle               // bottle in a bottle (json version)
)

type MessageRecipient

type MessageRecipient struct {
	Type      int    `json:"typ,omitempty"` // always 0 (for now)
	Recipient []byte `json:"key"`           // recipient's public key
	Data      []byte `json:"dat"`           // encrypted key payload (only recipient's eyes)
	// contains filtered or unexported fields
}

func (*MessageRecipient) OpenWith

func (r *MessageRecipient) OpenWith(k any) ([]byte, error)

type MessageSignature

type MessageSignature struct {
	Type   int    `json:"typ,omitempty"` // always 0 (for now)
	Signer []byte `json:"key"`           // signature's key
	Data   []byte `json:"dat"`           // signature payload, similar format to jwt (NOTE: ECDSA signatures are weird)
	// contains filtered or unexported fields
}

func (*MessageSignature) Verify

func (sig *MessageSignature) Verify(buf []byte, opts ...crypto.SignerOpts) error

type OpenResult

type OpenResult struct {
	Decryption int                 // number of performed decryptions
	Signatures []*MessageSignature // verified message signatures
	Bottles    []*Bottle
}

func (*OpenResult) First

func (or *OpenResult) First() *Bottle

First returns the first (outside-most) bottle, that will be what has been passed to Open

func (*OpenResult) Last

func (or *OpenResult) Last() *Bottle

Last returns the last (inside-most) bottle, which will contain any relevant meta-data

func (*OpenResult) SignedBy

func (or *OpenResult) SignedBy(signer any) bool

SignedBy returns true if the message was signed by the signer (either a public key or a IDCard)

type Opener

type Opener struct {
	// contains filtered or unexported fields
}

Opener allows opening a Bottle

func MustOpener

func MustOpener(keys ...any) *Opener

MustOpener returns an opener that can be used to open a Bottle and panics if it fails

func NewOpener

func NewOpener(keys ...any) (*Opener, error)

NewOpener returns an opener that can be used to open a Bottle using any or all of the given keys.

func (*Opener) Open

func (o *Opener) Open(b *Bottle) ([]byte, *OpenResult, error)

Open opens the given Bottle, decrypting any encrypted elements, checking all signatures and returning the embedded buffer in the end

func (*Opener) OpenCbor

func (o *Opener) OpenCbor(b []byte) ([]byte, *OpenResult, error)

OpenCbor opens the given Bottle encoded as cbor data.

func (*Opener) OpenHttp

func (o *Opener) OpenHttp(req *http.Request) ([]byte, *OpenResult, error)

OpenHttp will read the data from a http.Request handling the content-type header.

func (*Opener) OpenJson

func (o *Opener) OpenJson(b []byte) ([]byte, *OpenResult, error)

OpenJson opens the given Bottle encoded as json data.

func (*Opener) Unmarshal

func (o *Opener) Unmarshal(b *Bottle, v any) (*OpenResult, error)

Unmarshal will open the given bottle and pour the contents into v

func (*Opener) UnmarshalCbor

func (o *Opener) UnmarshalCbor(b []byte, v any) (*OpenResult, error)

UnmarshalCbor will open the given cbor-encoded bottle and pour the contents into v

func (*Opener) UnmarshalHttp

func (o *Opener) UnmarshalHttp(req *http.Request, v any) (*OpenResult, error)

UnmarshalHttp will read the data from a http.Request and unmarshal it into v.

func (*Opener) UnmarshalJson

func (o *Opener) UnmarshalJson(b []byte, v any) (*OpenResult, error)

UnmarshalJson will open the given json-encoded bottle and pour the contents into v

type PrivateKey

type PrivateKey interface {
	Public() crypto.PublicKey
}

PrivateKey represents a private key using an unspecified algorithm.

All private keys must implement a method to retrieve the matching public key. The ones in the standard lbirary do.

type PublicKeyIntf

type PublicKeyIntf interface {
	Equal(x crypto.PublicKey) bool
}

PublicKeyIntf represents a public key using an unspecified algorithm.

all public key types in the standard library implement this interface

func ParsePKIXPublicKey

func ParsePKIXPublicKey(der []byte) (PublicKeyIntf, error)

ParsePKIXPublicKey parses a PKIX-encoded public key. It supports all key types supported by crypto/x509.ParsePKIXPublicKey as well as ML-KEM, ML-DSA, and SLH-DSA keys.

func PublicKey

func PublicKey(privKey crypto.PrivateKey) PublicKeyIntf

PublicKey returns the public key for a given private key, or nil if the argumlent is not a private key or if its Public() method returned nil.

type SubKey

type SubKey struct {
	Key      []byte     `json:"key" cbor:"1,keyasint"`                     // public key as PKIX
	Issued   time.Time  `json:"iss" cbor:"2,keyasint"`                     // issuance (addition) date
	Expires  *time.Time `json:"exp,omitempty" cbor:"3,keyasint,omitempty"` // expiration date (if any)
	Purposes []string   `json:"pur" cbor:"4,keyasint"`                     // purposes: can contain "sign", "decrypt"
}

SubKey is a key found in a given id card

func (*SubKey) AddPurpose

func (sk *SubKey) AddPurpose(purpose ...string)

func (*SubKey) HasPurpose

func (sk *SubKey) HasPurpose(purpose string) bool

HasPurpose returns true if the key has the specified purpose listed

func (*SubKey) String

func (sk *SubKey) String() string

Jump to

Keyboard shortcuts

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