Skip to content

Commit

Permalink
Fix, derToRaw does not handle r/s with leading 0s. (#117)
Browse files Browse the repository at this point in the history
* Updating the derToRaw function to handle signature parts that have leading (excluded) zeros.

* Adding test to validate that signatures with non-standard r/s can be parsed properly.

* Fixing linting issues

* Delete deno.lock

* Making the asn1 tests use static values. Moving from Buffer to Javascript native types for padding the array in derToRaw.

* Modifying derToRaw to only slice the buffer when it is not exactly 32 bytes.

* Making rEnd const

* Fixing typo in tests. Cleaning up redundent code in derToRaw.

* Bumping version.

Co-authored-by: James Cullum (Pseudonym) <JamesCullum@users.noreply.github.com>
  • Loading branch information
codydfns and JamesCullum committed Dec 31, 2022
1 parent 2ebe5fa commit 7598e31
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 6 deletions.
18 changes: 13 additions & 5 deletions lib/toolbox.js
Expand Up @@ -56,17 +56,25 @@ pkijs.setEngine(
}),
);

function extractBigNum(fullArray, start, end, expectedLength) {
let num = fullArray.slice(start, end);
if (num.length !== expectedLength){
num = Array(expectedLength).fill(0).concat(...num).slice(num.length);
}
return num;
}

