Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions const.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const ( //checkheader:ignore
_KeyTypeRSA cString = "RSA\x00"
_KeyTypeEC cString = "EC\x00"
_KeyTypeED25519 cString = "ED25519\x00"
_KeyTypeX25519 cString = "X25519\x00"
_KeyTypeMLKEM768 cString = "ML-KEM-768\x00"
_KeyTypeMLKEM1024 cString = "ML-KEM-1024\x00"

Expand Down
46 changes: 45 additions & 1 deletion ec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,29 @@

package openssl

import "github.com/golang-fips/openssl/v2/internal/ossl"
import (
"errors"
"strconv"

"github.com/golang-fips/openssl/v2/internal/ossl"
)

func curveID(curve string) int32 {
switch curve {
case "P-224":
return ossl.EVP_PKEY_EC
case "P-256":
return ossl.EVP_PKEY_EC
case "P-384":
return ossl.EVP_PKEY_EC
case "P-521":
return ossl.EVP_PKEY_EC
case "X25519":
return ossl.EVP_PKEY_X25519
default:
panic("openssl: unknown curve " + curve)
}
}

func curveNID(curve string) int32 {
switch curve {
Expand Down Expand Up @@ -64,3 +86,25 @@ func generateAndEncodeEcPublicKey(nid int32, newPubKeyPointFn func(group ossl.EC
defer ossl.EC_POINT_free(pt)
return encodeEcPoint(group, pt)
}

func extractPKEYRawPublic(pkey ossl.EVP_PKEY_PTR, pub []byte) error {
keylen := len(pub)
if _, err := ossl.EVP_PKEY_get_raw_public_key(pkey, base(pub), &keylen); err != nil {
return err
}
if keylen != len(pub) {
return errors.New("bad public key length: " + strconv.Itoa(keylen))
}
return nil
}

func extractPKEYRawPrivate(pkey ossl.EVP_PKEY_PTR, pub []byte) error {
keylen := len(pub)
if _, err := ossl.EVP_PKEY_get_raw_private_key(pkey, base(pub), &keylen); err != nil {
return err
}
if keylen != len(pub) {
return errors.New("bad public key length: " + strconv.Itoa(keylen))
}
return nil
}
150 changes: 94 additions & 56 deletions ecdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
"github.com/golang-fips/openssl/v2/internal/ossl"
)

const publicKeySizeX25519 = 32
const privateKeySizeX25519 = 32

type PublicKeyECDH struct {
_pkey ossl.EVP_PKEY_PTR
bytes []byte
Expand All @@ -30,9 +33,14 @@ func (k *PrivateKeyECDH) finalize() {
}

func NewPublicKeyECDH(curve string, bytes []byte) (*PublicKeyECDH, error) {
if len(bytes) != 1+2*curveSize(curve) {
expectedLen := publicKeySizeX25519
if curve != "X25519" {
expectedLen = 1 + 2*curveSize(curve)
}
if len(bytes) != expectedLen {
return nil, errors.New("NewPublicKeyECDH: wrong key length")
}

pkey, err := newECDHPkey(curve, bytes, false)
if err != nil {
return nil, err
Expand All @@ -45,7 +53,11 @@ func NewPublicKeyECDH(curve string, bytes []byte) (*PublicKeyECDH, error) {
func (k *PublicKeyECDH) Bytes() []byte { return k.bytes }

func NewPrivateKeyECDH(curve string, bytes []byte) (*PrivateKeyECDH, error) {
if len(bytes) != curveSize(curve) {
expectedLen := privateKeySizeX25519
if curve != "X25519" {
expectedLen = curveSize(curve)
}
if len(bytes) != expectedLen {
return nil, errors.New("NewPrivateKeyECDH: wrong key length")
}
pkey, err := newECDHPkey(curve, bytes, true)
Expand All @@ -65,40 +77,50 @@ func (k *PrivateKeyECDH) PublicKey() (*PublicKeyECDH, error) {
}()

var bytes []byte
switch vMajor {
case 1:
var err error
pkey, err = ossl.EVP_PKEY_new()
if err != nil {
return nil, err
}
key := getECKey(k._pkey)
if _, err := ossl.EVP_PKEY_set1_EC_KEY(pkey, key); err != nil {
return nil, err
}
pt := ossl.EC_KEY_get0_public_key(key)
if pt == nil {
return nil, fail("missing ECDH public key")
}
group := ossl.EC_KEY_get0_group(key)
if bytes, err = encodeEcPoint(group, pt); err != nil {
return nil, err
}
case 3:
if k.curve == "X25519" {
pkey = k._pkey
if _, err := ossl.EVP_PKEY_up_ref(pkey); err != nil {
return nil, err
}

var cbytes *byte
n, err := ossl.EVP_PKEY_get1_encoded_public_key(k._pkey, &cbytes)
if err != nil {
bytes = make([]byte, publicKeySizeX25519)
if err := extractPKEYRawPublic(pkey, bytes); err != nil {
return nil, err
}
bytes = goBytes(unsafe.Pointer(cbytes), n)
cryptoFree(unsafe.Pointer(cbytes))
default:
panic(errUnsupportedVersion())
} else {
switch vMajor {
case 1:
var err error
pkey, err = ossl.EVP_PKEY_new()
if err != nil {
return nil, err
}
key := getECKey(k._pkey)
if _, err := ossl.EVP_PKEY_set1_EC_KEY(pkey, key); err != nil {
return nil, err
}
pt := ossl.EC_KEY_get0_public_key(key)
if pt == nil {
return nil, fail("missing ECDH public key")
}
group := ossl.EC_KEY_get0_group(key)
if bytes, err = encodeEcPoint(group, pt); err != nil {
return nil, err
}
case 3:
pkey = k._pkey
if _, err := ossl.EVP_PKEY_up_ref(pkey); err != nil {
return nil, err
}
var cbytes *byte
n, err := ossl.EVP_PKEY_get1_encoded_public_key(k._pkey, &cbytes)
if err != nil {
return nil, err
}
bytes = goBytes(unsafe.Pointer(cbytes), n)
cryptoFree(unsafe.Pointer(cbytes))
default:
panic(errUnsupportedVersion())
}
}
pub := &PublicKeyECDH{pkey, bytes}
pkey = nil
Expand All @@ -107,6 +129,13 @@ func (k *PrivateKeyECDH) PublicKey() (*PublicKeyECDH, error) {
}

func newECDHPkey(curve string, bytes []byte, isPrivate bool) (ossl.EVP_PKEY_PTR, error) {
if curve == "X25519" {
if isPrivate {
return ossl.EVP_PKEY_new_raw_private_key(ossl.EVP_PKEY_X25519, nil, base(bytes), len(bytes))
} else {
return ossl.EVP_PKEY_new_raw_public_key(ossl.EVP_PKEY_X25519, nil, base(bytes), len(bytes))
}
}
nid := curveNID(curve)
switch vMajor {
case 1:
Expand Down Expand Up @@ -260,7 +289,7 @@ func ECDH(priv *PrivateKeyECDH, pub *PublicKeyECDH) ([]byte, error) {
}

func GenerateKeyECDH(curve string) (*PrivateKeyECDH, []byte, error) {
pkey, err := generateEVPPKey(ossl.EVP_PKEY_EC, 0, curve)
pkey, err := generateEVPPKey(curveID(curve), 0, curve)
if err != nil {
return nil, nil, err
}
Expand All @@ -270,34 +299,43 @@ func GenerateKeyECDH(curve string) (*PrivateKeyECDH, []byte, error) {
ossl.EVP_PKEY_free(pkey)
}
}()
var priv ossl.BIGNUM_PTR
switch vMajor {
case 1:
key := getECKey(pkey)
priv = ossl.EC_KEY_get0_private_key(key)
if priv == nil {
return nil, nil, fail("missing ECDH private key")
var bytes []byte
if curve == "X25519" {
bytes = make([]byte, privateKeySizeX25519)
keylen := len(bytes)
if _, err := ossl.EVP_PKEY_get_raw_private_key(pkey, base(bytes), &keylen); err != nil {
return nil, nil, err
}
case 3:
if _, err := ossl.EVP_PKEY_get_bn_param(pkey, _OSSL_PKEY_PARAM_PRIV_KEY.ptr(), &priv); err != nil {
} else {
var priv ossl.BIGNUM_PTR
switch vMajor {
case 1:
key := getECKey(pkey)
priv = ossl.EC_KEY_get0_private_key(key)
if priv == nil {
return nil, nil, fail("missing ECDH private key")
}
case 3:
if _, err := ossl.EVP_PKEY_get_bn_param(pkey, _OSSL_PKEY_PARAM_PRIV_KEY.ptr(), &priv); err != nil {
return nil, nil, err
}
defer ossl.BN_clear_free(priv)
default:
panic(errUnsupportedVersion())
}
// We should not leak bit length of the secret scalar in the key.
// For this reason, we use BN_bn2binpad instead of BN_bn2bin with fixed length.
// The fixed length is the order of the large prime subgroup of the curve,
// returned by EVP_PKEY_get_bits, which is generally the upper bound for
// generating a private ECDH key.
bits, err := ossl.EVP_PKEY_get_bits(pkey)
if err != nil {
return nil, nil, err
}
bytes = make([]byte, (bits+7)/8)
if err := bnToBinPad(priv, bytes); err != nil {
return nil, nil, err
}
defer ossl.BN_clear_free(priv)
default:
panic(errUnsupportedVersion())
}
// We should not leak bit length of the secret scalar in the key.
// For this reason, we use BN_bn2binpad instead of BN_bn2bin with fixed length.
// The fixed length is the order of the large prime subgroup of the curve,
// returned by EVP_PKEY_get_bits, which is generally the upper bound for
// generating a private ECDH key.
bits, err := ossl.EVP_PKEY_get_bits(pkey)
if err != nil {
return nil, nil, err
}
bytes := make([]byte, (bits+7)/8)
if err := bnToBinPad(priv, bytes); err != nil {
return nil, nil, err
}
k = &PrivateKeyECDH{pkey, curve}
runtime.SetFinalizer(k, (*PrivateKeyECDH).finalize)
Expand Down
52 changes: 49 additions & 3 deletions ecdh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package openssl_test

import (
"bytes"
"crypto/rand"
"encoding/hex"
"strings"
"testing"
Expand All @@ -10,7 +11,7 @@ import (
)

func TestECDH(t *testing.T) {
for _, tt := range []string{"P-256", "P-384", "P-521"} {
for _, tt := range []string{"P-256", "P-384", "P-521", "X25519"} {
t.Run(tt, func(t *testing.T) {
name := tt
aliceKey, alicPrivBytes, err := openssl.GenerateKeyECDH(name)
Expand Down Expand Up @@ -107,6 +108,14 @@ var ecdhvectors = []struct {
"01ba52c56fc8776d9e8f5db4f0cc27636d0b741bbe05400697942e80b739884a83bde99e0f6716939e632bc8986fa18dccd443a348b6c3e522497955a4f3c302f676",
SharedSecret: "005fc70477c3e63bc3954bd0df3ea0d1f41ee21746ed95fc5e1fdf90930d5e136672d72cc770742d1711c3c3a4c334a0ad9759436a4d3c5bf6e74b9578fac148c831",
},
// X25519 test vector from RFC 7748, Section 6.1.
{
Name: "X25519",
PrivateKey: "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
PublicKey: "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a",
PeerPublicKey: "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
SharedSecret: "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742",
},
}

func TestECDHVectors(t *testing.T) {
Expand Down Expand Up @@ -217,6 +226,14 @@ var invalidECDHPrivateKeys = map[string][]string{
"11fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409",
"03fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4a30d0f077e5f2cd6ff980291ee134ba0776b937113388f5d76df6e3d2270c812",
},
"X25519": {
// X25519 only rejects bad lengths.
"",
"01",
"01010101010101010101010101010101010101010101010101010101010101",
"000101010101010101010101010101010101010101010101010101010101010101",
strings.Repeat("01", 200),
},
}

var invalidECDHPublicKeys = map[string][]string{
Expand Down Expand Up @@ -262,10 +279,11 @@ var invalidECDHPublicKeys = map[string][]string{
"0400c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16651",
"04000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
"X25519": {},
}

func TestECDHNewPrivateKeyECDH_Invalid(t *testing.T) {
for _, curve := range []string{"P-256", "P-384", "P-521"} {
for _, curve := range []string{"P-256", "P-384", "P-521", "X25519"} {
t.Run(curve, func(t *testing.T) {
for _, input := range invalidECDHPrivateKeys[curve] {
k, err := openssl.NewPrivateKeyECDH(curve, hexDecode(t, input))
Expand All @@ -280,7 +298,7 @@ func TestECDHNewPrivateKeyECDH_Invalid(t *testing.T) {
}

func TestECDHNewPublicKeyECDH_Invalid(t *testing.T) {
for _, curve := range []string{"P-256", "P-384", "P-521"} {
for _, curve := range []string{"P-256", "P-384", "P-521", "X25519"} {
t.Run(curve, func(t *testing.T) {
for _, input := range invalidECDHPublicKeys[curve] {
k, err := openssl.NewPublicKeyECDH(curve, hexDecode(t, input))
Expand All @@ -293,3 +311,31 @@ func TestECDHNewPublicKeyECDH_Invalid(t *testing.T) {
})
}
}

func TestX25519Failure(t *testing.T) {
identity := hexDecode(t, "0000000000000000000000000000000000000000000000000000000000000000")
lowOrderPoint := hexDecode(t, "e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800")
randomScalar := make([]byte, 32)
rand.Read(randomScalar)

t.Run("identity point", func(t *testing.T) { testX25519Failure(t, randomScalar, identity) })
t.Run("low order point", func(t *testing.T) { testX25519Failure(t, randomScalar, lowOrderPoint) })
}

func testX25519Failure(t *testing.T, private, public []byte) {
priv, err := openssl.NewPrivateKeyECDH("X25519", private)
if err != nil {
t.Fatal(err)
}
pub, err := openssl.NewPublicKeyECDH("X25519", public)
if err != nil {
t.Fatal(err)
}
secret, err := openssl.ECDH(priv, pub)
if err == nil {
t.Error("expected ECDH error")
}
if secret != nil {
t.Errorf("unexpected ECDH output: %x", secret)
}
}
Loading
Loading