Skip to content

Commit ee24449

Browse files
qmuntalCopilot
andauthored
Support ECDH with x25519 curve (#327)
* support x25519 * fix ossl 1 * include EVP_PKEY_up_ref in openssl 1 * Update ec.go Co-authored-by: Copilot <[email protected]> * fix mariner and azl3 * skip if curve not supported * fix ossl 1.1.0 --------- Co-authored-by: Copilot <[email protected]>
1 parent fd14c72 commit ee24449

File tree

12 files changed

+263
-88
lines changed

12 files changed

+263
-88
lines changed

const.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const ( //checkheader:ignore
4242
_KeyTypeRSA cString = "RSA\x00"
4343
_KeyTypeEC cString = "EC\x00"
4444
_KeyTypeED25519 cString = "ED25519\x00"
45+
_KeyTypeX25519 cString = "X25519\x00"
4546
_KeyTypeMLKEM768 cString = "ML-KEM-768\x00"
4647
_KeyTypeMLKEM1024 cString = "ML-KEM-1024\x00"
4748

ec.go

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,55 @@
22

33
package openssl
44

5-
import "github.com/golang-fips/openssl/v2/internal/ossl"
5+
import (
6+
"errors"
7+
"strconv"
8+
"sync"
9+
10+
"github.com/golang-fips/openssl/v2/internal/ossl"
11+
)
12+
13+
func SupportsCurve(curve string) bool {
14+
switch curve {
15+
case "P-224", "P-256", "P-384", "P-521":
16+
return true
17+
case "X25519":
18+
return supportsX25519()
19+
default:
20+
return false
21+
}
22+
}
23+
24+
var supportsX25519 = sync.OnceValue(func() bool {
25+
if !versionAtOrAbove(1, 1, 1) {
26+
// X25519 support was added in OpenSSL 1.1.0, but the APIs we use
27+
// to implement it were only added in 1.1.1.
28+
return false
29+
}
30+
ctx, _ := ossl.EVP_PKEY_CTX_new_id(ossl.EVP_PKEY_X25519, nil)
31+
if ctx != nil {
32+
ossl.EVP_PKEY_CTX_free(ctx)
33+
return true
34+
}
35+
return false
36+
})
37+
38+
func curveID(curve string) int32 {
39+
switch curve {
40+
case "P-224":
41+
return ossl.EVP_PKEY_EC
42+
case "P-256":
43+
return ossl.EVP_PKEY_EC
44+
case "P-384":
45+
return ossl.EVP_PKEY_EC
46+
case "P-521":
47+
return ossl.EVP_PKEY_EC
48+
case "X25519":
49+
return ossl.EVP_PKEY_X25519
50+
default:
51+
panic("openssl: unknown curve " + curve)
52+
}
53+
}
654

755
func curveNID(curve string) int32 {
856
switch curve {
@@ -64,3 +112,25 @@ func generateAndEncodeEcPublicKey(nid int32, newPubKeyPointFn func(group ossl.EC
64112
defer ossl.EC_POINT_free(pt)
65113
return encodeEcPoint(group, pt)
66114
}
115+
116+
func extractPKEYRawPublic(pkey ossl.EVP_PKEY_PTR, pub []byte) error {
117+
keylen := len(pub)
118+
if _, err := ossl.EVP_PKEY_get_raw_public_key(pkey, base(pub), &keylen); err != nil {
119+
return err
120+
}
121+
if keylen != len(pub) {
122+
return errors.New("bad public key length: " + strconv.Itoa(keylen))
123+
}
124+
return nil
125+
}
126+
127+
func extractPKEYRawPrivate(pkey ossl.EVP_PKEY_PTR, pub []byte) error {
128+
keylen := len(pub)
129+
if _, err := ossl.EVP_PKEY_get_raw_private_key(pkey, base(pub), &keylen); err != nil {
130+
return err
131+
}
132+
if keylen != len(pub) {
133+
return errors.New("bad private key length: " + strconv.Itoa(keylen))
134+
}
135+
return nil
136+
}

ecdh.go

Lines changed: 94 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"github.com/golang-fips/openssl/v2/internal/ossl"
1212
)
1313

14+
const publicKeySizeX25519 = 32
15+
const privateKeySizeX25519 = 32
16+
1417
type PublicKeyECDH struct {
1518
_pkey ossl.EVP_PKEY_PTR
1619
bytes []byte
@@ -30,9 +33,14 @@ func (k *PrivateKeyECDH) finalize() {
3033
}
3134

3235
func NewPublicKeyECDH(curve string, bytes []byte) (*PublicKeyECDH, error) {
33-
if len(bytes) != 1+2*curveSize(curve) {
36+
expectedLen := publicKeySizeX25519
37+
if curve != "X25519" {
38+
expectedLen = 1 + 2*curveSize(curve)
39+
}
40+
if len(bytes) != expectedLen {
3441
return nil, errors.New("NewPublicKeyECDH: wrong key length")
3542
}
43+
3644
pkey, err := newECDHPkey(curve, bytes, false)
3745
if err != nil {
3846
return nil, err
@@ -45,7 +53,11 @@ func NewPublicKeyECDH(curve string, bytes []byte) (*PublicKeyECDH, error) {
4553
func (k *PublicKeyECDH) Bytes() []byte { return k.bytes }
4654

4755
func NewPrivateKeyECDH(curve string, bytes []byte) (*PrivateKeyECDH, error) {
48-
if len(bytes) != curveSize(curve) {
56+
expectedLen := privateKeySizeX25519
57+
if curve != "X25519" {
58+
expectedLen = curveSize(curve)
59+
}
60+
if len(bytes) != expectedLen {
4961
return nil, errors.New("NewPrivateKeyECDH: wrong key length")
5062
}
5163
pkey, err := newECDHPkey(curve, bytes, true)
@@ -65,40 +77,50 @@ func (k *PrivateKeyECDH) PublicKey() (*PublicKeyECDH, error) {
6577
}()
6678

6779
var bytes []byte
68-
switch vMajor {
69-
case 1:
70-
var err error
71-
pkey, err = ossl.EVP_PKEY_new()
72-
if err != nil {
73-
return nil, err
74-
}
75-
key := getECKey(k._pkey)
76-
if _, err := ossl.EVP_PKEY_set1_EC_KEY(pkey, key); err != nil {
77-
return nil, err
78-
}
79-
pt := ossl.EC_KEY_get0_public_key(key)
80-
if pt == nil {
81-
return nil, fail("missing ECDH public key")
82-
}
83-
group := ossl.EC_KEY_get0_group(key)
84-
if bytes, err = encodeEcPoint(group, pt); err != nil {
85-
return nil, err
86-
}
87-
case 3:
80+
if k.curve == "X25519" {
8881
pkey = k._pkey
8982
if _, err := ossl.EVP_PKEY_up_ref(pkey); err != nil {
9083
return nil, err
9184
}
92-
93-
var cbytes *byte
94-
n, err := ossl.EVP_PKEY_get1_encoded_public_key(k._pkey, &cbytes)
95-
if err != nil {
85+
bytes = make([]byte, publicKeySizeX25519)
86+
if err := extractPKEYRawPublic(pkey, bytes); err != nil {
9687
return nil, err
9788
}
98-
bytes = goBytes(unsafe.Pointer(cbytes), n)
99-
cryptoFree(unsafe.Pointer(cbytes))
100-
default:
101-
panic(errUnsupportedVersion())
89+
} else {
90+
switch vMajor {
91+
case 1:
92+
var err error
93+
pkey, err = ossl.EVP_PKEY_new()
94+
if err != nil {
95+
return nil, err
96+
}
97+
key := getECKey(k._pkey)
98+
if _, err := ossl.EVP_PKEY_set1_EC_KEY(pkey, key); err != nil {
99+
return nil, err
100+
}
101+
pt := ossl.EC_KEY_get0_public_key(key)
102+
if pt == nil {
103+
return nil, fail("missing ECDH public key")
104+
}
105+
group := ossl.EC_KEY_get0_group(key)
106+
if bytes, err = encodeEcPoint(group, pt); err != nil {
107+
return nil, err
108+
}
109+
case 3:
110+
pkey = k._pkey
111+
if _, err := ossl.EVP_PKEY_up_ref(pkey); err != nil {
112+
return nil, err
113+
}
114+
var cbytes *byte
115+
n, err := ossl.EVP_PKEY_get1_encoded_public_key(k._pkey, &cbytes)
116+
if err != nil {
117+
return nil, err
118+
}
119+
bytes = goBytes(unsafe.Pointer(cbytes), n)
120+
cryptoFree(unsafe.Pointer(cbytes))
121+
default:
122+
panic(errUnsupportedVersion())
123+
}
102124
}
103125
pub := &PublicKeyECDH{pkey, bytes}
104126
pkey = nil
@@ -107,6 +129,13 @@ func (k *PrivateKeyECDH) PublicKey() (*PublicKeyECDH, error) {
107129
}
108130

109131
func newECDHPkey(curve string, bytes []byte, isPrivate bool) (ossl.EVP_PKEY_PTR, error) {
132+
if curve == "X25519" {
133+
if isPrivate {
134+
return ossl.EVP_PKEY_new_raw_private_key(ossl.EVP_PKEY_X25519, nil, base(bytes), len(bytes))
135+
} else {
136+
return ossl.EVP_PKEY_new_raw_public_key(ossl.EVP_PKEY_X25519, nil, base(bytes), len(bytes))
137+
}
138+
}
110139
nid := curveNID(curve)
111140
switch vMajor {
112141
case 1:
@@ -260,7 +289,7 @@ func ECDH(priv *PrivateKeyECDH, pub *PublicKeyECDH) ([]byte, error) {
260289
}
261290

262291
func GenerateKeyECDH(curve string) (*PrivateKeyECDH, []byte, error) {
263-
pkey, err := generateEVPPKey(ossl.EVP_PKEY_EC, 0, curve)
292+
pkey, err := generateEVPPKey(curveID(curve), 0, curve)
264293
if err != nil {
265294
return nil, nil, err
266295
}
@@ -270,34 +299,43 @@ func GenerateKeyECDH(curve string) (*PrivateKeyECDH, []byte, error) {
270299
ossl.EVP_PKEY_free(pkey)
271300
}
272301
}()
273-
var priv ossl.BIGNUM_PTR
274-
switch vMajor {
275-
case 1:
276-
key := getECKey(pkey)
277-
priv = ossl.EC_KEY_get0_private_key(key)
278-
if priv == nil {
279-
return nil, nil, fail("missing ECDH private key")
302+
var bytes []byte
303+
if curve == "X25519" {
304+
bytes = make([]byte, privateKeySizeX25519)
305+
keylen := len(bytes)
306+
if _, err := ossl.EVP_PKEY_get_raw_private_key(pkey, base(bytes), &keylen); err != nil {
307+
return nil, nil, err
280308
}
281-
case 3:
282-
if _, err := ossl.EVP_PKEY_get_bn_param(pkey, _OSSL_PKEY_PARAM_PRIV_KEY.ptr(), &priv); err != nil {
309+
} else {
310+
var priv ossl.BIGNUM_PTR
311+
switch vMajor {
312+
case 1:
313+
key := getECKey(pkey)
314+
priv = ossl.EC_KEY_get0_private_key(key)
315+
if priv == nil {
316+
return nil, nil, fail("missing ECDH private key")
317+
}
318+
case 3:
319+
if _, err := ossl.EVP_PKEY_get_bn_param(pkey, _OSSL_PKEY_PARAM_PRIV_KEY.ptr(), &priv); err != nil {
320+
return nil, nil, err
321+
}
322+
defer ossl.BN_clear_free(priv)
323+
default:
324+
panic(errUnsupportedVersion())
325+
}
326+
// We should not leak bit length of the secret scalar in the key.
327+
// For this reason, we use BN_bn2binpad instead of BN_bn2bin with fixed length.
328+
// The fixed length is the order of the large prime subgroup of the curve,
329+
// returned by EVP_PKEY_get_bits, which is generally the upper bound for
330+
// generating a private ECDH key.
331+
bits, err := ossl.EVP_PKEY_get_bits(pkey)
332+
if err != nil {
333+
return nil, nil, err
334+
}
335+
bytes = make([]byte, (bits+7)/8)
336+
if err := bnToBinPad(priv, bytes); err != nil {
283337
return nil, nil, err
284338
}
285-
defer ossl.BN_clear_free(priv)
286-
default:
287-
panic(errUnsupportedVersion())
288-
}
289-
// We should not leak bit length of the secret scalar in the key.
290-
// For this reason, we use BN_bn2binpad instead of BN_bn2bin with fixed length.
291-
// The fixed length is the order of the large prime subgroup of the curve,
292-
// returned by EVP_PKEY_get_bits, which is generally the upper bound for
293-
// generating a private ECDH key.
294-
bits, err := ossl.EVP_PKEY_get_bits(pkey)
295-
if err != nil {
296-
return nil, nil, err
297-
}
298-
bytes := make([]byte, (bits+7)/8)
299-
if err := bnToBinPad(priv, bytes); err != nil {
300-
return nil, nil, err
301339
}
302340
k = &PrivateKeyECDH{pkey, curve}
303341
runtime.SetFinalizer(k, (*PrivateKeyECDH).finalize)

0 commit comments

Comments
 (0)