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