Skip to content

Commit

Permalink
c++ - generateKey
Browse files Browse the repository at this point in the history
  • Loading branch information
boorad committed Jun 27, 2024
1 parent aadeeca commit 7a1fd56
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 29 deletions.
1 change: 1 addition & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ add_library(
"../cpp/webcrypto/MGLWebCrypto.cpp"
"../cpp/webcrypto/crypto_aes.cpp"
"../cpp/webcrypto/crypto_ec.cpp"
"../cpp/webcrypto/crypto_keygen.cpp"
)

target_include_directories(
Expand Down
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
25 changes: 25 additions & 0 deletions cpp/Utils/MGLUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,26 @@ 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 {
public:
Expand Down Expand Up @@ -300,6 +320,11 @@ enum KeyVariant {
kvDH,
};

enum FnMode {
kAsync,
kSync,
};

} // namespace margelo

#endif /* MGLUtils_h */
26 changes: 19 additions & 7 deletions cpp/webcrypto/MGLWebCrypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@
#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;

Expand All @@ -46,7 +49,7 @@ jsi::Value createWebCryptoObject(jsi::Runtime &rt) {

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

Expand All @@ -67,21 +70,30 @@ jsi::Value createWebCryptoObject(jsi::Runtime &rt) {
return toJSI(rt, std::move(out));
});

auto generateSecretKey = HOSTFN("generateSecretKey", 2) {
return SecretKeyGen::DoKeyGen(rt, args);
});

auto generateSecretKeySync = HOSTFN("generateSecretKeySync", 2) {
return SecretKeyGen::DoKeyGenSync(rt, args);
});

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 ssv = SubtleSignVerify();
auto params = ssv.GetParamsFromJS(rt, args);
ByteSource out;
ssv.DoSignVerify(rt, params, out);
return ssv.EncodeOutput(rt, params, out);
});

obj.setProperty(rt,
"createKeyObjectHandle",
std::move(createKeyObjectHandle));
obj.setProperty(rt, "ecExportKey", std::move(ecExportKey));
obj.setProperty(rt, "generateSecretKey", std::move(generateSecretKey));
obj.setProperty(rt, "generateSecretKeySync", std::move(generateSecretKeySync));
obj.setProperty(rt, "signVerify", std::move(signVerify));
return obj;
};

} // namespace margelo

6 changes: 3 additions & 3 deletions cpp/webcrypto/crypto_aes.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "crypto_aes.h"

#ifdef ANDROID
#include "JSIUtils/MGLJSIMacros.h"
#include "JSIUtils/MGLJSIUtils.h"
#include "Utils/MGLUtils.h"
#else
#include "MGLJSIUtils.h"
Expand Down Expand Up @@ -318,7 +318,7 @@ ByteSource GetByteSourceFromJS(jsi::Runtime &rt,
if (data.size() > INT_MAX) {
throw jsi::JSError(rt, "arg is too big (> int32): " + name);
}
return std::move(data);
return data;
}

