Skip to content

Commit

Permalink
Add X25519Kyber768Draft00 experimental HPKE KEM
Browse files Browse the repository at this point in the history
This change also adds the ability to produce test vectors for the
draft specification:

  https://datatracker.ietf.org/doc/draft-westerbaan-cfrg-hpke-xyber768d00/

This change also updates the known answer test vectors from RFC9180.
  • Loading branch information
chris-wood committed Apr 12, 2023
1 parent f4c0e87 commit 161705b
Show file tree
Hide file tree
Showing 13 changed files with 564 additions and 131 deletions.
13 changes: 8 additions & 5 deletions hpke/aead.go
Expand Up @@ -7,11 +7,14 @@ import (

type encdecContext struct {
// Serialized parameters
suite Suite
exporterSecret []byte
key []byte
baseNonce []byte
sequenceNumber []byte
suite Suite
sharedSecret []byte
secret []byte
keyScheduleContext []byte
exporterSecret []byte
key []byte
baseNonce []byte
sequenceNumber []byte

// Operational parameters
cipher.AEAD
Expand Down
4 changes: 2 additions & 2 deletions hpke/aead_test.go
Expand Up @@ -44,12 +44,12 @@ func setupAeadTest() (*sealContext, *openContext, error) {

sealer := &sealContext{
&encdecContext{
suite, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn),
suite, nil, nil, nil, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn),
},
}
opener := &openContext{
&encdecContext{
suite, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn),
suite, nil, nil, nil, nil, nil, baseNonce, make([]byte, Nn), aead, make([]byte, Nn),
},
}
return sealer, opener, nil
Expand Down
82 changes: 35 additions & 47 deletions hpke/algs.go
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/cloudflare/circl/dh/x448"
"github.com/cloudflare/circl/ecc/p384"
"github.com/cloudflare/circl/kem"
"github.com/cloudflare/circl/kem/kyber/kyber768"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
Expand All @@ -35,6 +36,9 @@ const (
// KEM_X448_HKDF_SHA512 is a KEM using X448 Diffie-Hellman function and
// HKDF with SHA-512.
KEM_X448_HKDF_SHA512 KEM = 0x21
// KEM_X25519_KYBER768_DRAFT00 is a hybrid KEM built on DHKEM(X25519, HKDF-SHA256)
// and Kyber768Draft00
KEM_X25519_KYBER768_DRAFT00 KEM = 0x30
)

// IsValid returns true if the KEM identifier is supported by the HPKE package.
Expand All @@ -44,7 +48,8 @@ func (k KEM) IsValid() bool {
KEM_P384_HKDF_SHA384,
KEM_P521_HKDF_SHA512,
KEM_X25519_HKDF_SHA256,
KEM_X448_HKDF_SHA512:
KEM_X448_HKDF_SHA512,
KEM_X25519_KYBER768_DRAFT00:
return true
default:
return false
Expand All @@ -65,32 +70,8 @@ func (k KEM) Scheme() kem.AuthScheme {
return dhkemx25519hkdfsha256
case KEM_X448_HKDF_SHA512:
return dhkemx448hkdfsha512
default:
panic(ErrInvalidKEM)
}
}

func (k KEM) validatePublicKey(pk kem.PublicKey) bool {
switch k {
case KEM_P256_HKDF_SHA256, KEM_P384_HKDF_SHA384, KEM_P521_HKDF_SHA512:
pub, ok := pk.(*shortKEMPubKey)
return ok && k == pub.scheme.id && pub.Validate()
case KEM_X25519_HKDF_SHA256, KEM_X448_HKDF_SHA512:
pub, ok := pk.(*xKEMPubKey)
return ok && k == pub.scheme.id && pub.Validate()
default:
panic(ErrInvalidKEM)
}
}

