main
Raw Download raw file
  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}