crypto: add RSA encryption
authorseishun <vvnicholas@gmail.com>
Mon, 5 May 2014 14:35:28 +0000 (17:35 +0300)
committerFedor Indutny <fedor@indutny.com>
Mon, 11 Aug 2014 18:00:34 +0000 (22:00 +0400)
Reviewed-By: Fedor Indutny <fedor@indutny.com>
doc/api/crypto.markdown
lib/crypto.js
src/node_crypto.cc
src/node_crypto.h
test/simple/test-crypto.js

index 0c324c5..d1ed683 100644 (file)
@@ -593,6 +593,22 @@ Exports the encoded public key from the supplied SPKAC.
 
 Exports the encoded challenge associated with the SPKAC.
 
+## crypto.publicEncrypt(public_key, buffer)
+
+Encrypts `buffer` with `public_key`. Only RSA is currently supported.
+
+## crypto.privateDecrypt(private_key, buffer)
+
+Decrypts `buffer` with `private_key`.
+
+`private_key` can be an object or a string. If `private_key` is a string, it is
+treated as the key with no passphrase.
+
+`private_key`:
+
+* `key` : A string holding the PEM encoded private key
+* `passphrase` : A string of passphrase for the private key
+
 ## crypto.DEFAULT_ENCODING
 
 The default encoding to use for functions that can take either strings
index c425660..16a3e38 100644 (file)
@@ -355,6 +355,16 @@ Verify.prototype.verify = function(object, signature, sigEncoding) {
   return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding));
 };
 
+exports.publicEncrypt = function(object, buffer) {
+  return binding.publicEncrypt(toBuf(object), buffer);
+};
+
+exports.privateDecrypt = function(options, buffer) {
+  var key = options.key || options;
+  var passphrase = options.passphrase || null;
+  return binding.privateDecrypt(toBuf(key), buffer, passphrase);
+};
+
 
 
 exports.createDiffieHellman = exports.DiffieHellman = DiffieHellman;
index 4abc513..03c2d25 100644 (file)
@@ -65,6 +65,9 @@ static const char PUBLIC_KEY_PFX[] =  "-----BEGIN PUBLIC KEY-----";
 static const int PUBLIC_KEY_PFX_LEN = sizeof(PUBLIC_KEY_PFX) - 1;
 static const char PUBRSA_KEY_PFX[] =  "-----BEGIN RSA PUBLIC KEY-----";
 static const int PUBRSA_KEY_PFX_LEN = sizeof(PUBRSA_KEY_PFX) - 1;
+static const char CERTIFICATE_PFX[] =  "-----BEGIN CERTIFICATE-----";
+static const int CERTIFICATE_PFX_LEN = sizeof(CERTIFICATE_PFX) - 1;
+
 static const int X509_NAME_FLAGS = ASN1_STRFLGS_ESC_CTRL
                                  | ASN1_STRFLGS_ESC_MSB
                                  | XN_FLAG_SEP_MULTILINE
@@ -3544,6 +3547,139 @@ void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) {
 }
 
 
