diff --git a/CHANGELOG.md b/CHANGELOG.md index 417f5e52..b79ad869 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.2.1 + * Added finalizers for `ssl.EVP_PKEY` and running tests under `valgrind` unable + to find any obvious memory leaks. + * Increased Flutter SDK constraint to `>=1.22.0-12.1.pre` (current beta). + # 0.2.0 * Added `ios` support. * Added `<2.0.0` upper-bound on Flutter SDK constraint. diff --git a/README.md b/README.md index 41335799..4b81fc9c 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,6 @@ Future main() async { **Missing:** * Exceptions and errors thrown for invalid input is not tested yet. - * Finalizers not implemented yet, hence, memory leaks of keys is a known - issues in the native implementation. * The native implementation executes on the main-thread, however, all expensive APIs are asynchronous, so they can be offloaded in the future. diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index e7ec0c1c..8a2a4382 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -69,6 +69,7 @@ add_library( # Source files ../src/webcrypto.c + ../src/webcrypto_dart_dl.c ../src/symbols.generated.c ${dart_dl_sources} ${crypto_sources} diff --git a/example/pubspec.lock b/example/pubspec.lock index 0f374edc..0cfaffd4 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -35,28 +35,28 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.2" + version: "2.5.0-nullsafety" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0-nullsafety.2" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0-nullsafety" cli_util: dependency: transitive description: @@ -70,14 +70,14 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0-nullsafety" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.13" + version: "1.15.0-nullsafety.2" convert: dependency: "direct main" description: @@ -119,7 +119,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.0-nullsafety" ffi: dependency: transitive description: @@ -133,7 +133,7 @@ packages: name: file url: "https://pub.dartlang.org" source: hosted - version: "5.2.1" + version: "6.0.0-nullsafety.1" flutter: dependency: "direct main" description: flutter @@ -194,13 +194,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.dartlang.org" - source: hosted - version: "0.16.1" io: dependency: transitive description: @@ -214,7 +207,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.3-nullsafety.1" json_rpc_2: dependency: transitive description: @@ -235,14 +228,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.8" + version: "0.12.10-nullsafety" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.2" mime: dependency: transitive description: @@ -284,35 +277,35 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.0" + version: "1.10.0-nullsafety.1" platform: dependency: transitive description: name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.0.0-nullsafety.1" pool: dependency: transitive description: name: pool url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.5.0-nullsafety.1" process: dependency: transitive description: name: process url: "https://pub.dartlang.org" source: hosted - version: "3.0.13" + version: "4.0.0-nullsafety.1" pub_semver: dependency: transitive description: @@ -359,42 +352,42 @@ packages: name: source_map_stack_trace url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety.2" source_maps: dependency: transitive description: name: source_maps url: "https://pub.dartlang.org" source: hosted - version: "0.10.9" + version: "0.10.10-nullsafety.1" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.5" + version: "1.10.0-nullsafety" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0-nullsafety" sync_http: dependency: transitive description: @@ -408,42 +401,42 @@ packages: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety" test: dependency: "direct dev" description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.15.2" + version: "1.16.0-nullsafety.4" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.17" + version: "0.2.19-nullsafety" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.10" + version: "0.3.12-nullsafety.4" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0-nullsafety.2" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.2" vm_service: dependency: transitive description: @@ -501,5 +494,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=2.9.0-14.0.dev <3.0.0" + dart: ">=2.10.0-4.0.dev <2.10.0" flutter: ">=1.17.0 <2.0.0" diff --git a/ios/Classes/include_webcrypto.c b/ios/Classes/include_webcrypto.c index b549aedb..56310d68 100644 --- a/ios/Classes/include_webcrypto.c +++ b/ios/Classes/include_webcrypto.c @@ -22,3 +22,4 @@ #include "../../src/symbols.generated.c" #include "../../src/webcrypto.c" +#include "../../src/webcrypto_dart_dl.c" diff --git a/lib/src/boringssl/evp.dart b/lib/src/boringssl/evp.dart index 98e45ab1..867035d8 100644 --- a/lib/src/boringssl/evp.dart +++ b/lib/src/boringssl/evp.dart @@ -40,9 +40,16 @@ final EVP_PKEY_new = resolve(Sym.EVP_PKEY_new) /// ```c /// void EVP_PKEY_free(EVP_PKEY *pkey); /// ``` -final EVP_PKEY_free = resolve(Sym.EVP_PKEY_free) - .lookupFunc)>() - .asFunction)>(); +final EVP_PKEY_free_ = + resolve(Sym.EVP_PKEY_free).lookupFunc)>(); + +/// EVP_PKEY_free frees all data referenced by pkey and then frees pkey itself. +/// +/// ```c +/// void EVP_PKEY_free(EVP_PKEY *pkey); +/// ``` +final EVP_PKEY_free = + EVP_PKEY_free_.asFunction)>(); /// EVP_PKEY_id returns the type of pkey, which is one of the EVP_PKEY_* values. /// @@ -86,7 +93,7 @@ final EVP_PKEY_set1_RSA = resolve(Sym.EVP_PKEY_set1_RSA) .lookupFunc, Pointer)>() .asFunction, Pointer)>(); -final EVP_PKEY_get0_RSA = resolve(Sym.EVP_PKEY_get0_RSA) +final EVP_PKEY_get1_RSA = resolve(Sym.EVP_PKEY_get1_RSA) .lookupFunc Function(Pointer)>() .asFunction Function(Pointer)>(); @@ -94,7 +101,7 @@ final EVP_PKEY_set1_EC_KEY = resolve(Sym.EVP_PKEY_set1_EC_KEY) .lookupFunc, Pointer)>() .asFunction, Pointer)>(); -final EVP_PKEY_get0_EC_KEY = resolve(Sym.EVP_PKEY_get0_EC_KEY) +final EVP_PKEY_get1_EC_KEY = resolve(Sym.EVP_PKEY_get1_EC_KEY) .lookupFunc Function(Pointer)>() .asFunction Function(Pointer)>(); diff --git a/lib/src/boringssl/lookup/lookup_symbol_dart.dart b/lib/src/boringssl/lookup/lookup_symbol_dart.dart index fec5edde..e641ea0d 100644 --- a/lib/src/boringssl/lookup/lookup_symbol_dart.dart +++ b/lib/src/boringssl/lookup/lookup_symbol_dart.dart @@ -23,37 +23,9 @@ final Pointer Function(Sym) lookupSymbol = () { return lookup; } - try { - // If there is no binary webcrypto library to be found we check if the - // current executable already contains BoringSSL symbols. This happens to be - // the case for the Dart Linux release at-least. - final library = DynamicLibrary.executable(); - - // CRYPTO_library_init initializes the crypto library. It must be called if - // the library is built with BORINGSSL_NO_STATIC_INITIALIZER. Otherwise, it - // does nothing and a static initializer is used instead. It is safe to call - // this function multiple times and concurrently from multiple threads. - // - // On some ARM configurations, this function may require filesystem access - // and should be called before entering a sandbox. - // - // OPENSSL_EXPORT void CRYPTO_library_init(void); - // ignore: non_constant_identifier_names - final CRYPTO_library_init = library - .lookup>('CRYPTO_library_init') - .asFunction(); - - // Always initalize BoringSSL to be on the safe side. - CRYPTO_library_init(); - - return (Sym s) => library.lookup(s.name); - } on ArgumentError { - // pass, we'll throw UnsupportedError a few lines further down. - } - throw UnsupportedError( 'package:webcrypto cannot be used from Dart or `pub run test` ' - 'unless `pub run webcrypto:setup` has been run for the current ' + 'unless `flutter pub run webcrypto:setup` has been run for the current ' 'root project.', ); }(); diff --git a/lib/src/boringssl/lookup/lookup_symbol_flutter.dart b/lib/src/boringssl/lookup/lookup_symbol_flutter.dart index befd338a..82f2bbab 100644 --- a/lib/src/boringssl/lookup/lookup_symbol_flutter.dart +++ b/lib/src/boringssl/lookup/lookup_symbol_flutter.dart @@ -33,7 +33,12 @@ final Pointer Function(Sym) lookupSymbol = () { .asFunction Function(int)>(); // Return a function from Sym to lookup using `webcrypto_lookup_symbol` - return (Sym s) => webcrypto_lookup_symbol(s.index); + final lookup = (Sym s) => webcrypto_lookup_symbol(s.index); + + // Initialize the dynamic linking with Dart. + initialize_dart_dl(lookup); + + return lookup; } on ArgumentError { final lookup = lookupLibraryInDotDartTool(); if (lookup != null) { diff --git a/lib/src/boringssl/lookup/symbols.generated.dart b/lib/src/boringssl/lookup/symbols.generated.dart index 3f11a14b..95fd6a8f 100644 --- a/lib/src/boringssl/lookup/symbols.generated.dart +++ b/lib/src/boringssl/lookup/symbols.generated.dart @@ -20,6 +20,8 @@ library symbols.generated; /// BoringSSL symbols used in `package:webcrypto`. enum Sym { + webcrypto_dart_dl_initialize, + webcrypto_dart_dl_attach_finalizer, BN_bin2bn, BN_bn2bin_padded, BN_free, @@ -109,8 +111,8 @@ enum Sym { EVP_PKEY_encrypt, EVP_PKEY_encrypt_init, EVP_PKEY_free, - EVP_PKEY_get0_EC_KEY, - EVP_PKEY_get0_RSA, + EVP_PKEY_get1_EC_KEY, + EVP_PKEY_get1_RSA, EVP_PKEY_id, EVP_PKEY_new, EVP_PKEY_set1_EC_KEY, @@ -159,6 +161,8 @@ enum Sym { } const _SymName = [ + 'webcrypto_dart_dl_initialize', + 'webcrypto_dart_dl_attach_finalizer', 'BN_bin2bn', 'BN_bn2bin_padded', 'BN_free', @@ -248,8 +252,8 @@ const _SymName = [ 'EVP_PKEY_encrypt', 'EVP_PKEY_encrypt_init', 'EVP_PKEY_free', - 'EVP_PKEY_get0_EC_KEY', - 'EVP_PKEY_get0_RSA', + 'EVP_PKEY_get1_EC_KEY', + 'EVP_PKEY_get1_RSA', 'EVP_PKEY_id', 'EVP_PKEY_new', 'EVP_PKEY_set1_EC_KEY', diff --git a/lib/src/boringssl/lookup/utils.dart b/lib/src/boringssl/lookup/utils.dart index 2c37e3c2..69b6d39e 100644 --- a/lib/src/boringssl/lookup/utils.dart +++ b/lib/src/boringssl/lookup/utils.dart @@ -34,7 +34,8 @@ String get libraryFileName { ); } -/// Look for the webcrypto binary library in the `.dart_tool/webcrypto/` folder. +/// Look for the webcrypto binary library in the `.dart_tool/webcrypto/` folder, +/// and initialize it with [initialize_dart_dl]. /// /// Returns `null` if it could not be found. Pointer Function(Sym) lookupLibraryInDotDartTool() { @@ -58,11 +59,35 @@ Pointer Function(Sym) lookupLibraryInDotDartTool() { .asFunction Function(int)>(); // Return a function from Sym to lookup using `webcrypto_lookup_symbol` - return (Sym s) => webcrypto_lookup_symbol(s.index); + final lookup = (Sym s) => webcrypto_lookup_symbol(s.index); + + // Initialize library + initialize_dart_dl(lookup); + + return lookup; } return null; } +/// Initialize library for use with Dart dynamic linking, by calling +/// `webcrypto_dart_dl_initialize`. +/// +/// This must be called before we start using the library. +void initialize_dart_dl(Pointer Function(Sym) lookup) { + final webcrypto_dart_dl_initialize = lookup(Sym.webcrypto_dart_dl_initialize) + .cast)>>() + .asFunction)>(); + + if (webcrypto_dart_dl_initialize(NativeApi.initializeApiDLData) != 1) { + throw UnsupportedError( + 'package:webcrypto does not work with this version of the Dart DL API.' + 'Please update to a newer version of package:webcrypto. And ensure that' + 'you have rebuilt the current version with ' + '`flutter pub run webcrypt:setup` if running locally.', + ); + } +} + /// Find the `.dart_tool/` folder, returns `null` if unable to find it. Uri _findDotDartTool() { // HACK: Because 'dart:isolate' is unavailable in Flutter we have no means diff --git a/lib/src/boringssl/webcrypto_dart_dl.dart b/lib/src/boringssl/webcrypto_dart_dl.dart new file mode 100644 index 00000000..1c86c58f --- /dev/null +++ b/lib/src/boringssl/webcrypto_dart_dl.dart @@ -0,0 +1,57 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ignore_for_file: non_constant_identifier_names + +/// This library maps symbols from: +/// src/webcrypto_dart_dl.h +library webcrypto_dart_dl; + +import 'dart:ffi'; +import 'lookup/lookup.dart'; + +/// Function pointer for de-allocation of a pointer, when attaching a finalizer +/// using webcrypto_attach_finalizer. +/// +/// ```c +/// void (*webcrypto_finalizer_t)(void*); +/// ``` +typedef webcrypto_finalizer_t = void Function(Pointer); + +/// Attach a finalizer for pointer to object, such that `finalizer(pointer)` will +/// be called when `object` is collected by the Dart garbage collector. +/// +/// The external_allocation_size is used by the Dart garbage collector as a hint +/// about the size of the external allocation. +/// +/// Returns 1 on success. +/// +/// ```c +/// int webcrypto_dart_dl_attach_finalizer(Dart_Handle object, +/// void* pointer, +/// webcrypto_finalizer_t finalizer, +/// intptr_t external_allocation_size); +/// ``` +final webcrypto_dart_dl_attach_finalizer = + resolve(Sym.webcrypto_dart_dl_attach_finalizer) + .lookupFunc< + Int32 Function( + Handle, + Pointer, + Pointer>, + IntPtr, + )>() + .asFunction< + int Function(Object, Pointer, + Pointer>, int)>(); diff --git a/lib/src/impl_ffi/impl_ffi.dart b/lib/src/impl_ffi/impl_ffi.dart index 55535861..463f59ab 100644 --- a/lib/src/impl_ffi/impl_ffi.dart +++ b/lib/src/impl_ffi/impl_ffi.dart @@ -26,6 +26,7 @@ import 'package:meta/meta.dart'; import '../jsonwebkey.dart' show JsonWebKey; import '../webcrypto/webcrypto.dart'; import '../boringssl/boringssl.dart' as ssl; +import '../boringssl/webcrypto_dart_dl.dart' as dl; part 'impl_ffi.aescbc.dart'; part 'impl_ffi.aesctr.dart'; diff --git a/lib/src/impl_ffi/impl_ffi.ec_common.dart b/lib/src/impl_ffi/impl_ffi.ec_common.dart index 6b044b8c..190180d6 100644 --- a/lib/src/impl_ffi/impl_ffi.ec_common.dart +++ b/lib/src/impl_ffi/impl_ffi.ec_common.dart @@ -69,22 +69,30 @@ void _validateEllipticCurveKey( ffi.Pointer key, EllipticCurve curve, ) { - _checkData(ssl.EVP_PKEY_id(key) == ssl.EVP_PKEY_EC, - message: 'key is not an EC key'); - - final ec = ssl.EVP_PKEY_get0_EC_KEY(key); - _checkData(ec.address != 0, fallback: 'key is not an EC key'); - _checkDataIsOne(ssl.EC_KEY_check_key(ec), fallback: 'invalid key'); - - // When importing BoringSSL will compute the public key if omitted, and - // leave a flag, such that exporting the private key won't include the - // public key. - final encFlags = ssl.EC_KEY_get_enc_flags(ec); - ssl.EC_KEY_set_enc_flags(ec, encFlags & ~ssl.EC_PKEY_NO_PUBKEY); - - // Check the curve of the imported key - final nid = ssl.EC_GROUP_get_curve_name(ssl.EC_KEY_get0_group(ec)); - _checkData(_ecCurveToNID(curve) == nid, message: 'incorrect elliptic curve'); + final scope = _Scope(); + try { + _checkData(ssl.EVP_PKEY_id(key) == ssl.EVP_PKEY_EC, + message: 'key is not an EC key'); + + final ec = ssl.EVP_PKEY_get1_EC_KEY(key); + _checkData(ec.address != 0, fallback: 'key is not an EC key'); + scope.defer(() => ssl.EC_KEY_free(ec)); + + _checkDataIsOne(ssl.EC_KEY_check_key(ec), fallback: 'invalid key'); + + // When importing BoringSSL will compute the public key if omitted, and + // leave a flag, such that exporting the private key won't include the + // public key. + final encFlags = ssl.EC_KEY_get_enc_flags(ec); + ssl.EC_KEY_set_enc_flags(ec, encFlags & ~ssl.EC_PKEY_NO_PUBKEY); + + // Check the curve of the imported key + final nid = ssl.EC_GROUP_get_curve_name(ssl.EC_KEY_get0_group(ec)); + _checkData(_ecCurveToNID(curve) == nid, + message: 'incorrect elliptic curve'); + } finally { + scope.release(); + } } ffi.Pointer _importPkcs8EcPrivateKey( @@ -93,15 +101,10 @@ ffi.Pointer _importPkcs8EcPrivateKey( ) { final key = _withDataAsCBS(keyData, ssl.EVP_parse_private_key); _checkData(key.address != 0, fallback: 'unable to parse key'); + _attachFinalizerEVP_PKEY(key); - try { - _validateEllipticCurveKey(key, curve); - return key; - } catch (_) { - // We only free key if an exception/error was thrown - ssl.EVP_PKEY_free(key); - rethrow; - } + _validateEllipticCurveKey(key, curve); + return key; } ffi.Pointer _importSpkiEcPublicKey( @@ -113,16 +116,11 @@ ffi.Pointer _importSpkiEcPublicKey( // a FormatException. Notice that this the case for private/public keys, and RSA keys. final key = _withDataAsCBS(keyData, ssl.EVP_parse_public_key); _checkData(key.address != 0, fallback: 'unable to parse key'); + _attachFinalizerEVP_PKEY(key); - try { - _validateEllipticCurveKey(key, curve); + _validateEllipticCurveKey(key, curve); - return key; - } catch (_) { - // We only free key if an exception/error was thrown - ssl.EVP_PKEY_free(key); - rethrow; - } + return key; } ffi.Pointer _importJwkEcPrivateOrPublicKey( @@ -211,10 +209,10 @@ ffi.Pointer _importJwkEcPrivateOrPublicKey( _checkDataIsOne(ssl.EC_KEY_check_key(ec), fallback: 'invalid EC key'); // Wrap with an EVP_KEY - final key = scope.create(ssl.EVP_PKEY_new, ssl.EVP_PKEY_free); + final key = _createEVP_PKEYwithFinalizer(); _checkOpIsOne(ssl.EVP_PKEY_set1_EC_KEY(key, ec)); - return scope.move(key); + return key; } finally { scope.release(); } @@ -246,15 +244,12 @@ ffi.Pointer _importRawEcPublicKey( // Copy pub point to ec _checkDataIsOne(ssl.EC_KEY_set_public_key(ec, pub), fallback: 'invalid keyData'); - final key = ssl.EVP_PKEY_new(); - try { - _checkOpIsOne(ssl.EVP_PKEY_set1_EC_KEY(key, ec)); - _validateEllipticCurveKey(key, curve); - return key; - } catch (_) { - ssl.EVP_PKEY_free(key); - rethrow; - } + + final key = _createEVP_PKEYwithFinalizer(); + _checkOpIsOne(ssl.EVP_PKEY_set1_EC_KEY(key, ec)); + _validateEllipticCurveKey(key, curve); + + return key; } finally { ssl.EC_POINT_free(pub); } @@ -264,20 +259,26 @@ ffi.Pointer _importRawEcPublicKey( } Uint8List _exportRawEcPublicKey(ffi.Pointer key) { - final ec = ssl.EVP_PKEY_get0_EC_KEY(key); - _checkOp(ec.address != null, fallback: 'internal key type invariant error'); - - return _withOutCBB((cbb) { - return _checkOpIsOne( - ssl.EC_POINT_point2cbb( - cbb, - ssl.EC_KEY_get0_group(ec), - ssl.EC_KEY_get0_public_key(ec), - ssl.POINT_CONVERSION_UNCOMPRESSED, - ffi.nullptr, - ), - fallback: 'formatting failed'); - }); + final scope = _Scope(); + try { + final ec = ssl.EVP_PKEY_get1_EC_KEY(key); + _checkOp(ec.address != null, fallback: 'internal key type invariant error'); + scope.defer(() => ssl.EC_KEY_free(ec)); + + return _withOutCBB((cbb) { + return _checkOpIsOne( + ssl.EC_POINT_point2cbb( + cbb, + ssl.EC_KEY_get0_group(ec), + ssl.EC_KEY_get0_public_key(ec), + ssl.POINT_CONVERSION_UNCOMPRESSED, + ffi.nullptr, + ), + fallback: 'formatting failed'); + }); + } finally { + scope.release(); + } } Map _exportJwkEcPrivateOrPublicKey( @@ -289,8 +290,9 @@ Map _exportJwkEcPrivateOrPublicKey( final scope = _Scope(); try { - final ec = ssl.EVP_PKEY_get0_EC_KEY(key); + final ec = ssl.EVP_PKEY_get1_EC_KEY(key); _checkOp(ec.address != 0, fallback: 'internal key type invariant error'); + scope.defer(() => ssl.EC_KEY_free(ec)); final group = ssl.EC_KEY_get0_group(ec); final curve = _ecCurveFromNID(ssl.EC_GROUP_get_curve_name(group)); @@ -349,7 +351,7 @@ KeyPair, ffi.Pointer> _checkOpIsOne(ssl.EC_KEY_generate_key(ecPriv)); - final privKey = scope.create(ssl.EVP_PKEY_new, ssl.EVP_PKEY_free); + final privKey = _createEVP_PKEYwithFinalizer(); _checkOpIsOne(ssl.EVP_PKEY_set1_EC_KEY(privKey, ecPriv)); final ecPub = ssl.EC_KEY_new_by_curve_name(_ecCurveToNID(curve)); @@ -360,12 +362,12 @@ KeyPair, ffi.Pointer> ssl.EC_KEY_get0_public_key(ecPriv), )); - final pubKey = scope.create(ssl.EVP_PKEY_new, ssl.EVP_PKEY_free); + final pubKey = _createEVP_PKEYwithFinalizer(); _checkOpIsOne(ssl.EVP_PKEY_set1_EC_KEY(pubKey, ecPub)); return _KeyPair( - privateKey: scope.move(privKey), - publicKey: scope.move(pubKey), + privateKey: privKey, + publicKey: pubKey, ); } finally { scope.release(); diff --git a/lib/src/impl_ffi/impl_ffi.ecdh.dart b/lib/src/impl_ffi/impl_ffi.ecdh.dart index cdb29e59..314e0d2b 100644 --- a/lib/src/impl_ffi/impl_ffi.ecdh.dart +++ b/lib/src/impl_ffi/impl_ffi.ecdh.dart @@ -66,16 +66,11 @@ Future ecdhPublicKey_importJsonWebKey( expectedAlg: null, // ECDH has no validation of 'jwk.alg' )); -class _EcdhPrivateKey with _Disposable implements EcdhPrivateKey { +class _EcdhPrivateKey implements EcdhPrivateKey { final ffi.Pointer _key; _EcdhPrivateKey(this._key); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future deriveBits(int length, EcdhPublicKey publicKey) async { ArgumentError.checkNotNull(length, 'length'); @@ -90,62 +85,74 @@ class _EcdhPrivateKey with _Disposable implements EcdhPrivateKey { if (length <= 0) { throw ArgumentError.value(length, 'length', 'must be positive'); } - final _publicKey = publicKey as _EcdhPublicKey; - - final pubEcKey = ssl.EVP_PKEY_get0_EC_KEY(_publicKey._key); - final privEcKey = ssl.EVP_PKEY_get0_EC_KEY(_key); - - // Check that public/private key uses the same elliptic curve. - if (ssl.EC_GROUP_get_curve_name(ssl.EC_KEY_get0_group(pubEcKey)) != - ssl.EC_GROUP_get_curve_name(ssl.EC_KEY_get0_group(privEcKey))) { - // Note: web crypto will throw an InvalidAccessError here. - throw ArgumentError.value( - publicKey, - 'publicKey', - 'Public and private key for ECDH key derivation have the same ' - 'elliptic curve', - ); - } - // Field size rounded up to 8 bits is the maximum number of bits we can - // derive. The most significant bits will be zero in this case. - final fieldSize = ssl.EC_GROUP_get_degree(ssl.EC_KEY_get0_group(privEcKey)); - final maxLength = 8 * (fieldSize / 8).ceil(); - if (length > maxLength) { - throw _OperationError( - 'Length in ECDH key derivation is too large. ' - 'Maximum allowed is $maxLength bits.', - ); + final scope = _Scope(); + try { + final _publicKey = publicKey as _EcdhPublicKey; + + final pubEcKey = ssl.EVP_PKEY_get1_EC_KEY(_publicKey._key); + _checkOp(pubEcKey.address != 0, fallback: 'not an ec key'); + scope.defer(() => ssl.EC_KEY_free(pubEcKey)); + + final privEcKey = ssl.EVP_PKEY_get1_EC_KEY(_key); + _checkOp(privEcKey.address != 0, fallback: 'not an ec key'); + scope.defer(() => ssl.EC_KEY_free(privEcKey)); + + // Check that public/private key uses the same elliptic curve. + if (ssl.EC_GROUP_get_curve_name(ssl.EC_KEY_get0_group(pubEcKey)) != + ssl.EC_GROUP_get_curve_name(ssl.EC_KEY_get0_group(privEcKey))) { + // Note: web crypto will throw an InvalidAccessError here. + throw ArgumentError.value( + publicKey, + 'publicKey', + 'Public and private key for ECDH key derivation have the same ' + 'elliptic curve', + ); + } + + // Field size rounded up to 8 bits is the maximum number of bits we can + // derive. The most significant bits will be zero in this case. + final fieldSize = + ssl.EC_GROUP_get_degree(ssl.EC_KEY_get0_group(privEcKey)); + final maxLength = 8 * (fieldSize / 8).ceil(); + if (length > maxLength) { + throw _OperationError( + 'Length in ECDH key derivation is too large. ' + 'Maximum allowed is $maxLength bits.', + ); + } + + if (length == 0) { + return Uint8List.fromList([]); + } + + final lengthInBytes = (length / 8).ceil(); + final derived = _withOutPointer(lengthInBytes, (ffi.Pointer p) { + final outLen = ssl.ECDH_compute_key( + p, + lengthInBytes, + ssl.EC_KEY_get0_public_key(pubEcKey), + privEcKey, + ffi.nullptr, + ); + _checkOp(outLen != -1, fallback: 'ECDH key derivation failed'); + _checkOp( + outLen == lengthInBytes, + message: 'internal error in ECDH key derivation', + ); + }); + + // Only return the first [length] bits from derived. + final zeroBits = lengthInBytes * 8 - length; + assert(zeroBits < 8); + if (zeroBits > 0) { + derived.last &= ((0xff << zeroBits) & 0xff); + } + + return derived; + } finally { + scope.release(); } - - if (length == 0) { - return Uint8List.fromList([]); - } - - final lengthInBytes = (length / 8).ceil(); - final derived = _withOutPointer(lengthInBytes, (ffi.Pointer p) { - final outLen = ssl.ECDH_compute_key( - p, - lengthInBytes, - ssl.EC_KEY_get0_public_key(pubEcKey), - privEcKey, - ffi.nullptr, - ); - _checkOp(outLen != -1, fallback: 'ECDH key derivation failed'); - _checkOp( - outLen == lengthInBytes, - message: 'internal error in ECDH key derivation', - ); - }); - - // Only return the first [length] bits from derived. - final zeroBits = lengthInBytes * 8 - length; - assert(zeroBits < 8); - if (zeroBits > 0) { - derived.last &= ((0xff << zeroBits) & 0xff); - } - - return derived; } @override @@ -164,16 +171,11 @@ class _EcdhPrivateKey with _Disposable implements EcdhPrivateKey { } } -class _EcdhPublicKey with _Disposable implements EcdhPublicKey { +class _EcdhPublicKey implements EcdhPublicKey { final ffi.Pointer _key; _EcdhPublicKey(this._key); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future> exportJsonWebKey() async => // Neither Chrome or Firefox produces 'use': 'enc' for ECDH, we choose to diff --git a/lib/src/impl_ffi/impl_ffi.ecdsa.dart b/lib/src/impl_ffi/impl_ffi.ecdsa.dart index d7f7b876..c91b47d2 100644 --- a/lib/src/impl_ffi/impl_ffi.ecdsa.dart +++ b/lib/src/impl_ffi/impl_ffi.ecdsa.dart @@ -92,12 +92,18 @@ Uint8List _convertEcdsaDerSignatureToWebCryptoSignature( ffi.Pointer key, Uint8List signature, ) { - final ecdsa = _withDataAsCBS(signature, ssl.ECDSA_SIG_parse); - _checkOp(ecdsa.address != 0, message: 'internal error formatting signature'); + final scope = _Scope(); try { + final ecdsa = _withDataAsCBS(signature, ssl.ECDSA_SIG_parse); + _checkOp(ecdsa.address != 0, + message: 'internal error formatting signature'); + scope.defer(() => ssl.ECDSA_SIG_free(ecdsa)); + // Read EC key and get the number of bytes required to encode R and S. - final ec = ssl.EVP_PKEY_get0_EC_KEY(key); + final ec = ssl.EVP_PKEY_get1_EC_KEY(key); _checkOp(ec.address != 0, message: 'internal key type invariant violation'); + scope.defer(() => ssl.EC_KEY_free(ec)); + final N = ssl.BN_num_bytes(ssl.EC_GROUP_get0_order(ssl.EC_KEY_get0_group( ec, ))); @@ -121,7 +127,7 @@ Uint8List _convertEcdsaDerSignatureToWebCryptoSignature( }); }); } finally { - ssl.ECDSA_SIG_free(ecdsa); + scope.release(); } } @@ -135,23 +141,29 @@ Uint8List _convertEcdsaWebCryptoSignatureToDerSignature( ffi.Pointer key, Uint8List signature, ) { - // Read EC key and get the number of bytes required to encode R and S. - final ec = ssl.EVP_PKEY_get0_EC_KEY(key); - _checkOp(ec.address != 0, message: 'internal key type invariant violation'); - final N = ssl.BN_num_bytes(ssl.EC_GROUP_get0_order(ssl.EC_KEY_get0_group( - ec, - ))); - - if (N * 2 != signature.length) { - // If the signature format is invalid we consider the signature invalid and - // return false from verification method. This follows: - // https://chromium.googlesource.com/chromium/src/+/43d62c50b705f88c67b14539e91fd8fd017f70c4/components/webcrypto/algorithms/ecdsa.cc#111 - return null; - } - - final ecdsa = ssl.ECDSA_SIG_new(); - _checkOp(ecdsa.address != 0, message: 'internal error formatting signature'); + final scope = _Scope(); try { + // Read EC key and get the number of bytes required to encode R and S. + final ec = ssl.EVP_PKEY_get1_EC_KEY(key); + _checkOp(ec.address != 0, message: 'internal key type invariant violation'); + scope.defer(() => ssl.EC_KEY_free(ec)); + + final N = ssl.BN_num_bytes(ssl.EC_GROUP_get0_order(ssl.EC_KEY_get0_group( + ec, + ))); + + if (N * 2 != signature.length) { + // If the signature format is invalid we consider the signature invalid and + // return false from verification method. This follows: + // https://chromium.googlesource.com/chromium/src/+/43d62c50b705f88c67b14539e91fd8fd017f70c4/components/webcrypto/algorithms/ecdsa.cc#111 + return null; + } + + final ecdsa = ssl.ECDSA_SIG_new(); + _checkOp(ecdsa.address != 0, + message: 'internal error formatting signature'); + scope.defer(() => ssl.ECDSA_SIG_free(ecdsa)); + return _withAllocation(2, (ffi.Pointer> RS) { // Access R and S from the ecdsa signature final R = RS.elementAt(0); @@ -176,20 +188,15 @@ Uint8List _convertEcdsaWebCryptoSignatureToDerSignature( )); }); } finally { - ssl.ECDSA_SIG_free(ecdsa); + scope.release(); } } -class _EcdsaPrivateKey with _Disposable implements EcdsaPrivateKey { +class _EcdsaPrivateKey implements EcdsaPrivateKey { final ffi.Pointer _key; _EcdsaPrivateKey(this._key); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future signBytes(List data, Hash hash) { ArgumentError.checkNotNull(data, 'data'); @@ -232,16 +239,11 @@ class _EcdsaPrivateKey with _Disposable implements EcdsaPrivateKey { } } -class _EcdsaPublicKey with _Disposable implements EcdsaPublicKey { +class _EcdsaPublicKey implements EcdsaPublicKey { final ffi.Pointer _key; _EcdsaPublicKey(this._key); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future verifyBytes(List signature, List data, Hash hash) { ArgumentError.checkNotNull(signature, 'signature'); diff --git a/lib/src/impl_ffi/impl_ffi.rsa_common.dart b/lib/src/impl_ffi/impl_ffi.rsa_common.dart index 3ca323d7..5461b154 100644 --- a/lib/src/impl_ffi/impl_ffi.rsa_common.dart +++ b/lib/src/impl_ffi/impl_ffi.rsa_common.dart @@ -15,42 +15,46 @@ part of impl_ffi; ffi.Pointer _importPkcs8RsaPrivateKey(List keyData) { - final key = _withDataAsCBS(keyData, ssl.EVP_parse_private_key); - _checkData(key.address != 0, fallback: 'unable to parse key'); - + final scope = _Scope(); try { + final key = _withDataAsCBS(keyData, ssl.EVP_parse_private_key); + _checkData(key.address != 0, fallback: 'unable to parse key'); + _attachFinalizerEVP_PKEY(key); + _checkData(ssl.EVP_PKEY_id(key) == ssl.EVP_PKEY_RSA, message: 'key is not an RSA key'); - final rsa = ssl.EVP_PKEY_get0_RSA(key); + final rsa = ssl.EVP_PKEY_get1_RSA(key); _checkData(rsa.address != 0, fallback: 'key is not an RSA key'); + scope.defer(() => ssl.RSA_free(rsa)); + _checkData(ssl.RSA_check_key(rsa) == 1, fallback: 'invalid key'); return key; - } catch (_) { - // We only free key if an exception/error was thrown - ssl.EVP_PKEY_free(key); - rethrow; + } finally { + scope.release(); } } ffi.Pointer _importSpkiRsaPublicKey(List keyData) { - final key = _withDataAsCBS(keyData, ssl.EVP_parse_public_key); - _checkData(key.address != 0, fallback: 'unable to parse key'); - + final scope = _Scope(); try { + final key = _withDataAsCBS(keyData, ssl.EVP_parse_public_key); + _checkData(key.address != 0, fallback: 'unable to parse key'); + _attachFinalizerEVP_PKEY(key); + _checkData(ssl.EVP_PKEY_id(key) == ssl.EVP_PKEY_RSA, message: 'key is not an RSA key'); - final rsa = ssl.EVP_PKEY_get0_RSA(key); + final rsa = ssl.EVP_PKEY_get1_RSA(key); _checkData(rsa.address != 0, fallback: 'key is not an RSA key'); + scope.defer(() => ssl.RSA_free(rsa)); + _checkData(ssl.RSA_check_key(rsa) == 1, fallback: 'invalid key'); return key; - } catch (_) { - // We only free key if an exception/error was thrown - ssl.EVP_PKEY_free(key); - rethrow; + } finally { + scope.release(); } } @@ -146,10 +150,10 @@ ffi.Pointer _importJwkRsaPrivateOrPublicKey( _checkDataIsOne(ssl.RSA_check_key(rsa), fallback: 'invalid RSA key'); - final key = scope.create(ssl.EVP_PKEY_new, ssl.EVP_PKEY_free); + final key = _createEVP_PKEYwithFinalizer(); _checkOpIsOne(ssl.EVP_PKEY_set1_RSA(key, rsa)); - return scope.move(key); + return key; } finally { scope.release(); } @@ -167,8 +171,9 @@ Map _exportJwkRsaPrivateOrPublicKey( final scope = _Scope(); try { - final rsa = ssl.EVP_PKEY_get0_RSA(key); + final rsa = ssl.EVP_PKEY_get1_RSA(key); _checkOp(rsa.address != 0, fallback: 'internal key type error'); + scope.defer(() => ssl.RSA_free(rsa)); String encodeBN(ffi.Pointer bn) { final N = ssl.BN_num_bytes(bn); @@ -250,52 +255,39 @@ _KeyPair, ffi.Pointer> throw UnsupportedError('publicExponent is not supported, try 3 or 65537'); } - ffi.Pointer privRSA, pubRSA; - ffi.Pointer privKey, pubKey; + final scope = _Scope(); try { // Generate private RSA key - privRSA = ssl.RSA_new(); - _checkOp(privRSA.address != 0, fallback: 'allocation failure'); - _withBIGNUM((e) { - _checkOp(ssl.BN_set_word(e, publicExponent.toInt()) == 1); - _checkOp( - ssl.RSA_generate_key_ex(privRSA, modulusLength, e, ffi.nullptr) == 1); - }); + final privRSA = scope.create(ssl.RSA_new, ssl.RSA_free); + + final e = scope.create(ssl.BN_new, ssl.BN_free); + _checkOpIsOne(ssl.BN_set_word(e, publicExponent.toInt())); + _checkOpIsOne(ssl.RSA_generate_key_ex( + privRSA, + modulusLength, + e, + ffi.nullptr, + )); // Copy out the public RSA key - final pubRSA = ssl.RSAPublicKey_dup(privRSA); - _checkOp(pubRSA.address != 0); + final pubRSA = scope.create( + () => ssl.RSAPublicKey_dup(privRSA), + ssl.RSA_free, + ); // Create private key - privKey = ssl.EVP_PKEY_new(); - _checkOp(privKey.address != 0, fallback: 'allocation failure'); + final privKey = _createEVP_PKEYwithFinalizer(); _checkOp(ssl.EVP_PKEY_set1_RSA(privKey, privRSA) == 1); // Create public key - pubKey = ssl.EVP_PKEY_new(); - _checkOp(pubKey.address != 0, fallback: 'allocation failure'); + final pubKey = _createEVP_PKEYwithFinalizer(); _checkOp(ssl.EVP_PKEY_set1_RSA(pubKey, pubRSA) == 1); return _KeyPair( privateKey: privKey, publicKey: pubKey, ); - } catch (_) { - // Free privKey/pubKey on exception - if (privKey != null) { - ssl.EVP_PKEY_free(privKey); - } - if (pubKey != null) { - ssl.EVP_PKEY_free(pubKey); - } - rethrow; } finally { - // Always free RSA keys, we create a new reference with set1 method - if (privRSA != null) { - ssl.RSA_free(privRSA); - } - if (pubRSA != null) { - ssl.RSA_free(pubRSA); - } + scope.release(); } } diff --git a/lib/src/impl_ffi/impl_ffi.rsaoaep.dart b/lib/src/impl_ffi/impl_ffi.rsaoaep.dart index df4d2b59..9fcf22ff 100644 --- a/lib/src/impl_ffi/impl_ffi.rsaoaep.dart +++ b/lib/src/impl_ffi/impl_ffi.rsaoaep.dart @@ -176,17 +176,12 @@ Future _rsaOaepeEncryptOrDecryptBytes( } } -class _RsaOaepPrivateKey with _Disposable implements RsaOaepPrivateKey { +class _RsaOaepPrivateKey implements RsaOaepPrivateKey { final ffi.Pointer _key; final _Hash _hash; _RsaOaepPrivateKey(this._key, this._hash); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future decryptBytes(List data, {List label}) async { ArgumentError.checkNotNull(data, 'data'); @@ -217,17 +212,12 @@ class _RsaOaepPrivateKey with _Disposable implements RsaOaepPrivateKey { } } -class _RsaOaepPublicKey with _Disposable implements RsaOaepPublicKey { +class _RsaOaepPublicKey implements RsaOaepPublicKey { final ffi.Pointer _key; final _Hash _hash; _RsaOaepPublicKey(this._key, this._hash); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future encryptBytes(List data, {List label}) async { ArgumentError.checkNotNull(data, 'data'); diff --git a/lib/src/impl_ffi/impl_ffi.rsapss.dart b/lib/src/impl_ffi/impl_ffi.rsapss.dart index de709acd..8cf32f5b 100644 --- a/lib/src/impl_ffi/impl_ffi.rsapss.dart +++ b/lib/src/impl_ffi/impl_ffi.rsapss.dart @@ -97,17 +97,12 @@ Future rsaPssPublicKey_importJsonWebKey( ); } -class _RsaPssPrivateKey with _Disposable implements RsaPssPrivateKey { +class _RsaPssPrivateKey implements RsaPssPrivateKey { final ffi.Pointer _key; final _Hash _hash; _RsaPssPrivateKey(this._key, this._hash); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future signBytes(List data, int saltLength) { ArgumentError.checkNotNull(data, 'data'); @@ -170,17 +165,12 @@ class _RsaPssPrivateKey with _Disposable implements RsaPssPrivateKey { } } -class _RsaPssPublicKey with _Disposable implements RsaPssPublicKey { +class _RsaPssPublicKey implements RsaPssPublicKey { final ffi.Pointer _key; final _Hash _hash; _RsaPssPublicKey(this._key, this._hash); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future verifyBytes( List signature, diff --git a/lib/src/impl_ffi/impl_ffi.rsassapkcs1v15.dart b/lib/src/impl_ffi/impl_ffi.rsassapkcs1v15.dart index df444364..ab97e456 100644 --- a/lib/src/impl_ffi/impl_ffi.rsassapkcs1v15.dart +++ b/lib/src/impl_ffi/impl_ffi.rsassapkcs1v15.dart @@ -98,19 +98,12 @@ Future rsassaPkcs1V15PublicKey_importJsonWebKey( ); } -class _RsassaPkcs1V15PrivateKey - with _Disposable - implements RsassaPkcs1V15PrivateKey { +class _RsassaPkcs1V15PrivateKey implements RsassaPkcs1V15PrivateKey { final ffi.Pointer _key; final _Hash _hash; _RsassaPkcs1V15PrivateKey(this._key, this._hash); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future signBytes(List data) { ArgumentError.checkNotNull(data, 'data'); @@ -158,19 +151,12 @@ class _RsassaPkcs1V15PrivateKey } } -class _RsassaPkcs1V15PublicKey - with _Disposable - implements RsassaPkcs1V15PublicKey { +class _RsassaPkcs1V15PublicKey implements RsassaPkcs1V15PublicKey { final ffi.Pointer _key; final _Hash _hash; _RsassaPkcs1V15PublicKey(this._key, this._hash); - @override - void _finalize() { - ssl.EVP_PKEY_free(_key); - } - @override Future verifyBytes(List signature, List data) { ArgumentError.checkNotNull(signature, 'signature'); diff --git a/lib/src/impl_ffi/impl_ffi.utils.dart b/lib/src/impl_ffi/impl_ffi.utils.dart index aeddbc8b..7dfc3d0c 100644 --- a/lib/src/impl_ffi/impl_ffi.utils.dart +++ b/lib/src/impl_ffi/impl_ffi.utils.dart @@ -14,15 +14,40 @@ part of impl_ffi; -/// Mixin for classes that needs to be finalized. -abstract class _Disposable { - // TODO: implement finalizer, when supported in dart:ffi - - @protected - void _finalize(); +/// Attach a finalizer for [key], this means that [ssl.EVP_PKEY_free] will +/// automatically be called with [key] is garbage collected by Dart. +/// +/// This takes ownership of [key], and the caller **may not** call +/// [ssl.EVP_PKEY_free]. +/// +/// Callers should be ware that the Dart GC may collect [key] as soon as it +/// deems the object to not be in use anymore. This can happy at any point when +/// the VM (optimizer) determines that no code-path passing [key] to an FFI +/// function can be called again. For this reason, users should take extra care +/// to make sure that all accesses to [key] takes an extra reference. +void _attachFinalizerEVP_PKEY(ffi.Pointer key) { + final ret = dl.webcrypto_dart_dl_attach_finalizer( + key, + key.cast(), + ssl.EVP_PKEY_free_.cast(), + // We don't really have an estimate of how much space the EVP_PKEY structure + // takes up, but if we make it some non-trivial size then hopefully the GC + // will prioritize freeing them. + 4096, + ); + if (ret != 1) { + throw AssertionError('package:webcrypto failed to attached finalizer'); + } +} - // ignore: unused_element - static void dispose(_Disposable obj) => obj._finalize(); +/// Create an [ssl.EVP_PKEY] with finalizer attached. +/// +/// See [_attachFinalizerEVP_PKEY] for notes on how the finalizer works. +ffi.Pointer _createEVP_PKEYwithFinalizer() { + final key = ssl.EVP_PKEY_new(); + _checkOp(key.address != 0, fallback: 'allocation failure'); + _attachFinalizerEVP_PKEY(key); + return key; } /// Throw [OperationError] if [condition] is `false`. @@ -326,18 +351,6 @@ Uint8List _withOutCBB(void Function(ffi.Pointer) fn) { }); } -/// Invoke [fn] with a new [ffi.Pointer] instance that is released -/// when [fn] returns. -R _withBIGNUM(R Function(ffi.Pointer) fn) { - final bn = ssl.BN_new(); - _checkOp(bn.address != 0, fallback: 'allocation failure'); - try { - return fn(bn); - } finally { - ssl.BN_free(bn); - } -} - /// Convert [Stream>] to [Uint8List]. Future _bufferStream(Stream> data) async { ArgumentError.checkNotNull(data, 'data'); diff --git a/pubspec.yaml b/pubspec.yaml index ac1d60c7..29f12c3a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,13 +13,13 @@ # limitations under the License. name: webcrypto -version: 0.2.0 +version: 0.2.1 description: Cross-platform implementation of Web Cryptography APIs for Flutter. homepage: https://github.com/google/webcrypto.dart environment: sdk: '>=2.8.0 <3.0.0' - flutter: '>=1.17.0 <2.0.0' + flutter: '>=1.22.0-12.1.pre <2.0.0' dependencies: ffi: ^0.1.3 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2a0ad130..b2a61dba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,6 +114,7 @@ add_library( # Source files ../src/webcrypto.c + ../src/webcrypto_dart_dl.c ../src/symbols.generated.c ${dart_dl_sources} ${crypto_sources} diff --git a/src/symbols.generated.c b/src/symbols.generated.c index 2240de3a..43fcef67 100644 --- a/src/symbols.generated.c +++ b/src/symbols.generated.c @@ -22,6 +22,8 @@ #include "symbols.h" void* _webcrypto_symbol_table[] = { + (void*)&webcrypto_dart_dl_initialize, + (void*)&webcrypto_dart_dl_attach_finalizer, (void*)&BN_bin2bn, (void*)&BN_bn2bin_padded, (void*)&BN_free, @@ -111,8 +113,8 @@ void* _webcrypto_symbol_table[] = { (void*)&EVP_PKEY_encrypt, (void*)&EVP_PKEY_encrypt_init, (void*)&EVP_PKEY_free, - (void*)&EVP_PKEY_get0_EC_KEY, - (void*)&EVP_PKEY_get0_RSA, + (void*)&EVP_PKEY_get1_EC_KEY, + (void*)&EVP_PKEY_get1_RSA, (void*)&EVP_PKEY_id, (void*)&EVP_PKEY_new, (void*)&EVP_PKEY_set1_EC_KEY, diff --git a/src/symbols.h b/src/symbols.h index e15d718b..685c3b48 100644 --- a/src/symbols.h +++ b/src/symbols.h @@ -19,6 +19,8 @@ #include +#include "webcrypto_dart_dl.h" + // BoringSSL headers #include #include diff --git a/src/symbols.yaml b/src/symbols.yaml index 3c360b0c..5b642198 100644 --- a/src/symbols.yaml +++ b/src/symbols.yaml @@ -42,6 +42,8 @@ # - `lib/src/boringssl/lookup/symbols.generated.dart` # will be generated using `tool/generate_symbols_table.dart`, using the symbols # listed in this file. +- webcrypto_dart_dl_initialize +- webcrypto_dart_dl_attach_finalizer - BN_bin2bn - BN_bn2bin_padded - BN_free @@ -131,8 +133,8 @@ - EVP_PKEY_encrypt - EVP_PKEY_encrypt_init - EVP_PKEY_free -- EVP_PKEY_get0_EC_KEY -- EVP_PKEY_get0_RSA +- EVP_PKEY_get1_EC_KEY +- EVP_PKEY_get1_RSA - EVP_PKEY_id - EVP_PKEY_new - EVP_PKEY_set1_EC_KEY diff --git a/src/webcrypto.c b/src/webcrypto.c index 1cbfd347..69e68935 100644 --- a/src/webcrypto.c +++ b/src/webcrypto.c @@ -14,9 +14,9 @@ * limitations under the License. */ +#include #include -#include "include/dart_api_dl.h" #include "symbols.h" // Macro for annotating all functions to be exported diff --git a/src/webcrypto_dart_dl.c b/src/webcrypto_dart_dl.c new file mode 100644 index 00000000..63b7953d --- /dev/null +++ b/src/webcrypto_dart_dl.c @@ -0,0 +1,72 @@ +#include "webcrypto_dart_dl.h" + +#include + +// See webcrypto_dart_dl.h +int webcrypto_dart_dl_initialize(void* initialize_api_dl_data) { + if (Dart_InitializeApiDL(initialize_api_dl_data) != 0) { + return -1; + } + // Check symbols used are present + if (Dart_NewFinalizableHandle_DL == NULL) { + return -1; + } + return 1; +} + +// peer attached +typedef struct _finalizable_pointer { + void* pointer; + webcrypto_finalizer_t finalizer; +} _finalizable_pointer; + +// Callback from Dart_NewFinalizableHandle_DL when we have attached a finalizer +// to some Dart object. +void _webcrypto_finalizer_callback(void* isolate_callback_data, void* peer) { + _finalizable_pointer* p = (_finalizable_pointer*)peer; + + // If pointer or finalizer is NULL, we've already deallocated, we can assert + // that this doesn't happen. + assert(p->pointer != NULL); + assert(p->finalizer != NULL); + if (p->pointer == NULL || p->finalizer == NULL) { + // Abort if this happens in production where we have no asserts. + return; + } + + // Call the finalizer + p->finalizer(p->pointer); + + // Ensure that assertions will trigger upon double deallocation. + p->pointer = NULL; + p->finalizer = NULL; + + // Free the peer + OPENSSL_free(p); +} + +// See webcrypto_dart_dl.h +int webcrypto_dart_dl_attach_finalizer(Dart_Handle object, + void* pointer, + webcrypto_finalizer_t finalizer, + intptr_t external_allocation_size) { + // Create a _finalizable_pointer to be attached as peer. + _finalizable_pointer* peer = OPENSSL_malloc(sizeof(_finalizable_pointer)); + peer->finalizer = finalizer; + peer->pointer = pointer; + + // Attaced peer and _webcrypto_finalizer_callback + Dart_FinalizableHandle handle; + // NOTE: we have check the availability of Dart_NewFinalizableHandle_DL in + // webcrypto_dart_dl_initialize. + handle = Dart_NewFinalizableHandle_DL(object, (void*)peer, + external_allocation_size, + &_webcrypto_finalizer_callback); + + // Check if the operation was successful + if (handle == NULL) { + OPENSSL_free(peer); + return -1; + } + return 1; +} diff --git a/src/webcrypto_dart_dl.h b/src/webcrypto_dart_dl.h new file mode 100644 index 00000000..a6ac4c7a --- /dev/null +++ b/src/webcrypto_dart_dl.h @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FINALIZER_H +#define FINALIZER_H + +#include +#include "include/dart_api_dl.h" + +// Initialize Dart API with dynamic linking. +// +// Must be called with `NativeApi.initializeApiDLData` from `dart:ffi`, before +// using other functions. +// +// Returns 1 on success. +int webcrypto_dart_dl_initialize(void* initialize_api_dl_data); + +// Function pointer for de-allocation of a pointer, when attaching a finalizer +// using webcrypto_attach_finalizer. +typedef void (*webcrypto_finalizer_t)(void*); + +// Attach a finalizer for pointer to object, such that `finalizer(pointer)` will +// be called when `object` is collected by the Dart garbage collector. +// +// The external_allocation_size is used by the Dart garbage collector as a hint +// about the size of the external allocation. +// +// Returns 1 on success. +int webcrypto_dart_dl_attach_finalizer(Dart_Handle object, + void* pointer, + webcrypto_finalizer_t finalizer, + intptr_t external_allocation_size); + +#endif // FINALIZER_H