| // 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 !android,linux,amd64,cgo,!noopenssl openssl,cgo |
| |
| // The purpose of this file is to improve performance, as demonstrated by |
| // benchmarks when linked against openssl-1.0.1f (with further improvements in |
| // openssl-1.0.2, at the time this comment was written). |
| // go test -bench ECDSA v.io/v23/security |
| // |
| // See https://go-review.googlesource.com/#/c/8968/ for why the Go standard |
| // library (as of Go 1.5) does not have performance on par with OpenSSL. |
| // |
| // By default (without an explicit build tag), this is disabled for darwin |
| // since OpenSSL has been marked deprecated on OS X since version 10.7. The |
| // last openssl release on OS X was version 0.9.8 and compiling this file will |
| // show these deprecation warnings. Those sensitive to performance are |
| // encouraged to compile a later version of OpenSSL and set the build tag. |
| // |
| // Currently, this file is disabled for linux/arm as a temporary hack. In |
| // practice, linux/arm binaries are often cross-compiled on a linux/amd64 host. |
| // The author of this comment hadn't changed the vanadium devtools to download |
| // and build a cross-compiled OpenSSL library at the time this comment was |
| // written. |
| // |
| // TODO(ashankar): Figure out how to remove the "amd64" tag hack. |
| |
| 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 |
| in := uchar(der) |
| k := C.openssl_d2i_ECPrivateKey(&in, 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 |
| in := uchar(der) |
| k := C.openssl_d2i_EC_PUBKEY(&in, 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 |
| } |