+template <PublicKeyCipher::Operation operation,
+          PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
+          PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
+bool PublicKeyCipher::Cipher(const char* key_pem,
+                             int key_pem_len,
+                             const char* passphrase,
+                             const unsigned char* data,
+                             int len,
+                             unsigned char** out,
+                             size_t* out_len) {
+  EVP_PKEY* pkey = NULL;
+  EVP_PKEY_CTX* ctx = NULL;
+  BIO* bp = NULL;
+  X509* x509 = NULL;
+  bool fatal = true;
+
+  bp = BIO_new(BIO_s_mem());
+  if (bp == NULL)
+    goto exit;
+
+  if (!BIO_write(bp, key_pem, key_pem_len))
+    goto exit;
+
+  // Check if this is a PKCS#8 or RSA public key before trying as X.509 and
+  // private key.
+  if (operation == kEncrypt &&
+      strncmp(key_pem, PUBLIC_KEY_PFX, PUBLIC_KEY_PFX_LEN) == 0) {
+    pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL);
+    if (pkey == NULL)
+      goto exit;
+  } else if (operation == kEncrypt &&
+             strncmp(key_pem, PUBRSA_KEY_PFX, PUBRSA_KEY_PFX_LEN) == 0) {
+    RSA* rsa = PEM_read_bio_RSAPublicKey(bp, NULL, NULL, NULL);
+    if (rsa) {
+      pkey = EVP_PKEY_new();
+      if (pkey)
+        EVP_PKEY_set1_RSA(pkey, rsa);
+      RSA_free(rsa);
+    }
+    if (pkey == NULL)
+      goto exit;
+  } else if (operation == kEncrypt &&
+             strncmp(key_pem, CERTIFICATE_PFX, CERTIFICATE_PFX_LEN) == 0) {
+    x509 = PEM_read_bio_X509(bp, NULL, CryptoPemCallback, NULL);
+    if (x509 == NULL)
+      goto exit;
+
+    pkey = X509_get_pubkey(x509);
+    if (pkey == NULL)
+      goto exit;
+  } else {
+    pkey = PEM_read_bio_PrivateKey(bp,
+                                   NULL,
+                                   CryptoPemCallback,
+                                   const_cast<char*>(passphrase));
+    if (pkey == NULL)
+      goto exit;
+  }
+
+  ctx = EVP_PKEY_CTX_new(pkey, NULL);
+  if (!ctx)
+    goto exit;
+  if (EVP_PKEY_cipher_init(ctx) <= 0)
+    goto exit;
+  if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0)
+    goto exit;
+  if (EVP_PKEY_cipher(ctx, NULL, out_len, data, len) <= 0)
+    goto exit;
+
+  *out = new unsigned char[*out_len];
+
+  if (EVP_PKEY_cipher(ctx, *out, out_len, data, len) <= 0)
+    goto exit;
+
+  fatal = false;
+
+ exit:
+  if (pkey != NULL)
+    EVP_PKEY_free(pkey);
+  if (bp != NULL)
+    BIO_free_all(bp);
+  if (ctx != NULL)
+    EVP_PKEY_CTX_free(ctx);
+
+  return !fatal;
+}
+
+
+template <PublicKeyCipher::Operation operation,
+          PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
+          PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher>
+void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {
+  Environment* env = Environment::GetCurrent(args.GetIsolate());
+  HandleScope scope(env->isolate());
+
+  ASSERT_IS_BUFFER(args[0]);
+  char* kbuf = Buffer::Data(args[0]);
+  ssize_t klen = Buffer::Length(args[0]);
+
+  ASSERT_IS_BUFFER(args[1]);
+  char* buf = Buffer::Data(args[1]);
+  ssize_t len = Buffer::Length(args[1]);
+
+  String::Utf8Value passphrase(args[2]);
+
+  unsigned char* out_value = NULL;
+  size_t out_len = -1;
+
+  bool r = Cipher<operation, EVP_PKEY_cipher_init, EVP_PKEY_cipher>(
+      kbuf,
+      klen,
+      args.Length() >= 3 && !args[2]->IsNull() ? *passphrase : NULL,
+      reinterpret_cast<const unsigned char*>(buf),
+      len,
+      &out_value,
+      &out_len);
+
+  if (out_len <= 0 || !r) {
+    delete[] out_value;
+    out_value = NULL;
+    out_len = 0;
+    if (!r) {
+      return ThrowCryptoError(env,
+        ERR_get_error());
+    }
+  }
+
+  args.GetReturnValue().Set(
+      Buffer::New(env, reinterpret_cast<char*>(out_value), out_len));
+  delete[] out_value;
+}
+
+
 void DiffieHellman::Initialize(Environment* env, Handle<Object> target) {
   Local<FunctionTemplate> t = FunctionTemplate::New(env->isolate(), New);
 
@@ -4730,6 +4866,16 @@ void InitCrypto(Handle<Object> target,
   NODE_SET_METHOD(target, "getSSLCiphers", GetSSLCiphers);
   NODE_SET_METHOD(target, "getCiphers", GetCiphers);
   NODE_SET_METHOD(target, "getHashes", GetHashes);
+  NODE_SET_METHOD(target,
+                  "publicEncrypt",
+                  PublicKeyCipher::Cipher<PublicKeyCipher::kEncrypt,
+                                          EVP_PKEY_encrypt_init,
+                                          EVP_PKEY_encrypt>);
+  NODE_SET_METHOD(target,
+                  "privateDecrypt",
+                  PublicKeyCipher::Cipher<PublicKeyCipher::kDecrypt,
+                                          EVP_PKEY_decrypt_init,
+                                          EVP_PKEY_decrypt>);
 }
 
 }  // namespace crypto
