algs.go raw

   1  package hpke
   2  
   3  import (
   4  	"crypto"
   5  	"crypto/aes"
   6  	"crypto/cipher"
   7  	"crypto/ecdh"
   8  	_ "crypto/sha256" // Linking sha256.
   9  	_ "crypto/sha512" // Linking sha512.
  10  	"fmt"
  11  	"hash"
  12  	"io"
  13  
  14  	"github.com/cloudflare/circl/dh/x25519"
  15  	"github.com/cloudflare/circl/dh/x448"
  16  	"github.com/cloudflare/circl/kem"
  17  	"github.com/cloudflare/circl/kem/kyber/kyber768"
  18  	"github.com/cloudflare/circl/kem/xwing"
  19  	"golang.org/x/crypto/chacha20poly1305"
  20  	"golang.org/x/crypto/hkdf"
  21  )
  22  
  23  type KEM uint16
  24  
  25  //nolint:golint,stylecheck
  26  const (
  27  	// KEM_P256_HKDF_SHA256 is a KEM using P256 curve and HKDF with SHA-256.
  28  	KEM_P256_HKDF_SHA256 KEM = 0x10
  29  	// KEM_P384_HKDF_SHA384 is a KEM using P384 curve and HKDF with SHA-384.
  30  	KEM_P384_HKDF_SHA384 KEM = 0x11
  31  	// KEM_P521_HKDF_SHA512 is a KEM using P521 curve and HKDF with SHA-512.
  32  	KEM_P521_HKDF_SHA512 KEM = 0x12
  33  	// KEM_X25519_HKDF_SHA256 is a KEM using X25519 Diffie-Hellman function
  34  	// and HKDF with SHA-256.
  35  	KEM_X25519_HKDF_SHA256 KEM = 0x20
  36  	// KEM_X448_HKDF_SHA512 is a KEM using X448 Diffie-Hellman function and
  37  	// HKDF with SHA-512.
  38  	KEM_X448_HKDF_SHA512 KEM = 0x21
  39  	// KEM_X25519_KYBER768_DRAFT00 is a hybrid KEM built on DHKEM(X25519, HKDF-SHA256)
  40  	// and Kyber768Draft00
  41  	KEM_X25519_KYBER768_DRAFT00 KEM = 0x30
  42  	// KEM_XWING is a hybrid KEM using X25519 and ML-KEM-768.
  43  	KEM_XWING KEM = 0x647a
  44  )
  45  
  46  // IsValid returns true if the KEM identifier is supported by the HPKE package.
  47  func (k KEM) IsValid() bool {
  48  	switch k {
  49  	case KEM_P256_HKDF_SHA256,
  50  		KEM_P384_HKDF_SHA384,
  51  		KEM_P521_HKDF_SHA512,
  52  		KEM_X25519_HKDF_SHA256,
  53  		KEM_X448_HKDF_SHA512,
  54  		KEM_X25519_KYBER768_DRAFT00,
  55  		KEM_XWING:
  56  		return true
  57  	default:
  58  		return false
  59  	}
  60  }
  61  
  62  // Scheme returns an instance of a KEM that supports authentication. Panics if
  63  // the KEM identifier is invalid.
  64  func (k KEM) Scheme() kem.Scheme {
  65  	switch k {
  66  	case KEM_P256_HKDF_SHA256:
  67  		return dhkemp256hkdfsha256
  68  	case KEM_P384_HKDF_SHA384:
  69  		return dhkemp384hkdfsha384
  70  	case KEM_P521_HKDF_SHA512:
  71  		return dhkemp521hkdfsha512
  72  	case KEM_X25519_HKDF_SHA256:
  73  		return dhkemx25519hkdfsha256
  74  	case KEM_X448_HKDF_SHA512:
  75  		return dhkemx448hkdfsha512
  76  	case KEM_X25519_KYBER768_DRAFT00:
  77  		return hybridkemX25519Kyber768
  78  	case KEM_XWING:
  79  		return kemXwing
  80  	default:
  81  		panic(ErrInvalidKEM)
  82  	}
  83  }
  84  
  85  type KDF uint16
  86  
  87  //nolint:golint,stylecheck
  88  const (
  89  	// KDF_HKDF_SHA256 is a KDF using HKDF with SHA-256.
  90  	KDF_HKDF_SHA256 KDF = 0x01
  91  	// KDF_HKDF_SHA384 is a KDF using HKDF with SHA-384.
  92  	KDF_HKDF_SHA384 KDF = 0x02
  93  	// KDF_HKDF_SHA512 is a KDF using HKDF with SHA-512.
  94  	KDF_HKDF_SHA512 KDF = 0x03
  95  )
  96  
  97  func (k KDF) IsValid() bool {
  98  	switch k {
  99  	case KDF_HKDF_SHA256,
 100  		KDF_HKDF_SHA384,
 101  		KDF_HKDF_SHA512:
 102  		return true
 103  	default:
 104  		return false
 105  	}
 106  }
 107  
 108  // ExtractSize returns the size (in bytes) of the pseudorandom key produced
 109  // by KDF.Extract.
 110  func (k KDF) ExtractSize() int {
 111  	switch k {
 112  	case KDF_HKDF_SHA256:
 113  		return crypto.SHA256.Size()
 114  	case KDF_HKDF_SHA384:
 115  		return crypto.SHA384.Size()
 116  	case KDF_HKDF_SHA512:
 117  		return crypto.SHA512.Size()
 118  	default:
 119  		panic(ErrInvalidKDF)
 120  	}
 121  }
 122  
 123  // Extract derives a pseudorandom key from a high-entropy, secret input and a
 124  // salt. The size of the output is determined by KDF.ExtractSize.
 125  func (k KDF) Extract(secret, salt []byte) (pseudorandomKey []byte) {
 126  	return hkdf.Extract(k.hash(), secret, salt)
 127  }
 128  
 129  // Expand derives a variable length pseudorandom string from a pseudorandom key
 130  // and an information string. Panics if the pseudorandom key is less
 131  // than N bytes, or if the output length is greater than 255*N bytes,
 132  // where N is the size returned by KDF.Extract function.
 133  func (k KDF) Expand(pseudorandomKey, info []byte, outputLen uint) []byte {
 134  	extractSize := k.ExtractSize()
 135  	if len(pseudorandomKey) < extractSize {
 136  		panic(fmt.Errorf("pseudorandom key must be %v bytes", extractSize))
 137  	}
 138  	maxLength := uint(255 * extractSize)
 139  	if outputLen > maxLength {
 140  		panic(fmt.Errorf("output length must be less than %v bytes", maxLength))
 141  	}
 142  	output := make([]byte, outputLen)
 143  	rd := hkdf.Expand(k.hash(), pseudorandomKey[:extractSize], info)
 144  	_, err := io.ReadFull(rd, output)
 145  	if err != nil {
 146  		panic(err)
 147  	}
 148  	return output
 149  }
 150  
 151  func (k KDF) hash() func() hash.Hash {
 152  	switch k {
 153  	case KDF_HKDF_SHA256:
 154  		return crypto.SHA256.New
 155  	case KDF_HKDF_SHA384:
 156  		return crypto.SHA384.New
 157  	case KDF_HKDF_SHA512:
 158  		return crypto.SHA512.New
 159  	default:
 160  		panic(ErrInvalidKDF)
 161  	}
 162  }
 163  
 164  type AEAD uint16
 165  
 166  //nolint:golint,stylecheck
 167  const (
 168  	// AEAD_AES128GCM is AES-128 block cipher in Galois Counter Mode (GCM).
 169  	AEAD_AES128GCM AEAD = 0x01
 170  	// AEAD_AES256GCM is AES-256 block cipher in Galois Counter Mode (GCM).
 171  	AEAD_AES256GCM AEAD = 0x02
 172  	// AEAD_ChaCha20Poly1305 is ChaCha20 stream cipher and Poly1305 MAC.
 173  	AEAD_ChaCha20Poly1305 AEAD = 0x03
 174  )
 175  
 176  // New instantiates an AEAD cipher from the identifier, returns an error if the
 177  // identifier is not known.
 178  func (a AEAD) New(key []byte) (cipher.AEAD, error) {
 179  	switch a {
 180  	case AEAD_AES128GCM, AEAD_AES256GCM:
 181  		block, err := aes.NewCipher(key)
 182  		if err != nil {
 183  			return nil, err
 184  		}
 185  		return cipher.NewGCM(block)
 186  	case AEAD_ChaCha20Poly1305:
 187  		return chacha20poly1305.New(key)
 188  	default:
 189  		panic(ErrInvalidAEAD)
 190  	}
 191  }
 192  
 193  func (a AEAD) IsValid() bool {
 194  	switch a {
 195  	case AEAD_AES128GCM,
 196  		AEAD_AES256GCM,
 197  		AEAD_ChaCha20Poly1305:
 198  		return true
 199  	default:
 200  		return false
 201  	}
 202  }
 203  
 204  // KeySize returns the size in bytes of the keys used by the AEAD cipher.
 205  func (a AEAD) KeySize() uint {
 206  	switch a {
 207  	case AEAD_AES128GCM:
 208  		return 16
 209  	case AEAD_AES256GCM:
 210  		return 32
 211  	case AEAD_ChaCha20Poly1305:
 212  		return chacha20poly1305.KeySize
 213  	default:
 214  		panic(ErrInvalidAEAD)
 215  	}
 216  }
 217  
 218  // NonceSize returns the size in bytes of the nonce used by the AEAD cipher.
 219  func (a AEAD) NonceSize() uint {
 220  	switch a {
 221  	case AEAD_AES128GCM,
 222  		AEAD_AES256GCM,
 223  		AEAD_ChaCha20Poly1305:
 224  		return 12
 225  	default:
 226  		panic(ErrInvalidAEAD)
 227  	}
 228  }
 229  
 230  // CipherLen returns the length of a ciphertext corresponding to a message of
 231  // length mLen.
 232  func (a AEAD) CipherLen(mLen uint) uint {
 233  	switch a {
 234  	case AEAD_AES128GCM, AEAD_AES256GCM, AEAD_ChaCha20Poly1305:
 235  		return mLen + 16
 236  	default:
 237  		panic(ErrInvalidAEAD)
 238  	}
 239  }
 240  
 241  var (
 242  	dhkemp256hkdfsha256, dhkemp384hkdfsha384, dhkemp521hkdfsha512 shortKEM
 243  	dhkemx25519hkdfsha256, dhkemx448hkdfsha512                    xKEM
 244  	hybridkemX25519Kyber768                                       hybridKEM
 245  	kemXwing                                                      genericNoAuthKEM
 246  )
 247  
 248  func init() {
 249  	dhkemp256hkdfsha256.Curve = ecdh.P256()
 250  	dhkemp256hkdfsha256.dhKemBase.id = KEM_P256_HKDF_SHA256
 251  	dhkemp256hkdfsha256.dhKemBase.name = "HPKE_KEM_P256_HKDF_SHA256"
 252  	dhkemp256hkdfsha256.dhKemBase.Hash = crypto.SHA256
 253  	dhkemp256hkdfsha256.dhKemBase.dhKEM = dhkemp256hkdfsha256
 254  
 255  	dhkemp384hkdfsha384.Curve = ecdh.P384()
 256  	dhkemp384hkdfsha384.dhKemBase.id = KEM_P384_HKDF_SHA384
 257  	dhkemp384hkdfsha384.dhKemBase.name = "HPKE_KEM_P384_HKDF_SHA384"
 258  	dhkemp384hkdfsha384.dhKemBase.Hash = crypto.SHA384
 259  	dhkemp384hkdfsha384.dhKemBase.dhKEM = dhkemp384hkdfsha384
 260  
 261  	dhkemp521hkdfsha512.Curve = ecdh.P521()
 262  	dhkemp521hkdfsha512.dhKemBase.id = KEM_P521_HKDF_SHA512
 263  	dhkemp521hkdfsha512.dhKemBase.name = "HPKE_KEM_P521_HKDF_SHA512"
 264  	dhkemp521hkdfsha512.dhKemBase.Hash = crypto.SHA512
 265  	dhkemp521hkdfsha512.dhKemBase.dhKEM = dhkemp521hkdfsha512
 266  
 267  	dhkemx25519hkdfsha256.size = x25519.Size
 268  	dhkemx25519hkdfsha256.dhKemBase.id = KEM_X25519_HKDF_SHA256
 269  	dhkemx25519hkdfsha256.dhKemBase.name = "HPKE_KEM_X25519_HKDF_SHA256"
 270  	dhkemx25519hkdfsha256.dhKemBase.Hash = crypto.SHA256
 271  	dhkemx25519hkdfsha256.dhKemBase.dhKEM = dhkemx25519hkdfsha256
 272  
 273  	dhkemx448hkdfsha512.size = x448.Size
 274  	dhkemx448hkdfsha512.dhKemBase.id = KEM_X448_HKDF_SHA512
 275  	dhkemx448hkdfsha512.dhKemBase.name = "HPKE_KEM_X448_HKDF_SHA512"
 276  	dhkemx448hkdfsha512.dhKemBase.Hash = crypto.SHA512
 277  	dhkemx448hkdfsha512.dhKemBase.dhKEM = dhkemx448hkdfsha512
 278  
 279  	hybridkemX25519Kyber768.kemBase.id = KEM_X25519_KYBER768_DRAFT00
 280  	hybridkemX25519Kyber768.kemBase.name = "HPKE_KEM_X25519_KYBER768_HKDF_SHA256"
 281  	hybridkemX25519Kyber768.kemBase.Hash = crypto.SHA256
 282  	hybridkemX25519Kyber768.kemA = dhkemx25519hkdfsha256
 283  	hybridkemX25519Kyber768.kemB = kyber768.Scheme()
 284  
 285  	kemXwing.Scheme = xwing.Scheme()
 286  	kemXwing.name = "HPKE_KEM_XWING"
 287  }
 288