1// Copyright (c) 2020, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15package hpke 16 17import ( 18 "crypto" 19 "crypto/rand" 20 21 "golang.org/x/crypto/curve25519" 22 "golang.org/x/crypto/hkdf" 23) 24 25const ( 26 rfcLabel string = "HPKE-07" 27) 28 29func getKDFHash(kdfID uint16) crypto.Hash { 30 switch kdfID { 31 case HKDFSHA256: 32 return crypto.SHA256 33 case HKDFSHA384: 34 return crypto.SHA384 35 case HKDFSHA512: 36 return crypto.SHA512 37 } 38 panic("unknown KDF") 39} 40 41func labeledExtract(kdfHash crypto.Hash, salt, suiteID, label, ikm []byte) []byte { 42 var labeledIKM []byte 43 labeledIKM = append(labeledIKM, rfcLabel...) 44 labeledIKM = append(labeledIKM, suiteID...) 45 labeledIKM = append(labeledIKM, label...) 46 labeledIKM = append(labeledIKM, ikm...) 47 return hkdf.Extract(kdfHash.New, labeledIKM, salt) 48} 49 50func labeledExpand(kdfHash crypto.Hash, prk, suiteID, label, info []byte, length int) []byte { 51 lengthU16 := uint16(length) 52 if int(lengthU16) != length { 53 panic("length must be a valid uint16 value") 54 } 55 56 var labeledInfo []byte 57 labeledInfo = appendBigEndianUint16(labeledInfo, lengthU16) 58 labeledInfo = append(labeledInfo, rfcLabel...) 59 labeledInfo = append(labeledInfo, suiteID...) 60 labeledInfo = append(labeledInfo, label...) 61 labeledInfo = append(labeledInfo, info...) 62 63 reader := hkdf.Expand(kdfHash.New, prk, labeledInfo) 64 key := make([]uint8, length) 65 _, err := reader.Read(key) 66 if err != nil { 67 panic("failed to perform HKDF expand operation") 68 } 69 return key 70} 71 72// GenerateKeyPair generates a random key pair. 73func GenerateKeyPair() (publicKey, secretKeyOut []byte, err error) { 74 // Generate a new private key. 75 var secretKey [curve25519.ScalarSize]byte 76 _, err = rand.Read(secretKey[:]) 77 if err != nil { 78 return 79 } 80 // Compute the corresponding public key. 81 publicKey, err = curve25519.X25519(secretKey[:], curve25519.Basepoint) 82 if err != nil { 83 return 84 } 85 return publicKey, secretKey[:], nil 86} 87 88// x25519Encap returns an ephemeral, fixed-length symmetric key |sharedSecret| 89// and a fixed-length encapsulation of that key |enc| that can be decapsulated 90// by the receiver with the secret key corresponding to |publicKeyR|. 91// Internally, |keygenOptional| is used to generate an ephemeral keypair. If 92// |keygenOptional| is nil, |GenerateKeyPair| will be substituted. 93func x25519Encap(publicKeyR []byte, keygen GenerateKeyPairFunc) ([]byte, []byte, error) { 94 if keygen == nil { 95 keygen = GenerateKeyPair 96 } 97 publicKeyEphem, secretKeyEphem, err := keygen() 98 if err != nil { 99 return nil, nil, err 100 } 101 dh, err := curve25519.X25519(secretKeyEphem, publicKeyR) 102 if err != nil { 103 return nil, nil, err 104 } 105 sharedSecret := extractAndExpand(dh, publicKeyEphem, publicKeyR) 106 return sharedSecret, publicKeyEphem, nil 107} 108 109// x25519Decap uses the receiver's secret key |secretKeyR| to recover the 110// ephemeral symmetric key contained in |enc|. 111func x25519Decap(enc, secretKeyR []byte) ([]byte, error) { 112 dh, err := curve25519.X25519(secretKeyR, enc) 113 if err != nil { 114 return nil, err 115 } 116 // For simplicity, we recompute the receiver's public key. A production 117 // implementation of HPKE should incorporate it into the receiver key 118 // and halve the number of point multiplications needed. 119 publicKeyR, err := curve25519.X25519(secretKeyR, curve25519.Basepoint) 120 if err != nil { 121 return nil, err 122 } 123 return extractAndExpand(dh, enc, publicKeyR[:]), nil 124} 125 126func extractAndExpand(dh, enc, publicKeyR []byte) []byte { 127 var kemContext []byte 128 kemContext = append(kemContext, enc...) 129 kemContext = append(kemContext, publicKeyR...) 130 131 suite := []byte("KEM") 132 suite = appendBigEndianUint16(suite, X25519WithHKDFSHA256) 133 134 kdfHash := getKDFHash(HKDFSHA256) 135 prk := labeledExtract(kdfHash, nil, suite, []byte("eae_prk"), dh) 136 return labeledExpand(kdfHash, prk, suite, []byte("shared_secret"), kemContext, 32) 137} 138