diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js new file mode 100644 index 00000000..b453efc8 --- /dev/null +++ b/src/crypto/public_key/elliptic/ecdsa.js @@ -0,0 +1,66 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// 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 + +// Implementation of ECDSA following RFC6637 for Openpgpjs + +/** + * @requires crypto/public_key/jsbn + * @requires crypto/public_key/elliptic/curves + * @module crypto/public_key/elliptic/ecdsa + */ + +'use strict'; + +import curves from './curves.js'; +import BigInteger from '../jsbn.js'; + +/** + * Sign a message using the provided key + * @param {String} oid Elliptic curve for the key + * @param {enums.hash} hash_algo Hash algorithm used to sign + * @param {Uint8Array} m Message to sign + * @param {BigInteger} w Private key used to sign + * @return {{r: BigInteger, s: BigInteger}} Signature of the message + */ +function sign(oid, hash_algo, m, w) { + const curve = curves.get(oid); + const key = curve.keyFromPrivate(w.toByteArray()); + const signature = key.sign(m, hash_algo); + return { + r: new BigInteger(signature.r), + s: new BigInteger(signature.s) + }; +} + +/** + * Verifies if a signature is valid for a message + * @param {String} oid Elliptic curve for the key + * @param {enums.hash} hash_algo Hash algorithm used in the signature + * @param {{r: BigInteger, s: BigInteger}} signature Signature to verify + * @param {Uint8Array} m Message to verify + * @param {BigInteger} gw Public key used to verify the message + */ +function verify(oid, hash_algo, signature, m, gw) { + const curve = curves.get(oid); + const key = curve.keyFromPublic(gw.toByteArray()); + return key.verify(m, {r: signature.r.toByteArray(), s: signature.s.toByteArray()}, hash_algo); +} + +module.exports = { + sign: sign, + verify: verify +}; diff --git a/src/crypto/public_key/elliptic/index.js b/src/crypto/public_key/elliptic/index.js index 1868f192..fab93a04 100644 --- a/src/crypto/public_key/elliptic/index.js +++ b/src/crypto/public_key/elliptic/index.js @@ -26,8 +26,10 @@ 'use strict'; import {get, generate} from './curves.js'; +import ecdsa from './ecdsa.js'; module.exports = { + ecdsa: ecdsa, get: get, generate: generate }; diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js index 55524053..e4b56fa2 100644 --- a/test/crypto/elliptic.js +++ b/test/crypto/elliptic.js @@ -4,6 +4,13 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : var expect = require('chai').expect; +var bin2bi = function (bytes) { + var mpi = new openpgp.MPI(); + bytes = openpgp.util.bin2str(bytes); + mpi.fromBytes(bytes); + return mpi.toBigInteger(); +}; + describe('Elliptic Curve Cryptography', function () { var elliptic_curves = openpgp.crypto.publicKey.elliptic; var key_data = { @@ -193,4 +200,95 @@ describe('Elliptic Curve Cryptography', function () { done(); }); }); + describe('ECDSA signature', function () { + var verify_signature = function (oid, hash, r, s, pub, message) { + if (openpgp.util.isString(message)) { + message = openpgp.util.str2Uint8Array(message); + } else if (!openpgp.util.isUint8Array(message)) { + message = new Uint8Array(message); + } + return function () { + var ecdsa = elliptic_curves.ecdsa; + return ecdsa.verify(oid, + hash, + {r: bin2bi(r), s: bin2bi(s)}, + message, + bin2bi(pub) + ); + }; + }; + var secp256k1_dummy_value = new Uint8Array([ + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + var secp256k1_dummy_point = new Uint8Array([0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + var secp256k1_invalid_point = new Uint8Array([0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + it('Invalid curve oid', function (done) { + var res = verify_signature('invalid oid', 8, [], [], [], []); + expect(res).to.throw(Error, /Not valid curve/); + res = verify_signature("\x00", 8, [], [], [], []); + expect(res).to.throw(Error, /Not valid curve/); + done(); + }); + it('Invalid public key', function (done) { + var res = verify_signature('secp256k1', 8, [], [], [], []); + expect(res).to.throw(Error, /Unknown point format/); + res = verify_signature('secp256k1', 8, [], [], secp256k1_invalid_point, []); + expect(res).to.throw(Error, /Unknown point format/); + done(); + }); + it('Invalid signature', function (done) { + var res = verify_signature('secp256k1', 8, [], [], secp256k1_dummy_point, []); + expect(res()).to.be.false; + done(); + }); + + var p384_message = new Uint8Array([ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]); + var p384_r = new Uint8Array([ + 0x9D, 0x07, 0xCA, 0xA5, 0x9F, 0xBE, 0xB8, 0x76, + 0xA9, 0xB9, 0x66, 0x0F, 0xA0, 0x64, 0x70, 0x5D, + 0xE6, 0x37, 0x40, 0x43, 0xD0, 0x8E, 0x40, 0xA8, + 0x8B, 0x37, 0x83, 0xE7, 0xBC, 0x1C, 0x4C, 0x86, + 0xCB, 0x3C, 0xD5, 0x9B, 0x68, 0xF0, 0x65, 0xEB, + 0x3A, 0xB6, 0xD6, 0xA6, 0xCF, 0x85, 0x3D, 0xA9]); + var p384_s = new Uint8Array([ + 0x32, 0x85, 0x78, 0xCC, 0xEA, 0xC5, 0x22, 0x83, + 0x10, 0x73, 0x1C, 0xCF, 0x10, 0x8A, 0x52, 0x11, + 0x8E, 0x49, 0x9E, 0xCF, 0x7E, 0x17, 0x18, 0xC3, + 0x11, 0x11, 0xBC, 0x0F, 0x6D, 0x98, 0xE2, 0x16, + 0x68, 0x58, 0x23, 0x1D, 0x11, 0xEF, 0x3D, 0x21, + 0x30, 0x75, 0x24, 0x39, 0x48, 0x89, 0x03, 0xDC]); + it('Valid signature', function (done) { + var res = verify_signature('p384', 8, p384_r, p384_s, key_data.p384.pub, p384_message); + expect(res()).to.be.true; + done(); + }); + it('Sign and verify message', function (done) { + var curve = elliptic_curves.get('p521'); + var keyPair = curve.genKeyPair(); + var keyPublic = bin2bi(keyPair.getPublic()); + var keyPrivate = bin2bi(keyPair.getPrivate()); + var oid = curve.oid; + var message = p384_message; + var signature = elliptic_curves.ecdsa.sign(oid, 10, message, keyPrivate); + var verified = elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic); + expect(verified).to.be.true; + done(); + }); + }); });