hpke.go raw

   1  // Package hpke implements the Hybrid Public Key Encryption (HPKE) standard
   2  // specified by draft-irtf-cfrg-hpke-07.
   3  //
   4  // HPKE works for any combination of a public-key encapsulation mechanism
   5  // (KEM), a key derivation function (KDF), and an authenticated encryption
   6  // scheme with additional data (AEAD).
   7  //
   8  // Specification in
   9  // https://datatracker.ietf.org/doc/draft-irtf-cfrg-hpke
  10  //
  11  // BUG(cjpatton): This package does not implement the "Export-Only" mode of the
  12  // HPKE context. In particular, it does not recognize the AEAD codepoint
  13  // reserved for this purpose (0xFFFF).
  14  package hpke
  15  
  16  import (
  17  	"crypto/rand"
  18  	"encoding"
  19  	"errors"
  20  	"io"
  21  
  22  	"github.com/cloudflare/circl/kem"
  23  )
  24  
  25  const versionLabel = "HPKE-v1"
  26  
  27  // Context defines the capabilities of an HPKE context.
  28  type Context interface {
  29  	encoding.BinaryMarshaler
  30  	// Export takes a context string exporterContext and a desired length (in
  31  	// bytes), and produces a secret derived from the internal exporter secret
  32  	// using the corresponding KDF Expand function. It panics if length is
  33  	// greater than 255*N bytes, where N is the size (in bytes) of the KDF's
  34  	// output.
  35  	Export(exporterContext []byte, length uint) []byte
  36  	// Suite returns the cipher suite corresponding to this context.
  37  	Suite() Suite
  38  }
  39  
  40  // Sealer encrypts a plaintext using an AEAD encryption.
  41  type Sealer interface {
  42  	Context
  43  	// Seal takes a plaintext and associated data to produce a ciphertext.
  44  	// The nonce is handled by the Sealer and incremented after each call.
  45  	Seal(pt, aad []byte) (ct []byte, err error)
  46  }
  47  
  48  // Opener decrypts a ciphertext using an AEAD encryption.
  49  type Opener interface {
  50  	Context
  51  	// Open takes a ciphertext and associated data to recover, if successful,
  52  	// the plaintext. The nonce is handled by the Opener and incremented after
  53  	// each call.
  54  	Open(ct, aad []byte) (pt []byte, err error)
  55  }
  56  
  57  // modeID represents an HPKE variant.
  58  type modeID = uint8
  59  
  60  const (
  61  	// modeBase to enable encryption to the holder of a given KEM private key.
  62  	modeBase modeID = 0x00
  63  	// modePSK extends the base mode by allowing the Receiver to authenticate
  64  	// that the sender possessed a given pre-shared key (PSK).
  65  	modePSK modeID = 0x01
  66  	// modeAuth extends the base mode by allowing the Receiver to authenticate
  67  	// that the sender possessed a given KEM private key.
  68  	modeAuth modeID = 0x02
  69  	// modeAuthPSK provides a combination of the PSK and Auth modes.
  70  	modeAuthPSK modeID = 0x03
  71  )
  72  
  73  // Suite is an HPKE cipher suite consisting of a KEM, KDF, and AEAD algorithm.
  74  type Suite struct {
  75  	kemID  KEM
  76  	kdfID  KDF
  77  	aeadID AEAD
  78  }
  79  
  80  // NewSuite builds a Suite from a specified set of algorithms. Panics
  81  // if an algorithm identifier is not valid.
  82  func NewSuite(kemID KEM, kdfID KDF, aeadID AEAD) Suite {
  83  	s := Suite{kemID, kdfID, aeadID}
  84  	if !s.isValid() {
  85  		panic(ErrInvalidHPKESuite)
  86  	}
  87  	return s
  88  }
  89  
  90  type state struct {
  91  	Suite
  92  	modeID modeID
  93  	skS    kem.PrivateKey
  94  	pkS    kem.PublicKey
  95  	psk    []byte
  96  	pskID  []byte
  97  	info   []byte
  98  }
  99  
 100  // Sender performs hybrid public-key encryption.
 101  type Sender struct {
 102  	state
 103  	pkR kem.PublicKey
 104  }
 105  
 106  // NewSender creates a Sender with knowledge of the receiver's public-key.
 107  func (suite Suite) NewSender(pkR kem.PublicKey, info []byte) (*Sender, error) {
 108  	return &Sender{
 109  		state: state{Suite: suite, info: info},
 110  		pkR:   pkR,
 111  	}, nil
 112  }
 113  
 114  // Setup generates a new HPKE context used for Base Mode encryption.
 115  // Returns the Sealer and corresponding encapsulated key.
 116  func (s *Sender) Setup(rnd io.Reader) (enc []byte, seal Sealer, err error) {
 117  	s.modeID = modeBase
 118  	return s.allSetup(rnd)
 119  }
 120  
 121  // SetupAuth generates a new HPKE context used for Auth Mode encryption.
 122  // Returns the Sealer and corresponding encapsulated key.
 123  func (s *Sender) SetupAuth(rnd io.Reader, skS kem.PrivateKey) (
 124  	enc []byte, seal Sealer, err error,
 125  ) {
 126  	s.modeID = modeAuth
 127  	s.state.skS = skS
 128  	return s.allSetup(rnd)
 129  }
 130  
 131  // SetupPSK generates a new HPKE context used for PSK Mode encryption.
 132  // Returns the Sealer and corresponding encapsulated key.
 133  func (s *Sender) SetupPSK(rnd io.Reader, psk, pskID []byte) (
 134  	enc []byte, seal Sealer, err error,
 135  ) {
 136  	s.modeID = modePSK
 137  	s.state.psk = psk
 138  	s.state.pskID = pskID
 139  	return s.allSetup(rnd)
 140  }
 141  
 142  // SetupAuthPSK generates a new HPKE context used for Auth-PSK Mode encryption.
 143  // Returns the Sealer and corresponding encapsulated key.
 144  func (s *Sender) SetupAuthPSK(rnd io.Reader, skS kem.PrivateKey, psk, pskID []byte) (
 145  	enc []byte, seal Sealer, err error,
 146  ) {
 147  	s.modeID = modeAuthPSK
 148  	s.state.skS = skS
 149  	s.state.psk = psk
 150  	s.state.pskID = pskID
 151  	return s.allSetup(rnd)
 152  }
 153  
 154  // Receiver performs hybrid public-key decryption.
 155  type Receiver struct {
 156  	state
 157  	skR kem.PrivateKey
 158  	enc []byte
 159  }
 160  
 161  // NewReceiver creates a Receiver with knowledge of a private key.
 162  func (suite Suite) NewReceiver(skR kem.PrivateKey, info []byte) (
 163  	*Receiver, error,
 164  ) {
 165  	return &Receiver{state: state{Suite: suite, info: info}, skR: skR}, nil
 166  }
 167  
 168  // Setup generates a new HPKE context used for Base Mode encryption.
 169  // Setup takes an encapsulated key and returns an Opener.
 170  func (r *Receiver) Setup(enc []byte) (Opener, error) {
 171  	r.modeID = modeBase
 172  	r.enc = enc
 173  	return r.allSetup()
 174  }
 175  
 176  // SetupAuth generates a new HPKE context used for Auth Mode encryption.
 177  // SetupAuth takes an encapsulated key and a public key, and returns an Opener.
 178  func (r *Receiver) SetupAuth(enc []byte, pkS kem.PublicKey) (Opener, error) {
 179  	r.modeID = modeAuth
 180  	r.enc = enc
 181  	r.state.pkS = pkS
 182  	return r.allSetup()
 183  }
 184  
 185  // SetupPSK generates a new HPKE context used for PSK Mode encryption.
 186  // SetupPSK takes an encapsulated key, and a pre-shared key; and returns an
 187  // Opener.
 188  func (r *Receiver) SetupPSK(enc, psk, pskID []byte) (Opener, error) {
 189  	r.modeID = modePSK
 190  	r.enc = enc
 191  	r.state.psk = psk
 192  	r.state.pskID = pskID
 193  	return r.allSetup()
 194  }
 195  
 196  // SetupAuthPSK generates a new HPKE context used for Auth-PSK Mode encryption.
 197  // SetupAuthPSK takes an encapsulated key, a public key, and a pre-shared key;
 198  // and returns an Opener.
 199  func (r *Receiver) SetupAuthPSK(
 200  	enc, psk, pskID []byte, pkS kem.PublicKey,
 201  ) (Opener, error) {
 202  	r.modeID = modeAuthPSK
 203  	r.enc = enc
 204  	r.state.psk = psk
 205  	r.state.pskID = pskID
 206  	r.state.pkS = pkS
 207  	return r.allSetup()
 208  }
 209  
 210  func (s *Sender) allSetup(rnd io.Reader) ([]byte, Sealer, error) {
 211  	scheme := s.kemID.Scheme()
 212  
 213  	if rnd == nil {
 214  		rnd = rand.Reader
 215  	}
 216  	seed := make([]byte, scheme.EncapsulationSeedSize())
 217  	_, err := io.ReadFull(rnd, seed)
 218  	if err != nil {
 219  		return nil, nil, err
 220  	}
 221  
 222  	var enc, ss []byte
 223  	switch s.modeID {
 224  	case modeBase, modePSK:
 225  		enc, ss, err = scheme.EncapsulateDeterministically(s.pkR, seed)
 226  	case modeAuth, modeAuthPSK:
 227  		authScheme, ok := scheme.(kem.AuthScheme)
 228  		if !ok {
 229  			return nil, nil, ErrInvalidAuthKEM
 230  		}
 231  
 232  		enc, ss, err = authScheme.AuthEncapsulateDeterministically(s.pkR, s.skS, seed)
 233  	}
 234  	if err != nil {
 235  		return nil, nil, err
 236  	}
 237  
 238  	ctx, err := s.keySchedule(ss, s.info, s.psk, s.pskID)
 239  	if err != nil {
 240  		return nil, nil, err
 241  	}
 242  
 243  	return enc, &sealContext{ctx}, nil
 244  }
 245  
 246  func (r *Receiver) allSetup() (Opener, error) {
 247  	var err error
 248  	var ss []byte
 249  	scheme := r.kemID.Scheme()
 250  	switch r.modeID {
 251  	case modeBase, modePSK:
 252  		ss, err = scheme.Decapsulate(r.skR, r.enc)
 253  	case modeAuth, modeAuthPSK:
 254  		authScheme, ok := scheme.(kem.AuthScheme)
 255  		if !ok {
 256  			return nil, ErrInvalidAuthKEM
 257  		}
 258  
 259  		ss, err = authScheme.AuthDecapsulate(r.skR, r.enc, r.pkS)
 260  	}
 261  	if err != nil {
 262  		return nil, err
 263  	}
 264  
 265  	ctx, err := r.keySchedule(ss, r.info, r.psk, r.pskID)
 266  	if err != nil {
 267  		return nil, err
 268  	}
 269  	return &openContext{ctx}, nil
 270  }
 271  
 272  var (
 273  	ErrInvalidHPKESuite       = errors.New("hpke: invalid HPKE suite")
 274  	ErrInvalidKDF             = errors.New("hpke: invalid KDF identifier")
 275  	ErrInvalidKEM             = errors.New("hpke: invalid KEM identifier")
 276  	ErrInvalidAuthKEM         = errors.New("hpke: KEM does not support Auth mode")
 277  	ErrInvalidAEAD            = errors.New("hpke: invalid AEAD identifier")
 278  	ErrInvalidKEMPublicKey    = errors.New("hpke: invalid KEM public key")
 279  	ErrInvalidKEMPrivateKey   = errors.New("hpke: invalid KEM private key")
 280  	ErrInvalidKEMSharedSecret = errors.New("hpke: invalid KEM shared secret")
 281  	ErrInvalidKEMDeriveKey    = errors.New("hpke: too many tries to derive KEM key")
 282  	ErrAEADSeqOverflows       = errors.New("hpke: AEAD sequence number overflows")
 283  )
 284