8

I need to use node.js built in module 'crypto' from my C++ addon. I tried to find examples of C++ addons which use built in modules, but failed. I looked in node_crypto.h/.cc and it has so different function signatures comparing with node.js crypto documentation, protected constructors, etc. node_crypto.h contains InitCrypto() declaration with one parameter, but node_crypto.cc has not definition of such function. There is only InitCrypto with four parameters. I tried anyway use InitCrypto with one parameter and got "symbol lookup error".

I could pass result of require('crypto') to my addon and then work with this object, but this is so unsecure. Our JS code works on client's server.

For now I think that it is simpler for C++ addon to use smth like openssl lib instead of built in node module 'crypto'.

So I need some working example with C++ addon which is using 'crypto' module or link to some article about this.

Any example using any built in module from C++ addon would be helpful.

ZachB
  • 13,051
  • 4
  • 61
  • 89
Dzenly
  • 1,611
  • 12
  • 18

1 Answers1

9

I used the same way when had a need to encrypt/decrypt data in a Nodejs addon.

As I understand, classes from node_crypto.h are used to make native bindings in Nodejs, I couldn't use them in my addon.

Then I tried to use OpenSSL from Nodejs but couldn't do it because OpenSSL is linked statically into the Nodejs executable.

After that I tried to call a JavaScript code from C++ and finally got the following solution - to invoke Nodejs functions from C++ code:

using namespace v8;

// persistent handle for the crypto module
static Persistent<Object> node_crypto;

// Addon startup procedure
void Init(Local<Object> exports, Local<Object> module)
{
    Isolate* isolate = Isolate::GetCurrent();
    HandleScope scope(isolate);

    // get `require` function
    Local<Function> require = module->Get(String::NewFromUtf8(isolate, "require")).As<Function>();

    // call require('crypto')
    Local<Value> args[] = { String::NewFromUtf8(isolate, "crypto") };
    Local<Object> crypto = require->Call(module, 1, args).As<Object>();

    // store crypto module in persistent handle for further use
    node_crypto.Reset(isolate, crypto);   
}

NODE_MODULE(addon, Init);

// must be invoked in then Node main thread since the function uses V8 API
std::string encrypt(std::string const& key, std::string const& text)
{
    Isolate* isolate = Isolate::GetCurrent();
    HandleScope scope(isolate);

    // get local handle from persistent
    Local<Object> crypto = Local<Object>::New(isolate, node_crypto);

    // get `createCipher` function from the crypto module
    Local<Function> createCipher = crypto->Get(String::NewFromUtf8(isolate, "createCipher")).As<Function>();

    // call crypto.createCipher("aes256", key)
    Local<Value> create_args[] =
    {
        String::NewFromUtf8(isolate, "aes256"),
        String::NewFromUtf8(isolate, key.c_str())
    };
    Local<Object> cipher = createCipher->Call(crypto, 2, create_args).As<Object>();

    // get update and final functions from the crypto module
    Local<Function> update = cipher->Get(String::NewFromUtf8(isolate, "update")).As<Function>();
    Local<Function> final = cipher->Get(String::NewFromUtf8(isolate, "final")).As<Function>();

    // buf1 = cipher.update(text), buf2 = cipher.final()
    Local<Value> update_args[] = { node::Buffer::New(isolate, text.data(), text.size()) };

    Local<Value> buf1 = update->Call(cipher, 1, update_args);
    Local<Value> buf2 = final->Call(cipher, 0, nullptr);

    // concatenate update and final buffers into result string
    char const* const data1 = node::Buffer::Data(buf1);
    char const* const data2 = node::Buffer::Data(buf2);

    size_t const size1 = node::Buffer::Length(buf1);
    size_t const size2 = node::Buffer::Lenght(buf2);

    std::string result;
    result.reserve(size1 + size2);
    result.append(data1, size1);
    result.append(data2, size2);
    return result;
}

std::string decrypt(std::string const& key, std::string const& text)
{
    // similar as in encrypt, use createDecipher instead
}

As you can see, C++ code with V8 API is quite verbose. In the real project I used utility functions from my v8pp library to get object properties and call functions with data conversion to V8 handles.

pmed
  • 1,536
  • 8
  • 13
  • Big thank you! This solution is more secure then passing result of require('crypto') to addon. If nobody will post pure C++ solution till the end of November, I will mark this as answer. Am I understand correctly that someone can correct built in modules in node_src_root/lib/*.js files and these corrected *.js files will be used by the addon? Did you any performance testing? Say, try to cache some functions in global objects and comparison with third party crypto libs? – Dzenly Nov 06 '15 at 08:10
  • 1
    I used this as a quick and dirty way to avoid an external crypto library dependency in my addon, because building such a stuff like OpenSSL would be a headache. Yes, you are right this solution used `lib\*.js` from node sources. If someone have changed the sources and would run this modified Nodejs it may be unsecure. I needed to encrypt/decrypt a block of data only once on application start. So I didn't do any performance tests for this solution. – pmed Nov 06 '15 at 10:17
  • Great solution! – Icebob Sep 09 '16 at 17:21
  • 2
    Thanks! By the way, Nodejs exports OpenSSL symbols on Windows since version 6.4.0 (in pull request https://github.com/nodejs/node/pull/7983). So that approach from node-gyp wiki (https://github.com/nodejs/node-gyp/wiki/Linking-to-OpenSSL) may also work. – pmed Sep 09 '16 at 19:46
  • Unfortunately Node API(previously n-api) no longer provides `module` access and it needs passed in from js: https://github.com/nodejs/node-addon-api/issues/449 – Dickeylth Nov 05 '21 at 03:48
  • As far as I understand, there is still `NODE_MODULE_INIT` macro for [Context-aware addons](https://nodejs.org/dist/latest/docs/api/addons.html#context-aware-addons) that allows access to `exports`, `module`, `context` for native C++ addons. Node API is a C abstraction over the Node.js C++ addon API, so it can drop some things in favour to be more abstract. – pmed Nov 06 '21 at 11:51