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