func (k KEM) validatePrivateKey(sk kem.PrivateKey) bool {
switch k {
case KEM_P256_HKDF_SHA256, KEM_P384_HKDF_SHA384, KEM_P521_HKDF_SHA512:
priv, ok := sk.(*shortKEMPrivKey)
return ok && k == priv.scheme.id && priv.Validate()
case KEM_X25519_HKDF_SHA256, KEM_X448_HKDF_SHA512:
priv, ok := sk.(*xKEMPrivKey)
return ok && k == priv.scheme.id && priv.Validate()
case KEM_X25519_KYBER768_DRAFT00:
return hybridkemX25519Kyber768
default:
panic(ErrInvalidKEM)
}
Expand Down Expand Up @@ -243,36 +224,43 @@ func (a AEAD) CipherLen(mLen uint) uint {
var (
dhkemp256hkdfsha256, dhkemp384hkdfsha384, dhkemp521hkdfsha512 shortKEM
dhkemx25519hkdfsha256, dhkemx448hkdfsha512 xKEM
hybridkemX25519Kyber768 hybridKEM
)

func init() {
dhkemp256hkdfsha256.Curve = elliptic.P256()
dhkemp256hkdfsha256.kemBase.id = KEM_P256_HKDF_SHA256
dhkemp256hkdfsha256.kemBase.name = "HPKE_KEM_P256_HKDF_SHA256"
dhkemp256hkdfsha256.kemBase.Hash = crypto.SHA256
dhkemp256hkdfsha256.kemBase.dhKEM = dhkemp256hkdfsha256
dhkemp256hkdfsha256.dhKemBase.id = KEM_P256_HKDF_SHA256
dhkemp256hkdfsha256.dhKemBase.name = "HPKE_KEM_P256_HKDF_SHA256"
dhkemp256hkdfsha256.dhKemBase.Hash = crypto.SHA256
dhkemp256hkdfsha256.dhKemBase.dhKEM = dhkemp256hkdfsha256

dhkemp384hkdfsha384.Curve = p384.P384()
dhkemp384hkdfsha384.kemBase.id = KEM_P384_HKDF_SHA384
dhkemp384hkdfsha384.kemBase.name = "HPKE_KEM_P384_HKDF_SHA384"
dhkemp384hkdfsha384.kemBase.Hash = crypto.SHA384
dhkemp384hkdfsha384.kemBase.dhKEM = dhkemp384hkdfsha384
dhkemp384hkdfsha384.dhKemBase.id = KEM_P384_HKDF_SHA384
dhkemp384hkdfsha384.dhKemBase.name = "HPKE_KEM_P384_HKDF_SHA384"
dhkemp384hkdfsha384.dhKemBase.Hash = crypto.SHA384
dhkemp384hkdfsha384.dhKemBase.dhKEM = dhkemp384hkdfsha384

dhkemp521hkdfsha512.Curve = elliptic.P521()
dhkemp521hkdfsha512.kemBase.id = KEM_P521_HKDF_SHA512
dhkemp521hkdfsha512.kemBase.name = "HPKE_KEM_P521_HKDF_SHA512"
dhkemp521hkdfsha512.kemBase.Hash = crypto.SHA512
dhkemp521hkdfsha512.kemBase.dhKEM = dhkemp521hkdfsha512
dhkemp521hkdfsha512.dhKemBase.id = KEM_P521_HKDF_SHA512
dhkemp521hkdfsha512.dhKemBase.name = "HPKE_KEM_P521_HKDF_SHA512"
dhkemp521hkdfsha512.dhKemBase.Hash = crypto.SHA512
dhkemp521hkdfsha512.dhKemBase.dhKEM = dhkemp521hkdfsha512

dhkemx25519hkdfsha256.size = x25519.Size
dhkemx25519hkdfsha256.kemBase.id = KEM_X25519_HKDF_SHA256
dhkemx25519hkdfsha256.kemBase.name = "HPKE_KEM_X25519_HKDF_SHA256"
dhkemx25519hkdfsha256.kemBase.Hash = crypto.SHA256
dhkemx25519hkdfsha256.kemBase.dhKEM = dhkemx25519hkdfsha256
dhkemx25519hkdfsha256.dhKemBase.id = KEM_X25519_HKDF_SHA256
dhkemx25519hkdfsha256.dhKemBase.name = "HPKE_KEM_X25519_HKDF_SHA256"
dhkemx25519hkdfsha256.dhKemBase.Hash = crypto.SHA256
dhkemx25519hkdfsha256.dhKemBase.dhKEM = dhkemx25519hkdfsha256

dhkemx448hkdfsha512.size = x448.Size
dhkemx448hkdfsha512.kemBase.id = KEM_X448_HKDF_SHA512
dhkemx448hkdfsha512.kemBase.name = "HPKE_KEM_X448_HKDF_SHA512"
dhkemx448hkdfsha512.kemBase.Hash = crypto.SHA512
dhkemx448hkdfsha512.kemBase.dhKEM = dhkemx448hkdfsha512
dhkemx448hkdfsha512.dhKemBase.id = KEM_X448_HKDF_SHA512
dhkemx448hkdfsha512.dhKemBase.name = "HPKE_KEM_X448_HKDF_SHA512"
dhkemx448hkdfsha512.dhKemBase.Hash = crypto.SHA512
dhkemx448hkdfsha512.dhKemBase.dhKEM = dhkemx448hkdfsha512

hybridkemX25519Kyber768.kemBase.id = KEM_X25519_KYBER768_DRAFT00
hybridkemX25519Kyber768.kemBase.name = "HPKE_KEM_X25519_KYBER768_HKDF_SHA256"
hybridkemX25519Kyber768.kemBase.Hash = crypto.SHA256
hybridkemX25519Kyber768.kemA = dhkemx25519hkdfsha256
hybridkemX25519Kyber768.kemB = kyber768.Scheme()
}
26 changes: 1 addition & 25 deletions hpke/hpke.go
Expand Up @@ -105,10 +105,6 @@ type Sender struct {

// NewSender creates a Sender with knowledge of the receiver's public-key.
func (suite Suite) NewSender(pkR kem.PublicKey, info []byte) (*Sender, error) {
if !suite.kemID.validatePublicKey(pkR) {
return nil, ErrInvalidKEMPublicKey
}

return &Sender{
state: state{Suite: suite, info: info},
pkR: pkR,
Expand All @@ -127,10 +123,6 @@ func (s *Sender) Setup(rnd io.Reader) (enc []byte, seal Sealer, err error) {
func (s *Sender) SetupAuth(rnd io.Reader, skS kem.PrivateKey) (
enc []byte, seal Sealer, err error,
) {
if !s.kemID.validatePrivateKey(skS) {
return nil, nil, ErrInvalidKEMPrivateKey
}

s.modeID = modeAuth
s.state.skS = skS
return s.allSetup(rnd)
Expand All @@ -152,10 +144,6 @@ func (s *Sender) SetupPSK(rnd io.Reader, psk, pskID []byte) (
func (s *Sender) SetupAuthPSK(rnd io.Reader, skS kem.PrivateKey, psk, pskID []byte) (
enc []byte, seal Sealer, err error,
) {
if !s.kemID.validatePrivateKey(skS) {
return nil, nil, ErrInvalidKEMPrivateKey
}

s.modeID = modeAuthPSK
s.state.skS = skS
s.state.psk = psk
Expand All @@ -174,10 +162,6 @@ type Receiver struct {
func (suite Suite) NewReceiver(skR kem.PrivateKey, info []byte) (
*Receiver, error,
) {
if !suite.kemID.validatePrivateKey(skR) {
return nil, ErrInvalidKEMPrivateKey
}

return &Receiver{state: state{Suite: suite, info: info}, skR: skR}, nil
}

Expand All @@ -192,10 +176,6 @@ func (r *Receiver) Setup(enc []byte) (Opener, error) {
// 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) {
if !r.kemID.validatePublicKey(pkS) {
return nil, ErrInvalidKEMPublicKey
}

r.modeID = modeAuth
r.enc = enc
r.state.pkS = pkS
Expand All @@ -219,10 +199,6 @@ func (r *Receiver) SetupPSK(enc, psk, pskID []byte) (Opener, error) {
func (r *Receiver) SetupAuthPSK(
enc, psk, pskID []byte, pkS kem.PublicKey,
) (Opener, error) {
if !r.kemID.validatePublicKey(pkS) {
return nil, ErrInvalidKEMPublicKey
}

r.modeID = modeAuthPSK
r.enc = enc
r.state.psk = psk
Expand All @@ -237,7 +213,7 @@ func (s *Sender) allSetup(rnd io.Reader) ([]byte, Sealer, error) {
if rnd == nil {
rnd = rand.Reader
}
seed := make([]byte, scheme.SeedSize())
seed := make([]byte, scheme.EncapsulationSeedSize())
_, err := io.ReadFull(rnd, seed)
if err != nil {
return nil, nil, err
Expand Down
1 change: 1 addition & 0 deletions hpke/hybrid-x25119-kyber768-test-vectors.json

Large diffs are not rendered by default.

0 comments on commit 161705b

Please sign in to comment.