Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement webcrypto import ECDSA key and export as SPKI #200

Merged
merged 49 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
fc270b7
Update README
ospfranco Nov 28, 2023
e7a886b
Make test UI a bit nicer and fix an unhandled error on randomFill
ospfranco Nov 28, 2023
4153b5a
Fix some tests and some types
ospfranco Nov 28, 2023
8e2b6c2
Add a guard for buffer enconding
ospfranco Nov 28, 2023
91d2079
Add type to import pbkdf2
ospfranco Nov 29, 2023
69797eb
Import almost working
ospfranco Nov 30, 2023
f0bca91
Add crypto_ec. file
ospfranco Nov 30, 2023
067d025
Working internal openSLL key generatin
ospfranco Nov 30, 2023
d428aca
Compiling C++ implementation of import EC key and fix JS classes and …
ospfranco Nov 30, 2023
60fc1af
Minor type fixes
ospfranco Nov 30, 2023
c1fe74f
Simplify test structure
ospfranco Dec 1, 2023
6a7107b
Simplify module
ospfranco Dec 1, 2023
4e2e383
Working ECDSA import and SPKI export
ospfranco Dec 1, 2023
debcda6
Typescript fixes
ospfranco Dec 1, 2023
81e68c3
Add types to parent package
ospfranco Dec 1, 2023
e6ad40f
Dependencies
ospfranco Dec 1, 2023
fddc858
Update gradle wrapper
ospfranco Dec 1, 2023
26d61aa
Update Java version on action
ospfranco Dec 1, 2023
4438955
Update Java version on action
ospfranco Dec 1, 2023
e40020e
Update Java version on action
ospfranco Dec 1, 2023
6b29968
Remove yarn.lock changes action
ospfranco Dec 1, 2023
f90833d
Do not use build caches
ospfranco Dec 1, 2023
0b192e8
Do not use build caches
ospfranco Dec 1, 2023
b278ae1
Change react native android dependency
ospfranco Dec 1, 2023
91b70e3
Fix Android compilation
ospfranco Dec 1, 2023
3267110
Change lint to run on example folder
ospfranco Dec 1, 2023
757be80
Change lint to run on example folder
ospfranco Dec 1, 2023
7b7d3d4
Change lint to run on example folder
ospfranco Dec 1, 2023
d4da5f7
Change lint to run on example folder
ospfranco Dec 1, 2023
77d9f93
Merge branch 'osp/minor-fixes' into ECDSA
ospfranco Dec 1, 2023
2ce6edf
Adjust Android compilation
ospfranco Dec 1, 2023
310bd73
JS linting
ospfranco Dec 1, 2023
82c5709
cpp linting
ospfranco Dec 1, 2023
28d76cb
Fix android header
ospfranco Dec 1, 2023
d938cd3
Update Lint Report action version
ospfranco Dec 2, 2023
fd04d7d
Update Lint Report action version
ospfranco Dec 2, 2023
0c53ab3
Update JDK Setup
ospfranco Dec 2, 2023
3d02e8a
Merge branch 'osp/minor-fixes' into ECDSA
ospfranco Dec 2, 2023
c0834a9
Update dependencies
ospfranco Dec 2, 2023
34c1539
Update dependencies
ospfranco Dec 2, 2023
dba27ff
linting
ospfranco Dec 2, 2023
8c609f3
Reinsert deps
ospfranco Dec 2, 2023
0463e24
lock
ospfranco Dec 2, 2023
8afc4a5
lock
ospfranco Dec 2, 2023
01eb5a8
Merge branch 'osp/minor-fixes' into ECDSA
ospfranco Dec 2, 2023
0c4b58f
Simplify example app build.gradle
ospfranco Dec 7, 2023
3fb6019
Add pickFirst instructions to the README
ospfranco Dec 7, 2023
54bd203
remove pickFirst from build.gradle
ospfranco Dec 7, 2023
3be5c0e
Merge branch 'main' into ECDSA
ospfranco Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,31 @@ const hashed = Crypto.createHash('sha256')
.digest('hex');
```

## Android build errors

If you get an error similar to this:

```
Execution failed for task ':app:mergeDebugNativeLibs'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeNativeLibsTask$MergeNativeLibsTaskWorkAction
> 2 files found with path 'lib/arm64-v8a/libcrypto.so' from inputs:
- /Users/osp/Developer/mac_test/node_modules/react-native-quick-crypto/android/build/intermediates/library_jni/debug/jni/arm64-v8a/libcrypto.so
- /Users/osp/.gradle/caches/transforms-3/e13f88164840fe641a466d05cd8edac7/transformed/jetified-flipper-0.182.0/jni/arm64-v8a/libcrypto.so
```

It means you have a transitive dependency where two libraries depend on OpenSSL and are generating a `libcrypto.so` file. You can get around this issue by adding the following in your `app/build.gradle`:

```groovy
packagingOptions {
// Should prevent clashes with other libraries that use OpenSSL
pickFirst '**/libcrypto.so'
}
```

> This caused by flipper which also depends on OpenSSL

This just tells Gradle to grab whatever OpenSSL version it finds first and link against that, but as you can imagine this is not correct if the packages depend on different OpenSSL versions (quick-crypto depends on `com.android.ndk.thirdparty:openssl:1.1.1q-beta-1`). You should make sure all the OpenSSL versions match and you have no conflicts or errors.

---

## Sponsors
Expand Down
2 changes: 2 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ add_library(
"../cpp/Sig/MGLSignInstaller.cpp"
"../cpp/Sig/MGLVerifyInstaller.cpp"
"../cpp/Sig/MGLSignHostObjects.cpp"
"../cpp/webcrypto/MGLWebCrypto.cpp"
"../cpp/webcrypto/crypto_ec.cpp"
)

set_target_properties(
Expand Down
8 changes: 3 additions & 5 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,9 @@ android {
"**/libturbomodulejsijni.so",
"**/MANIFEST.MF",
]
// Should prevent clashes with other libraries that use OpenSSL
pickFirst '**/x86/libcrypto.so'
pickFirst '**/x86_64/libcrypto.so'
pickFirst '**/armeabi-v7a/libcrypto.so'
pickFirst '**/arm64-v8a/libcrypto.so'
// Setting pickFirst on this level does nothing
// pickFirst should be added on the app/build.gradle
// pickFirst '**/libcrypto.so'
}

buildTypes {
Expand Down
10 changes: 10 additions & 0 deletions cpp/JSIUtils/MGLJSIMacros.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ inline void Assert(const AssertionInfo &info) {
Abort();
}

#define HOSTFN(name, basecount) \
jsi::Function::createFromHostFunction( \
rt, \
jsi::PropNameID::forAscii(rt, name), \
basecount, \
[=](jsi::Runtime &rt, \
const jsi::Value &thisValue, \
const jsi::Value *args, \
size_t count) -> jsi::Value

#define HOST_LAMBDA(name, body) HOST_LAMBDA_CAP(name, [=], body)

#define HOST_LAMBDA_CAP(name, capture, body) \
Expand Down
111 changes: 71 additions & 40 deletions cpp/MGLKeys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <jsi/jsi.h>
#include <openssl/bio.h>
#include <openssl/ec.h>

#include <algorithm>
#include <optional>
Expand All @@ -20,11 +21,13 @@
#include "JSIUtils/MGLJSIUtils.h"
#include "JSIUtils/MGLTypedArray.h"
#include "Utils/MGLUtils.h"
#include "webcrypto/crypto_ec.h"
#else
#include "MGLJSIMacros.h"
#include "MGLJSIUtils.h"
#include "MGLTypedArray.h"
#include "MGLUtils.h"
#include "crypto_ec.h"
#endif

namespace margelo {
Expand Down Expand Up @@ -818,54 +821,36 @@ ManagedEVPPKey ManagedEVPPKey::GetParsedKey(jsi::Runtime& runtime,
// symmetric_key_len_(symmetric_key_.size()),
// asymmetric_key_() {}
//
// KeyObjectData::KeyObjectData(
// KeyType type,
// const ManagedEVPPKey& pkey)
//: key_type_(type),
KeyObjectData::KeyObjectData(KeyType type,
const ManagedEVPPKey& pkey)
: key_type_(type),
// symmetric_key_(),
// symmetric_key_len_(0),
// asymmetric_key_{pkey} {}
//
// void KeyObjectData::MemoryInfo(MemoryTracker* tracker) const {
// switch (GetKeyType()) {
// case kKeyTypeSecret:
// tracker->TrackFieldWithSize("symmetric_key", symmetric_key_.size());
// break;
// case kKeyTypePrivate:
// // Fall through
// case kKeyTypePublic:
// tracker->TrackFieldWithSize("key", asymmetric_key_);
// break;
// default:
// UNREACHABLE();
// }
// }
//
asymmetric_key_{pkey} {}

// std::shared_ptr<KeyObjectData> KeyObjectData::CreateSecret(ByteSource key)
// {
// CHECK(key);
// return std::shared_ptr<KeyObjectData>(new KeyObjectData(std::move(key)));
// }
//
// std::shared_ptr<KeyObjectData> KeyObjectData::CreateAsymmetric(
// KeyType
// key_type,
// const
// ManagedEVPPKey&
// pkey) {
// CHECK(pkey);
// return std::shared_ptr<KeyObjectData>(new KeyObjectData(key_type, pkey));
// }
//
// KeyType KeyObjectData::GetKeyType() const {
// return key_type_;
// }
//
// ManagedEVPPKey KeyObjectData::GetAsymmetricKey() const {

std::shared_ptr<KeyObjectData> KeyObjectData::CreateAsymmetric(
KeyType key_type,
const ManagedEVPPKey& pkey
) {
CHECK(pkey);
return std::shared_ptr<KeyObjectData>(new KeyObjectData(key_type, pkey));
}

KeyType KeyObjectData::GetKeyType() const {
return key_type_;
}

ManagedEVPPKey KeyObjectData::GetAsymmetricKey() const {
// CHECK_NE(key_type_, kKeyTypeSecret);
// return asymmetric_key_;
// }
//
return asymmetric_key_;
}

// const char* KeyObjectData::GetSymmetricKey() const {
// CHECK_EQ(key_type_, kKeyTypeSecret);
// return symmetric_key_.data<char>();
Expand Down Expand Up @@ -1033,6 +1018,51 @@ ManagedEVPPKey ManagedEVPPKey::GetParsedKey(jsi::Runtime& runtime,
//
// args.GetReturnValue().Set(key->data_->GetKeyType());
//}

jsi::Value KeyObjectHandle::get(
jsi::Runtime &rt,
const jsi::PropNameID &propNameID) {
auto name = propNameID.utf8(rt);

if (name == "initECRaw") {
return HOSTFN("initECRaw", 2) {
CHECK(args[0].isString());
std::string curveName = args[0].asString(rt).utf8(rt);
int id = OBJ_txt2nid(curveName.c_str());
ECKeyPointer eckey(EC_KEY_new_by_curve_name(id));
if (!eckey) {
return false;
}
// TODO(osp) add validation
auto buf = args[1].asObject(rt).getArrayBuffer(rt);

const EC_GROUP* group = EC_KEY_get0_group(eckey.get());
ECPointPointer pub(ECDH::BufferToPoint(rt, group, buf));

if (!pub ||
!eckey ||
!EC_KEY_set_public_key(eckey.get(), pub.get())) {
return false;
}

EVPKeyPointer pkey(EVP_PKEY_new());
if (!EVP_PKEY_assign_EC_KEY(pkey.get(), eckey.get())) {
return false;
}

eckey.release(); // Release ownership of the key

this->data_ =
KeyObjectData::CreateAsymmetric(
kKeyTypePublic,
ManagedEVPPKey(std::move(pkey)));

return true;
});
}
return {};
}

//
// void KeyObjectHandle::InitECRaw(const FunctionCallbackInfo<Value>& args) {
// Environment* env = Environment::GetCurrent(args);
Expand Down Expand Up @@ -1456,4 +1486,5 @@ ManagedEVPPKey ManagedEVPPKey::GetParsedKey(jsi::Runtime& runtime,
// void RegisterExternalReferences(ExternalReferenceRegistry * registry) {
// KeyObjectHandle::RegisterExternalReferences(registry);
// }

} // namespace margelo
43 changes: 43 additions & 0 deletions cpp/MGLKeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
#include "Utils/MGLUtils.h"
#else
#include "MGLUtils.h"
#include "JSIUtils/MGLSmartHostObject.h"
#endif

// This file should roughly match https://github.com/nodejs/node/blob/main/src/crypto/crypto_keys.cc

namespace margelo {

namespace jsi = facebook::jsi;
Expand Down Expand Up @@ -118,6 +121,46 @@ class ManagedEVPPKey {
EVPKeyPointer pkey_;
};

// Analogous to the KeyObjectData class on node
// https://github.com/nodejs/node/blob/main/src/crypto/crypto_keys.h#L132
class KeyObjectData {
public:
// static std::shared_ptr<KeyObjectData> CreateSecret(ByteSource key);

static std::shared_ptr<KeyObjectData> CreateAsymmetric(
KeyType type,
const ManagedEVPPKey& pkey);

KeyType GetKeyType() const;

// These functions allow unprotected access to the raw key material and should
// only be used to implement cryptographic operations requiring the key.
ManagedEVPPKey GetAsymmetricKey() const;
// const char* GetSymmetricKey() const;
// size_t GetSymmetricKeySize() const;

private:
// explicit KeyObjectData(ByteSource symmetric_key);

KeyObjectData(
KeyType type,
const ManagedEVPPKey& pkey);

const KeyType key_type_;
// const ByteSource symmetric_key_;
const ManagedEVPPKey asymmetric_key_;
};

// Analoguous to the KeyObjectHandle class in node
// https://github.com/nodejs/node/blob/main/src/crypto/crypto_keys.h#L164
class JSI_EXPORT KeyObjectHandle: public jsi::HostObject {
public:
KeyObjectHandle() {}
jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID);
// TODO(osp) this should be protected
std::shared_ptr<KeyObjectData> data_;
};

} // namespace margelo

#endif /* MGLCipherKeys_h */
13 changes: 8 additions & 5 deletions cpp/MGLQuickCryptoHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "Sig/MGLSignInstaller.h"
#include "Sig/MGLVerifyInstaller.h"
#include "fastpbkdf2/MGLPbkdf2HostObject.h"
#include "webcrypto/MGLWebCrypto.h"
#else
#include "MGLCreateCipherInstaller.h"
#include "MGLCreateDecipherInstaller.h"
Expand All @@ -34,6 +35,7 @@
#include "MGLRandomHostObject.h"
#include "MGLSignInstaller.h"
#include "MGLVerifyInstaller.h"
#include "MGLWebCrypto.h"
#endif

namespace margelo {
Expand Down Expand Up @@ -105,11 +107,12 @@ MGLQuickCryptoHostObject::MGLQuickCryptoHostObject(
return jsi::Object::createFromHostObject(runtime, hostObject);
}));

// createSign
this->fields.push_back(getSignFieldDefinition(jsCallInvoker, workerQueue));

// createVerify
this->fields.push_back(getVerifyFieldDefinition(jsCallInvoker, workerQueue));
// subtle API created from a simple jsi::Object
// because this FieldDefinition is only good for returning
// objects and too convoluted
this->fields.push_back(JSI_VALUE("webcrypto", {
return createWebCryptoObject(runtime);
}));
}

} // namespace margelo
5 changes: 5 additions & 0 deletions cpp/Random/MGLRandomHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ MGLRandomHostObject::MGLRandomHostObject(
throw std::runtime_error("First argument it not an array buffer");
}

if (!arguments[0].isObject()
|| !arguments[0].asObject(runtime).isArrayBuffer(runtime)) {
throw std::runtime_error("First argument it not an array buffer");
}

auto result = arguments[0].asObject(runtime).getArrayBuffer(runtime);
auto resultSize = result.size(runtime);
auto *resultData = result.data(runtime);
Expand Down
33 changes: 25 additions & 8 deletions cpp/Utils/MGLUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ namespace margelo {

namespace jsi = facebook::jsi;

jsi::Object ByteSourceToArrayBuffer(jsi::Runtime &rt,
ByteSource &source) {
jsi::Function array_buffer_ctor = rt.global()
.getPropertyAsFunction(rt, "ArrayBuffer");
jsi::Object o = array_buffer_ctor.callAsConstructor(
rt,
(int)source.size())
.getObject(rt);

jsi::ArrayBuffer buf = o.getArrayBuffer(rt);
// You cannot share raw memory between native and JS
// always copy the data
// see https://github.com/facebook/hermes/pull/419 and https://github.com/facebook/hermes/issues/564.
memcpy(buf.data(rt), source.data(), source.size());
return o;
}

ByteSource ArrayBufferToByteSource(jsi::Runtime& runtime,
const jsi::ArrayBuffer& buffer) {
if (buffer.size(runtime) == 0) return ByteSource();
Expand Down Expand Up @@ -96,14 +113,14 @@ ByteSource& ByteSource::operator=(ByteSource&& other) noexcept {
// return Buffer::New(env, ab, 0, ab->ByteLength());
// }

// ByteSource ByteSource::FromBIO(const BIOPointer& bio) {
//// CHECK(bio);
// BUF_MEM* bptr;
// BIO_get_mem_ptr(bio.get(), &bptr);
// ByteSource::Builder out(bptr->length);
// memcpy(out.data<void>(), bptr->data, bptr->length);
// return std::move(out).release();
//}
ByteSource ByteSource::FromBIO(const BIOPointer& bio) {
// CHECK(bio);
BUF_MEM* bptr;
BIO_get_mem_ptr(bio.get(), &bptr);
ByteSource::Builder out(bptr->length);
memcpy(out.data<void>(), bptr->data, bptr->length);
return std::move(out).release();
}

// ByteSource ByteSource::FromEncodedString(Environment* env,
// Local<String> key,
Expand Down
Loading
Loading