Skip to content

Commit

Permalink
feat: subtle.encrypt() & subtle.decrypt() support for aes (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
boorad committed Jul 1, 2024
1 parent be578ed commit 8a7f07f
Show file tree
Hide file tree
Showing 35 changed files with 2,535 additions and 358 deletions.
2 changes: 2 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ add_library(
"../cpp/Sig/MGLVerifyInstaller.cpp"
"../cpp/Sig/MGLSignHostObjects.cpp"
"../cpp/webcrypto/MGLWebCrypto.cpp"
"../cpp/webcrypto/crypto_aes.cpp"
"../cpp/webcrypto/crypto_ec.cpp"
"../cpp/webcrypto/crypto_keygen.cpp"
)

target_include_directories(
Expand Down
8 changes: 8 additions & 0 deletions cpp/JSIUtils/MGLJSIUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,12 @@ inline bool CheckIsInt32(const jsi::Value &value) {
return (d >= std::numeric_limits<int32_t>::lowest() && d <= std::numeric_limits<int32_t>::max());
}

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

#endif /* MGLJSIUtils_h */
7 changes: 3 additions & 4 deletions cpp/MGLKeys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ jsi::Value ManagedEVPPKey::ToEncodedPublicKey(jsi::Runtime& rt,
// Note that this has the downside of containing sensitive data of the
// private key.
auto data = KeyObjectData::CreateAsymmetric(kKeyTypePublic, std::move(key));
auto handle = KeyObjectHandle::Create(rt, data);
auto handle = KeyObjectHandle::Create(data);
auto out = jsi::Object::createFromHostObject(rt, handle);
return jsi::Value(std::move(out));
} else
Expand All @@ -583,7 +583,7 @@ jsi::Value ManagedEVPPKey::ToEncodedPrivateKey(jsi::Runtime& rt,
if (!key) return {};
if (config.output_key_object_) {
auto data = KeyObjectData::CreateAsymmetric(kKeyTypePrivate, std::move(key));
auto handle = KeyObjectHandle::Create(rt, data);
auto handle = KeyObjectHandle::Create(data);
auto out = jsi::Object::createFromHostObject(rt, handle);
return jsi::Value(std::move(out));
} else
Expand Down Expand Up @@ -907,8 +907,7 @@ jsi::Value KeyObjectHandle::get(
// registry->Register(Equals);
// }

std::shared_ptr<KeyObjectHandle> KeyObjectHandle::Create(jsi::Runtime &rt,
std::shared_ptr<KeyObjectData> data) {
std::shared_ptr<KeyObjectHandle> KeyObjectHandle::Create(std::shared_ptr<KeyObjectData> data) {
auto handle = std::make_shared<KeyObjectHandle>();
handle->data_ = data;
return handle;
Expand Down
3 changes: 1 addition & 2 deletions cpp/MGLKeys.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ class JSI_EXPORT KeyObjectHandle: public jsi::HostObject {
jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID);
const std::shared_ptr<KeyObjectData>& Data();

static std::shared_ptr<KeyObjectHandle> Create(jsi::Runtime &rt,
std::shared_ptr<KeyObjectData> data);
static std::shared_ptr<KeyObjectHandle> Create(std::shared_ptr<KeyObjectData> data);

protected:
jsi::Value Export(jsi::Runtime &rt);
Expand Down
12 changes: 6 additions & 6 deletions cpp/MGLQuickCryptoHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ MGLQuickCryptoHostObject::MGLQuickCryptoHostObject(
// 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);
}));
// subtle API
this->fields.push_back(JSI_VALUE("webcrypto", {
auto hostObject = std::make_shared<MGLWebCryptoHostObject>(
jsCallInvoker, workerQueue);
return jsi::Object::createFromHostObject(runtime, hostObject);
}));
}

} // namespace margelo
34 changes: 34 additions & 0 deletions cpp/Utils/MGLUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,38 @@ std::string DecodeBase64(const std::string &in, bool remove_linebreaks) {
return base64_decode(in, remove_linebreaks);
}

MUST_USE_RESULT CSPRNGResult CSPRNG(void* buffer, size_t length) {
unsigned char* buf = static_cast<unsigned char*>(buffer);
do {
if (1 == RAND_status()) {
#if OPENSSL_VERSION_MAJOR >= 3
if (1 == RAND_bytes_ex(nullptr, buf, length, 0)) return {true};
#else
while (length > INT_MAX && 1 == RAND_bytes(buf, INT_MAX)) {
buf += INT_MAX;
length -= INT_MAX;
}
if (length <= INT_MAX && 1 == RAND_bytes(buf, static_cast<int>(length)))
return {true};
#endif
}
#if OPENSSL_VERSION_MAJOR >= 3
const auto code = ERR_peek_last_error();
// A misconfigured OpenSSL 3 installation may report 1 from RAND_poll()
// and RAND_status() but fail in RAND_bytes() if it cannot look up
// a matching algorithm for the CSPRNG.
if (ERR_GET_LIB(code) == ERR_LIB_RAND) {
const auto reason = ERR_GET_REASON(code);
if (reason == RAND_R_ERROR_INSTANTIATING_DRBG ||
reason == RAND_R_UNABLE_TO_FETCH_DRBG ||
reason == RAND_R_UNABLE_TO_CREATE_DRBG) {
return {false};
}
}
#endif
} while (1 == RAND_poll());

return {false};
}

} // namespace margelo
26 changes: 26 additions & 0 deletions cpp/Utils/MGLUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ using EVPMDPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using ECDSASigPointer = DeleteFnPtr<ECDSA_SIG, ECDSA_SIG_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using CipherCtxPointer = DeleteFnPtr<EVP_CIPHER_CTX, EVP_CIPHER_CTX_free>;


#ifdef __GNUC__
#define MUST_USE_RESULT __attribute__((warn_unused_result))
#else
#define MUST_USE_RESULT
#endif

struct CSPRNGResult {
const bool ok;
MUST_USE_RESULT bool is_ok() const { return ok; }
MUST_USE_RESULT bool is_err() const { return !ok; }
};

// Either succeeds with exactly |length| bytes of cryptographically
// strong pseudo-random data, or fails. This function may block.
// Don't assume anything about the contents of |buffer| on error.
// As a special case, |length == 0| can be used to check if the CSPRNG
// is properly seeded without consuming entropy.
MUST_USE_RESULT CSPRNGResult CSPRNG(void* buffer, size_t length);

template <typename T>
class NonCopyableMaybe {
Expand Down Expand Up @@ -299,6 +320,11 @@ enum KeyVariant {
kvDH,
};

enum FnMode {
kAsync,
kSync,
};

} // namespace margelo

#endif /* MGLUtils_h */
99 changes: 62 additions & 37 deletions cpp/webcrypto/MGLWebCrypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,59 +15,84 @@
#include "JSIUtils/MGLJSIMacros.h"
#include "Sig/MGLSignHostObjects.h"
#include "Utils/MGLUtils.h"
#include "webcrypto/crypto_aes.h"
#include "webcrypto/crypto_ec.h"
#include "webcrypto/crypto_keygen.h"
#else
#include "MGLJSIMacros.h"
#include "MGLSignHostObjects.h"
#include "MGLUtils.h"
#include "crypto_aes.h"
#include "crypto_ec.h"
#include "crypto_keygen.h"
#endif

namespace margelo {

namespace jsi = facebook::jsi;
namespace react = facebook::react;

jsi::Value createWebCryptoObject(jsi::Runtime &rt) {
auto obj = jsi::Object(rt);
MGLWebCryptoHostObject::MGLWebCryptoHostObject(
std::shared_ptr<react::CallInvoker> jsCallInvoker,
std::shared_ptr<DispatchQueue::dispatch_queue> workerQueue)
: MGLSmartHostObject(jsCallInvoker, workerQueue) {

auto aesCipher = JSIF([=]) {
auto aes = AESCipher();
auto params = aes.GetParamsFromJS(runtime, arguments);
ByteSource out;
WebCryptoCipherStatus status = aes.DoCipher(params, &out);
if (status != WebCryptoCipherStatus::OK) {
throw jsi::JSError(runtime, "error in DoCipher, status: " +
std::to_string(static_cast<int>(status)));
}
return toJSI(runtime, std::move(out));
};

auto createKeyObjectHandle = HOSTFN("createKeyObjectHandle", 0) {
auto keyObjectHandleHostObject =
std::make_shared<KeyObjectHandle>();
return jsi::Object::createFromHostObject(rt, keyObjectHandleHostObject);
});
auto createKeyObjectHandle = JSIF([=]) {
auto keyObjectHandleHostObject = std::make_shared<KeyObjectHandle>();
return jsi::Object::createFromHostObject(runtime, keyObjectHandleHostObject);
};

auto ecExportKey = HOSTFN("ecExportKey", 2) {
ByteSource out;
std::shared_ptr<KeyObjectHandle> handle =
std::static_pointer_cast<KeyObjectHandle>(
args[1].asObject(rt).getHostObject(rt));
std::shared_ptr<KeyObjectData> key_data = handle->Data();
WebCryptoKeyExportStatus status = ECDH::doExport(rt,
key_data,
static_cast<WebCryptoKeyFormat>(args[0].asNumber()),
{}, // blank params
&out);
if (status != WebCryptoKeyExportStatus::OK) {
throw jsi::JSError(rt, "error exporting key, status: " + std::to_string(static_cast<int>(status)));
}
return toJSI(rt, std::move(out));
});
auto ecExportKey = JSIF([=]) {
ByteSource out;
std::shared_ptr<KeyObjectHandle> handle =
std::static_pointer_cast<KeyObjectHandle>(
arguments[1].asObject(runtime).getHostObject(runtime));
std::shared_ptr<KeyObjectData> key_data = handle->Data();
WebCryptoKeyExportStatus status = ECDH::doExport(runtime,
key_data,
static_cast<WebCryptoKeyFormat>(arguments[0].asNumber()),
{}, // blank params
&out);
if (status != WebCryptoKeyExportStatus::OK) {
throw jsi::JSError(runtime, "error exporting key, status: " + std::to_string(static_cast<int>(status)));
}
return toJSI(runtime, std::move(out));
};

auto signVerify = HOSTFN("signVerify", 4) {
auto ssv = SubtleSignVerify();
auto params = ssv.GetParamsFromJS(rt, args);
ByteSource out;
ssv.DoSignVerify(rt, params, out);
return ssv.EncodeOutput(rt, params, out);
});
auto generateSecretKeySync = JSIF([=]) {
auto skg = new SecretKeyGen();
CHECK(skg->GetParamsFromJS(runtime, arguments));
CHECK(skg->DoKeyGen());
auto out = jsi::Object::createFromHostObject(runtime, skg->GetHandle());
return jsi::Value(std::move(out));
};

obj.setProperty(rt,
"createKeyObjectHandle",
std::move(createKeyObjectHandle));
obj.setProperty(rt, "ecExportKey", std::move(ecExportKey));
obj.setProperty(rt, "signVerify", std::move(signVerify));
return obj;
auto signVerify = JSIF([=]) {
auto ssv = SubtleSignVerify();
auto params = ssv.GetParamsFromJS(runtime, arguments);
ByteSource out;
ssv.DoSignVerify(runtime, params, out);
return ssv.EncodeOutput(runtime, params, out);
};

this->fields.push_back(buildPair("aesCipher", aesCipher));
this->fields.push_back(buildPair("createKeyObjectHandle", createKeyObjectHandle));
this->fields.push_back(buildPair("ecExportKey", ecExportKey));
this->fields.push_back(GenerateSecretKeyFieldDefinition(jsCallInvoker, workerQueue));
this->fields.push_back(buildPair("generateSecretKeySync", generateSecretKeySync));
this->fields.push_back(buildPair("signVerify", signVerify));
};

} // namespace margelo

7 changes: 6 additions & 1 deletion cpp/webcrypto/MGLWebCrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ enum WebCryptoKeyFormat {
kWebCryptoKeyFormatJWK
};

jsi::Value createWebCryptoObject(jsi::Runtime &rt);
class MGLWebCryptoHostObject : public MGLSmartHostObject {
public:
MGLWebCryptoHostObject(
std::shared_ptr<react::CallInvoker> jsCallInvoker,
std::shared_ptr<DispatchQueue::dispatch_queue> workerQueue);
};

} // namespace margelo

Expand Down
Loading

0 comments on commit 8a7f07f

Please sign in to comment.