package hpke
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/ecdh"
"crypto/rand"
"encoding/binary"
"errors"
"math/bits"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
var testingOnlyGenerateKey func () (*ecdh .PrivateKey , error )
type hkdfKDF struct {
hash crypto .Hash
}
func (kdf *hkdfKDF ) LabeledExtract (suiteID []byte , salt []byte , label string , inputKey []byte ) []byte {
labeledIKM := make ([]byte , 0 , 7 +len (suiteID )+len (label )+len (inputKey ))
labeledIKM = append (labeledIKM , []byte ("HPKE-v1" )...)
labeledIKM = append (labeledIKM , suiteID ...)
labeledIKM = append (labeledIKM , label ...)
labeledIKM = append (labeledIKM , inputKey ...)
return hkdf .Extract (kdf .hash .New , labeledIKM , salt )
}
func (kdf *hkdfKDF ) LabeledExpand (suiteID []byte , randomKey []byte , label string , info []byte , length uint16 ) []byte {
labeledInfo := make ([]byte , 0 , 2 +7 +len (suiteID )+len (label )+len (info ))
labeledInfo = binary .BigEndian .AppendUint16 (labeledInfo , length )
labeledInfo = append (labeledInfo , []byte ("HPKE-v1" )...)
labeledInfo = append (labeledInfo , suiteID ...)
labeledInfo = append (labeledInfo , label ...)
labeledInfo = append (labeledInfo , info ...)
out := make ([]byte , length )
n , err := hkdf .Expand (kdf .hash .New , randomKey , labeledInfo ).Read (out )
if err != nil || n != int (length ) {
panic ("hpke: LabeledExpand failed unexpectedly" )
}
return out
}
type dhKEM struct {
dh ecdh .Curve
kdf hkdfKDF
suiteID []byte
nSecret uint16
}
var SupportedKEMs = map [uint16 ]struct {
curve ecdh .Curve
hash crypto .Hash
nSecret uint16
}{
0x0020 : {ecdh .X25519 (), crypto .SHA256 , 32 },
}
func newDHKem(kemID uint16 ) (*dhKEM , error ) {
suite , ok := SupportedKEMs [kemID ]
if !ok {
return nil , errors .New ("unsupported suite ID" )
}
return &dhKEM {
dh : suite .curve ,
kdf : hkdfKDF {suite .hash },
suiteID : binary .BigEndian .AppendUint16 ([]byte ("KEM" ), kemID ),
nSecret : suite .nSecret ,
}, nil
}
func (dh *dhKEM ) ExtractAndExpand (dhKey , kemContext []byte ) []byte {
eaePRK := dh .kdf .LabeledExtract (dh .suiteID [:], nil , "eae_prk" , dhKey )
return dh .kdf .LabeledExpand (dh .suiteID [:], eaePRK , "shared_secret" , kemContext , dh .nSecret )
}
func (dh *dhKEM ) Encap (pubRecipient *ecdh .PublicKey ) (sharedSecret []byte , encapPub []byte , err error ) {
var privEph *ecdh .PrivateKey
if testingOnlyGenerateKey != nil {
privEph , err = testingOnlyGenerateKey ()
} else {
privEph , err = dh .dh .GenerateKey (rand .Reader )
}
if err != nil {
return nil , nil , err
}
dhVal , err := privEph .ECDH (pubRecipient )
if err != nil {
return nil , nil , err
}
encPubEph := privEph .PublicKey ().Bytes ()
encPubRecip := pubRecipient .Bytes ()
kemContext := append (encPubEph , encPubRecip ...)
return dh .ExtractAndExpand (dhVal , kemContext ), encPubEph , nil
}
type Sender struct {
aead cipher .AEAD
kem *dhKEM
sharedSecret []byte
suiteID []byte
key []byte
baseNonce []byte
exporterSecret []byte
seqNum uint128
}
var aesGCMNew = func (key []byte ) (cipher .AEAD , error ) {
block , err := aes .NewCipher (key )
if err != nil {
return nil , err
}
return cipher .NewGCM (block )
}
var SupportedAEADs = map [uint16 ]struct {
keySize int
nonceSize int
aead func ([]byte ) (cipher .AEAD , error )
}{
0x0001 : {keySize : 16 , nonceSize : 12 , aead : aesGCMNew },
0x0002 : {keySize : 32 , nonceSize : 12 , aead : aesGCMNew },
0x0003 : {keySize : chacha20poly1305 .KeySize , nonceSize : chacha20poly1305 .NonceSize , aead : chacha20poly1305 .New },
}
var SupportedKDFs = map [uint16 ]func () *hkdfKDF {
0x0001 : func () *hkdfKDF { return &hkdfKDF {crypto .SHA256 } },
}
func SetupSender (kemID , kdfID , aeadID uint16 , pub crypto .PublicKey , info []byte ) ([]byte , *Sender , error ) {
suiteID := SuiteID (kemID , kdfID , aeadID )
kem , err := newDHKem (kemID )
if err != nil {
return nil , nil , err
}
pubRecipient , ok := pub .(*ecdh .PublicKey )
if !ok {
return nil , nil , errors .New ("incorrect public key type" )
}
sharedSecret , encapsulatedKey , err := kem .Encap (pubRecipient )
if err != nil {
return nil , nil , err
}
kdfInit , ok := SupportedKDFs [kdfID ]
if !ok {
return nil , nil , errors .New ("unsupported KDF id" )
}
kdf := kdfInit ()
aeadInfo , ok := SupportedAEADs [aeadID ]
if !ok {
return nil , nil , errors .New ("unsupported AEAD id" )
}
pskIDHash := kdf .LabeledExtract (suiteID , nil , "psk_id_hash" , nil )
infoHash := kdf .LabeledExtract (suiteID , nil , "info_hash" , info )
ksContext := append ([]byte {0 }, pskIDHash ...)
ksContext = append (ksContext , infoHash ...)
secret := kdf .LabeledExtract (suiteID , sharedSecret , "secret" , nil )
key := kdf .LabeledExpand (suiteID , secret , "key" , ksContext , uint16 (aeadInfo .keySize ) )
baseNonce := kdf .LabeledExpand (suiteID , secret , "base_nonce" , ksContext , uint16 (aeadInfo .nonceSize ) )
exporterSecret := kdf .LabeledExpand (suiteID , secret , "exp" , ksContext , uint16 (kdf .hash .Size ()) )
aead , err := aeadInfo .aead (key )
if err != nil {
return nil , nil , err
}
return encapsulatedKey , &Sender {
kem : kem ,
aead : aead ,
sharedSecret : sharedSecret ,
suiteID : suiteID ,
key : key ,
baseNonce : baseNonce ,
exporterSecret : exporterSecret ,
}, nil
}
func (s *Sender ) nextNonce () []byte {
nonce := s .seqNum .bytes ()[16 -s .aead .NonceSize ():]
for i := range s .baseNonce {
nonce [i ] ^= s .baseNonce [i ]
}
if s .seqNum .bitLen () >= (s .aead .NonceSize ()*8 )-1 {
panic ("message limit reached" )
}
s .seqNum = s .seqNum .addOne ()
return nonce
}
func (s *Sender ) Seal (aad , plaintext []byte ) ([]byte , error ) {
ciphertext := s .aead .Seal (nil , s .nextNonce (), plaintext , aad )
return ciphertext , nil
}
func SuiteID (kemID , kdfID , aeadID uint16 ) []byte {
suiteID := make ([]byte , 0 , 4 +2 +2 +2 )
suiteID = append (suiteID , []byte ("HPKE" )...)
suiteID = binary .BigEndian .AppendUint16 (suiteID , kemID )
suiteID = binary .BigEndian .AppendUint16 (suiteID , kdfID )
suiteID = binary .BigEndian .AppendUint16 (suiteID , aeadID )
return suiteID
}
func ParseHPKEPublicKey (kemID uint16 , bytes []byte ) (*ecdh .PublicKey , error ) {
kemInfo , ok := SupportedKEMs [kemID ]
if !ok {
return nil , errors .New ("unsupported KEM id" )
}
return kemInfo .curve .NewPublicKey (bytes )
}
type uint128 struct {
hi, lo uint64
}
func (u uint128 ) addOne () uint128 {
lo , carry := bits .Add64 (u .lo , 1 , 0 )
return uint128 {u .hi + carry , lo }
}
func (u uint128 ) bitLen () int {
return bits .Len64 (u .hi ) + bits .Len64 (u .lo )
}
func (u uint128 ) bytes () []byte {
b := make ([]byte , 16 )
binary .BigEndian .PutUint64 (b [0 :], u .hi )
binary .BigEndian .PutUint64 (b [8 :], u .lo )
return b
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .