Skip to content

Commit 7598e31

Browse files
Fix, derToRaw does not handle r/s with leading 0s. (#117)
* 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>
1 parent 2ebe5fa commit 7598e31

File tree

5 files changed

+154
-6
lines changed

5 files changed

+154
-6
lines changed

lib/toolbox.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,25 @@ pkijs.setEngine(
5656
}),
5757
);
5858

59+
function extractBigNum(fullArray, start, end, expectedLength) {
60+
let num = fullArray.slice(start, end);
61+
if (num.length !== expectedLength){
62+
num = Array(expectedLength).fill(0).concat(...num).slice(num.length);
63+
}
64+
return num;
65+
}
66+
5967
/*
6068
Convert signature from DER to raw
6169
Expects Uint8Array
6270
*/
6371
function derToRaw(signature) {
64-
const rStart = signature[4] === 0 ? 5 : 4;
65-
const rEnd = rStart + 32;
66-
const sStart = signature[rEnd + 2] === 0 ? rEnd + 3 : rEnd + 2;
72+
const rStart = 4;
73+
const rEnd = rStart + signature[3];
74+
const sStart = rEnd + 2;
6775
return new Uint8Array([
68-
...signature.slice(rStart, rEnd),
69-
...signature.slice(sStart),
76+
...extractBigNum(signature, rStart, rEnd, 32),
77+
...extractBigNum(signature, sStart, signature.length, 32),
7078
]);
7179
}
7280

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fido2-lib",
3-
"version": "3.3.4",
3+
"version": "3.3.5",
44
"description": "A library for performing FIDO 2.0 / WebAuthn functionality",
55
"type": "module",
66
"main": "dist/main.cjs",

