diff --git a/README.md b/README.md index c0bfa20..a40b62a 100644 --- a/README.md +++ b/README.md @@ -46,22 +46,23 @@ This library developed and tested primary for Node.js, but it still can work in ```javascript var NodeRSA = require('node-rsa'); -var key = new NodeRSA([key], [options]); +var key = new NodeRSA([keyData, [format]], [options]); ``` -**key** - parameters of a generated key or the key in PEM format.
-**options** - additional settings +* keyData — `{string|buffer|object}` — parameters for generating key or the key in one of supported formats.
+* format — `{string}` — format for importing key. See more details about formats in [Export/Import](#importexport-keys) section.
+* options — `{object}` — additional settings. #### Options -You can specify some options by second constructor argument, or over `key.setOptions()` method. +You can specify some options by second/third constructor argument, or over `key.setOptions()` method. -* **environment** - working environment, `'browser'` or `'node'`. Default autodetect. -* **encryptionScheme** - padding scheme for encrypt/decrypt. Can be `'pkcs1_oaep'` or `'pkcs1'`. Default `'pkcs1_oaep'`. -* **signingScheme** - scheme used for signing and verifying. Can be `'pkcs1'` or `'pss'` or 'scheme-hash' format string (eg `'pss-sha1'`). Default `'pkcs1-sha256'`, or, if chosen pss: `'pss-sha1'`. +* environment — working environment, `'browser'` or `'node'`. Default autodetect. +* encryptionScheme — padding scheme for encrypt/decrypt. Can be `'pkcs1_oaep'` or `'pkcs1'`. Default `'pkcs1_oaep'`. +* signingScheme — scheme used for signing and verifying. Can be `'pkcs1'` or `'pss'` or 'scheme-hash' format string (eg `'pss-sha1'`). Default `'pkcs1-sha256'`, or, if chosen pss: `'pss-sha1'`. **Advanced options:**
You also can specify advanced options for some schemes like this: -``` +```javascript options = { encryptionScheme: { scheme: 'pkcs1_oaep', //scheme @@ -78,7 +79,6 @@ options = { This lib supporting next hash algorithms: `'md5'`, `'ripemd160'`, `'sha1'`, `'sha256'`, `'sha512'` in browser and node environment and additional `'md4'`, `'sha'`, `'sha224'`, `'sha384'` in node only. - #### Creating "empty" key ```javascript var key = new NodeRSA(); @@ -89,6 +89,15 @@ var key = new NodeRSA(); var key = new NodeRSA({b: 512}); ``` +Also you can use next method: + +```javascript +key.generateKeyPair([bits], [exp]); +``` + +* bits — `{int}` — key size in bits. 2048 by default. +* exp — `{int}` — public exponent. 65537 by default. + #### Load key from PEM string ```javascript @@ -103,19 +112,45 @@ var key = new NodeRSA('-----BEGIN RSA PRIVATE KEY-----\n'+ '-----END RSA PRIVATE KEY-----'); ``` -Also you can use next methods: - +### Import/Export keys ```javascript -key.generateKeyPair([bits], [exp]); -key.importKey(pem_string|buffer_contains_pem); +key.importKey(keyData, [format]); +key.exportKey([format]); ``` -**bits** - key size in bits. 2048 by default. -**exp** - public exponent. 65537 by default. -### Export keys +* keyData — `{string|buffer}` — key in PEM string **or** Buffer contains PEM string **or** Buffer contains DER encoded data. +* format — `{string}` — format id for export/import. + +#### Format string syntax +Format string composed of several parts: `scheme-[key_type]-[output_type]`
+ +Scheme — NodeRSA supports multiple format schemes for import/export keys: + + * `'pkcs1'` — public key starts from `'-----BEGIN RSA PUBLIC KEY-----'` header and private key starts from `'-----BEGIN RSA PRIVATE KEY-----' header` + * `'pkcs8'` — public key starts from `'-----BEGIN PUBLIC KEY-----'` header and private key starts from `'-----BEGIN PRIVATE KEY-----' header` + +Key type — can be `'private'` or `'public'`. Default `'private'`
+Output type — can be: + + * `'pem'` — Base64 encoded string with header and footer. Used by default. + * `'der'` — Binary encoded key data. + +**Notice:** For import, if *keyData* is PEM string or buffer containing string, you can do not specify format, but if you provide *keyData* as DER you must specify it in format string. + +**Shortcuts and examples** + * `'private'` or `'pkcs1'` or `'pkcs1-private'` == `'pkcs1-private-pem'` — private key encoded in pcks1 scheme as pem string. + * `'public'` or `'pkcs8-public'` == `'pkcs8-public-pem'` — public key encoded in pcks8 scheme as pem string. + * `'pkcs8'` or `'pkcs8-private'` == `'pkcs8-private-pem'` — private key encoded in pcks8 scheme as pem string. + * `'pkcs1-der'` == `'pkcs1-private-der'` — private key encoded in pcks1 scheme as binary buffer. + * `'pkcs8-public-der'` — public key encoded in pcks8 scheme as binary buffer. + +**Code example** + ```javascript -key.exportPrivate(); -key.exportPublic(); +var keyData = '-----BEGIN PUBLIC KEY----- ... -----BEGIN PRIVATE KEY-----'; +key.importKey(keyData, 'pkcs8'); +var publicDer = key.exportKey('pkcs8-public-der'); +var privateDer = key.exportKey('pkcs1-der'); ``` ### Properties @@ -125,7 +160,7 @@ key.exportPublic(); key.isPrivate(); key.isPublic([strict]); ``` -**strict** - if true method will return false if key pair have private exponent. Default `false`. +strict — `{boolean}` — if true method will return false if key pair have private exponent. Default `false`. ```javascript key.isEmpty(); @@ -149,16 +184,18 @@ Return max data size for encrypt in bytes. key.encrypt(buffer, [encoding], [source_encoding]); ``` Return encrypted data.
-**buffer** - data for encrypting, may be string, Buffer, or any object/array. Arrays and objects will encoded to JSON string first.
-**encoding** - encoding for output result, may be `'buffer'`, `'binary'`, `'hex'` or `'base64'`. Default `'buffer'`.
-**source_encoding** - source encoding, works only with string buffer. Can take standard Node.js Buffer encodings (hex, utf8, base64, etc). `'utf8'` by default.
+ +* buffer — `{buffer}` — data for encrypting, may be string, Buffer, or any object/array. Arrays and objects will encoded to JSON string first.
+* encoding — `{string}` — encoding for output result, may be `'buffer'`, `'binary'`, `'hex'` or `'base64'`. Default `'buffer'`.
+* source_encoding — `{string}` — source encoding, works only with string buffer. Can take standard Node.js Buffer encodings (hex, utf8, base64, etc). `'utf8'` by default.
```javascript key.decrypt(buffer, [encoding]); ``` Return decrypted data.
-**buffer** - data for decrypting. Takes Buffer object or base64 encoded string.
-**encoding** - encoding for result string. Can also take `'buffer'` for raw Buffer object, or `'json'` for automatic JSON.parse result. Default `'buffer'`. + +* buffer — `{buffer}` — data for decrypting. Takes Buffer object or base64 encoded string.
+* encoding — `{string}` — encoding for result string. Can also take `'buffer'` for raw Buffer object, or `'json'` for automatic JSON.parse result. Default `'buffer'`. ### Signing/Verifying ```javascript @@ -170,10 +207,11 @@ Return signature for buffer. All the arguments are the same as for `encrypt` met key.verify(buffer, signature, [source_encoding], [signature_encoding]) ``` Return result of check, `true` or `false`.
-**buffer** - data for check, same as `encrypt` method.
-**signature** - signature for check, result of `sign` method.
-**source_encoding** - same as for `encrypt` method.
-**signature_encoding** - encoding of given signature. May be `'buffer'`, `'binary'`, `'hex'` or `'base64'`. Default `'buffer'`. + +* buffer — `{buffer}` — data for check, same as `encrypt` method.
+* signature — `{string}` — signature for check, result of `sign` method.
+* source_encoding — `{string}` — same as for `encrypt` method.
+* signature_encoding — `{string}` — encoding of given signature. May be `'buffer'`, `'binary'`, `'hex'` or `'base64'`. Default `'buffer'`. ## Contributing @@ -181,6 +219,11 @@ Questions, comments, bug reports, and pull requests are all welcome. ## Changelog +### 0.2.10 + * **Methods `.exportPrivate()` and `.exportPublic()` was replaced by `.exportKey([format])`.** + * By default `.exportKey()` returns private key as `.exportPrivate()`, if you need public key from `.exportPublic()` you must specify format as `'public'` or `'pkcs8-public-pem'`. + * Method `.importKey(key, [format])` now has second argument. + ### 0.2.0 * **`.getPublicPEM()` method was renamed to `.exportPublic()`** * **`.getPrivatePEM()` method was renamed to `.exportPrivate()`** diff --git a/package.json b/package.json index d3be58c..608ea37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-rsa", - "version": "0.2.0", + "version": "0.2.10", "description": "Node.js RSA library", "main": "src/NodeRSA.js", "scripts": { diff --git a/src/NodeRSA.js b/src/NodeRSA.js index 97528e2..572d11d 100644 --- a/src/NodeRSA.js +++ b/src/NodeRSA.js @@ -13,8 +13,7 @@ var ber = require('asn1').Ber; var _ = require('lodash'); var utils = require('./utils'); var schemes = require('./schemes/schemes.js'); - -var PUBLIC_RSA_OID = '1.2.840.113549.1.1.1'; +var formats = require('./formats/formats.js'); module.exports = (function () { var SUPPORTED_HASH_ALGORITHMS = { @@ -25,15 +24,28 @@ module.exports = (function () { var DEFAULT_ENCRYPTION_SCHEME = 'pkcs1_oaep'; var DEFAULT_SIGNING_SCHEME = 'pkcs1'; + var DEFAULT_EXPORT_FORMAT = 'private'; + var EXPORT_FORMAT_ALIASES = { + 'private': 'pkcs1-private-pem', + 'private-der': 'pkcs1-private-der', + 'public': 'pkcs8-public-pem', + 'public-der': 'pkcs8-public-der' + }; + /** * @param key {string|buffer|object} Key in PEM format, or data for generate key {b: bits, e: exponent} * @constructor */ - function NodeRSA(key, options) { + function NodeRSA(key, format, options) { if (!this instanceof NodeRSA) { return new NodeRSA(key, options); } + if (_.isObject(format)) { + options = format; + format = undefined; + } + this.$options = { signingScheme: DEFAULT_SIGNING_SCHEME, signingSchemeOptions: { @@ -49,14 +61,15 @@ module.exports = (function () { rsaUtils: this }; this.keyPair = new rsa.Key(); - this.setOptions(options); this.$cache = {}; if (Buffer.isBuffer(key) || _.isString(key)) { - this.importKey(key); + this.importKey(key, format); } else if (_.isObject(key)) { this.generateKeyPair(key.b, key.e); } + + this.setOptions(options); } /** @@ -98,6 +111,7 @@ module.exports = (function () { if (!schemes.isSignature(this.$options.signingScheme)) { throw Error('Unsupported signing scheme'); } + if (this.$options.signingSchemeOptions.hash && _.indexOf(SUPPORTED_HASH_ALGORITHMS[this.$options.environment], this.$options.signingSchemeOptions.hash) == -1) { throw Error('Unsupported hashing algorithm for ' + this.$options.environment + ' environment'); @@ -142,88 +156,51 @@ module.exports = (function () { } this.keyPair.generate(bits, exp.toString(16)); - this.$recalculateCache(); + this.$cache = {}; return this; }; /** - * Load key from PEM string - * @param pem {string} + * Importing key + * @param keyData {string|buffer} + * @param format {string} */ - NodeRSA.prototype.importKey = function (pem) { - if (Buffer.isBuffer(pem)) { - pem = pem.toString('utf8'); + NodeRSA.prototype.importKey = function (keyData, format) { + if (!keyData) { + throw Error("Empty key given"); } - if (/^\s*-----BEGIN RSA PRIVATE KEY-----\s*([A-Za-z0-9+/=]+\s*)+-----END RSA PRIVATE KEY-----\s*$/g.test(pem)) { - this.$loadFromPrivatePEM(pem, 'base64'); - } else if (/^\s*-----BEGIN PUBLIC KEY-----\s*([A-Za-z0-9+/=]+\s*)+-----END PUBLIC KEY-----\s*$/g.test(pem)) { - this.$loadFromPublicPEM(pem, 'base64'); - } else - throw Error('Invalid PEM format'); - - this.$recalculateCache(); - }; + if (format) { + format = EXPORT_FORMAT_ALIASES[format] || format; + } - /** - * Make key form private PEM string - * - * @param privatePEM {string} - */ - NodeRSA.prototype.$loadFromPrivatePEM = function (privatePEM, encoding) { - var pem = privatePEM - .replace('-----BEGIN RSA PRIVATE KEY-----', '') - .replace('-----END RSA PRIVATE KEY-----', '') - .replace(/\s+|\n\r|\n|\r$/gm, ''); - var reader = new ber.Reader(new Buffer(pem, 'base64')); - - reader.readSequence(); - reader.readString(2, true); // just zero - this.keyPair.setPrivate( - reader.readString(2, true), // modulus - reader.readString(2, true), // publicExponent - reader.readString(2, true), // privateExponent - reader.readString(2, true), // prime1 - reader.readString(2, true), // prime2 - reader.readString(2, true), // exponent1 -- d mod (p1) - reader.readString(2, true), // exponent2 -- d mod (q-1) - reader.readString(2, true) // coefficient -- (inverse of q) mod p - ); + if (!formats.detectAndImport(this.keyPair, keyData, format) && format === undefined) { + throw Error("Key format must be specified"); + } + this.$cache = {}; }; /** - * Make key form public PEM string - * - * @param publicPEM {string} + * Exporting key + * @param format {string} */ - NodeRSA.prototype.$loadFromPublicPEM = function (publicPEM, encoding) { - var pem = publicPEM - .replace('-----BEGIN PUBLIC KEY-----', '') - .replace('-----END PUBLIC KEY-----', '') - .replace(/\s+|\n\r|\n|\r$/gm, ''); - var reader = new ber.Reader(new Buffer(pem, 'base64')); - - reader.readSequence(); - var header = new ber.Reader(reader.readString(0x30, true)); - if (header.readOID(0x06, true) !== PUBLIC_RSA_OID) { - throw Error('Invalid Public key PEM format'); + NodeRSA.prototype.exportKey = function (format) { + format = format || DEFAULT_EXPORT_FORMAT; + format = EXPORT_FORMAT_ALIASES[format] || format; + + if (!this.$cache[format]) { + this.$cache[format] = formats.detectAndExport(this.keyPair, format); } - var body = new ber.Reader(reader.readString(0x03, true)); - body.readByte(); - body.readSequence(); - this.keyPair.setPublic( - body.readString(0x02, true), // modulus - body.readString(0x02, true) // publicExponent - ); + return this.$cache[format]; }; /** * Check if key pair contains private key */ NodeRSA.prototype.isPrivate = function () { - return this.keyPair.n && this.keyPair.e && this.keyPair.d || false; + return this.keyPair.isPrivate(); }; /** @@ -231,7 +208,7 @@ module.exports = (function () { * @param strict {boolean} - public key only, return false if have private exponent */ NodeRSA.prototype.isPublic = function (strict) { - return this.keyPair.n && this.keyPair.e && !(strict && this.keyPair.d) || false; + return this.keyPair.isPublic(strict); }; /** @@ -251,7 +228,7 @@ module.exports = (function () { */ NodeRSA.prototype.encrypt = function (buffer, encoding, source_encoding) { try { - var res = this.keyPair.encrypt(this.$getDataForEcrypt(buffer, source_encoding)); + var res = this.keyPair.encrypt(this.$getDataForEncrypt(buffer, source_encoding)); if (encoding == 'buffer' || !encoding) { return res; @@ -274,9 +251,11 @@ module.exports = (function () { try { buffer = _.isString(buffer) ? new Buffer(buffer, 'base64') : buffer; var res = this.keyPair.decrypt(buffer); + if (res === null) { throw Error('Key decrypt method returns null.'); } + return this.$getDecryptedData(res, encoding); } catch (e) { throw Error('Error during decryption (probably incorrect key). Original error: ' + e); @@ -295,11 +274,13 @@ module.exports = (function () { if (!this.isPrivate()) { throw Error("It is not private key"); } - var res = this.keyPair.sign(this.$getDataForEcrypt(buffer, source_encoding)); + + var res = this.keyPair.sign(this.$getDataForEncrypt(buffer, source_encoding)); if (encoding && encoding != 'buffer') { res = res.toString(encoding); } + return res; }; @@ -317,27 +298,21 @@ module.exports = (function () { throw Error("It is not public key"); } signature_encoding = (!signature_encoding || signature_encoding == 'buffer' ? null : signature_encoding); - return this.keyPair.verify(this.$getDataForEcrypt(buffer, source_encoding), signature, signature_encoding); - }; - - NodeRSA.prototype.exportPrivate = function () { - if (!this.isPrivate()) { - throw Error("It is not private key"); - } - return this.$cache.privatePEM; - }; - - NodeRSA.prototype.exportPublic = function () { - if (!this.isPublic()) { - throw Error("It is not public key"); - } - return this.$cache.publicPEM; + return this.keyPair.verify(this.$getDataForEncrypt(buffer, source_encoding), signature, signature_encoding); }; + /** + * Returns key size in bits + * @returns {int} + */ NodeRSA.prototype.getKeySize = function () { return this.keyPair.keySize; }; + /** + * Returns max message length in bytes (for 1 chunk) depending on current encryption scheme + * @returns {int} + */ NodeRSA.prototype.getMaxMessageSize = function () { return this.keyPair.maxMessageLength; }; @@ -349,7 +324,7 @@ module.exports = (function () { * @param encoding {string} - optional. Encoding for given string. Default utf8. * @returns {Buffer} */ - NodeRSA.prototype.$getDataForEcrypt = function (buffer, encoding) { + NodeRSA.prototype.$getDataForEncrypt = function (buffer, encoding) { if (_.isString(buffer) || _.isNumber(buffer)) { return new Buffer('' + buffer, encoding || 'utf8'); } else if (Buffer.isBuffer(buffer)) { @@ -379,85 +354,5 @@ module.exports = (function () { } }; - /** - * private - * Recalculating properties - */ - NodeRSA.prototype.$recalculateCache = function () { - this.$cache.privatePEM = this.$makePrivatePEM(); - this.$cache.publicPEM = this.$makePublicPEM(); - }; - - /** - * private - * @returns {string} private PEM string - */ - NodeRSA.prototype.$makePrivatePEM = function () { - if (!this.isPrivate()) { - return null; - } - - var n = this.keyPair.n.toBuffer(); - var d = this.keyPair.d.toBuffer(); - var p = this.keyPair.p.toBuffer(); - var q = this.keyPair.q.toBuffer(); - var dmp1 = this.keyPair.dmp1.toBuffer(); - var dmq1 = this.keyPair.dmq1.toBuffer(); - var coeff = this.keyPair.coeff.toBuffer(); - - var length = n.length + d.length + p.length + q.length + dmp1.length + dmq1.length + coeff.length + 512; // magic - var writer = new ber.Writer({size: length}); - - writer.startSequence(); - writer.writeInt(0); - writer.writeBuffer(n, 2); - writer.writeInt(this.keyPair.e); - writer.writeBuffer(d, 2); - writer.writeBuffer(p, 2); - writer.writeBuffer(q, 2); - writer.writeBuffer(dmp1, 2); - writer.writeBuffer(dmq1, 2); - writer.writeBuffer(coeff, 2); - writer.endSequence(); - - return '-----BEGIN RSA PRIVATE KEY-----\n' + - utils.linebrk(writer.buffer.toString('base64'), 64) + - '\n-----END RSA PRIVATE KEY-----'; - }; - - /** - * private - * @returns {string} public PEM string - */ - NodeRSA.prototype.$makePublicPEM = function () { - if (!this.isPublic()) { - return null; - } - - var n = this.keyPair.n.toBuffer(); - var length = n.length + 512; // magic - - var bodyWriter = new ber.Writer({size: length}); - bodyWriter.writeByte(0); - bodyWriter.startSequence(); - bodyWriter.writeBuffer(n, 2); - bodyWriter.writeInt(this.keyPair.e); - bodyWriter.endSequence(); - var body = bodyWriter.buffer; - - var writer = new ber.Writer({size: length}); - writer.startSequence(); - writer.startSequence(); - writer.writeOID(PUBLIC_RSA_OID); - writer.writeNull(); - writer.endSequence(); - writer.writeBuffer(body, 3); - writer.endSequence(); - - return '-----BEGIN PUBLIC KEY-----\n' + - utils.linebrk(writer.buffer.toString('base64'), 64) + - '\n-----END PUBLIC KEY-----'; - }; - return NodeRSA; })(); diff --git a/src/formats/formats.js b/src/formats/formats.js new file mode 100644 index 0000000..e975b2a --- /dev/null +++ b/src/formats/formats.js @@ -0,0 +1,94 @@ +var _ = require('lodash'); + +function formatParse(format) { + format = format.split('-'); + var keyType = 'private'; + var keyOpt = {type: 'default'}; + + for (var i = 1; i < format.length; i++) { + if (format[i]) { + switch (format[i]) { + case 'public': + keyType = format[i]; + break; + case 'private': + keyType = format[i]; + break; + case 'pem': + keyOpt.type = format[i]; + break; + case 'der': + keyOpt.type = format[i]; + break; + } + } + } + + return {scheme: format[0], keyType: keyType, keyOpt: keyOpt}; +} + +module.exports = { + pkcs1: require('./pkcs1'), + pkcs8: require('./pkcs8'), + + isPrivateExport: function (format) { + return module.exports[format] && typeof module.exports[format].privateExport === 'function'; + }, + + isPrivateImport: function (format) { + return module.exports[format] && typeof module.exports[format].privateImport === 'function'; + }, + + isPublicExport: function (format) { + return module.exports[format] && typeof module.exports[format].publicExport === 'function'; + }, + + isPublicImport: function (format) { + return module.exports[format] && typeof module.exports[format].publicImport === 'function'; + }, + + detectAndImport: function (key, data, format) { + if (format === undefined) { + for (var scheme in module.exports) { + if (typeof module.exports[scheme].autoImport === 'function' && module.exports[scheme].autoImport(key, data)) { + return true; + } + } + } else if (format) { + var fmt = formatParse(format); + if (module.exports[fmt.scheme]) { + if (fmt.keyType === 'private') { + module.exports[fmt.scheme].privateImport(key, data, fmt.keyOpt); + } else { + module.exports[fmt.scheme].publicImport(key, data, fmt.keyOpt); + } + } else { + throw Error('Unsupported key format'); + } + } + + return false; + }, + + detectAndExport: function (key, format) { + if (format) { + var fmt = formatParse(format); + + if (module.exports[fmt.scheme]) { + if (fmt.keyType === 'private') { + if (!key.isPrivate()) { + throw Error("It is not private key"); + } + return module.exports[fmt.scheme].privateExport(key, fmt.keyOpt); + } else { + if (!key.isPublic()) { + throw Error("It is not public key"); + } + return module.exports[fmt.scheme].publicExport(key, fmt.keyOpt); + } + } else { + throw Error('Unsupported key format'); + } + } + } +}; \ No newline at end of file diff --git a/src/formats/pkcs1.js b/src/formats/pkcs1.js new file mode 100644 index 0000000..715e7c0 --- /dev/null +++ b/src/formats/pkcs1.js @@ -0,0 +1,143 @@ +var ber = require('asn1').Ber; +var _ = require('lodash'); +var utils = require('../utils'); + +module.exports = { + privateExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var d = key.d.toBuffer(); + var p = key.p.toBuffer(); + var q = key.q.toBuffer(); + var dmp1 = key.dmp1.toBuffer(); + var dmq1 = key.dmq1.toBuffer(); + var coeff = key.coeff.toBuffer(); + + var length = n.length + d.length + p.length + q.length + dmp1.length + dmq1.length + coeff.length + 512; // magic + var writer = new ber.Writer({size: length}); + + writer.startSequence(); + writer.writeInt(0); + writer.writeBuffer(n, 2); + writer.writeInt(key.e); + writer.writeBuffer(d, 2); + writer.writeBuffer(p, 2); + writer.writeBuffer(q, 2); + writer.writeBuffer(dmp1, 2); + writer.writeBuffer(dmq1, 2); + writer.writeBuffer(coeff, 2); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return '-----BEGIN RSA PRIVATE KEY-----\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n-----END RSA PRIVATE KEY-----'; + } + }, + + privateImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = data.replace('-----BEGIN RSA PRIVATE KEY-----', '') + .replace('-----END RSA PRIVATE KEY-----', '') + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = new Buffer(pem, 'base64'); + } else { + throw Error('Unsupported key format'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + reader.readString(2, true); // just zero + key.setPrivate( + reader.readString(2, true), // modulus + reader.readString(2, true), // publicExponent + reader.readString(2, true), // privateExponent + reader.readString(2, true), // prime1 + reader.readString(2, true), // prime2 + reader.readString(2, true), // exponent1 -- d mod (p1) + reader.readString(2, true), // exponent2 -- d mod (q-1) + reader.readString(2, true) // coefficient -- (inverse of q) mod p + ); + }, + + publicExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var length = n.length + 512; // magic + + var bodyWriter = new ber.Writer({size: length}); + bodyWriter.startSequence(); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.endSequence(); + + if (options.type === 'der') { + return bodyWriter.buffer; + } else { + return '-----BEGIN RSA PUBLIC KEY-----\n' + utils.linebrk(bodyWriter.buffer.toString('base64'), 64) + '\n-----END RSA PUBLIC KEY-----'; + } + }, + + publicImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = data.replace('-----BEGIN RSA PUBLIC KEY-----', '') + .replace('-----END RSA PUBLIC KEY-----', '') + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = new Buffer(pem, 'base64'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var body = new ber.Reader(buffer); + body.readSequence(); + key.setPublic( + body.readString(0x02, true), // modulus + body.readString(0x02, true) // publicExponent + ); + }, + + /** + * Trying autodetect and import key + * @param key + * @param data + */ + autoImport: function (key, data) { + if (/^\s*-----BEGIN RSA PRIVATE KEY-----\s*([A-Za-z0-9+/=]+\s*)+-----END RSA PRIVATE KEY-----\s*$/g.test(data)) { + module.exports.privateImport(key, data); + return true; + } + + if (/^\s*-----BEGIN RSA PUBLIC KEY-----\s*([A-Za-z0-9+/=]+\s*)+-----END RSA PUBLIC KEY-----\s*$/g.test(data)) { + module.exports.publicImport(key, data); + return true; + } + + return false; + } +}; \ No newline at end of file diff --git a/src/formats/pkcs8.js b/src/formats/pkcs8.js new file mode 100644 index 0000000..9647879 --- /dev/null +++ b/src/formats/pkcs8.js @@ -0,0 +1,182 @@ +var ber = require('asn1').Ber; +var _ = require('lodash'); +var PUBLIC_RSA_OID = '1.2.840.113549.1.1.1'; +var utils = require('../utils'); + +module.exports = { + privateExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var d = key.d.toBuffer(); + var p = key.p.toBuffer(); + var q = key.q.toBuffer(); + var dmp1 = key.dmp1.toBuffer(); + var dmq1 = key.dmq1.toBuffer(); + var coeff = key.coeff.toBuffer(); + + var length = n.length + d.length + p.length + q.length + dmp1.length + dmq1.length + coeff.length + 512; // magic + var bodyWriter = new ber.Writer({size: length}); + + bodyWriter.startSequence(); + bodyWriter.writeInt(0); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.writeBuffer(d, 2); + bodyWriter.writeBuffer(p, 2); + bodyWriter.writeBuffer(q, 2); + bodyWriter.writeBuffer(dmp1, 2); + bodyWriter.writeBuffer(dmq1, 2); + bodyWriter.writeBuffer(coeff, 2); + bodyWriter.endSequence(); + + var writer = new ber.Writer({size: length}); + writer.startSequence(); + writer.writeInt(0); + writer.startSequence(); + writer.writeOID(PUBLIC_RSA_OID); + writer.writeNull(); + writer.endSequence(); + writer.writeBuffer(bodyWriter.buffer, 4); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return '-----BEGIN PRIVATE KEY-----\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n-----END PRIVATE KEY-----'; + } + }, + + privateImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = data.replace('-----BEGIN PRIVATE KEY-----', '') + .replace('-----END PRIVATE KEY-----', '') + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = new Buffer(pem, 'base64'); + } else { + throw Error('Unsupported key format'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + reader.readInt(0); + var header = new ber.Reader(reader.readString(0x30, true)); + + if (header.readOID(0x06, true) !== PUBLIC_RSA_OID) { + throw Error('Invalid Public key format'); + } + + var body = new ber.Reader(reader.readString(0x04, true)); + body.readSequence(); + body.readString(2, true); // just zero + key.setPrivate( + body.readString(2, true), // modulus + body.readString(2, true), // publicExponent + body.readString(2, true), // privateExponent + body.readString(2, true), // prime1 + body.readString(2, true), // prime2 + body.readString(2, true), // exponent1 -- d mod (p1) + body.readString(2, true), // exponent2 -- d mod (q-1) + body.readString(2, true) // coefficient -- (inverse of q) mod p + ); + }, + + publicExport: function (key, options) { + options = options || {}; + + var n = key.n.toBuffer(); + var length = n.length + 512; // magic + + var bodyWriter = new ber.Writer({size: length}); + bodyWriter.writeByte(0); + bodyWriter.startSequence(); + bodyWriter.writeBuffer(n, 2); + bodyWriter.writeInt(key.e); + bodyWriter.endSequence(); + + var writer = new ber.Writer({size: length}); + writer.startSequence(); + writer.startSequence(); + writer.writeOID(PUBLIC_RSA_OID); + writer.writeNull(); + writer.endSequence(); + writer.writeBuffer(bodyWriter.buffer, 3); + writer.endSequence(); + + if (options.type === 'der') { + return writer.buffer; + } else { + return '-----BEGIN PUBLIC KEY-----\n' + utils.linebrk(writer.buffer.toString('base64'), 64) + '\n-----END PUBLIC KEY-----'; + } + }, + + publicImport: function (key, data, options) { + options = options || {}; + var buffer; + + if (options.type !== 'der') { + if (Buffer.isBuffer(data)) { + data = data.toString('utf8'); + } + + if (_.isString(data)) { + var pem = data.replace('-----BEGIN PUBLIC KEY-----', '') + .replace('-----END PUBLIC KEY-----', '') + .replace(/\s+|\n\r|\n|\r$/gm, ''); + buffer = new Buffer(pem, 'base64'); + } + } else if (Buffer.isBuffer(data)) { + buffer = data; + } else { + throw Error('Unsupported key format'); + } + + var reader = new ber.Reader(buffer); + reader.readSequence(); + var header = new ber.Reader(reader.readString(0x30, true)); + + if (header.readOID(0x06, true) !== PUBLIC_RSA_OID) { + throw Error('Invalid Public key format'); + } + + var body = new ber.Reader(reader.readString(0x03, true)); + body.readByte(); + body.readSequence(); + key.setPublic( + body.readString(0x02, true), // modulus + body.readString(0x02, true) // publicExponent + ); + }, + + /** + * Trying autodetect and import key + * @param key + * @param data + */ + autoImport: function (key, data) { + if (/^\s*-----BEGIN PRIVATE KEY-----\s*([A-Za-z0-9+/=]+\s*)+-----END PRIVATE KEY-----\s*$/g.test(data)) { + module.exports.privateImport(key, data); + return true; + } + + if (/^\s*-----BEGIN PUBLIC KEY-----\s*([A-Za-z0-9+/=]+\s*)+-----END PUBLIC KEY-----\s*$/g.test(data)) { + module.exports.publicImport(key, data); + return true; + } + + return false; + } +}; diff --git a/src/libs/rsa.js b/src/libs/rsa.js index 245bb29..fe1dccd 100644 --- a/src/libs/rsa.js +++ b/src/libs/rsa.js @@ -278,6 +278,21 @@ module.exports.Key = (function() { return this.signingScheme.verify.apply(this.signingScheme, arguments); }; + /** + * Check if key pair contains private key + */ + RSAKey.prototype.isPrivate = function () { + return this.n && this.e && this.d || false; + }; + + /** + * Check if key pair contains public key + * @param strict {boolean} - public key only, return false if have private exponent + */ + RSAKey.prototype.isPublic = function (strict) { + return this.n && this.e && !(strict && this.d) || false; + }; + Object.defineProperty(RSAKey.prototype, 'keySize', { get: function() { return this.cache.keyBitLength; } }); diff --git a/src/schemes/pkcs1.js b/src/schemes/pkcs1.js index d46e297..d6907c6 100644 --- a/src/schemes/pkcs1.js +++ b/src/schemes/pkcs1.js @@ -115,7 +115,7 @@ module.exports.makeScheme = function (key, options) { } else { var signer = crypt.createSign('RSA-' + hashAlgorithm.toUpperCase()); signer.update(buffer); - return signer.sign(this.options.rsaUtils.exportPrivate()); + return signer.sign(this.options.rsaUtils.exportKey('private')); } }; @@ -137,7 +137,7 @@ module.exports.makeScheme = function (key, options) { } else { var verifier = crypt.createVerify('RSA-' + hashAlgorithm.toUpperCase()); verifier.update(buffer); - return verifier.verify(this.options.rsaUtils.exportPublic(), signature, signature_encoding); + return verifier.verify(this.options.rsaUtils.exportKey('public'), signature, signature_encoding); } }; diff --git a/test/keys/private_pkcs1.der b/test/keys/private_pkcs1.der new file mode 100644 index 0000000..3afd0ad Binary files /dev/null and b/test/keys/private_pkcs1.der differ diff --git a/test/private.key b/test/keys/private_pkcs1.pem similarity index 100% rename from test/private.key rename to test/keys/private_pkcs1.pem diff --git a/test/keys/private_pkcs8.der b/test/keys/private_pkcs8.der new file mode 100644 index 0000000..633b0c0 Binary files /dev/null and b/test/keys/private_pkcs8.der differ diff --git a/test/keys/private_pkcs8.pem b/test/keys/private_pkcs8.pem new file mode 100644 index 0000000..af76cbe --- /dev/null +++ b/test/keys/private_pkcs8.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIJ1j4SkML+89rff +mXvnVIEzx3kt/jPhBlfhtz4OpNj3NVj+uY8ZtOQK54qcgoCm44Yxck5oLA0f3BR+ +023tPR+gTCOXXq4gLybiHBKOM0vlMqoxV7JLK/TigG6J2/vq7nI2IsLgnzmVuZbd +YDJiRMJub9UlkBc0m6oKbsBVaZAxAgMBAAECgYARhHeg9+sT0+vNBNk3KfjO4IUI +YIShsrWHV2ejjZPVDly7dni0Hl+Lv2sX0aw0WjRy8HDI1nQTzxNeLSl2uURbqZ0+ +BAV3cIXGs6Aq30Aak60jXPNU5vVR13LCr+ZYE/IRPAQrOruULacBwxSvSuUWusOZ +RPZ1p8d65acUxn/VgQJBAMK1+2wf3+mtuC5DgXaU5awvP3pqLH+OcjwWEGa6QqZ8 +sxeMJlhi/4OyvxMiX+KuIapxKAaQEcegZ7WeYtRngQcCQQCrhhwHxP0MshhR5pL4 +lFbAs56NTr+th2DEEhoafabQt2n/PuUszuBvQfkCX7s85qOQB2cNyUEOHLGoicc7 +hW8HAkEAnbmM0SmA2GpAqDlGxigXZENhyGf5Y9qf7sxwvMZk9zhMWubMqQyIrsY8 +weM2iQlFfCvtoGNUA8GMQTLsG+M8QQJAU4nitwoDMR7AZ4tEQ5uD0SDqdOpIwsxv +na8vVX2jNH5QOse4OKWde7KHA9f6SoQOX4SrdXRyodvpzysjhOLNGwJAA0jKGgnf +e6CE3NsIiSeQw8q2C8s5QBGdvJr9uR9F7EuJzJjNc826pkNjwllAeWyE3xwJxlxi +MY9hixx3YsoHqg== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/test/keys/public_pkcs1.der b/test/keys/public_pkcs1.der new file mode 100644 index 0000000..58891a8 Binary files /dev/null and b/test/keys/public_pkcs1.der differ diff --git a/test/keys/public_pkcs1.pem b/test/keys/public_pkcs1.pem new file mode 100644 index 0000000..8b6b542 --- /dev/null +++ b/test/keys/public_pkcs1.pem @@ -0,0 +1,5 @@ +-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAIJ1j4SkML+89rffmXvnVIEzx3kt/jPhBlfhtz4OpNj3NVj+uY8ZtOQK +54qcgoCm44Yxck5oLA0f3BR+023tPR+gTCOXXq4gLybiHBKOM0vlMqoxV7JLK/Ti +gG6J2/vq7nI2IsLgnzmVuZbdYDJiRMJub9UlkBc0m6oKbsBVaZAxAgMBAAE= +-----END RSA PUBLIC KEY----- \ No newline at end of file diff --git a/test/keys/public_pkcs8.der b/test/keys/public_pkcs8.der new file mode 100644 index 0000000..5d2c1c3 Binary files /dev/null and b/test/keys/public_pkcs8.der differ diff --git a/test/keys/public_pkcs8.pem b/test/keys/public_pkcs8.pem new file mode 100644 index 0000000..a08ccf6 --- /dev/null +++ b/test/keys/public_pkcs8.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCCdY+EpDC/vPa335l751SBM8d5 +Lf4z4QZX4bc+DqTY9zVY/rmPGbTkCueKnIKApuOGMXJOaCwNH9wUftNt7T0foEwj +l16uIC8m4hwSjjNL5TKqMVeySyv04oBuidv76u5yNiLC4J85lbmW3WAyYkTCbm/V +JZAXNJuqCm7AVWmQMQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/test/private_pkcs1.pem b/test/private_pkcs1.pem new file mode 100644 index 0000000..2ea486c --- /dev/null +++ b/test/private_pkcs1.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCCdY+EpDC/vPa335l751SBM8d5Lf4z4QZX4bc+DqTY9zVY/rmP +GbTkCueKnIKApuOGMXJOaCwNH9wUftNt7T0foEwjl16uIC8m4hwSjjNL5TKqMVey +Syv04oBuidv76u5yNiLC4J85lbmW3WAyYkTCbm/VJZAXNJuqCm7AVWmQMQIDAQAB +AoGAEYR3oPfrE9PrzQTZNyn4zuCFCGCEobK1h1dno42T1Q5cu3Z4tB5fi79rF9Gs +NFo0cvBwyNZ0E88TXi0pdrlEW6mdPgQFd3CFxrOgKt9AGpOtI1zzVOb1Uddywq/m +WBPyETwEKzq7lC2nAcMUr0rlFrrDmUT2dafHeuWnFMZ/1YECQQDCtftsH9/prbgu +Q4F2lOWsLz96aix/jnI8FhBmukKmfLMXjCZYYv+Dsr8TIl/iriGqcSgGkBHHoGe1 +nmLUZ4EHAkEAq4YcB8T9DLIYUeaS+JRWwLOejU6/rYdgxBIaGn2m0Ldp/z7lLM7g +b0H5Al+7POajkAdnDclBDhyxqInHO4VvBwJBAJ25jNEpgNhqQKg5RsYoF2RDYchn ++WPan+7McLzGZPc4TFrmzKkMiK7GPMHjNokJRXwr7aBjVAPBjEEy7BvjPEECQFOJ +4rcKAzEewGeLREObg9Eg6nTqSMLMb52vL1V9ozR+UDrHuDilnXuyhwPX+kqEDl+E +q3V0cqHb6c8rI4TizRsCQANIyhoJ33ughNzbCIknkMPKtgvLOUARnbya/bkfRexL +icyYzXPNuqZDY8JZQHlshN8cCcZcYjGPYYscd2LKB6o= +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/test/tests.js b/test/tests.js index 90c1d76..f6aea3a 100644 --- a/test/tests.js +++ b/test/tests.js @@ -159,8 +159,8 @@ describe("NodeRSA", function(){ } }); - describe("PEM", function(){ - var privateKeyPEM = "-----BEGIN RSA PRIVATE KEY-----\n"+ + describe("Imprt/Export keys", function(){ + var privateKeyPKCS1 = "-----BEGIN RSA PRIVATE KEY-----\n"+ "MIIFwgIBAAKCAUEAsE1edyfToZRv6cFOkB0tAJ5qJor4YF5CccJAL0fS/o1Yk10V\n"+ "SXH4Xx4peSJgYQKkO0HqO1hAz6k9dFQB4U1CnWtRjtNEcIfycqrZrhu6you5syb6\n"+ "ScV3Zu/9bm7/DyaLlx/gJhUPR1OxOzaqsEvlu7hbDhNLIYo1zKFb/aUBbD6+UcaG\n"+ @@ -194,7 +194,7 @@ describe("NodeRSA", function(){ "RywhDUAjW8U0RBnzlmXhQQ6B9bjqooS2MsRrJrS5CU682fb3hBo=\n"+ "-----END RSA PRIVATE KEY-----"; - var publicKeyPEM = "-----BEGIN PUBLIC KEY-----\n"+ + var publicKeyPKCS8 = "-----BEGIN PUBLIC KEY-----\n"+ "MIIBYjANBgkqhkiG9w0BAQEFAAOCAU8AMIIBSgKCAUEAsE1edyfToZRv6cFOkB0t\n"+ "AJ5qJor4YF5CccJAL0fS/o1Yk10VSXH4Xx4peSJgYQKkO0HqO1hAz6k9dFQB4U1C\n"+ "nWtRjtNEcIfycqrZrhu6you5syb6ScV3Zu/9bm7/DyaLlx/gJhUPR1OxOzaqsEvl\n"+ @@ -205,11 +205,10 @@ describe("NodeRSA", function(){ "KY4kQIIx8JEBsAYzgyP2iy0CAwEAAQ==\n"+ "-----END PUBLIC KEY-----"; - var privateKeyPEMNotTrimmed = ' \n\n \n\n ' + privateKeyPEM + '\n \n \n\n '; - var publicKeyPEMNotTrimmed = '\n\n\n\n ' + publicKeyPEM + '\n \n\n\n '; + var privateKeyPEMNotTrimmed = ' \n\n \n\n ' + privateKeyPKCS1 + '\n \n \n\n '; + var publicKeyPEMNotTrimmed = '\n\n\n\n ' + publicKeyPKCS8 + '\n \n\n\n '; - var fileKey = __dirname + "/private.key"; - var fileKeyPEM = "-----BEGIN RSA PRIVATE KEY-----\n"+ + var fileKeyPKCS1 = "-----BEGIN RSA PRIVATE KEY-----\n"+ "MIICXAIBAAKBgQCCdY+EpDC/vPa335l751SBM8d5Lf4z4QZX4bc+DqTY9zVY/rmP\n"+ "GbTkCueKnIKApuOGMXJOaCwNH9wUftNt7T0foEwjl16uIC8m4hwSjjNL5TKqMVey\n"+ "Syv04oBuidv76u5yNiLC4J85lbmW3WAyYkTCbm/VJZAXNJuqCm7AVWmQMQIDAQAB\n"+ @@ -224,64 +223,116 @@ describe("NodeRSA", function(){ "q3V0cqHb6c8rI4TizRsCQANIyhoJ33ughNzbCIknkMPKtgvLOUARnbya/bkfRexL\n"+ "icyYzXPNuqZDY8JZQHlshN8cCcZcYjGPYYscd2LKB6o=\n"+ "-----END RSA PRIVATE KEY-----"; + var keysFolder = __dirname + '/keys/'; + var keys_formats = { + 'pkcs1-private-der': {public: false, der: true, file: 'private_pkcs1.der'}, + 'pkcs1-private-pem': {public: false, der: false, file: 'private_pkcs1.pem'}, + 'pkcs8-private-der': {public: false, der: true, file: 'private_pkcs8.der'}, + 'pkcs8-private-pem': {public: false, der: false, file: 'private_pkcs8.pem'}, + 'pkcs1-public-der': {public: true, der: true, file: 'public_pkcs1.der'}, + 'pkcs1-public-pem': {public: true, der: false, file: 'public_pkcs1.pem'}, + 'pkcs8-public-der': {public: true, der: true, file: 'public_pkcs8.der'}, + 'pkcs8-public-pem': {public: true, der: false, file: 'public_pkcs8.pem'}, + + 'private': {public: false, der: false, file: 'private_pkcs1.pem'}, + 'public': {public: true, der: false, file: 'public_pkcs8.pem'}, + 'private-der': {public: false, der: true, file: 'private_pkcs1.der'}, + 'public-der': {public: true, der: true, file: 'public_pkcs8.der'}, + + 'pkcs1': {public: false, der: false, file: 'private_pkcs1.pem'}, + 'pkcs1-private': {public: false, der: false, file: 'private_pkcs1.pem'}, + 'pkcs1-der': {public: false, der: true, file: 'private_pkcs1.der'}, + 'pkcs8': {public: false, der: false, file: 'private_pkcs8.pem'}, + 'pkcs8-private': {public: false, der: false, file: 'private_pkcs8.pem'}, + 'pkcs8-der': {public: false, der: true, file: 'private_pkcs8.der'}, + 'pkcs1-public': {public: true, der: false, file: 'public_pkcs1.pem'}, + 'pkcs8-public': {public: true, der: false, file: 'public_pkcs8.pem'} + }; describe("Good cases", function () { - it(".loadFromPrivatePEM() should load private key from (not trimmed) PEM string", function(){ - privateNodeRSA = new NodeRSA(privateKeyPEMNotTrimmed); - assert.instanceOf(privateNodeRSA.keyPair, Object); - assert(privateNodeRSA.isPrivate()); - assert(privateNodeRSA.isPublic()); - assert(!privateNodeRSA.isPublic(true)); - }); + describe("Common cases", function () { + it("should load private key from (not trimmed) PKCS1-PEM string", function () { + privateNodeRSA = new NodeRSA(privateKeyPEMNotTrimmed); + assert.instanceOf(privateNodeRSA.keyPair, Object); + assert(privateNodeRSA.isPrivate()); + assert(privateNodeRSA.isPublic()); + assert(!privateNodeRSA.isPublic(true)); + }); - it(".loadFromPublicPEM() should load public key from (not trimmed) PEM string", function(){ - publicNodeRSA = new NodeRSA(publicKeyPEMNotTrimmed); - assert.instanceOf(privateNodeRSA.keyPair, Object); - assert(publicNodeRSA.isPublic()); - assert(publicNodeRSA.isPublic(true)); - assert(!publicNodeRSA.isPrivate()); - }); + it("should load public key from (not trimmed) PKCS8-PEM string", function () { + publicNodeRSA = new NodeRSA(publicKeyPEMNotTrimmed); + assert.instanceOf(privateNodeRSA.keyPair, Object); + assert(publicNodeRSA.isPublic()); + assert(publicNodeRSA.isPublic(true)); + assert(!publicNodeRSA.isPrivate()); + }); - it(".exportPrivate() should return private PEM string", function(){ - assert.equal(privateNodeRSA.exportPrivate(), privateKeyPEM); - }); + it(".exportKey() should return private PEM string", function () { + assert.equal(privateNodeRSA.exportKey('private'), privateKeyPKCS1); + assert.equal(privateNodeRSA.exportKey(), privateKeyPKCS1); + }); - it(".exportPublic() from public key should return public PEM string", function(){ - assert.equal(publicNodeRSA.exportPublic(), publicKeyPEM); - }); + it(".exportKey() from public key should return pkcs8 public PEM string", function () { + assert.equal(publicNodeRSA.exportKey('public'), publicKeyPKCS8); + }); - it(".exportPublic() from private key should return public PEM string", function(){ - assert.equal(privateNodeRSA.exportPublic(), publicKeyPEM); - }); + it(".exportKey() from private key should return pkcs8 public PEM string", function () { + assert.equal(privateNodeRSA.exportKey('public'), publicKeyPKCS8); + }); - it("should create key from buffer/fs.readFileSync output", function(){ - var key = new NodeRSA(fs.readFileSync(fileKey)); - assert.equal(key.exportPrivate(), fileKeyPEM); - key = new NodeRSA(); - key.importKey(fs.readFileSync(fileKey)); - assert.equal(key.exportPrivate(), fileKeyPEM); + it("should create and load key from buffer/fs.readFileSync output", function () { + var key = new NodeRSA(fs.readFileSync(keysFolder + 'private_pkcs1.pem')); + assert.equal(key.exportKey(), fileKeyPKCS1); + key = new NodeRSA(); + key.importKey(fs.readFileSync(keysFolder + 'private_pkcs1.pem')); + assert.equal(key.exportKey(), fileKeyPKCS1); + }); }); - it("should load PEM from buffer/fs.readFileSync output", function(){ - var key = new NodeRSA(); - assert.equal(key.isEmpty(), true); - key.importKey(fs.readFileSync(fileKey)); - assert.equal(key.isEmpty(), false); - assert.equal(key.exportPrivate(), fileKeyPEM); + describe("Different key formats", function () { + var sampleKey = new NodeRSA(fileKeyPKCS1); + + for(var format in keys_formats) { + (function(format) { + var options = keys_formats[format]; + + it("should load from " + options.file + " (" + format + ")", function () { + var key = new NodeRSA(fs.readFileSync(keysFolder + options.file), format); + if (options.public) { + assert.equal(key.exportKey('public'), sampleKey.exportKey('public')); + } else { + assert.equal(key.exportKey(), sampleKey.exportKey()); + } + }); + + it("should export to \"" + format + "\" format", function () { + var keyData = fs.readFileSync(keysFolder + options.file); + var exported = sampleKey.exportKey(format); + + if (options.der) { + assert(Buffer.isBuffer(exported)); + assert.equal(exported.toString('hex'), keyData.toString('hex')); + } else { + assert(_.isString(exported)); + assert.equal(exported.replace(/\s+|\n\r|\n|\r$/gm, ''), keyData.toString('utf8').replace(/\s+|\n\r|\n|\r$/gm, '')); + } + }); + })(format); + } }); }); describe("Bad cases", function () { it("not public key", function(){ var key = new NodeRSA(); - assert.throw(function(){ key.exportPrivate(); }, Error, "It is not private key"); - assert.throw(function(){ key.exportPublic(); }, Error, "It is not public key"); + assert.throw(function(){ key.exportKey(); }, Error, "It is not private key"); + assert.throw(function(){ key.exportKey('public'); }, Error, "It is not public key"); }); it("not private key", function(){ - var key = new NodeRSA(publicKeyPEM); - assert.throw(function(){ key.exportPrivate(); }, Error, "It is not private key"); - assert.doesNotThrow(function(){ key.exportPublic(); }, Error, "It is not public key"); + var key = new NodeRSA(publicKeyPKCS8); + assert.throw(function(){ key.exportKey(); }, Error, "It is not private key"); + assert.doesNotThrow(function(){ key.exportKey('public'); }, Error, "It is not public key"); }); }); }); @@ -355,7 +406,7 @@ describe("NodeRSA", function(){ (function (i) { var suit = dataBundle[i]; it("should sign " + i, function () { - key = new NodeRSA(generatedKeys[generatedKeys.length - 1].exportPrivate(), { + key = new NodeRSA(generatedKeys[generatedKeys.length - 1].exportKey(), { signingScheme: scheme + '-sha256', environment: env }); @@ -376,7 +427,7 @@ describe("NodeRSA", function(){ for (var alg in signHashAlgorithms[env]) { (function (alg) { it("signing with custom algorithm (" + alg + ")", function () { - var key = new NodeRSA(generatedKeys[generatedKeys.length - 1].exportPrivate(), { + var key = new NodeRSA(generatedKeys[generatedKeys.length - 1].exportKey(), { signingScheme: scheme + '-' + alg, environment: env }); @@ -392,7 +443,7 @@ describe("NodeRSA", function(){ describe("Bad cases" + (envs.length > 1 ? " in " + env + " environment" : ""), function () { it("incorrect data for verifying", function () { - var key = new NodeRSA(generatedKeys[0].exportPrivate(), { + var key = new NodeRSA(generatedKeys[0].exportKey(), { signingScheme: scheme + '-sha256', environment: env }); @@ -401,7 +452,7 @@ describe("NodeRSA", function(){ }); it("incorrect key for signing", function () { - var key = new NodeRSA(generatedKeys[0].exportPublic(), { + var key = new NodeRSA(generatedKeys[0].exportKey('pkcs8-public'), { signingScheme: scheme + '-sha256', environment: env }); @@ -411,11 +462,11 @@ describe("NodeRSA", function(){ }); it("incorrect key for verifying", function () { - var key1 = new NodeRSA(generatedKeys[0].exportPrivate(), { + var key1 = new NodeRSA(generatedKeys[0].exportKey(), { signingScheme: scheme + '-sha256', environment: env }); - var key2 = new NodeRSA(generatedKeys[1].exportPublic(), { + var key2 = new NodeRSA(generatedKeys[1].exportKey('pkcs8-public'), { signingScheme: scheme + '-sha256', environment: env }); @@ -432,11 +483,11 @@ describe("NodeRSA", function(){ }); it("different algorithms", function () { - var singKey = new NodeRSA(generatedKeys[0].exportPrivate(), { + var singKey = new NodeRSA(generatedKeys[0].exportKey(), { signingScheme: scheme + '-md5', environment: env }); - var verifyKey = new NodeRSA(generatedKeys[0].exportPrivate(), { + var verifyKey = new NodeRSA(generatedKeys[0].exportKey(), { signingScheme: scheme + '-sha1', environment: env }); @@ -455,11 +506,11 @@ describe("NodeRSA", function(){ for (var alg in signHashAlgorithms['browser']) { (function (alg) { it("signing with custom algorithm (" + alg + ") (equal test)", function () { - var nodeKey = new NodeRSA(generatedKeys[5].exportPrivate(), { + var nodeKey = new NodeRSA(generatedKeys[5].exportKey(), { signingScheme: scheme + '-' + alg, environment: 'node' }); - var browserKey = new NodeRSA(generatedKeys[5].exportPrivate(), { + var browserKey = new NodeRSA(generatedKeys[5].exportKey(), { signingScheme: scheme + '-' + alg, environment: 'browser' }); @@ -468,11 +519,11 @@ describe("NodeRSA", function(){ }); it("sign in node & verify in browser (" + alg + ")", function () { - var nodeKey = new NodeRSA(generatedKeys[5].exportPrivate(), { + var nodeKey = new NodeRSA(generatedKeys[5].exportKey(), { signingScheme: scheme + '-' + alg, environment: 'node' }); - var browserKey = new NodeRSA(generatedKeys[5].exportPrivate(), { + var browserKey = new NodeRSA(generatedKeys[5].exportKey(), { signingScheme: scheme + '-' + alg, environment: 'browser' }); @@ -481,11 +532,11 @@ describe("NodeRSA", function(){ }); it("sign in browser & verify in node (" + alg + ")", function () { - var nodeKey = new NodeRSA(generatedKeys[5].exportPrivate(), { + var nodeKey = new NodeRSA(generatedKeys[5].exportKey(), { signingScheme: scheme + '-' + alg, environment: 'node' }); - var browserKey = new NodeRSA(generatedKeys[5].exportPrivate(), { + var browserKey = new NodeRSA(generatedKeys[5].exportKey(), { signingScheme: scheme + '-' + alg, environment: 'browser' });