1// Copyright 2020 The BoringSSL Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package hpke 16 17import ( 18 "crypto" 19 "crypto/hkdf" 20 "crypto/rand" 21 22 "golang.org/x/crypto/curve25519" 23) 24 25const ( 26 versionLabel string = "HPKE-v1" 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, versionLabel...) 44 labeledIKM = append(labeledIKM, suiteID...) 45 labeledIKM = append(labeledIKM, label...) 46 labeledIKM = append(labeledIKM, ikm...) 47 ret, err := hkdf.Extract(kdfHash.New, labeledIKM, salt) 48 if err != nil { 49 panic(err) 50 } 51 return ret 52} 53 54func labeledExpand(kdfHash crypto.Hash, prk, suiteID, label, info []byte, length int) []byte { 55 lengthU16 := uint16(length) 56 if int(lengthU16) != length { 57 panic("length must be a valid uint16 value") 58 } 59 60 var labeledInfo []byte 61 labeledInfo = appendBigEndianUint16(labeledInfo, lengthU16) 62 labeledInfo = append(labeledInfo, versionLabel...) 63 labeledInfo = append(labeledInfo, suiteID...) 64 labeledInfo = append(labeledInfo, label...) 65 labeledInfo = append(labeledInfo, info...) 66 67 key, err := hkdf.Expand(kdfHash.New, prk, string(labeledInfo), length) 68 if err != nil { 69 panic(err) 70 } 71 return key 72} 73 74// GenerateKeyPairX25519 generates a random X25519 key pair. 75func GenerateKeyPairX25519() (publicKey, secretKeyOut []byte, err error) { 76 // Generate a new private key. 77 var secretKey [curve25519.ScalarSize]byte 78 _, err = rand.Read(secretKey[:]) 79 if err != nil { 80 return 81 } 82 // Compute the corresponding public key. 83 publicKey, err = curve25519.X25519(secretKey[:], curve25519.Basepoint) 84 if err != nil { 85 return 86 } 87 return publicKey, secretKey[:], nil 88} 89 90// x25519Encap returns an ephemeral, fixed-length symmetric key |sharedSecret| 91// and a fixed-length encapsulation of that key |enc| that can be decapsulated 92// by the receiver with the secret key corresponding to |publicKeyR|. 93// Internally, |keygenOptional| is used to generate an ephemeral keypair. If 94// |keygenOptional| is nil, |GenerateKeyPairX25519| will be substituted. 95func x25519Encap(publicKeyR []byte, keygen GenerateKeyPairFunc) ([]byte, []byte, error) { 96 if keygen == nil { 97 keygen = GenerateKeyPairX25519 98 } 99 publicKeyEphem, secretKeyEphem, err := keygen() 100 if err != nil { 101 return nil, nil, err 102 } 103 dh, err := curve25519.X25519(secretKeyEphem, publicKeyR) 104 if err != nil { 105 return nil, nil, err 106 } 107 sharedSecret := extractAndExpand(dh, publicKeyEphem, publicKeyR) 108 return sharedSecret, publicKeyEphem, nil 109} 110 111// x25519Decap uses the receiver's secret key |secretKeyR| to recover the 112// ephemeral symmetric key contained in |enc|. 113func x25519Decap(enc, secretKeyR []byte) ([]byte, error) { 114 dh, err := curve25519.X25519(secretKeyR, enc) 115 if err != nil { 116 return nil, err 117 } 118 // For simplicity, we recompute the receiver's public key. A production 119 // implementation of HPKE should incorporate it into the receiver key 120 // and halve the number of point multiplications needed. 121 publicKeyR, err := curve25519.X25519(secretKeyR, curve25519.Basepoint) 122 if err != nil { 123 return nil, err 124 } 125 return extractAndExpand(dh, enc, publicKeyR[:]), nil 126} 127 128func extractAndExpand(dh, enc, publicKeyR []byte) []byte { 129 var kemContext []byte 130 kemContext = append(kemContext, enc...) 131 kemContext = append(kemContext, publicKeyR...) 132 133 suite := []byte("KEM") 134 suite = appendBigEndianUint16(suite, X25519WithHKDFSHA256) 135 136 kdfHash := getKDFHash(HKDFSHA256) 137 prk := labeledExtract(kdfHash, nil, suite, []byte("eae_prk"), dh) 138 return labeledExpand(kdfHash, prk, suite, []byte("shared_secret"), kemContext, 32) 139} 140