From 88552c51aead1520a0d20d42b4af39520baab5d1 Mon Sep 17 00:00:00 2001 From: Mark Cavage Date: Wed, 8 Jun 2011 18:20:17 -0700 Subject: [PATCH] Support for signature verification with RSA/DSA public keys Fixes #1166. --- doc/api/crypto.markdown | 10 ++-- src/node_crypto.cc | 76 +++++++++++++++++++++--------- test/fixtures/test_rsa_privkey.pem | 15 ++++++ test/fixtures/test_rsa_pubkey.pem | 6 +++ test/simple/test-crypto.js | 16 +++++++ 5 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/test_rsa_privkey.pem create mode 100644 test/fixtures/test_rsa_pubkey.pem diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown index b47808ba98..90d5595293 100644 --- a/doc/api/crypto.markdown +++ b/doc/api/crypto.markdown @@ -139,10 +139,12 @@ This is the mirror of the signing object above. Updates the verifier object with data. This can be called many times with new data as it is streamed. -### verifier.verify(cert, signature, signature_format='binary') +### verifier.verify(object, signature, signature_format='binary') -Verifies the signed data by using the `cert` which is a string containing -the PEM encoded certificate, and `signature`, which is the previously calculated -signature for the data, in the `signature_format` which can be `'binary'`, `'hex'` or `'base64'`. +Verifies the signed data by using the `object` and `signature`. `object` is a +string containing a PEM encoded object, which can be one of RSA public key, +DSA public key, or X.509 certificate. `signature` is the previously calculated +signature for the data, in the `signature_format` which can be `'binary'`, +`'hex'` or `'base64'`. Returns true or false depending on the validity of the signature for the data and public key. diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 81fb95e1ba..f000433361 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -42,6 +42,11 @@ return ThrowException(Exception::TypeError(String::New("Not a string or buffer"))); \ } +static const char *RSA_PUB_KEY_PFX = "-----BEGIN RSA PUBLIC KEY-----"; +static const char *DSA_PUB_KEY_PFX = "-----BEGIN PUBLIC KEY-----"; +static const int RSA_PUB_KEY_PFX_LEN = strlen(RSA_PUB_KEY_PFX); +static const int DSA_PUB_KEY_PFX_LEN = strlen(DSA_PUB_KEY_PFX); + namespace node { namespace crypto { @@ -1497,7 +1502,7 @@ class Cipher : public ObjectWrap { static Handle CipherInitIv(const Arguments& args) { Cipher *cipher = ObjectWrap::Unwrap(args.This()); - + HandleScope scope; cipher->incomplete_base64=NULL; @@ -1532,7 +1537,7 @@ class Cipher : public ObjectWrap { assert(iv_written == iv_len); String::Utf8Value cipherType(args[0]->ToString()); - + bool r = cipher->CipherInitIv(*cipherType, key_buf,key_len,iv_buf,iv_len); delete [] key_buf; @@ -1826,7 +1831,7 @@ class Decipher : public ObjectWrap { static Handle DecipherInit(const Arguments& args) { Decipher *cipher = ObjectWrap::Unwrap(args.This()); - + HandleScope scope; cipher->incomplete_utf8=NULL; @@ -1850,7 +1855,7 @@ class Decipher : public ObjectWrap { assert(key_written == key_len); String::Utf8Value cipherType(args[0]->ToString()); - + bool r = cipher->DecipherInit(*cipherType, key_buf,key_len); delete [] key_buf; @@ -1864,7 +1869,7 @@ class Decipher : public ObjectWrap { static Handle DecipherInitIv(const Arguments& args) { Decipher *cipher = ObjectWrap::Unwrap(args.This()); - + HandleScope scope; cipher->incomplete_utf8=NULL; @@ -1900,7 +1905,7 @@ class Decipher : public ObjectWrap { assert(iv_written == iv_len); String::Utf8Value cipherType(args[0]->ToString()); - + bool r = cipher->DecipherInitIv(*cipherType, key_buf,key_len,iv_buf,iv_len); delete [] key_buf; @@ -2265,7 +2270,7 @@ class Hmac : public ObjectWrap { } int r; - + if( Buffer::HasInstance(args[0])) { Local buffer_obj = args[0]->ToObject(); char *buffer_data = Buffer::Data(buffer_obj); @@ -2756,29 +2761,58 @@ class Verify : public ObjectWrap { int VerifyFinal(char* key_pem, int key_pemLen, unsigned char* sig, int siglen) { if (!initialised_) return 0; + EVP_PKEY* pkey = NULL; BIO *bp = NULL; - EVP_PKEY* pkey; - X509 *x509; + X509 *x509 = NULL; + int r = 0; bp = BIO_new(BIO_s_mem()); - if(!BIO_write(bp, key_pem, key_pemLen)) return 0; - - x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL ); - if (x509==NULL) return 0; + if (bp == NULL) { + ERR_print_errors_fp(stderr); + return 0; + } + if(!BIO_write(bp, key_pem, key_pemLen)) { + ERR_print_errors_fp(stderr); + return 0; + } - pkey=X509_get_pubkey(x509); - if (pkey==NULL) return 0; + // Check if this is an RSA or DSA "raw" public key before trying + // X.509 + if (strncmp(key_pem, RSA_PUB_KEY_PFX, RSA_PUB_KEY_PFX_LEN) == 0 || + strncmp(key_pem, DSA_PUB_KEY_PFX, DSA_PUB_KEY_PFX_LEN) == 0) { + pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL); + if (pkey == NULL) { + ERR_print_errors_fp(stderr); + return 0; + } + } else { + // X.509 fallback + x509 = PEM_read_bio_X509(bp, NULL, NULL, NULL); + if (x509 == NULL) { + ERR_print_errors_fp(stderr); + return 0; + } - int r = EVP_VerifyFinal(&mdctx, sig, siglen, pkey); - EVP_PKEY_free (pkey); + pkey = X509_get_pubkey(x509); + if (pkey == NULL) { + ERR_print_errors_fp(stderr); + return 0; + } + } - if (r != 1) { + r = EVP_VerifyFinal(&mdctx, sig, siglen, pkey); + if (r != 1) ERR_print_errors_fp (stderr); - } - X509_free(x509); - BIO_free(bp); + + if(pkey != NULL) + EVP_PKEY_free (pkey); + if (x509 != NULL) + X509_free(x509); + if (bp != NULL) + BIO_free(bp); EVP_MD_CTX_cleanup(&mdctx); initialised_ = false; + return r; } diff --git a/test/fixtures/test_rsa_privkey.pem b/test/fixtures/test_rsa_privkey.pem new file mode 100644 index 0000000000..425518a066 --- /dev/null +++ b/test/fixtures/test_rsa_privkey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF +NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F +UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB +AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA +QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK +kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg +f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u +412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc +mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7 +kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA +gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW +G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI +7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA== +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/test_rsa_pubkey.pem b/test/fixtures/test_rsa_pubkey.pem new file mode 100644 index 0000000000..b3bbf6cb90 --- /dev/null +++ b/test/fixtures/test_rsa_pubkey.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3 +6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6 +Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw +oYi+1hqp1fIekaxsyQIDAQAB +-----END PUBLIC KEY----- diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js index 647ba2060e..df5bd3719a 100644 --- a/test/simple/test-crypto.js +++ b/test/simple/test-crypto.js @@ -36,6 +36,8 @@ var path = require('path'); var caPem = fs.readFileSync(common.fixturesDir + '/test_ca.pem', 'ascii'); var certPem = fs.readFileSync(common.fixturesDir + '/test_cert.pem', 'ascii'); var keyPem = fs.readFileSync(common.fixturesDir + '/test_key.pem', 'ascii'); +var rsaPubPem = fs.readFileSync(common.fixturesDir + '/test_rsa_pubkey.pem', 'ascii'); +var rsaKeyPem = fs.readFileSync(common.fixturesDir + '/test_rsa_privkey.pem', 'ascii'); try { var credentials = crypto.createCredentials( @@ -144,3 +146,17 @@ assert.equal(txt, plaintext, 'encryption and decryption with key and iv'); assert.throws(function() { crypto.createHash('sha1').update({foo: 'bar'}); }, /string or buffer/); + + +// Test RSA key signing/verification +var rsaSign = crypto.createSign('RSA-SHA1'); +var rsaVerify = crypto.createVerify('RSA-SHA1'); +assert.ok(rsaSign); +assert.ok(rsaVerify); + +rsaSign.update(rsaPubPem); +var rsaSignature = rsaSign.sign(rsaKeyPem, 'hex'); +assert.equal(rsaSignature, '5c50e3145c4e2497aadb0eabc83b342d0b0021ece0d4c4a064b7c8f020d7e2688b122bfb54c724ac9ee169f83f66d2fe90abeb95e8e1290e7e177152a4de3d944cf7d4883114a20ed0f78e70e25ef0f60f06b858e6af42a2f276ede95bbc6bc9a9bbdda15bd663186a6f40819a7af19e577bb2efa5e579a1f5ce8a0d4ca8b8f6'); + +rsaVerify.update(rsaPubPem); +assert.equal(rsaVerify.verify(rsaPubPem, rsaSignature, 'hex'), 1);