blob: 9c101168513f9d37ab904f5579f1874b4ae49def [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build openssl
// OpenSSL's libcrypto may have faster implementations of ECDSA signing and
// verification on some architectures (not amd64 after Go 1.6 which includes
// https://go-review.googlesource.com/#/c/8968/). This file enables use
// of libcrypto's implementation of ECDSA operations in those situations.
package security
// #cgo pkg-config: libcrypto
// #include <openssl/bn.h>
// #include <openssl/ec.h>
// #include <openssl/ecdsa.h>
// #include <openssl/err.h>
// #include <openssl/objects.h>
// #include <openssl/opensslv.h>
// #include <openssl/x509.h>
//
// void openssl_init_locks();
// EC_KEY* openssl_d2i_EC_PUBKEY(const unsigned char* data, long len, unsigned long* e);
// EC_KEY* openssl_d2i_ECPrivateKey(const unsigned char* data, long len, unsigned long* e);
// ECDSA_SIG* openssl_ECDSA_do_sign(const unsigned char* data, int len, EC_KEY* key, unsigned long *e);
import "C"
import (
"crypto/ecdsa"
"crypto/x509"
"fmt"
"math/big"
"runtime"
"sync"
"unsafe"
"v.io/v23/verror"
)
var (
errOpenSSL = verror.Register(pkgPath+".errOpenSSL", verror.NoRetry, "{1:}{2:} OpenSSL error ({3}): {4} in {5}:{6}")
errUnsupportedCurve = verror.Register(pkgPath+".errUnsupportedCurve", verror.NoRetry, "{1:}{2:} elliptic curve {3} is not supported")
opensslLocks []sync.RWMutex
)
func init() {
C.ERR_load_crypto_strings()
opensslLocks = make([]sync.RWMutex, C.CRYPTO_num_locks())
C.openssl_init_locks()
}
//export openssl_lock
func openssl_lock(mode, n C.int) {
l := &opensslLocks[int(n)]
if (mode & C.CRYPTO_LOCK) == 0 {
if (mode & C.CRYPTO_READ) == 0 {
l.Unlock()
} else {
l.RUnlock()
}
} else {
if (mode & C.CRYPTO_READ) == 0 {
l.Lock()
} else {
l.RLock()
}
}
}
type opensslECPublicKey struct {
k *C.EC_KEY
h Hash
der []byte
}
func (k *opensslECPublicKey) MarshalBinary() ([]byte, error) {
cpy := make([]byte, len(k.der))
copy(cpy, k.der)
return cpy, nil
}
func (k *opensslECPublicKey) String() string { return publicKeyString(k) }
func (k *opensslECPublicKey) hash() Hash { return k.h }
func (k *opensslECPublicKey) verify(digest []byte, signature *Signature) bool {
sig := C.ECDSA_SIG_new()
sig.r = C.BN_bin2bn(uchar(signature.R), C.int(len(signature.R)), sig.r)
sig.s = C.BN_bin2bn(uchar(signature.S), C.int(len(signature.S)), sig.s)
status := C.ECDSA_do_verify(uchar(digest), C.int(len(digest)), sig, k.k)
C.ECDSA_SIG_free(sig)
return status == 1
}
func newOpenSSLPublicKey(golang *ecdsa.PublicKey) (PublicKey, error) {
der, err := x509.MarshalPKIXPublicKey(golang)
if err != nil {
return nil, err
}
return unmarshalPublicKeyImpl(der)
}
type opensslSigner struct {
k *C.EC_KEY
}
func (k *opensslSigner) sign(data []byte) (r, s *big.Int, err error) {
var errno C.ulong
sig := C.openssl_ECDSA_do_sign(uchar(data), C.int(len(data)), k.k, &errno)
if sig == nil {
return nil, nil, opensslMakeError(errno)
}
var (
rlen = (int(C.BN_num_bits(sig.r)) + 7) / 8
slen = (int(C.BN_num_bits(sig.s)) + 7) / 8
buf []byte
)
if rlen > slen {
buf = make([]byte, rlen)
} else {
buf = make([]byte, slen)
}
r = big.NewInt(0).SetBytes(buf[0:int(C.BN_bn2bin(sig.r, uchar(buf)))])
s = big.NewInt(0).SetBytes(buf[0:int(C.BN_bn2bin(sig.s, uchar(buf)))])
C.ECDSA_SIG_free(sig)
return r, s, nil
}
func newOpenSSLSigner(golang *ecdsa.PrivateKey) (Signer, error) {
der, err := x509.MarshalECPrivateKey(golang)
if err != nil {
return nil, err
}
pubkey, err := newOpenSSLPublicKey(&golang.PublicKey)
if err != nil {
return nil, err
}
var errno C.ulong
k := C.openssl_d2i_ECPrivateKey(uchar(der), C.long(len(der)), &errno)
if k == nil {
return nil, opensslMakeError(errno)
}
impl := &opensslSigner{k}
runtime.SetFinalizer(impl, func(k *opensslSigner) { C.EC_KEY_free(k.k) })
return &ecdsaSigner{
sign: func(data []byte) (r, s *big.Int, err error) {
return impl.sign(data)
},
pubkey: pubkey,
impl: impl,
}, nil
}
func opensslMakeError(errno C.ulong) error {
return verror.New(errOpenSSL, nil, errno, C.GoString(C.ERR_func_error_string(errno)), C.GoString(C.ERR_lib_error_string(errno)), C.GoString(C.ERR_reason_error_string(errno)))
}
func uchar(b []byte) *C.uchar {
if len(b) == 0 {
return nil
}
return (*C.uchar)(unsafe.Pointer(&b[0]))
}
func openssl_version() string {
return fmt.Sprintf("%v (CFLAGS:%v)", C.GoString(C.SSLeay_version(C.SSLEAY_VERSION)), C.GoString(C.SSLeay_version(C.SSLEAY_CFLAGS)))
}
func openssl_hash_for_key(k *C.EC_KEY) (Hash, error) {
switch nid := C.EC_GROUP_get_curve_name(C.EC_KEY_get0_group(k)); nid {
case C.NID_secp224r1, C.NID_X9_62_prime256v1:
return SHA256Hash, nil
case C.NID_secp384r1:
return SHA384Hash, nil
case C.NID_secp521r1:
return SHA512Hash, nil
default:
var h Hash
return h, verror.New(errUnsupportedCurve, nil, C.GoString(C.OBJ_nid2sn(C.int(nid))))
}
}
func newInMemoryECDSASignerImpl(key *ecdsa.PrivateKey) (Signer, error) {
return newOpenSSLSigner(key)
}
func newECDSAPublicKeyImpl(key *ecdsa.PublicKey) PublicKey {
if key, err := newOpenSSLPublicKey(key); err == nil {
return key
}
return newGoStdlibPublicKey(key)
}
func unmarshalPublicKeyImpl(der []byte) (PublicKey, error) {
var errno C.ulong
k := C.openssl_d2i_EC_PUBKEY(uchar(der), C.long(len(der)), &errno)
if k == nil {
return nil, opensslMakeError(errno)
}
h, err := openssl_hash_for_key(k)
if err != nil {
return nil, err
}
dercpy := make([]byte, len(der))
copy(dercpy, der)
ret := &opensslECPublicKey{k, h, dercpy}
runtime.SetFinalizer(ret, func(k *opensslECPublicKey) { C.EC_KEY_free(k.k) })
return ret, nil
}