summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/crypto.markdown16
-rw-r--r--lib/crypto.js10
-rw-r--r--src/node_crypto.cc146
-rw-r--r--src/node_crypto.h29
-rw-r--r--test/simple/test-crypto.js34
5 files changed, 235 insertions, 0 deletions
diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown
index 0c324c543..d1ed68300 100644
--- a/doc/api/crypto.markdown
+++ b/doc/api/crypto.markdown
@@ -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
diff --git a/lib/crypto.js b/lib/crypto.js
index c4256609b..16a3e38e2 100644
--- a/lib/crypto.js
+++ b/lib/crypto.js
@@ -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;
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 4abc513dc..03c2d25b2 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -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
diff --git a/src/node_crypto.h b/src/node_crypto.h
index 530f8e884..9531df0ca 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -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() {
diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js
index 4965b627f..fd9896663 100644
--- a/test/simple/test-crypto.js
+++ b/test/simple/test-crypto.js
@@ -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');