index 530f8e8..9531df0 100644 (file)
@@ -559,6 +559,35 @@ class Verify : public SignBase {
   }
 };
 
+class PublicKeyCipher {
+ public:
+  typedef int (*EVP_PKEY_cipher_init_t)(EVP_PKEY_CTX *ctx);
+  typedef int (*EVP_PKEY_cipher_t)(EVP_PKEY_CTX *ctx,
+                                   unsigned char *out, size_t *outlen,
+                                   const unsigned char *in, size_t inlen);
+
+  enum Operation {
+    kEncrypt,
+    kDecrypt
+  };
+
+  template <Operation operation,
+            EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
+            EVP_PKEY_cipher_t EVP_PKEY_cipher>
+  static bool Cipher(const char* key_pem,
+                     int key_pem_len,
+                     const char* passphrase,
+                     const unsigned char* data,
+                     int len,
+                     unsigned char** out,
+                     size_t* out_len);
+
+  template <Operation operation,
+            EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init,
+            EVP_PKEY_cipher_t EVP_PKEY_cipher>
+  static void Cipher(const v8::FunctionCallbackInfo<v8::Value>& args);
+};
+
 class DiffieHellman : public BaseObject {
  public:
   ~DiffieHellman() {
index 4965b62..fd98966 100644 (file)
@@ -823,6 +823,40 @@ var p = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' +
 var bad_dh = crypto.createDiffieHellman(p, 'hex');
 assert.equal(bad_dh.verifyError, constants.DH_NOT_SUITABLE_GENERATOR);
 
+// Test RSA encryption/decryption
+(function() {
+  var input = 'I AM THE WALRUS';
+  var bufferToEncrypt = new Buffer(input);
+
+  var encryptedBuffer = crypto.publicEncrypt(rsaPubPem, bufferToEncrypt);
+
+  var decryptedBuffer = crypto.privateDecrypt(rsaKeyPem, encryptedBuffer);
+  assert.equal(input, decryptedBuffer.toString());
+
+  var decryptedBufferWithPassword = crypto.privateDecrypt({
+    key: rsaKeyPemEncrypted,
+    passphrase: 'password'
+  }, encryptedBuffer);
+  assert.equal(input, decryptedBufferWithPassword.toString());
+
+  encryptedBuffer = crypto.publicEncrypt(certPem, bufferToEncrypt);
+
+  decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer);
+  assert.equal(input, decryptedBuffer.toString());
+
+  encryptedBuffer = crypto.publicEncrypt(keyPem, bufferToEncrypt);
+
+  decryptedBuffer = crypto.privateDecrypt(keyPem, encryptedBuffer);
+  assert.equal(input, decryptedBuffer.toString());
+
+  assert.throws(function() {
+    crypto.privateDecrypt({
+      key: rsaKeyPemEncrypted,
+      passphrase: 'wrong'
+    }, encryptedBuffer);
+  });
+})();
+
 // Test RSA key signing/verification
 var rsaSign = crypto.createSign('RSA-SHA1');
 var rsaVerify = crypto.createVerify('RSA-SHA1');