Skip to content

Commit

Permalink
feat: subtle.generateKey support for ecdsa, ecdh (#304)
Browse files Browse the repository at this point in the history
  • Loading branch information
boorad committed Jun 12, 2024
1 parent d83b780 commit 0a8ea51
Show file tree
Hide file tree
Showing 26 changed files with 1,537 additions and 497 deletions.
65 changes: 51 additions & 14 deletions cpp/Cipher/MGLGenerateKeyPairInstaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <utility>

Expand All @@ -18,9 +19,11 @@
#ifdef ANDROID
#include "JSIUtils/MGLJSIMacros.h"
#include "JSIUtils/MGLTypedArray.h"
#include "webcrypto/crypto_ec.h"
#else
#include "MGLJSIMacros.h"
#include "MGLTypedArray.h"
#include "crypto_ec.h"
#endif

using namespace facebook;
Expand All @@ -29,44 +32,78 @@ namespace margelo {

std::mutex m;

// Current implementation only supports RSA schemes (check line config.variant =
// ) As more encryption schemes are added this will require an abstraction that
// supports more schemes
FieldDefinition getGenerateKeyPairFieldDefinition(
std::shared_ptr<react::CallInvoker> jsCallInvoker,
std::shared_ptr<DispatchQueue::dispatch_queue> workerQueue) {
return buildPair(
"generateKeyPair", JSIF([=]) {
auto config = std::make_shared<RsaKeyPairGenConfig>(

KeyVariant variant =
static_cast<KeyVariant>((int)arguments[0].asNumber());
std::shared_ptr<RsaKeyPairGenConfig> rsaConfig;
std::shared_ptr<EcKeyPairGenConfig> ecConfig;

// switch on variant to get proper config from arguments
// outside of lambda 🤮
if (variant == kvRSA_SSA_PKCS1_v1_5 ||
variant == kvRSA_PSS ||
variant == kvRSA_OAEP
) {
rsaConfig = std::make_shared<RsaKeyPairGenConfig>(
prepareRsaKeyGenConfig(runtime, arguments));
} else
if (variant == kvEC) {
ecConfig = std::make_shared<EcKeyPairGenConfig>(
prepareEcKeyGenConfig(runtime, arguments));
} else {
throw std::runtime_error("KeyVariant not implemented"
+ std::to_string((int)variant));
}

auto promiseConstructor =
runtime.global().getPropertyAsFunction(runtime, "Promise");

auto promise = promiseConstructor.callAsConstructor(
runtime,
jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, "executor"), 2,
[&jsCallInvoker, config](
runtime,
jsi::PropNameID::forAscii(runtime, "executor"),
4,
[&jsCallInvoker, variant, rsaConfig, ecConfig](
jsi::Runtime &runtime, const jsi::Value &,
const jsi::Value *promiseArgs, size_t) -> jsi::Value {
auto resolve =
std::make_shared<jsi::Value>(runtime, promiseArgs[0]);
auto reject =
std::make_shared<jsi::Value>(runtime, promiseArgs[1]);

std::thread t([&runtime, resolve, reject,
jsCallInvoker, config]() {
std::thread t([&runtime, resolve, reject, jsCallInvoker,
variant, rsaConfig, ecConfig]() {
m.lock();
try {
jsCallInvoker->invokeAsync([&runtime, config, resolve]() {
auto keys = generateRSAKeyPair(runtime, config);
auto publicKey = toJSI(runtime, keys.first);
auto privateKey = toJSI(runtime, keys.second);
jsCallInvoker->invokeAsync([&runtime, resolve,
variant, rsaConfig, ecConfig]() {
std::pair<jsi::Value, jsi::Value> keys;

// switch on variant to get proper generateKeyPair
if (variant == kvRSA_SSA_PKCS1_v1_5 ||
variant == kvRSA_PSS ||
variant == kvRSA_OAEP
) {
keys = generateRsaKeyPair(runtime, rsaConfig);
} else
if (variant == kvEC) {
keys = generateEcKeyPair(runtime, ecConfig);
} else {
throw std::runtime_error("KeyVariant not implemented"
+ std::to_string((int)variant));
}

auto res = jsi::Array::createWithElements(
runtime,
jsi::Value::undefined(),
publicKey,
privateKey);
keys.first,
keys.second);
resolve->asObject(runtime).asFunction(runtime).call(
runtime, std::move(res));
});
Expand Down
34 changes: 25 additions & 9 deletions cpp/Cipher/MGLGenerateKeyPairSyncInstaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,47 @@
#include "JSIUtils/MGLJSIMacros.h"
#include "JSIUtils/MGLJSIUtils.h"
#include "JSIUtils/MGLTypedArray.h"
#include "webcrypto/crypto_ec.h"
#else
#include "MGLJSIMacros.h"
#include "MGLJSIUtils.h"
#include "MGLTypedArray.h"
#include "crypto_ec.h"
#endif

using namespace facebook;

namespace margelo {

// Current implementation only supports RSA schemes (check line config.variant =
// ) As more encryption schemes are added this will require an abstraction that
// supports more schemes
FieldDefinition getGenerateKeyPairSyncFieldDefinition(
std::shared_ptr<react::CallInvoker> jsCallInvoker,
std::shared_ptr<DispatchQueue::dispatch_queue> workerQueue) {
return buildPair(
"generateKeyPairSync", JSIF([=]) {
auto config = std::make_shared<RsaKeyPairGenConfig>(
prepareRsaKeyGenConfig(runtime, arguments));
auto keys = generateRSAKeyPair(runtime, std::move(config));
auto publicKey = toJSI(runtime, keys.first);
auto privateKey = toJSI(runtime, keys.second);
std::pair<jsi::Value, jsi::Value> keys;
KeyVariant variant =
static_cast<KeyVariant>((int)arguments[0].asNumber());

// switch on variant to get proper config/genKeyPair
if (variant == kvRSA_SSA_PKCS1_v1_5 ||
variant == kvRSA_PSS ||
variant == kvRSA_OAEP
) {
auto config = std::make_shared<RsaKeyPairGenConfig>(
prepareRsaKeyGenConfig(runtime, arguments));
keys = generateRsaKeyPair(runtime, config);
} else
if (variant == kvEC) {
auto config = std::make_shared<EcKeyPairGenConfig>(
prepareEcKeyGenConfig(runtime, arguments));
keys = generateEcKeyPair(runtime, config);
} else {
throw std::runtime_error("KeyVariant not implemented: " +
std::to_string((int)variant));
}
// keys.first = publicKey keys.second = privateKey
return jsi::Array::createWithElements(
runtime, jsi::Value::undefined(), publicKey, privateKey);
runtime, jsi::Value::undefined(), keys.first, keys.second);
});
}
} // namespace margelo
25 changes: 13 additions & 12 deletions cpp/Cipher/MGLRsa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace jsi = facebook::jsi;

EVPKeyCtxPointer setup(std::shared_ptr<RsaKeyPairGenConfig> config) {
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new_id(
config->variant == kKeyVariantRSA_PSS ? EVP_PKEY_RSA_PSS : EVP_PKEY_RSA,
config->variant == kvRSA_PSS ? EVP_PKEY_RSA_PSS : EVP_PKEY_RSA,
nullptr));

if (EVP_PKEY_keygen_init(ctx.get()) <= 0) return EVPKeyCtxPointer();
Expand All @@ -43,7 +43,7 @@ EVPKeyCtxPointer setup(std::shared_ptr<RsaKeyPairGenConfig> config) {
bn.release();
}

if (config->variant == kKeyVariantRSA_PSS) {
if (config->variant == kvRSA_PSS) {
if (config->md != nullptr &&
EVP_PKEY_CTX_set_rsa_pss_keygen_md(ctx.get(), config->md) <= 0) {
return EVPKeyCtxPointer();
Expand Down Expand Up @@ -94,20 +94,20 @@ RsaKeyPairGenConfig prepareRsaKeyGenConfig(jsi::Runtime& runtime,
// CHECK(args[*offset + 1]->IsUint32()); // Modulus bits
// CHECK(args[*offset + 2]->IsUint32()); // Exponent
config.variant =
static_cast<RSAKeyVariant>((int)arguments[offset].asNumber());
static_cast<KeyVariant>((int)arguments[offset].asNumber());

// TODO(osp)
// CHECK_IMPLIES(params->params.variant != kKeyVariantRSA_PSS,
// CHECK_IMPLIES(params->params.variant != kvRSA_PSS,
// args.Length() == 10);
// CHECK_IMPLIES(params->params.variant == kKeyVariantRSA_PSS,
// CHECK_IMPLIES(params->params.variant == kvRSA_PSS,
// args.Length() == 13);
config.modulus_bits =
static_cast<unsigned int>(arguments[offset + 1].asNumber());
config.exponent = static_cast<unsigned int>(arguments[offset + 2].asNumber());

offset += 3;

if (config.variant == kKeyVariantRSA_PSS) {
if (config.variant == kvRSA_PSS) {
if (!arguments[offset].isUndefined()) {
// TODO(osp) CHECK(string)
config.md = EVP_get_digestbyname(
Expand Down Expand Up @@ -153,8 +153,9 @@ RsaKeyPairGenConfig prepareRsaKeyGenConfig(jsi::Runtime& runtime,
return config;
}

std::pair<JSVariant, JSVariant> generateRSAKeyPair(
std::pair<jsi::Value, jsi::Value> generateRsaKeyPair(
jsi::Runtime& runtime, std::shared_ptr<RsaKeyPairGenConfig> config) {
// TODO: this is all copied into crypto_ec.cpp - template it up like Node?
CheckEntropy();

EVPKeyCtxPointer ctx = setup(config);
Expand All @@ -171,18 +172,18 @@ std::pair<JSVariant, JSVariant> generateRSAKeyPair(

config->key = ManagedEVPPKey(EVPKeyPointer(pkey));

OptionJSVariant publicBuffer =
jsi::Value publicBuffer =
ManagedEVPPKey::ToEncodedPublicKey(runtime, std::move(config->key),
config->public_key_encoding);
OptionJSVariant privateBuffer =
jsi::Value privateBuffer =
ManagedEVPPKey::ToEncodedPrivateKey(runtime, std::move(config->key),
config->private_key_encoding);

if (!publicBuffer.has_value() || !privateBuffer.has_value()) {
throw jsi::JSError(runtime, "Failed to encode public and/or private key");
if (publicBuffer.isUndefined() || privateBuffer.isUndefined()) {
throw jsi::JSError(runtime, "Failed to encode public and/or private key (RSA)");
}

return {std::move(publicBuffer.value()), std::move(privateBuffer.value())};
return {std::move(publicBuffer), std::move(privateBuffer)};
}

jsi::Value ExportJWKRsaKey(jsi::Runtime &rt,
Expand Down
10 changes: 2 additions & 8 deletions cpp/Cipher/MGLRsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ namespace margelo {

namespace jsi = facebook::jsi;

enum RSAKeyVariant {
kKeyVariantRSA_SSA_PKCS1_v1_5,
kKeyVariantRSA_PSS,
kKeyVariantRSA_OAEP
};

// On node there is a complete madness of structs/classes that encapsulate and
// initialize the data in a generic manner this is to be later be used to
// generate the keys in a thread-safe manner (I think) I'm however too dumb and
Expand All @@ -43,7 +37,7 @@ struct RsaKeyPairGenConfig {
PrivateKeyEncodingConfig private_key_encoding;
ManagedEVPPKey key;

RSAKeyVariant variant;
KeyVariant variant;
unsigned int modulus_bits;
unsigned int exponent;

Expand All @@ -57,7 +51,7 @@ struct RsaKeyPairGenConfig {
RsaKeyPairGenConfig prepareRsaKeyGenConfig(jsi::Runtime& runtime,
const jsi::Value* arguments);

std::pair<JSVariant, JSVariant> generateRSAKeyPair(
std::pair<jsi::Value, jsi::Value> generateRsaKeyPair(
jsi::Runtime& runtime, std::shared_ptr<RsaKeyPairGenConfig> config);

jsi::Value ExportJWKRsaKey(jsi::Runtime &rt,
Expand Down
9 changes: 9 additions & 0 deletions cpp/JSIUtils/MGLJSIUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#define MGLJSIUtils_h

#include <jsi/jsi.h>
#include <limits>

namespace jsi = facebook::jsi;

Expand All @@ -21,4 +22,12 @@ inline bool CheckSizeInt32(jsi::Runtime &runtime, jsi::ArrayBuffer &buffer) {
return buffer.size(runtime) <= INT_MAX;
}

inline bool CheckIsInt32(const jsi::Value &value) {
if (!value.isNumber()) {
return false;
}
double d = value.asNumber();
return (d >= std::numeric_limits<int32_t>::lowest() && d <= std::numeric_limits<int32_t>::max());
}

#endif /* MGLJSIUtils_h */
Loading

0 comments on commit 0a8ea51

Please sign in to comment.