main
1package x25519
2
3import (
4 "crypto/sha256"
5 "crypto/subtle"
6 "io"
7
8 "github.com/ProtonMail/go-crypto/openpgp/aes/keywrap"
9 "github.com/ProtonMail/go-crypto/openpgp/errors"
10 x25519lib "github.com/cloudflare/circl/dh/x25519"
11 "golang.org/x/crypto/hkdf"
12)
13
14const (
15 hkdfInfo = "OpenPGP X25519"
16 aes128KeySize = 16
17 // The size of a public or private key in bytes.
18 KeySize = x25519lib.Size
19)
20
21type PublicKey struct {
22 // Point represents the encoded elliptic curve point of the public key.
23 Point []byte
24}
25
26type PrivateKey struct {
27 PublicKey
28 // Secret represents the secret of the private key.
29 Secret []byte
30}
31
32// NewPrivateKey creates a new empty private key including the public key.
33func NewPrivateKey(key PublicKey) *PrivateKey {
34 return &PrivateKey{
35 PublicKey: key,
36 }
37}
38
39// Validate validates that the provided public key matches the private key.
40func Validate(pk *PrivateKey) (err error) {
41 var expectedPublicKey, privateKey x25519lib.Key
42 subtle.ConstantTimeCopy(1, privateKey[:], pk.Secret)
43 x25519lib.KeyGen(&expectedPublicKey, &privateKey)
44 if subtle.ConstantTimeCompare(expectedPublicKey[:], pk.PublicKey.Point) == 0 {
45 return errors.KeyInvalidError("x25519: invalid key")
46 }
47 return nil
48}
49
50// GenerateKey generates a new x25519 key pair.
51func GenerateKey(rand io.Reader) (*PrivateKey, error) {
52 var privateKey, publicKey x25519lib.Key
53 privateKeyOut := new(PrivateKey)
54 err := generateKey(rand, &privateKey, &publicKey)
55 if err != nil {
56 return nil, err
57 }
58 privateKeyOut.PublicKey.Point = publicKey[:]
59 privateKeyOut.Secret = privateKey[:]
60 return privateKeyOut, nil
61}
62
63func generateKey(rand io.Reader, privateKey *x25519lib.Key, publicKey *x25519lib.Key) error {
64 maxRounds := 10
65 isZero := true
66 for round := 0; isZero; round++ {
67 if round == maxRounds {
68 return errors.InvalidArgumentError("x25519: zero keys only, randomness source might be corrupt")
69 }
70 _, err := io.ReadFull(rand, privateKey[:])
71 if err != nil {
72 return err
73 }
74 isZero = constantTimeIsZero(privateKey[:])
75 }
76 x25519lib.KeyGen(publicKey, privateKey)
77 return nil
78}
79
80// Encrypt encrypts a sessionKey with x25519 according to
81// the OpenPGP crypto refresh specification section 5.1.6. The function assumes that the
82// sessionKey has the correct format and padding according to the specification.
83func Encrypt(rand io.Reader, publicKey *PublicKey, sessionKey []byte) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, err error) {
84 var ephemeralPrivate, ephemeralPublic, staticPublic, shared x25519lib.Key
85 // Check that the input static public key has 32 bytes
86 if len(publicKey.Point) != KeySize {
87 err = errors.KeyInvalidError("x25519: the public key has the wrong size")
88 return
89 }
90 copy(staticPublic[:], publicKey.Point)
91 // Generate ephemeral keyPair
92 err = generateKey(rand, &ephemeralPrivate, &ephemeralPublic)
93 if err != nil {
94 return
95 }
96 // Compute shared key
97 ok := x25519lib.Shared(&shared, &ephemeralPrivate, &staticPublic)
98 if !ok {
99 err = errors.KeyInvalidError("x25519: the public key is a low order point")
100 return
101 }
102 // Derive the encryption key from the shared secret
103 encryptionKey := applyHKDF(ephemeralPublic[:], publicKey.Point[:], shared[:])
104 ephemeralPublicKey = &PublicKey{
105 Point: ephemeralPublic[:],
106 }
107 // Encrypt the sessionKey with aes key wrapping
108 encryptedSessionKey, err = keywrap.Wrap(encryptionKey, sessionKey)
109 return
110}
111
112// Decrypt decrypts a session key stored in ciphertext with the provided x25519
113// private key and ephemeral public key.
114func Decrypt(privateKey *PrivateKey, ephemeralPublicKey *PublicKey, ciphertext []byte) (encodedSessionKey []byte, err error) {
115 var ephemeralPublic, staticPrivate, shared x25519lib.Key
116 // Check that the input ephemeral public key has 32 bytes
117 if len(ephemeralPublicKey.Point) != KeySize {
118 err = errors.KeyInvalidError("x25519: the public key has the wrong size")
119 return
120 }
121 copy(ephemeralPublic[:], ephemeralPublicKey.Point)
122 subtle.ConstantTimeCopy(1, staticPrivate[:], privateKey.Secret)
123 // Compute shared key
124 ok := x25519lib.Shared(&shared, &staticPrivate, &ephemeralPublic)
125 if !ok {
126 err = errors.KeyInvalidError("x25519: the ephemeral public key is a low order point")
127 return
128 }
129 // Derive the encryption key from the shared secret
130 encryptionKey := applyHKDF(ephemeralPublicKey.Point[:], privateKey.PublicKey.Point[:], shared[:])
131 // Decrypt the session key with aes key wrapping
132 encodedSessionKey, err = keywrap.Unwrap(encryptionKey, ciphertext)
133 return
134}
135
136func applyHKDF(ephemeralPublicKey []byte, publicKey []byte, sharedSecret []byte) []byte {
137 inputKey := make([]byte, 3*KeySize)
138 // ephemeral public key | recipient public key | shared secret
139 subtle.ConstantTimeCopy(1, inputKey[:KeySize], ephemeralPublicKey)
140 subtle.ConstantTimeCopy(1, inputKey[KeySize:2*KeySize], publicKey)
141 subtle.ConstantTimeCopy(1, inputKey[2*KeySize:], sharedSecret)
142 hkdfReader := hkdf.New(sha256.New, inputKey, []byte{}, []byte(hkdfInfo))
143 encryptionKey := make([]byte, aes128KeySize)
144 _, _ = io.ReadFull(hkdfReader, encryptionKey)
145 return encryptionKey
146}
147
148func constantTimeIsZero(bytes []byte) bool {
149 isZero := byte(0)
150 for _, b := range bytes {
151 isZero |= b
152 }
153 return isZero == 0
154}
155
156// ENCODING/DECODING ciphertexts:
157
158// EncodeFieldsLength returns the length of the ciphertext encoding
159// given the encrypted session key.
160func EncodedFieldsLength(encryptedSessionKey []byte, v6 bool) int {
161 lenCipherFunction := 0
162 if !v6 {
163 lenCipherFunction = 1
164 }
165 return KeySize + 1 + len(encryptedSessionKey) + lenCipherFunction
166}
167
168// EncodeField encodes x25519 session key encryption fields as
169// ephemeral x25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey
170// and writes it to writer.
171func EncodeFields(writer io.Writer, ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, v6 bool) (err error) {
172 lenAlgorithm := 0
173 if !v6 {
174 lenAlgorithm = 1
175 }
176 if _, err = writer.Write(ephemeralPublicKey.Point); err != nil {
177 return err
178 }
179 if _, err = writer.Write([]byte{byte(len(encryptedSessionKey) + lenAlgorithm)}); err != nil {
180 return err
181 }
182 if !v6 {
183 if _, err = writer.Write([]byte{cipherFunction}); err != nil {
184 return err
185 }
186 }
187 _, err = writer.Write(encryptedSessionKey)
188 return err
189}
190
191// DecodeField decodes a x25519 session key encryption as
192// ephemeral x25519 public key | follow byte length | cipherFunction (v3 only) | encryptedSessionKey.
193func DecodeFields(reader io.Reader, v6 bool) (ephemeralPublicKey *PublicKey, encryptedSessionKey []byte, cipherFunction byte, err error) {
194 var buf [1]byte
195 ephemeralPublicKey = &PublicKey{
196 Point: make([]byte, KeySize),
197 }
198 // 32 octets representing an ephemeral x25519 public key.
199 if _, err = io.ReadFull(reader, ephemeralPublicKey.Point); err != nil {
200 return nil, nil, 0, err
201 }
202 // A one-octet size of the following fields.
203 if _, err = io.ReadFull(reader, buf[:]); err != nil {
204 return nil, nil, 0, err
205 }
206 followingLen := buf[0]
207 // The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet).
208 if !v6 {
209 if _, err = io.ReadFull(reader, buf[:]); err != nil {
210 return nil, nil, 0, err
211 }
212 cipherFunction = buf[0]
213 followingLen -= 1
214 }
215 // The encrypted session key.
216 encryptedSessionKey = make([]byte, followingLen)
217 if _, err = io.ReadFull(reader, encryptedSessionKey); err != nil {
218 return nil, nil, 0, err
219 }
220 return ephemeralPublicKey, encryptedSessionKey, cipherFunction, nil
221}