Skip to content
This repository was archived by the owner on Apr 22, 2025. It is now read-only.

Commit cb9f8c1

Browse files
committed
FAB-1263 ECDSA signature malleability resistance
This change-set introduces ECDSA Signature malleability resistance. ECDSA signatures do not have unique representation and this can facilitate replay attacks and more. In order to have a unique representation, this change-set forses BCCSP to generate and accept only signatures with low-S. Bitcoin has also addressed this issue with the following BIP: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki Before merging this change-set, we need to ensure that client-sdks generates signatures properly in order to avoid massive rejection of transactions. This is a port of the GO implementation here: https://gerrit.hyperledger.org/r/#/c/2983 This changeset has been successfully tested with 2983. Change-Id: Iee78ee93f83ddfdd99526ea3cca9c11b33af8318 Signed-off-by: Jim Zhang <jzhang@us.ibm.com>
1 parent fab746c commit cb9f8c1

File tree

3 files changed

+83
-16
lines changed

3 files changed

+83
-16
lines changed

hfc/lib/impl/CryptoSuite_ECDSA_AES.js

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,14 @@ var api = require('../api.js');
2222
var crypto = require('crypto');
2323
var elliptic = require('elliptic');
2424
var EC = elliptic.ec;
25-
var sjcl = require('sjcl');
2625
var jsrsa = require('jsrsasign');
2726
var KEYUTIL = jsrsa.KEYUTIL;
2827
var util = require('util');
2928
var hashPrimitives = require('../hash.js');
3029
var utils = require('../utils');
3130
var ECDSAKey = require('./ecdsa/key.js');
32-
33-
// constants
34-
const AESKeyLength = 32;
35-
const HMACKeyLength = 32;
36-
const ECIESKDFOutput = 512; // bits
37-
const IVLength = 16; // bytes
31+
var BN = require('bn.js');
32+
var Signature = require('elliptic/lib/elliptic/ec/signature.js');
3833

3934
var logger = utils.getLogger('crypto_ecdsa_aes');
4035

@@ -236,6 +231,7 @@ var CryptoSuite_ECDSA_AES = class extends api.CryptoSuite {
236231
// module './ecdsa/key.js'
237232
var signKey = this._ecdsa.keyFromPrivate(key._key.prvKeyHex, 'hex');
238233
var sig = this._ecdsa.sign(digest, signKey);
234+
sig = _preventMalleability(sig, key._key.ecparams);
239235
logger.debug('ecdsa signature: ', sig);
240236
return sig.toDER();
241237
}
@@ -258,6 +254,11 @@ var CryptoSuite_ECDSA_AES = class extends api.CryptoSuite {
258254
throw new Error('A valid message is required to verify');
259255
}
260256

257+
if (!_checkMalleability(signature, key._key.ecparams)) {
258+
logger.error(new Error('Invalid S value in signature. Must be smaller than half of the order.').stack);
259+
return false;
260+
}
261+
261262
var pubKey = this._ecdsa.keyFromPublic(key.getPublicKey()._key.pubKeyHex, 'hex');
262263
// note that the signature is generated on the hash of the message, not the message itself
263264
return pubKey.verify(this.hash(digest), signature);
@@ -282,10 +283,58 @@ var CryptoSuite_ECDSA_AES = class extends api.CryptoSuite {
282283
}
283284
};
284285

