blob: 6a5d174c16b711d11d36a26bf98d82d1a8530355 [file] [log] [blame]
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build boringcrypto && linux && (amd64 || arm64) && !android && !msan
package boring
// #include "goboringcrypto.h"
import "C"
import (
"errors"
"runtime"
"unsafe"
)
type PublicKeyECDH struct {
curve string
key *C.GO_EC_POINT
group *C.GO_EC_GROUP
bytes []byte
}
func (k *PublicKeyECDH) finalize() {
C._goboringcrypto_EC_POINT_free(k.key)
}
type PrivateKeyECDH struct {
curve string
key *C.GO_EC_KEY
}
func (k *PrivateKeyECDH) finalize() {
C._goboringcrypto_EC_KEY_free(k.key)
}
func NewPublicKeyECDH(curve string, bytes []byte) (*PublicKeyECDH, error) {
if len(bytes) < 1 {
return nil, errors.New("NewPublicKeyECDH: missing key")
}
nid, err := curveNID(curve)
if err != nil {
return nil, err
}
group := C._goboringcrypto_EC_GROUP_new_by_curve_name(nid)
if group == nil {
return nil, fail("EC_GROUP_new_by_curve_name")
}
defer C._goboringcrypto_EC_GROUP_free(group)
key := C._goboringcrypto_EC_POINT_new(group)
if key == nil {
return nil, fail("EC_POINT_new")
}
ok := C._goboringcrypto_EC_POINT_oct2point(group, key, (*C.uint8_t)(unsafe.Pointer(&bytes[0])), C.size_t(len(bytes)), nil) != 0
if !ok {
C._goboringcrypto_EC_POINT_free(key)
return nil, errors.New("point not on curve")
}
k := &PublicKeyECDH{curve, key, group, append([]byte(nil), bytes...)}
// Note: Because of the finalizer, any time k.key is passed to cgo,
// that call must be followed by a call to runtime.KeepAlive(k),
// to make sure k is not collected (and finalized) before the cgo
// call returns.
runtime.SetFinalizer(k, (*PublicKeyECDH).finalize)
return k, nil
}
func (k *PublicKeyECDH) Bytes() []byte { return k.bytes }
func NewPrivateKeyECDH(curve string, bytes []byte) (*PrivateKeyECDH, error) {
nid, err := curveNID(curve)
if err != nil {
return nil, err
}
key := C._goboringcrypto_EC_KEY_new_by_curve_name(nid)
if key == nil {
return nil, fail("EC_KEY_new_by_curve_name")
}
b := bytesToBN(bytes)
ok := b != nil && C._goboringcrypto_EC_KEY_set_private_key(key, b) != 0
if b != nil {
C._goboringcrypto_BN_free(b)
}
if !ok {
C._goboringcrypto_EC_KEY_free(key)
return nil, fail("EC_KEY_set_private_key")
}
k := &PrivateKeyECDH{curve, key}
// Note: Same as in NewPublicKeyECDH regarding finalizer and KeepAlive.
runtime.SetFinalizer(k, (*PrivateKeyECDH).finalize)
return k, nil
}
func (k *PrivateKeyECDH) PublicKey() (*PublicKeyECDH, error) {
defer runtime.KeepAlive(k)
group := C._goboringcrypto_EC_KEY_get0_group(k.key)
if group == nil {
return nil, fail("EC_KEY_get0_group")
}
kbig := C._goboringcrypto_EC_KEY_get0_private_key(k.key)
if kbig == nil {
return nil, fail("EC_KEY_get0_private_key")
}
pt := C._goboringcrypto_EC_POINT_new(group)
if pt == nil {
return nil, fail("EC_POINT_new")
}
if C._goboringcrypto_EC_POINT_mul(group, pt, kbig, nil, nil, nil) == 0 {
C._goboringcrypto_EC_POINT_free(pt)
return nil, fail("EC_POINT_mul")
}
bytes, err := pointBytesECDH(k.curve, group, pt)
if err != nil {
C._goboringcrypto_EC_POINT_free(pt)
return nil, err
}
pub := &PublicKeyECDH{k.curve, pt, group, bytes}
// Note: Same as in NewPublicKeyECDH regarding finalizer and KeepAlive.
runtime.SetFinalizer(pub, (*PublicKeyECDH).finalize)
return pub, nil
}
func pointBytesECDH(curve string, group *C.GO_EC_GROUP, pt *C.GO_EC_POINT) ([]byte, error) {
out := make([]byte, 1+2*curveSize(curve))
n := C._goboringcrypto_EC_POINT_point2oct(group, pt, C.GO_POINT_CONVERSION_UNCOMPRESSED, (*C.uint8_t)(unsafe.Pointer(&out[0])), C.size_t(len(out)), nil)
if int(n) != len(out) {
return nil, fail("EC_POINT_point2oct")
}
return out, nil
}
func ECDH(priv *PrivateKeyECDH, pub *PublicKeyECDH) ([]byte, error) {
group := C._goboringcrypto_EC_KEY_get0_group(priv.key)
if group == nil {
return nil, fail("EC_KEY_get0_group")
}
privBig := C._goboringcrypto_EC_KEY_get0_private_key(priv.key)
if privBig == nil {
return nil, fail("EC_KEY_get0_private_key")
}
pt := C._goboringcrypto_EC_POINT_new(group)
if pt == nil {
return nil, fail("EC_POINT_new")
}
defer C._goboringcrypto_EC_POINT_free(pt)
if C._goboringcrypto_EC_POINT_mul(group, pt, nil, pub.key, privBig, nil) == 0 {
return nil, fail("EC_POINT_mul")
}
out, err := xCoordBytesECDH(priv.curve, group, pt)
if err != nil {
return nil, err
}
return out, nil
}
func xCoordBytesECDH(curve string, group *C.GO_EC_GROUP, pt *C.GO_EC_POINT) ([]byte, error) {
big := C._goboringcrypto_BN_new()
defer C._goboringcrypto_BN_free(big)
if C._goboringcrypto_EC_POINT_get_affine_coordinates_GFp(group, pt, big, nil, nil) == 0 {
return nil, fail("EC_POINT_get_affine_coordinates_GFp")
}
return bigBytesECDH(curve, big)
}
func bigBytesECDH(curve string, big *C.GO_BIGNUM) ([]byte, error) {
out := make([]byte, curveSize(curve))
if C._goboringcrypto_BN_bn2bin_padded((*C.uint8_t)(&out[0]), C.size_t(len(out)), big) == 0 {
return nil, fail("BN_bn2bin_padded")
}
return out, nil
}
func curveSize(curve string) int {
switch curve {
default:
panic("crypto/internal/boring: unknown curve " + curve)
case "P-256":
return 256 / 8
case "P-384":
return 384 / 8
case "P-521":
return (521 + 7) / 8
}
}
func GenerateKeyECDH(curve string) (*PrivateKeyECDH, []byte, error) {
nid, err := curveNID(curve)
if err != nil {
return nil, nil, err
}
key := C._goboringcrypto_EC_KEY_new_by_curve_name(nid)
if key == nil {
return nil, nil, fail("EC_KEY_new_by_curve_name")
}
if C._goboringcrypto_EC_KEY_generate_key_fips(key) == 0 {
C._goboringcrypto_EC_KEY_free(key)
return nil, nil, fail("EC_KEY_generate_key_fips")
}
group := C._goboringcrypto_EC_KEY_get0_group(key)
if group == nil {
C._goboringcrypto_EC_KEY_free(key)
return nil, nil, fail("EC_KEY_get0_group")
}
b := C._goboringcrypto_EC_KEY_get0_private_key(key)
if b == nil {
C._goboringcrypto_EC_KEY_free(key)
return nil, nil, fail("EC_KEY_get0_private_key")
}
bytes, err := bigBytesECDH(curve, b)
if err != nil {
C._goboringcrypto_EC_KEY_free(key)
return nil, nil, err
}
k := &PrivateKeyECDH{curve, key}
// Note: Same as in NewPublicKeyECDH regarding finalizer and KeepAlive.
runtime.SetFinalizer(k, (*PrivateKeyECDH).finalize)
return k, bytes, nil
}