vendor.github.com.cloudflare.circl.hpke.hpke.go Maven / Gradle / Ivy
The newest version!
// Package hpke implements the Hybrid Public Key Encryption (HPKE) standard
// specified by draft-irtf-cfrg-hpke-07.
//
// HPKE works for any combination of a public-key encapsulation mechanism
// (KEM), a key derivation function (KDF), and an authenticated encryption
// scheme with additional data (AEAD).
//
// Specification in
// https://datatracker.ietf.org/doc/draft-irtf-cfrg-hpke
//
// BUG(cjpatton): This package does not implement the "Export-Only" mode of the
// HPKE context. In particular, it does not recognize the AEAD codepoint
// reserved for this purpose (0xFFFF).
package hpke
import (
"crypto/rand"
"encoding"
"errors"
"io"
"github.com/cloudflare/circl/kem"
)
const versionLabel = "HPKE-v1"
// Context defines the capabilities of an HPKE context.
type Context interface {
encoding.BinaryMarshaler
// Export takes a context string exporterContext and a desired length (in
// bytes), and produces a secret derived from the internal exporter secret
// using the corresponding KDF Expand function. It panics if length is
// greater than 255*N bytes, where N is the size (in bytes) of the KDF's
// output.
Export(exporterContext []byte, length uint) []byte
// Suite returns the cipher suite corresponding to this context.
Suite() Suite
}
// Sealer encrypts a plaintext using an AEAD encryption.
type Sealer interface {
Context
// Seal takes a plaintext and associated data to produce a ciphertext.
// The nonce is handled by the Sealer and incremented after each call.
Seal(pt, aad []byte) (ct []byte, err error)
}
// Opener decrypts a ciphertext using an AEAD encryption.
type Opener interface {
Context
// Open takes a ciphertext and associated data to recover, if successful,
// the plaintext. The nonce is handled by the Opener and incremented after
// each call.
Open(ct, aad []byte) (pt []byte, err error)
}
// modeID represents an HPKE variant.
type modeID = uint8
const (
// modeBase to enable encryption to the holder of a given KEM private key.
modeBase modeID = 0x00
// modePSK extends the base mode by allowing the Receiver to authenticate
// that the sender possessed a given pre-shared key (PSK).
modePSK modeID = 0x01
// modeAuth extends the base mode by allowing the Receiver to authenticate
// that the sender possessed a given KEM private key.
modeAuth modeID = 0x02
// modeAuthPSK provides a combination of the PSK and Auth modes.
modeAuthPSK modeID = 0x03
)
// Suite is an HPKE cipher suite consisting of a KEM, KDF, and AEAD algorithm.
type Suite struct {
kemID KEM
kdfID KDF
aeadID AEAD
}
// NewSuite builds a Suite from a specified set of algorithms. Panics
// if an algorithm identifier is not valid.
func NewSuite(kemID KEM, kdfID KDF, aeadID AEAD) Suite {
s := Suite{kemID, kdfID, aeadID}
if !s.isValid() {
panic(ErrInvalidHPKESuite)
}
return s
}
type state struct {
Suite
modeID modeID
skS kem.PrivateKey
pkS kem.PublicKey
psk []byte
pskID []byte
info []byte
}
// Sender performs hybrid public-key encryption.
type Sender struct {
state
pkR kem.PublicKey
}
// NewSender creates a Sender with knowledge of the receiver's public-key.
func (suite Suite) NewSender(pkR kem.PublicKey, info []byte) (*Sender, error) {
return &Sender{
state: state{Suite: suite, info: info},
pkR: pkR,
}, nil
}
// Setup generates a new HPKE context used for Base Mode encryption.
// Returns the Sealer and corresponding encapsulated key.
func (s *Sender) Setup(rnd io.Reader) (enc []byte, seal Sealer, err error) {
s.modeID = modeBase
return s.allSetup(rnd)
}
// SetupAuth generates a new HPKE context used for Auth Mode encryption.
// Returns the Sealer and corresponding encapsulated key.
func (s *Sender) SetupAuth(rnd io.Reader, skS kem.PrivateKey) (
enc []byte, seal Sealer, err error,
) {
s.modeID = modeAuth
s.state.skS = skS
return s.allSetup(rnd)
}
// SetupPSK generates a new HPKE context used for PSK Mode encryption.
// Returns the Sealer and corresponding encapsulated key.
func (s *Sender) SetupPSK(rnd io.Reader, psk, pskID []byte) (
enc []byte, seal Sealer, err error,
) {
s.modeID = modePSK
s.state.psk = psk
s.state.pskID = pskID
return s.allSetup(rnd)
}
// SetupAuthPSK generates a new HPKE context used for Auth-PSK Mode encryption.
// Returns the Sealer and corresponding encapsulated key.
func (s *Sender) SetupAuthPSK(rnd io.Reader, skS kem.PrivateKey, psk, pskID []byte) (
enc []byte, seal Sealer, err error,
) {
s.modeID = modeAuthPSK
s.state.skS = skS
s.state.psk = psk
s.state.pskID = pskID
return s.allSetup(rnd)
}
// Receiver performs hybrid public-key decryption.
type Receiver struct {
state
skR kem.PrivateKey
enc []byte
}
// NewReceiver creates a Receiver with knowledge of a private key.
func (suite Suite) NewReceiver(skR kem.PrivateKey, info []byte) (
*Receiver, error,
) {
return &Receiver{state: state{Suite: suite, info: info}, skR: skR}, nil
}
// Setup generates a new HPKE context used for Base Mode encryption.
// Setup takes an encapsulated key and returns an Opener.
func (r *Receiver) Setup(enc []byte) (Opener, error) {
r.modeID = modeBase
r.enc = enc
return r.allSetup()
}
// SetupAuth generates a new HPKE context used for Auth Mode encryption.
// SetupAuth takes an encapsulated key and a public key, and returns an Opener.
func (r *Receiver) SetupAuth(enc []byte, pkS kem.PublicKey) (Opener, error) {
r.modeID = modeAuth
r.enc = enc
r.state.pkS = pkS
return r.allSetup()
}
// SetupPSK generates a new HPKE context used for PSK Mode encryption.
// SetupPSK takes an encapsulated key, and a pre-shared key; and returns an
// Opener.
func (r *Receiver) SetupPSK(enc, psk, pskID []byte) (Opener, error) {
r.modeID = modePSK
r.enc = enc
r.state.psk = psk
r.state.pskID = pskID
return r.allSetup()
}
// SetupAuthPSK generates a new HPKE context used for Auth-PSK Mode encryption.
// SetupAuthPSK takes an encapsulated key, a public key, and a pre-shared key;
// and returns an Opener.
func (r *Receiver) SetupAuthPSK(
enc, psk, pskID []byte, pkS kem.PublicKey,
) (Opener, error) {
r.modeID = modeAuthPSK
r.enc = enc
r.state.psk = psk
r.state.pskID = pskID
r.state.pkS = pkS
return r.allSetup()
}
func (s *Sender) allSetup(rnd io.Reader) ([]byte, Sealer, error) {
scheme := s.kemID.Scheme()
if rnd == nil {
rnd = rand.Reader
}
seed := make([]byte, scheme.EncapsulationSeedSize())
_, err := io.ReadFull(rnd, seed)
if err != nil {
return nil, nil, err
}
var enc, ss []byte
switch s.modeID {
case modeBase, modePSK:
enc, ss, err = scheme.EncapsulateDeterministically(s.pkR, seed)
case modeAuth, modeAuthPSK:
enc, ss, err = scheme.AuthEncapsulateDeterministically(s.pkR, s.skS, seed)
}
if err != nil {
return nil, nil, err
}
ctx, err := s.keySchedule(ss, s.info, s.psk, s.pskID)
if err != nil {
return nil, nil, err
}
return enc, &sealContext{ctx}, nil
}
func (r *Receiver) allSetup() (Opener, error) {
var err error
var ss []byte
scheme := r.kemID.Scheme()
switch r.modeID {
case modeBase, modePSK:
ss, err = scheme.Decapsulate(r.skR, r.enc)
case modeAuth, modeAuthPSK:
ss, err = scheme.AuthDecapsulate(r.skR, r.enc, r.pkS)
}
if err != nil {
return nil, err
}
ctx, err := r.keySchedule(ss, r.info, r.psk, r.pskID)
if err != nil {
return nil, err
}
return &openContext{ctx}, nil
}
var (
ErrInvalidHPKESuite = errors.New("hpke: invalid HPKE suite")
ErrInvalidKDF = errors.New("hpke: invalid KDF identifier")
ErrInvalidKEM = errors.New("hpke: invalid KEM identifier")
ErrInvalidAEAD = errors.New("hpke: invalid AEAD identifier")
ErrInvalidKEMPublicKey = errors.New("hpke: invalid KEM public key")
ErrInvalidKEMPrivateKey = errors.New("hpke: invalid KEM private key")
ErrInvalidKEMSharedSecret = errors.New("hpke: invalid KEM shared secret")
ErrAEADSeqOverflows = errors.New("hpke: AEAD sequence number overflows")
)