/*
Convert signature from DER to raw
Expects Uint8Array
*/
function derToRaw(signature) {
const rStart = signature[4] === 0 ? 5 : 4;
const rEnd = rStart + 32;
const sStart = signature[rEnd + 2] === 0 ? rEnd + 3 : rEnd + 2;
const rStart = 4;
const rEnd = rStart + signature[3];
const sStart = rEnd + 2;
return new Uint8Array([
...signature.slice(rStart, rEnd),
...signature.slice(sStart),
...extractBigNum(signature, rStart, rEnd, 32),
...extractBigNum(signature, sStart, signature.length, 32),
]);
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "fido2-lib",
"version": "3.3.4",
"version": "3.3.5",
"description": "A library for performing FIDO 2.0 / WebAuthn functionality",
"type": "module",
"main": "dist/main.cjs",
Expand Down
1 change: 1 addition & 0 deletions rollup.config.js
Expand Up @@ -16,6 +16,7 @@ const externals = [
"@peculiar/webcrypto",
];
const tests = [
"test/asn1.test.js",
"test/certUtils.test.js",
"test/extAppId.test.js",
"test/ext.test.js",
Expand Down
132 changes: 132 additions & 0 deletions test/asn1.test.js
@@ -0,0 +1,132 @@
import base64 from "@hexagon/base64";

// Testing lib
import * as chai from "chai";
import * as chaiAsPromised from "chai-as-promised";

import { Fido2Lib } from "../lib/main.js";
import { ecdsaPublicKey } from "./fixtures/ecdsaPublicKey.js";

chai.use(chaiAsPromised.default);
const assert = chai.assert;

const base64urlToArrayBuffer = (str) => {
return base64.toArrayBuffer(str);
};

const verifyAssertion = async (signature) => {
const input = {
credId: "UmFuZG9tQ3JlZElk",
clientData: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiTjJKaE1XVTRNV0ppWm1FeE4yRXlNVGM1T0RBeE5HUmpObU0yWm1JMVl6WTROVEJqTkRWak5EUXdOV1kzWkRKaU5tSTNNalpqT1dZMFpHTTJaRGt4WVEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
authenticatorData: "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAADw==",
signature: signature,
userHandle: "UmFuZG9tVXNlcklk",
};
const fido2 = new Fido2Lib({
timeout: 42,
rpId: "localhost",
rpName: "localhost",
challengeSize: 128,
attestation: "direct",
cryptoParams: [-7, -257],
authenticatorRequireResidentKey: true,
authenticatorUserVerification: "required",
});

const challenge = "N2JhMWU4MWJiZmExN2EyMTc5ODAxNGRjNmM2ZmI1YzY4NTBjNDVjNDQwNWY3ZDJiNmI3MjZjOWY0ZGM2ZDkxYQ";
return await fido2.assertionResult(
{
rawId: base64urlToArrayBuffer(input.credId),
response: {
clientDataJSON: input.clientData,
authenticatorData: base64urlToArrayBuffer(input.authenticatorData),
signature: input.signature,
userHandle: input.userHandle,
},
},
{
rpId: "localhost",
challenge: challenge,
origin: "http://localhost:3000",
factor: "first",
publicKey: ecdsaPublicKey.examplePem,
prevCounter: 1,
userHandle: input.userHandle,
}
);
};

describe("ECDSA ASN.1/Der to Raw conversion", function() {
it("can verify an ECDSA signature with r < 32 bytes and s = 32 bytes", async function() {
// r < 32 bytes | s = 32 bytes
const signature = "MEQCHyj3uP1iWNTCw0FpsiTe-e7dulZqWqepuXFmCwRmLBYCIQDQnxAkeQFwX-dmfg8XFz3TIx7wfh0MKw0hTCjc2WgMVw";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
it("can verify an ECDSA signature with r = 32 bytes and s < 32 bytes", async function() {
// r = 32 bytes | s < 32 bytes
const signature = "MEMCIGWd6pkFRvBAfse-jGeYfVhlWDKIRyQZyBA32IpdvbMEAh81mQqkXyT2dej9BdABFXdpqR8nzHO1Tq6gfLGjaiX1";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
it("can verify an ECDSA signature with r < 32 bytes and s < 32 bytes", async function() {
// r < 32 bytes | s < 32 bytes
const signature = "MEICHxWF148JkFV86_NzU-APP-yhVuUHEiVatHdeD6K6A0ACHwWsMWQo33oSBgJ3aSVeY1di7B_TU4GDAT0l3QtvPYg";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
it("can verify an ECDSA signature with r = 33 bytes and s < 32 bytes", async function() {
// r = 33 bytes | s < 32 bytes
const signature = "MEQCIQCw1qPkCZQl1ZGJProqe9MC8rGLAsAHZbAHDe9YNAFRSwIfAvp9Ar5cQm-5ANS3zG0P105PmPRRur6F3i03AiLwBw";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
it("can verify an ECDSA signature with r < 32 bytes and s = 33 bytes", async function() {
// r < 32 bytes | s = 33 bytes
const signature = "MEQCHxipYKPnzWezEzFiZWqvJ8Z4-nAJXnFHV4IarB1g818CIQDdVn-OE3uEjRd--Uqj3IA-Zr5RBJor_K9ZCxXuPpalbg";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
it("can verify an ECDSA signature with r = 32 bytes (0 padded) and s >= 32 bytes", async function() {
// r = 32 (0 padded) | s >= 32 bytes
const signature = "MEUCIADqTxqhzztnVk7XXwEeYhlBADK74-he2RsIbvB918TbAiEA4IYFEPc0-3rYRUhZzlWT2oLscUwszPL-9oZOnaFcNZw";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
it("can verify an ECDSA signature with r >= 32 bytes and s = 32 bytes (0 padded)", async function() {
// r >= 32 bytes | s = 32 (0 padded)
const signature = "MEUCIQCAvO4-mEuaX2tYR-AJ8t8vv1AxCqkJgfxIR1XL4yCy8AIgANf3_Cp4LzlzkG4U8VS0WCVrR6_pTBM5mwhUcERNakc";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
it("can verify an ECDSA signature with r = 30 bytes and s >= 32 bytes", async function() {
// r = 30 bytes | s >= 32 bytes
const signature = "MEMCHhCs-kZTCokgrPfb1CaKEznJjqVisSBzMAqv6S24AQIhAOBeIIWXFgOJwA-39dKfzcuG6woJ03tiR0N2ME9Lp206";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
it("can verify an ECDSA signature with r >= 32 bytes and s < 30 bytes", async function() {
// r >= 32 bytes | s < 30 bytes
const signature = "MEECID8aNcMNP3Q-mPSIdPc-ocNyH1vLo_Lh8JgxFXAV7s6DAh0pc_2hHfN6OBPpj_2asyt6I4FBz-ZeVbaGtI9UXQ";

const result = await verifyAssertion(signature);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
});
7 changes: 7 additions & 0 deletions test/fixtures/ecdsaPublicKey.js
@@ -0,0 +1,7 @@
// privateKey = "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgu9nv4cQyNEFUkAfb\n3KwStEnfml2iyzgFGVSnIBHrDjuhRANCAAQkFAXLiJbHHbIw7r7xnGN36pWb54FV\na+lkH5NN27NE2ZKytnq6jF04++6rdDgxJ4qkPX7QrrfQuuCFhujSZ+KH\n-----END PRIVATE KEY-----\n"
const ecdsaPublicKey = {
examplePem:
"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJBQFy4iWxx2yMO6+8Zxjd+qVm+eB\nVWvpZB+TTduzRNmSsrZ6uoxdOPvuq3Q4MSeKpD1+0K630LrghYbo0mfihw==\n-----END PUBLIC KEY-----\n",
};

export { ecdsaPublicKey };

0 comments on commit 7598e31

Please sign in to comment.