Support for signature verification with RSA/DSA public keys
authorMark Cavage <mark.cavage@joyent.com>
Thu, 9 Jun 2011 01:20:17 +0000 (18:20 -0700)
committerRyan Dahl <ry@tinyclouds.org>
Tue, 14 Jun 2011 10:50:00 +0000 (12:50 +0200)
Fixes #1166.

doc/api/crypto.markdown
src/node_crypto.cc
test/fixtures/test_rsa_privkey.pem [new file with mode: 0644]
test/fixtures/test_rsa_pubkey.pem [new file with mode: 0644]
test/simple/test-crypto.js

index b47808b..90d5595 100644 (file)
@@ -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.
index 81fb95e..f000433 100644 (file)
     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<Value> CipherInitIv(const Arguments& args) {
     Cipher *cipher = ObjectWrap::Unwrap<Cipher>(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<Value> DecipherInit(const Arguments& args) {
     Decipher *cipher = ObjectWrap::Unwrap<Decipher>(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<Value> DecipherInitIv(const Arguments& args) {
     Decipher *cipher = ObjectWrap::Unwrap<Decipher>(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<Object> 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 (file)
index 0000000..425518a
--- /dev/null
@@ -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 (file)
index 0000000..b3bbf6c
--- /dev/null
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
+6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
+Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
+oYi+1hqp1fIekaxsyQIDAQAB
+-----END PUBLIC KEY-----
index 647ba20..df5bd37 100644 (file)
@@ -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);