main
Raw Download raw file
  1// Package ecc implements a generic interface for ECDH, ECDSA, and EdDSA.
  2package ecc
  3
  4import (
  5	"crypto/subtle"
  6	"io"
  7
  8	"github.com/ProtonMail/go-crypto/openpgp/errors"
  9	x25519lib "github.com/cloudflare/circl/dh/x25519"
 10)
 11
 12type curve25519 struct{}
 13
 14func NewCurve25519() *curve25519 {
 15	return &curve25519{}
 16}
 17
 18func (c *curve25519) GetCurveName() string {
 19	return "curve25519"
 20}
 21
 22// MarshalBytePoint encodes the public point from native format, adding the prefix.
 23// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
 24func (c *curve25519) MarshalBytePoint(point []byte) []byte {
 25	return append([]byte{0x40}, point...)
 26}
 27
 28// UnmarshalBytePoint decodes the public point to native format, removing the prefix.
 29// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
 30func (c *curve25519) UnmarshalBytePoint(point []byte) []byte {
 31	if len(point) != x25519lib.Size+1 {
 32		return nil
 33	}
 34
 35	// Remove prefix
 36	return point[1:]
 37}
 38
 39// MarshalByteSecret encodes the secret scalar from native format.
 40// Note that the EC secret scalar differs from the definition of public keys in
 41// [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
 42// more uniform with how big integers are represented in OpenPGP, and (2) the
 43// leading zeros are truncated.
 44// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1
 45// Note that leading zero bytes are stripped later when encoding as an MPI.
 46func (c *curve25519) MarshalByteSecret(secret []byte) []byte {
 47	d := make([]byte, x25519lib.Size)
 48	copyReversed(d, secret)
 49
 50	// The following ensures that the private key is a number of the form
 51	// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of
 52	// the curve.
 53	//
 54	// This masking is done internally in the underlying lib and so is unnecessary
 55	// for security, but OpenPGP implementations require that private keys be
 56	// pre-masked.
 57	d[0] &= 127
 58	d[0] |= 64
 59	d[31] &= 248
 60
 61	return d
 62}
 63
 64// UnmarshalByteSecret decodes the secret scalar from native format.
 65// Note that the EC secret scalar differs from the definition of public keys in
 66// [Curve25519] in two ways: (1) the byte-ordering is big-endian, which is
 67// more uniform with how big integers are represented in OpenPGP, and (2) the
 68// leading zeros are truncated.
 69// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.1
 70func (c *curve25519) UnmarshalByteSecret(d []byte) []byte {
 71	if len(d) > x25519lib.Size {
 72		return nil
 73	}
 74
 75	// Ensure truncated leading bytes are re-added
 76	secret := make([]byte, x25519lib.Size)
 77	copyReversed(secret, d)
 78
 79	return secret
 80}
 81
 82// generateKeyPairBytes Generates a private-public key-pair.
 83// 'priv' is a private key; a little-endian scalar belonging to the set
 84// 2^{254} + 8 * [0, 2^{251}), in order to avoid the small subgroup of the
 85// curve. 'pub' is simply 'priv' * G where G is the base point.
 86// See https://cr.yp.to/ecdh.html and RFC7748, sec 5.
 87func (c *curve25519) generateKeyPairBytes(rand io.Reader) (priv, pub x25519lib.Key, err error) {
 88	_, err = io.ReadFull(rand, priv[:])
 89	if err != nil {
 90		return
 91	}
 92
 93	x25519lib.KeyGen(&pub, &priv)
 94	return
 95}
 96
 97func (c *curve25519) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) {
 98	priv, pub, err := c.generateKeyPairBytes(rand)
 99	if err != nil {
100		return
101	}
102
103	return pub[:], priv[:], nil
104}
105
106func (c *genericCurve) MaskSecret(secret []byte) []byte {
107	return secret
108}
109
110func (c *curve25519) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
111	// RFC6637 §8: "Generate an ephemeral key pair {v, V=vG}"
112	// ephemeralPrivate corresponds to `v`.
113	// ephemeralPublic corresponds to `V`.
114	ephemeralPrivate, ephemeralPublic, err := c.generateKeyPairBytes(rand)
115	if err != nil {
116		return nil, nil, err
117	}
118
119	// RFC6637 §8: "Obtain the authenticated recipient public key R"
120	// pubKey corresponds to `R`.
121	var pubKey x25519lib.Key
122	copy(pubKey[:], point)
123
124	// RFC6637 §8: "Compute the shared point S = vR"
125	//	"VB = convert point V to the octet string"
126	// sharedPoint corresponds to `VB`.
127	var sharedPoint x25519lib.Key
128	x25519lib.Shared(&sharedPoint, &ephemeralPrivate, &pubKey)
129
130	return ephemeralPublic[:], sharedPoint[:], nil
131}
132
133func (c *curve25519) Decaps(vsG, secret []byte) (sharedSecret []byte, err error) {
134	var ephemeralPublic, decodedPrivate, sharedPoint x25519lib.Key
135	// RFC6637 §8: "The decryption is the inverse of the method given."
136	// All quoted descriptions in comments below describe encryption, and
137	// the reverse is performed.
138	// vsG corresponds to `VB` in RFC6637 §8 .
139
140	// RFC6637 §8: "VB = convert point V to the octet string"
141	copy(ephemeralPublic[:], vsG)
142
143	// decodedPrivate corresponds to `r` in RFC6637 §8 .
144	copy(decodedPrivate[:], secret)
145
146	// RFC6637 §8: "Note that the recipient obtains the shared secret by calculating
147	//   S = rV = rvG, where (r,R) is the recipient's key pair."
148	// sharedPoint corresponds to `S`.
149	x25519lib.Shared(&sharedPoint, &decodedPrivate, &ephemeralPublic)
150
151	return sharedPoint[:], nil
152}
153
154func (c *curve25519) ValidateECDH(point []byte, secret []byte) (err error) {
155	var pk, sk x25519lib.Key
156	copy(sk[:], secret)
157	x25519lib.KeyGen(&pk, &sk)
158
159	if subtle.ConstantTimeCompare(point, pk[:]) == 0 {
160		return errors.KeyInvalidError("ecc: invalid curve25519 public point")
161	}
162
163	return nil
164}
165
166func copyReversed(out []byte, in []byte) {
167	l := len(in)
168	for i := 0; i < l; i++ {
169		out[i] = in[l-i-1]
170	}
171}