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