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