| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- // 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")
- )
|