bool ValidateIV(
Expand Down Expand Up @@ -509,7 +509,7 @@ AESCipherConfig AESCipher::GetParamsFromJS(jsi::Runtime &rt,
}

WebCryptoCipherStatus AESCipher::DoCipher(const AESCipherConfig &params,
ByteSource* out) {
ByteSource *out) {
// TODO: threading / async here, as we don't have jsi::Runtime
#define V(name, fn) \
case kKeyVariantAES_ ## name: \
Expand Down
2 changes: 1 addition & 1 deletion cpp/webcrypto/crypto_aes.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class AESCipher {
public:
AESCipher() {}
AESCipherConfig GetParamsFromJS(jsi::Runtime &rt, const jsi::Value *args);
WebCryptoCipherStatus DoCipher(const AESCipherConfig &params, ByteSource* out);
WebCryptoCipherStatus DoCipher(const AESCipherConfig &params, ByteSource *out);
};

} // namespace margelo
Expand Down
53 changes: 53 additions & 0 deletions cpp/webcrypto/crypto_keygen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "crypto_keygen.h"

#ifdef ANDROID
#include "JSIUtils/MGLJSIMacros.h"
#include "Utils/MGLUtils.h"
#else
#include "MGLJSIUtils.h"
#include "MGLUtils.h"
#endif

namespace margelo {

jsi::Value SecretKeyGen::DoKeyGen(jsi::Runtime &rt, const jsi::Value *args) {
auto skg = new SecretKeyGen(FnMode::kAsync);
CHECK(skg->getParamsFromJS(rt, args));
CHECK(skg->doKeyGen());
return toJSI(rt, std::move(skg->getKey()));
}

jsi::Value SecretKeyGen::DoKeyGenSync(jsi::Runtime &rt, const jsi::Value *args) {
auto skg = new SecretKeyGen(FnMode::kSync);
CHECK(skg->getParamsFromJS(rt, args));
CHECK(skg->doKeyGen());
return toJSI(rt, std::move(skg->getKey()));
}

bool SecretKeyGen::getParamsFromJS(jsi::Runtime &rt, const jsi::Value *args) {
SecretKeyGenConfig params;
unsigned int offset = 0;

// length
CHECK(CheckIsUint32(args[offset]));
uint32_t bits = (uint32_t)args[offset].asNumber();
params.length = bits / CHAR_BIT;

this->params_ = std::move(params);
return true;
}

bool SecretKeyGen::doKeyGen() {
// TODO: threading / async here, as we don't have jsi::Runtime
ByteSource::Builder bytes(this->params_.length);
if (CSPRNG(bytes.data<unsigned char>(), this->params_.length).is_err())
return false;
this->key_ = std::move(bytes).release();
return true;
}

ByteSource SecretKeyGen::getKey() {
return std::move(this->key_);
}

} // namespace margelo
45 changes: 45 additions & 0 deletions cpp/webcrypto/crypto_keygen.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#ifndef crypto_keygen_h
#define crypto_keygen_h

#include <jsi/jsi.h>

#include "MGLKeys.h"
#ifdef ANDROID
#include "Utils/MGLUtils.h"
#else
#include "MGLUtils.h"
#endif

namespace margelo
{

namespace jsi = facebook::jsi;

struct SecretKeyGenConfig {
size_t length; // In bytes.
ByteSource out; // Placeholder for the generated key bytes.

SecretKeyGenConfig() = default;
};

class SecretKeyGen {
public:
static jsi::Value DoKeyGen(jsi::Runtime &rt, const jsi::Value *args);
static jsi::Value DoKeyGenSync(jsi::Runtime &rt, const jsi::Value *args);
inline SecretKeyGen(FnMode mode) {
this->setMode(mode);
}
private:
inline void setMode(FnMode mode) { mode_ = mode; };
bool getParamsFromJS(jsi::Runtime &rt, const jsi::Value *args);
bool doKeyGen();
ByteSource getKey();

FnMode mode_;
SecretKeyGenConfig params_;
ByteSource key_;
};

} // namespace margelo

#endif // crypto_keygen_h
5 changes: 0 additions & 5 deletions src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import type {
} from './keys';
import { type CipherKey } from 'crypto'; // @types/node

export enum Fn {
kAsync,
kSync,
}

export type BufferLike = ArrayBuffer | Buffer | ArrayBufferView;
export type BinaryLike = string | ArrayBuffer | Buffer | TypedArray;
export type BinaryLikeNode = CipherKey | BinaryLike;
Expand Down
17 changes: 10 additions & 7 deletions src/aes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
type TagLength,
type AESLength,
type AesKeyGenParams,
SecretKeyType,
} from './keys';
import { generateKey } from './keygen';

Expand Down Expand Up @@ -293,13 +294,15 @@ export const aesGenerateKey = async (
);
}

const key = await generateKeyAsync('aes', { length }).catch((err: Error) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason' +
`[${err.message}]`,
{ name: 'OperationError', cause: err }
);
});
const key = await generateKeyAsync(SecretKeyType.AES, { length }).catch(
(err: Error) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason' +
`[${err.message}]`,
{ name: 'OperationError', cause: err }
);
}
);

return new CryptoKey(
key as SecretKeyObject,
Expand Down
6 changes: 1 addition & 5 deletions src/keygen.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { NativeQuickCrypto } from './NativeQuickCrypto/NativeQuickCrypto';
import { validateFunction } from './Utils';
import {
SecretKeyObject,
type AesKeyGenParams,
type SecretKeyType,
} from './keys';
import { SecretKeyObject, SecretKeyType, type AesKeyGenParams } from './keys';

export type KeyGenCallback = (err: Error | null, key?: SecretKeyObject) => void;

Expand Down
6 changes: 5 additions & 1 deletion src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export type KeyPairAlgorithm =
| CFRGKeyPairAlgorithm;

export type SecretKeyAlgorithm = 'HMAC' | AESAlgorithm;
export type SecretKeyType = 'hmac' | 'aes';
export type SecretKeyTypeIn = 'hmac' | 'aes';
export enum SecretKeyType {
AES,
HMAC,
}

export type SignVerifyAlgorithm =
| 'RSASSA-PKCS1-v1_5'
Expand Down

0 comments on commit 7a1fd56

Please sign in to comment.