Skip to content

Commit

Permalink
Use @noble/curves for Node signing (#565)
Browse files Browse the repository at this point in the history
This package is newer than elliptic, has fewer dependencies, is better
typed, and appears to be faster.

Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
  • Loading branch information
bestbeforetoday committed Feb 28, 2023
1 parent 6262dee commit c040e52
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 43 deletions.
2 changes: 1 addition & 1 deletion node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"dependencies": {
"@grpc/grpc-js": "^1.8.7",
"@hyperledger/fabric-protos": "~0.2.0",
"@noble/curves": "^0.7.3",
"asn1.js": "^5.4.1",
"elliptic": "^6.5.4",
"google-protobuf": "^3.21.2"
},
"optionalDependencies": {
Expand Down
12 changes: 0 additions & 12 deletions node/src/identity/asn1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
// @ts-nocheck

import { define } from 'asn1.js';
import BN from 'bn.js';
import { KeyObject } from 'crypto';

const ECPrivateKey = define('ECPrivateKey', function() {
Expand All @@ -20,13 +19,6 @@ const ECPrivateKey = define('ECPrivateKey', function() {
);
});

const ECSignature = define('ECSignature', function() {
return this.seq().obj(
this.key('r').int(),
this.key('s').int()
);
});

export function ecPrivateKeyAsRaw(privateKey: KeyObject): { privateKey: Buffer, curveObjectId: number[] } {
const privateKeyPem = privateKey.export({ format: 'der', type: 'sec1' });
const decodedDer = ECPrivateKey.decode(privateKeyPem, 'der');
Expand All @@ -35,7 +27,3 @@ export function ecPrivateKeyAsRaw(privateKey: KeyObject): { privateKey: Buffer,
curveObjectId: decodedDer.parameters,
};
}

export function ecRawSignatureAsDer(r: BN, s: BN): Buffer {
return ECSignature.encode({ r, s }, 'der');
}
24 changes: 4 additions & 20 deletions node/src/identity/hsmsigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import BN from 'bn.js';
import * as elliptic from 'elliptic';
import { P256 } from '@noble/curves/p256';
import * as pkcs11js from 'pkcs11js';
import { ecRawSignatureAsDer } from './asn1';
import { Signer } from './signer';

export interface HSMSignerOptions {
Expand Down Expand Up @@ -82,7 +80,6 @@ export class HSMSignerFactoryImpl implements HSMSignerFactory {
newSigner(hsmSignerOptions: Readonly<HSMSignerOptions>): HSMSigner {
const options = sanitizeOptions(hsmSignerOptions);

const supportedKeySize = 256;
const pkcs11 = this.#pkcs11;
const slot = this.#findSlotForLabel(options.label);
const session = pkcs11.C_OpenSession(slot, pkcs11js.CKF_SERIAL_SESSION);
Expand All @@ -97,26 +94,13 @@ export class HSMSignerFactoryImpl implements HSMSignerFactory {
throw err;
}

const definedCurves = elliptic.curves as unknown as Record<string, elliptic.curves.PresetCurve>;
const ecdsaCurve = definedCurves[`p${supportedKeySize}`];

// currently the only supported curve is p256 and it will always have an 'n' value
const curveBigNum = ecdsaCurve.n!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
const halfOrder = curveBigNum.shrn(1);
const compactSignatureLength = P256.CURVE.nByteLength * 2;

return {
signer: (digest) => {
pkcs11.C_SignInit(session, { mechanism: pkcs11js.CKM_ECDSA }, privateKeyHandle);
const sig = pkcs11.C_Sign(session, Buffer.from(digest), Buffer.alloc(supportedKeySize));

const r = new BN(sig.slice(0, sig.length / 2).toString('hex'), 16);
let s = new BN(sig.slice(sig.length / 2).toString('hex'), 16);

if (s.cmp(halfOrder) === 1) {
s = curveBigNum.sub(s);
}

const signature = new Uint8Array(ecRawSignatureAsDer(r, s));
const compactSignature = pkcs11.C_Sign(session, Buffer.from(digest), Buffer.alloc(compactSignatureLength));
const signature = P256.Signature.fromCompact(compactSignature).normalizeS().toDERRawBytes();
return Promise.resolve(signature);
},
close: () => pkcs11.C_CloseSession(session),
Expand Down
20 changes: 10 additions & 10 deletions node/src/identity/signers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { CurveFn } from '@noble/curves/abstract/weierstrass';
import { P256 } from '@noble/curves/p256';
import { P384 } from '@noble/curves/p384';
import { KeyObject } from 'crypto';
import { ec as EC } from 'elliptic';
import { ecPrivateKeyAsRaw } from './asn1';
import { HSMSignerFactory, HSMSignerFactoryImpl as HSMSignerFactoryImplType } from './hsmsigner';
import { Signer } from './signer';

const namedCurves: Record<string, EC> = {
'1.2.840.10045.3.1.7': new EC('p256'),
'1.3.132.0.34': new EC('p384'),
const namedCurves: Record<string, CurveFn> = {
'1.2.840.10045.3.1.7': P256,
'1.3.132.0.34': P384,
};

/**
Expand All @@ -38,18 +40,16 @@ export function newPrivateKeySigner(key: KeyObject): Signer {
}

function newECPrivateKeySigner(key: KeyObject): Signer {
const { privateKey: rawKey, curveObjectId } = ecPrivateKeyAsRaw(key);
const { privateKey, curveObjectId } = ecPrivateKeyAsRaw(key);
const curve = getCurve(curveObjectId);
const keyPair = curve.keyFromPrivate(rawKey, 'hex');

return (digest) => {
const signature = curve.sign(digest, keyPair, { canonical: true });
const signatureBytes = new Uint8Array(signature.toDER());
return Promise.resolve(signatureBytes);
const signature = curve.sign(digest, privateKey, { lowS: true }).toDERRawBytes();
return Promise.resolve(signature);
};
}

function getCurve(objectIdBytes: number[]): EC {
function getCurve(objectIdBytes: number[]): CurveFn {
const objectId = objectIdBytes.join('.');
const curve = namedCurves[objectId];
if (!curve) {
Expand Down

0 comments on commit c040e52

Please sign in to comment.