main
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}