Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix, derToRaw does not handle r/s with leading 0s. #117

Merged
merged 9 commits into from Dec 31, 2022
19 changes: 14 additions & 5 deletions lib/toolbox.js
Expand Up @@ -61,12 +61,21 @@ pkijs.setEngine(
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 expectedLength = 32;
const rStart = 4;
const rEnd = rStart + signature[3];
const sStart = rEnd + 2;
let r = signature.slice(rStart, rEnd);
let s = signature.slice(sStart);
if (r.length !== expectedLength) {
r = Array(expectedLength).fill(0).concat(...r).slice(r.length);
}
if (s.length !== expectedLength) {
s = Array(expectedLength).fill(0).concat(...s).slice(s.length);
}
return new Uint8Array([
...signature.slice(rStart, rEnd),
...signature.slice(sStart),
...r,
...s,
]);
}

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 converstion", function() {
codydfns marked this conversation as resolved.
Show resolved Hide resolved
it("can verify a 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 a ECDSA signature with r = 32 bytes and s < 32 bytes", async function() {
codydfns marked this conversation as resolved.
Show resolved Hide resolved
// 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 a 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 a 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 a 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 a 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 a 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 a 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 a 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 };