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