Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
72 changes: 71 additions & 1 deletion ec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,55 @@

package openssl

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

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

func SupportsCurve(curve string) bool {
switch curve {
case "P-224", "P-256", "P-384", "P-521":
return true
case "X25519":
return supportsX25519()
default:
return false
}
}

var supportsX25519 = sync.OnceValue(func() bool {
if !versionAtOrAbove(1, 1, 1) {
// X25519 support was added in OpenSSL 1.1.0, but the APIs we use
// to implement it were only added in 1.1.1.
return false
}
ctx, _ := ossl.EVP_PKEY_CTX_new_id(ossl.EVP_PKEY_X25519, nil)
if ctx != nil {
ossl.EVP_PKEY_CTX_free(ctx)
return true
}
return false
})

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 +112,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 private 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
Loading
Loading