285-
function _zeroBuffer(length) {
286-
var buf = new Buffer(length);
287-
buf.fill(0);
288-
return buf;
286+
// [Angelo De Caro] ECDSA signatures do not have unique representation and this can facilitate
287+
// replay attacks and more. In order to have a unique representation,
288+
// this change-set forses BCCSP to generate and accept only signatures
289+
// with low-S.
290+
// Bitcoin has also addressed this issue with the following BIP:
291+
// https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki
292+
// Before merging this change-set, we need to ensure that client-sdks
293+
// generates signatures properly in order to avoid massive rejection
294+
// of transactions.
295+
296+
// map for easy lookup of the "N/2" value per elliptic curve
297+
const halfOrdersForCurve = {
298+
'secp256r1': elliptic.curves['p256'].n.shrn(1),
299+
'secp384r1': elliptic.curves['p384'].n.shrn(1)
300+
};
301+
302+
function _preventMalleability(sig, curveParams) {
303+
var halfOrder = halfOrdersForCurve[curveParams.name];
304+
if (!halfOrder) {
305+
throw new Error('Can not find the half order needed to calculate "s" value for immalleable signatures. Unsupported curve name: ' + curve);
306+
}
307+
308+
// in order to guarantee 's' falls in the lower range of the order, as explained in the above link,
309+
// first see if 's' is larger than half of the order, if so, it needs to be specially treated
310+
if (sig.s.cmp(halfOrder) == 1) { // module 'bn.js', file lib/bn.js, method cmp()
311+
// convert from BigInteger used by jsrsasign Key objects and bn.js used by elliptic Signature objects
312+
var bigNum = new BN(curveParams.n.toString(16), 16);
313+
sig.s = bigNum.sub(sig.s);
314+
}
315+
316+
return sig;
317+
}
318+
319+
function _checkMalleability(sig, curveParams) {
320+
var halfOrder = halfOrdersForCurve[curveParams.name];
321+
if (!halfOrder) {
322+
throw new Error('Can not find the half order needed to calculate "s" value for immalleable signatures. Unsupported curve name: ' + curve);
323+
}
324+
325+
// first need to unmarshall the signature bytes into the object with r and s values
326+
var sigObject = new Signature(sig, 'hex');
327+
if (!sigObject.r || !sigObject.s) {
328+
throw new Error('Failed to load the signature object from the bytes.');
329+
}
330+
331+
// in order to guarantee 's' falls in the lower range of the order, as explained in the above link,
332+
// first see if 's' is larger than half of the order, if so, it is considered invalid in this context
333+
if (sigObject.s.cmp(halfOrder) == 1) { // module 'bn.js', file lib/bn.js, method cmp()
334+
return false;
335+
}
336+
337+
return true;
289338
}
290339

291340
module.exports = CryptoSuite_ECDSA_AES;

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"engine-strict": true,
1717
"engineStrict": true,
1818
"devDependencies": {
19+
"bn.js": "^4.11.6",
1920
"bunyan": "^1.8.1",
21+
"elliptic": "^6.3.2",
2022
"gulp": "^3.9.1",
2123
"gulp-debug": "^3.0.0",
2224
"gulp-eslint": "^3.0.1",

test/unit/headless-tests.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,14 @@ var asn1 = jsrsa.asn1;
12431243

12441244
var ecdsaKey = require('hfc/lib/impl/ecdsa/key.js');
12451245
var api = require('hfc/lib/api.js');
1246+
var elliptic = require('elliptic');
1247+
var BN = require('bn.js');
1248+
var Signature = require('elliptic/lib/elliptic/ec/signature.js');
1249+
1250+
const halfOrdersForCurve = {
1251+
'secp256r1': elliptic.curves['p256'].n.shrn(1),
1252+
'secp384r1': elliptic.curves['p384'].n.shrn(1)
1253+
};
12461254

12471255
test('\n\n ** CryptoSuite_ECDSA_AES - function tests **\n\n', function (t) {
12481256
resetDefaults();
@@ -1369,7 +1377,14 @@ test('\n\n ** CryptoSuite_ECDSA_AES - function tests **\n\n', function (t) {
13691377
var testSignature = function (msg) {
13701378
var sig = cryptoUtils.sign(key, cryptoUtils.hash(msg));
13711379
if (sig) {
1372-
t.pass('Valid signature object generated from sign()');
1380+
// test that signatures have low-S
1381+
var halfOrder = halfOrdersForCurve[key._key.ecparams.name];
1382+
var sigObject = new Signature(sig);
1383+
if (sigObject.s.cmp(halfOrder) == 1) {
1384+
t.fail('Invalid signature object: S value larger than N/2');
1385+
} else {
1386+
t.pass('Valid signature object generated from sign()');
1387+
}
13731388

13741389
// using internal calls to verify the signature
13751390
var pubKey = cryptoUtils._ecdsa.keyFromPublic(key.getPublicKey()._key.pubKeyHex, 'hex');
@@ -1412,20 +1427,21 @@ test('\n\n ** CryptoSuite_ECDSA_AES - function tests **\n\n', function (t) {
14121427
utils.setConfigSetting('crypto-hash-algo', 'SHA2');
14131428
cryptoUtils = utils.getCryptoSuite();
14141429

1415-
var testVerify = function (sig, msg) {
1430+
var testVerify = function (sig, msg, expected) {
14161431
// manually construct a key based on the saved privKeyHex and pubKeyHex
14171432
var f = new ECDSA({ curve: 'secp256r1' });
14181433
f.setPrivateKeyHex(TEST_KEY_PRIVATE);
14191434
f.setPublicKeyHex(TEST_KEY_PUBLIC);
14201435
f.isPrivate = true;
14211436
f.isPublic = false;
14221437

1423-
t.equal(cryptoUtils.verify(new ecdsaKey(f, 256), sig, msg), true,
1438+
t.equal(cryptoUtils.verify(new ecdsaKey(f, 256), sig, msg), expected,
14241439
'CryptoSuite_ECDSA_AES function tests: verify() method');
14251440
};
14261441

1427-
testVerify(TEST_MSG_SIGNATURE_SHA2_256, TEST_MSG);
1428-
testVerify(TEST_LONG_MSG_SIGNATURE_SHA2_256, TEST_LONG_MSG);
1442+
// these signatures have S values larger than N/2
1443+
testVerify(TEST_MSG_SIGNATURE_SHA2_256, TEST_MSG, false);
1444+
testVerify(TEST_LONG_MSG_SIGNATURE_SHA2_256, TEST_LONG_MSG, false);
14291445

14301446
// test importKey()
14311447
var pubKey = cryptoUtils.importKey(TEST_CERT_PEM, { algorithm: api.CryptoAlgorithms.X509Certificate });

0 commit comments

Comments
 (0)