From 944dece506c9dfe2fd607facdcc41c66b36d3024 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Thu, 15 Mar 2018 01:00:22 -0700 Subject: [PATCH] Adds Brainpool Curves + tests + docs --- README.md | 24 ++++--- src/crypto/public_key/dsa.js | 3 +- src/crypto/public_key/elgamal.js | 3 +- src/crypto/public_key/elliptic/curves.js | 50 +++++++++----- src/crypto/public_key/rsa.js | 3 +- src/enums.js | 27 +++++++- src/openpgp.js | 4 +- test/crypto/elliptic.js | 18 ++--- test/general/brainpool.js | 83 ++++++++++++++++++++++++ test/general/index.js | 1 + 10 files changed, 166 insertions(+), 50 deletions(-) create mode 100644 test/general/brainpool.js diff --git a/README.md b/README.md index 7ae0a4de..f3f673a9 100644 --- a/README.md +++ b/README.md @@ -48,14 +48,17 @@ OpenPGP.js [![Build Status](https://travis-ci.org/openpgpjs/openpgpjs.svg?branch * Version 3.0.0 of the library introduces support for public-key cryptography using [elliptic curves](https://wiki.gnupg.org/ECC). We use native implementations on browsers and Node.js when available or [Elliptic](https://github.com/indutny/elliptic) otherwise. Elliptic curve cryptography provides stronger security per bits of key, which allows for much faster operations. Currently the following curves are supported (* = when available): - | Curve | Encryption | Signature | Elliptic | NodeCrypto | WebCrypto | - |:---------- |:----------:|:---------:|:--------:|:----------:|:---------:| - | p256 | ECDH | ECDSA | Yes | Yes* | Yes* | - | p384 | ECDH | ECDSA | Yes | Yes* | Yes* | - | p521 | ECDH | ECDSA | Yes | Yes* | Yes* | - | secp256k1 | ECDH | ECDSA | Yes | Yes* | No | - | curve25519 | ECDH | N/A | Yes | No | No | - | ed25519 | N/A | EdDSA | Yes | No | No | + | Curve | Encryption | Signature | Elliptic | NodeCrypto | WebCrypto | + |:--------------- |:----------:|:---------:|:--------:|:----------:|:---------:| + | p256 | ECDH | ECDSA | Yes | Yes* | Yes* | + | p384 | ECDH | ECDSA | Yes | Yes* | Yes* | + | p521 | ECDH | ECDSA | Yes | Yes* | Yes* | + | secp256k1 | ECDH | ECDSA | Yes | Yes* | No | + | curve25519 | ECDH | N/A | Yes | No (TODO) | No | + | ed25519 | N/A | EdDSA | Yes | No (TODO) | No | + | brainpoolP256r1 | ECDH | ECDSA | Yes | No (TODO) | No | + | brainpoolP384r1 | ECDH | ECDSA | Yes | No (TODO) | No | + | brainpoolP512r1 | ECDH | ECDSA | Yes | No (TODO) | No | * Version 2.x of the library has been built from the ground up with Uint8Arrays. This allows for much better performance and memory usage than strings. @@ -199,8 +202,9 @@ var options = { ECC keys: -Possible values for curve are curve25519, ed25519, p256, p384, p521, or secp256k1. -Note that options both curve25519 and ed25519 generate a primary key for signing using Ed25519 +Possible values for curve are: `curve25519`, `ed25519`, `p256`, `p384`, `p521`, `secp256k1`, +`brainpoolP256r1`, `brainpoolP384r1`, or `brainpoolP512r1`. +Note that options both `curve25519` and `ed25519` generate a primary key for signing using Ed25519 and a subkey for encryption using Curve25519. ```js diff --git a/src/crypto/public_key/dsa.js b/src/crypto/public_key/dsa.js index ecf547ee..c686b7f6 100644 --- a/src/crypto/public_key/dsa.js +++ b/src/crypto/public_key/dsa.js @@ -14,10 +14,9 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -// -// A Digital signature algorithm implementation /** + * @fileoverview A Digital signature algorithm implementation * @requires bn.js * @requires crypto/hash * @requires crypto/random diff --git a/src/crypto/public_key/elgamal.js b/src/crypto/public_key/elgamal.js index 2ee9e062..542208ff 100644 --- a/src/crypto/public_key/elgamal.js +++ b/src/crypto/public_key/elgamal.js @@ -14,10 +14,9 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -// -// ElGamal implementation /** + * @fileoverview ElGamal implementation * @requires bn.js * @requires crypto/random * @module crypto/public_key/elgamal diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js index c676d167..fe12cd7b 100644 --- a/src/crypto/public_key/elliptic/curves.js +++ b/src/crypto/public_key/elliptic/curves.js @@ -38,20 +38,23 @@ import OID from '../../../type/oid'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); -const nodeCurves = {}; const webCurves = { 'p256': 'P-256', 'p384': 'P-384', 'p521': 'P-521' }; -if (nodeCrypto) { - const knownCurves = nodeCrypto.getCurves(); - nodeCurves.secp256k1 = knownCurves.includes('secp256k1') ? 'secp256k1' : undefined; - nodeCurves.p256 = knownCurves.includes('prime256v1') ? 'prime256v1' : undefined; - nodeCurves.p384 = knownCurves.includes('secp384r1') ? 'secp384r1' : undefined; - nodeCurves.p521 = knownCurves.includes('secp521r1') ? 'secp521r1' : undefined; - // TODO add more here -} +const knownCurves = nodeCrypto ? nodeCrypto.getCurves() : []; +const nodeCurves = nodeCrypto ? { + secp256k1: knownCurves.includes('secp256k1') ? 'secp256k1' : undefined, + p256: knownCurves.includes('prime256v1') ? 'prime256v1' : undefined, + p384: knownCurves.includes('secp384r1') ? 'secp384r1' : undefined, + p521: knownCurves.includes('secp521r1') ? 'secp521r1' : undefined, + ed25519: knownCurves.includes('ED25519') ? 'ED25519' : undefined, + curve25519: knownCurves.includes('X25519') ? 'X25519' : undefined, + brainpoolP256r1: knownCurves.includes('brainpoolP256r1') ? 'brainpoolP256r1' : undefined, + brainpoolP384r1: knownCurves.includes('brainpoolP384r1') ? 'brainpoolP384r1' : undefined, + brainpoolP512r1: knownCurves.includes('brainpoolP512r1') ? 'brainpoolP512r1' : undefined +} : {}; const curves = { p256: { @@ -92,22 +95,35 @@ const curves = { oid: [0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01], keyType: enums.publicKey.eddsa, hash: enums.hash.sha512, - payloadSize: 32 + node: false // nodeCurves.ed25519 TODO }, curve25519: { oid: [0x06, 0x08, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01], keyType: enums.publicKey.ecdsa, hash: enums.hash.sha256, - cipher: enums.symmetric.aes128 + cipher: enums.symmetric.aes128, + node: false // nodeCurves.curve25519 TODO }, - brainpoolP256r1: { // TODO 1.3.36.3.3.2.8.1.1.7 - oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07] + brainpoolP256r1: { + oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07], + keyType: enums.publicKey.ecdsa, + hash: enums.hash.sha256, + cipher: enums.symmetric.aes128, + node: false // nodeCurves.brainpoolP256r1 TODO }, - brainpoolP384r1: { // TODO 1.3.36.3.3.2.8.1.1.11 - oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B] + brainpoolP384r1: { + oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B], + keyType: enums.publicKey.ecdsa, + hash: enums.hash.sha384, + cipher: enums.symmetric.aes192, + node: false // nodeCurves.brainpoolP384r1 TODO }, - brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13 - oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D] + brainpoolP512r1: { + oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D], + keyType: enums.publicKey.ecdsa, + hash: enums.hash.sha512, + cipher: enums.symmetric.aes256, + node: false // nodeCurves.brainpoolP512r1 TODO } }; diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index 219962b3..2c2ed75c 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -14,10 +14,9 @@ // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -// -// RSA implementation /** + * @fileoverview RSA implementation * @requires bn.js * @requires crypto/public_key/prime * @requires crypto/random diff --git a/src/enums.js b/src/enums.js index e69f5def..ed4f7623 100644 --- a/src/enums.js +++ b/src/enums.js @@ -5,6 +5,7 @@ export default { /** Maps curve names under various standards to one + * @see {@link https://wiki.gnupg.org/ECC|ECC - GnuPG wiki} * @enum {String} * @readonly */ @@ -34,13 +35,14 @@ export default { "2b81040023": "p521", "2B81040023": "p521", - /** SECP256k1 Curve */ + /** SECG SECP256k1 Curve */ "secp256k1": "secp256k1", "1.3.132.0.10": "secp256k1", "2b8104000a": "secp256k1", "2B8104000A": "secp256k1", - /** Ed25519 Curve */ + /** Ed25519 */ + "ED25519": "ed25519", "ed25519": "ed25519", "Ed25519": "ed25519", "1.3.6.1.4.1.11591.15.1": "ed25519", @@ -48,12 +50,31 @@ export default { "2B06010401DA470F01": "ed25519", /** Curve25519 */ + "X25519": "curve25519", "cv25519": "curve25519", "curve25519": "curve25519", "Curve25519": "curve25519", "1.3.6.1.4.1.3029.1.5.1": "curve25519", "2b060104019755010501": "curve25519", - "2B060104019755010501": "curve25519" + "2B060104019755010501": "curve25519", + + /** BrainpoolP256r1 Curve */ + "brainpoolP256r1": "brainpoolP256r1", + "1.3.36.3.3.2.8.1.1.7": "brainpoolP256r1", + "2b2403030208010107": "brainpoolP256r1", + "2B2403030208010107": "brainpoolP256r1", + + /** BrainpoolP384r1 Curve */ + "brainpoolP384r1": "brainpoolP384r1", + "1.3.36.3.3.2.8.1.1.11": "brainpoolP384r1", + "2b240303020801010b": "brainpoolP384r1", + "2B240303020801010B": "brainpoolP384r1", + + /** BrainpoolP512r1 Curve */ + "brainpoolP512r1": "brainpoolP512r1", + "1.3.36.3.3.2.8.1.1.13": "brainpoolP512r1", + "2b240303020801010d": "brainpoolP512r1", + "2B240303020801010D": "brainpoolP512r1" }, /** A string to key specifier type diff --git a/src/openpgp.js b/src/openpgp.js index ed120d71..efd21cce 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -99,7 +99,9 @@ export function destroyWorker() { * @param {Array} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key * @param {Number} numBits (optional) number of bits for RSA keys: 2048 or 4096. - * @param {String} curve (optional) elliptic curve for ECC keys: curve25519, p256, p384, p521, or secp256k1 + * @param {String} curve (optional) elliptic curve for ECC keys: + * curve25519, p256, p384, p521, secp256k1, + * brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1. * @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @returns {Promise} The generated key object in the form: diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js index 26f816b8..e1156a63 100644 --- a/test/crypto/elliptic.js +++ b/test/crypto/elliptic.js @@ -139,22 +139,14 @@ describe('Elliptic Curve Cryptography', function () { } }; describe('Basic Operations', function () { - it('Creating curve with name', function (done) { - const names = ['p256', 'p384', 'p521', 'secp256k1', 'curve25519']; - names.forEach(function (name) { - expect(new elliptic_curves.Curve(name)).to.exist; - }); - done(); - }); - it('Creating curve from oid', function (done) { - const oids = ['2A8648CE3D030107', '2B81040022', '2B81040023', '2B8104000A']; - oids.forEach(function (oid) { - expect(new elliptic_curves.Curve(oid)).to.exist; - }); + it('Creating curve from name or oid', function (done) { + for (let name_or_oid in openpgp.enums.curves) { + expect(new elliptic_curves.Curve(name_or_oid)).to.exist; + } done(); }); it('Creating KeyPair', function () { - const names = ['p256', 'p384', 'p521', 'secp256k1', 'curve25519']; + const names = ['p256', 'p384', 'p521', 'secp256k1', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1']; return Promise.all(names.map(function (name) { const curve = new elliptic_curves.Curve(name); return curve.genKeyPair().then(keyPair => { diff --git a/test/general/brainpool.js b/test/general/brainpool.js new file mode 100644 index 00000000..2ecd75e5 --- /dev/null +++ b/test/general/brainpool.js @@ -0,0 +1,83 @@ +/* globals tryTests: true */ + +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +const chai = require('chai'); +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + +describe('Brainpool Cryptography', function () { + // TODO add test vectors encrypted and signed by GnuPG or other implementation + + function omnibus() { + it('Omnibus BrainpoolP256r1 Test', function () { + const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "brainpoolP256r1" }; + return openpgp.generateKey(options).then(function (firstKey) { + const hi = firstKey.key; + const pubHi = hi.toPublic(); + + const options = { userIds: { name: "Bye", email: "bye@good.bye" }, curve: "brainpoolP256r1" }; + return openpgp.generateKey(options).then(function (secondKey) { + const bye = secondKey.key; + const pubBye = bye.toPublic(); + + return Promise.all([ + // Signing message + openpgp.sign( + { data: 'Hi, this is me, Hi!', privateKeys: hi } + ).then(signed => { + const msg = openpgp.cleartext.readArmored(signed.data); + // Verifying signed message + return Promise.all([ + openpgp.verify( + { message: msg, publicKeys: pubHi } + ).then(output => expect(output.signatures[0].valid).to.be.true), + // Verifying detached signature + openpgp.verify( + { message: openpgp.message.fromText('Hi, this is me, Hi!'), + publicKeys: pubHi, + signature: openpgp.signature.readArmored(signed.data) } + ).then(output => expect(output.signatures[0].valid).to.be.true) + ]); + }), + // Encrypting and signing + openpgp.encrypt( + { data: 'Hi, Hi wrote this but only Bye can read it!', + publicKeys: [pubBye], + privateKeys: [hi] } + ).then(encrypted => { + const msg = openpgp.message.readArmored(encrypted.data); + // Decrypting and verifying + return openpgp.decrypt( + { message: msg, + privateKeys: bye, + publicKeys: [pubHi] } + ).then(output => { + expect(output.data).to.equal('Hi, Hi wrote this but only Bye can read it!'); + expect(output.signatures[0].valid).to.be.true; + }); + }) + ]); + }); + }); + }); + } + + omnibus(); + + tryTests('Brainpool Worker Tests', omnibus, { + if: typeof window !== 'undefined' && window.Worker, + before: function() { + openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + }, + beforeEach: function() { + openpgp.config.use_native = true; + }, + after: function() { + openpgp.destroyWorker(); + } + }); + + // TODO find test vectors +}); diff --git a/test/general/index.js b/test/general/index.js index 5a0087f9..ebd7cb32 100644 --- a/test/general/index.js +++ b/test/general/index.js @@ -10,6 +10,7 @@ describe('General', function () { require('./oid.js'); require('./ecc_nist.js'); require('./x25519.js'); + require('./brainpool.js'); require('./decompression.js'); });