rollup.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const externals = [
1616
"@peculiar/webcrypto",
1717
];
1818
const tests = [
19+
"test/asn1.test.js",
1920
"test/certUtils.test.js",
2021
"test/extAppId.test.js",
2122
"test/ext.test.js",

test/asn1.test.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import base64 from "@hexagon/base64";
2+
3+
// Testing lib
4+
import * as chai from "chai";
5+
import * as chaiAsPromised from "chai-as-promised";
6+
7+
import { Fido2Lib } from "../lib/main.js";
8+
import { ecdsaPublicKey } from "./fixtures/ecdsaPublicKey.js";
9+
10+
chai.use(chaiAsPromised.default);
11+
const assert = chai.assert;
12+
13+
const base64urlToArrayBuffer = (str) => {
14+
return base64.toArrayBuffer(str);
15+
};
16+
17+
const verifyAssertion = async (signature) => {
18+
const input = {
19+
credId: "UmFuZG9tQ3JlZElk",
20+
clientData: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiTjJKaE1XVTRNV0ppWm1FeE4yRXlNVGM1T0RBeE5HUmpObU0yWm1JMVl6WTROVEJqTkRWak5EUXdOV1kzWkRKaU5tSTNNalpqT1dZMFpHTTJaRGt4WVEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
21+
authenticatorData: "SZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2MFAAAADw==",
22+
signature: signature,
23+
userHandle: "UmFuZG9tVXNlcklk",
24+
};
25+
const fido2 = new Fido2Lib({
26+
timeout: 42,
27+
rpId: "localhost",
28+
rpName: "localhost",
29+
challengeSize: 128,
30+
attestation: "direct",
31+
cryptoParams: [-7, -257],
32+
authenticatorRequireResidentKey: true,
33+
authenticatorUserVerification: "required",
34+
});
35+
36+
const challenge = "N2JhMWU4MWJiZmExN2EyMTc5ODAxNGRjNmM2ZmI1YzY4NTBjNDVjNDQwNWY3ZDJiNmI3MjZjOWY0ZGM2ZDkxYQ";
37+
return await fido2.assertionResult(
38+
{
39+
rawId: base64urlToArrayBuffer(input.credId),
40+
response: {
41+
clientDataJSON: input.clientData,
42+
authenticatorData: base64urlToArrayBuffer(input.authenticatorData),
43+
signature: input.signature,
44+
userHandle: input.userHandle,
45+
},
46+
},
47+
{
48+
rpId: "localhost",
49+
challenge: challenge,
50+
origin: "http://localhost:3000",
51+
factor: "first",
52+
publicKey: ecdsaPublicKey.examplePem,
53+
prevCounter: 1,
54+
userHandle: input.userHandle,
55+
}
56+
);
57+
};
58+
59+
describe("ECDSA ASN.1/Der to Raw conversion", function() {
60+
it("can verify an ECDSA signature with r < 32 bytes and s = 32 bytes", async function() {
61+
// r < 32 bytes | s = 32 bytes
62+
const signature = "MEQCHyj3uP1iWNTCw0FpsiTe-e7dulZqWqepuXFmCwRmLBYCIQDQnxAkeQFwX-dmfg8XFz3TIx7wfh0MKw0hTCjc2WgMVw";
63+
64+
const result = await verifyAssertion(signature);
65+
assert.strictEqual(result.audit.validRequest, true);
66+
assert.strictEqual(result.audit.validExpectations, true);
67+
});
68+
it("can verify an ECDSA signature with r = 32 bytes and s < 32 bytes", async function() {
69+
// r = 32 bytes | s < 32 bytes
70+
const signature = "MEMCIGWd6pkFRvBAfse-jGeYfVhlWDKIRyQZyBA32IpdvbMEAh81mQqkXyT2dej9BdABFXdpqR8nzHO1Tq6gfLGjaiX1";
71+
72+
const result = await verifyAssertion(signature);
73+
assert.strictEqual(result.audit.validRequest, true);
74+
assert.strictEqual(result.audit.validExpectations, true);
75+
});
76+
it("can verify an ECDSA signature with r < 32 bytes and s < 32 bytes", async function() {
77+
// r < 32 bytes | s < 32 bytes
78+
const signature = "MEICHxWF148JkFV86_NzU-APP-yhVuUHEiVatHdeD6K6A0ACHwWsMWQo33oSBgJ3aSVeY1di7B_TU4GDAT0l3QtvPYg";
79+
80+
const result = await verifyAssertion(signature);
81+
assert.strictEqual(result.audit.validRequest, true);
82+
assert.strictEqual(result.audit.validExpectations, true);
83+
});
84+
it("can verify an ECDSA signature with r = 33 bytes and s < 32 bytes", async function() {
85+
// r = 33 bytes | s < 32 bytes
86+
const signature = "MEQCIQCw1qPkCZQl1ZGJProqe9MC8rGLAsAHZbAHDe9YNAFRSwIfAvp9Ar5cQm-5ANS3zG0P105PmPRRur6F3i03AiLwBw";
87+
88+
const result = await verifyAssertion(signature);
89+
assert.strictEqual(result.audit.validRequest, true);
90+
assert.strictEqual(result.audit.validExpectations, true);
91+
});
92+
it("can verify an ECDSA signature with r < 32 bytes and s = 33 bytes", async function() {
93+
// r < 32 bytes | s = 33 bytes
94+
const signature = "MEQCHxipYKPnzWezEzFiZWqvJ8Z4-nAJXnFHV4IarB1g818CIQDdVn-OE3uEjRd--Uqj3IA-Zr5RBJor_K9ZCxXuPpalbg";
95+
96+
const result = await verifyAssertion(signature);
97+
assert.strictEqual(result.audit.validRequest, true);
98+
assert.strictEqual(result.audit.validExpectations, true);
99+
});
100+
it("can verify an ECDSA signature with r = 32 bytes (0 padded) and s >= 32 bytes", async function() {
101+
// r = 32 (0 padded) | s >= 32 bytes
102+
const signature = "MEUCIADqTxqhzztnVk7XXwEeYhlBADK74-he2RsIbvB918TbAiEA4IYFEPc0-3rYRUhZzlWT2oLscUwszPL-9oZOnaFcNZw";
103+
104+
const result = await verifyAssertion(signature);
105+
assert.strictEqual(result.audit.validRequest, true);
106+
assert.strictEqual(result.audit.validExpectations, true);
107+
});
108+
it("can verify an ECDSA signature with r >= 32 bytes and s = 32 bytes (0 padded)", async function() {
109+
// r >= 32 bytes | s = 32 (0 padded)
110+
const signature = "MEUCIQCAvO4-mEuaX2tYR-AJ8t8vv1AxCqkJgfxIR1XL4yCy8AIgANf3_Cp4LzlzkG4U8VS0WCVrR6_pTBM5mwhUcERNakc";
111+
112+
const result = await verifyAssertion(signature);
113+
assert.strictEqual(result.audit.validRequest, true);
114+
assert.strictEqual(result.audit.validExpectations, true);
115+
});
116+
it("can verify an ECDSA signature with r = 30 bytes and s >= 32 bytes", async function() {
117+
// r = 30 bytes | s >= 32 bytes
118+
const signature = "MEMCHhCs-kZTCokgrPfb1CaKEznJjqVisSBzMAqv6S24AQIhAOBeIIWXFgOJwA-39dKfzcuG6woJ03tiR0N2ME9Lp206";
119+
120+
const result = await verifyAssertion(signature);
121+
assert.strictEqual(result.audit.validRequest, true);
122+
assert.strictEqual(result.audit.validExpectations, true);
123+
});
124+
it("can verify an ECDSA signature with r >= 32 bytes and s < 30 bytes", async function() {
125+
// r >= 32 bytes | s < 30 bytes
126+
const signature = "MEECID8aNcMNP3Q-mPSIdPc-ocNyH1vLo_Lh8JgxFXAV7s6DAh0pc_2hHfN6OBPpj_2asyt6I4FBz-ZeVbaGtI9UXQ";
127+
128+
const result = await verifyAssertion(signature);
129+
assert.strictEqual(result.audit.validRequest, true);
130+
assert.strictEqual(result.audit.validExpectations, true);
131+
});
132+
});

test/fixtures/ecdsaPublicKey.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// privateKey = "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgu9nv4cQyNEFUkAfb\n3KwStEnfml2iyzgFGVSnIBHrDjuhRANCAAQkFAXLiJbHHbIw7r7xnGN36pWb54FV\na+lkH5NN27NE2ZKytnq6jF04++6rdDgxJ4qkPX7QrrfQuuCFhujSZ+KH\n-----END PRIVATE KEY-----\n"
2+
const ecdsaPublicKey = {
3+
examplePem:
4+
"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJBQFy4iWxx2yMO6+8Zxjd+qVm+eB\nVWvpZB+TTduzRNmSsrZ6uoxdOPvuq3Q4MSeKpD1+0K630LrghYbo0mfihw==\n-----END PUBLIC KEY-----\n",
5+
};
6+
7+
export { ecdsaPublicKey };

0 commit comments

Comments
 (0)