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
4 changes: 2 additions & 2 deletions deno.jsonc
Expand Up @@ -18,10 +18,10 @@
"tasks": {

/* Run deno tests with `deno task test`, using default import map */
"test": "deno test --lock=deno-lock.json --ignore=test/dist test",
"test": "deno test --lock=deno-lock.json --ignore=test/dist,test/asn1.test.js test",
codydfns marked this conversation as resolved.
Show resolved Hide resolved

/* import_map.dist.json redirects ./lib -> ./dist, to make tests use dist/main.js instead of lib/main.js */
"test:dist": "deno test --lock=deno-lock.json --importmap=import_map.dist.json --ignore=test/dist test",
"test:dist": "deno test --lock=deno-lock.json --importmap=import_map.dist.json --ignore=test/dist,test/asn1.test.js test",

/* Run after changing dependencies */
"update-deps": "deno cache -r --lock=deno-lock.json --lock-write --importmap=import_map.json test/main.test.js lib/main.js",
Expand Down
6 changes: 5 additions & 1 deletion lib/toolbox.js
Expand Up @@ -62,10 +62,14 @@ pkijs.setEngine(
*/
function derToRaw(signature) {
const rStart = signature[4] === 0 ? 5 : 4;
const rEnd = rStart + 32;
const rEnd = 4 + signature[3];
JamesCullum marked this conversation as resolved.
Show resolved Hide resolved
const sStart = signature[rEnd + 2] === 0 ? rEnd + 3 : rEnd + 2;
const r = signature.slice(rStart, rEnd);
const s = signature.slice(sStart);
return new Uint8Array([
...(r.length < 32 ? Buffer.alloc(32-r.length, 0) : []),
...signature.slice(rStart, rEnd),
...(s.length < 32 ? Buffer.alloc(32-s.length, 0) : []),
...signature.slice(sStart),
]);
}
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
117 changes: 117 additions & 0 deletions test/asn1.test.js
@@ -0,0 +1,117 @@
// Testing lib
import * as chai from "chai";
import * as chaiAsPromised from "chai-as-promised";

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

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

const sha256 = (data) => {
return crypto.createHash("sha256").update(data).digest("hex");
};

const base64url = (buf) => {
return Buffer.from(buf).toString("base64url");
};
const base64urlToBuffer = (str) => {
return Buffer.from(str, "base64url");
};
const base64urlToArrayBuffer = (str) => {
const buf = base64urlToBuffer(str);
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
};

const generateSignature = (authenticatorData, clientData, privateKey) => {
const dataHash = sha256(clientData);
const authenticatorDataHex = base64urlToBuffer(authenticatorData).toString("hex");
let signature;
do {
signature = new Uint8Array(
crypto.sign(
null,
Buffer.from(authenticatorDataHex + dataHash, "hex"),
privateKey
)
);
} while(signature[3] >= 32);

return base64url(signature);
};

const generateAuthenticatorData = () => {
codydfns marked this conversation as resolved.
Show resolved Hide resolved
const flags = 5;
const counter = 15;

const rpidHash = sha256("localhost", "hex");
const authData = Buffer.from(
rpidHash + flags.toString(16).padStart(2, "0") + counter.toString(16).padStart(8, "0"),
"hex"
);

return base64url(authData);
};

const generateInput = (privateKey) => {
const challenge = sha256("SomeRandomChallenge");
const clientData = JSON.stringify({
type: "webauthn.get",
challenge: base64url(challenge),
origin: "http://localhost:3000",
crossOrigin: false,
});

const authenticatorData = generateAuthenticatorData();

return {
credId: base64url("RandomCredId"),
clientData: base64url(clientData),
authenticatorData: authenticatorData,
signature: generateSignature(authenticatorData, clientData, privateKey),
userHandle: base64url("RandomUserId"),
};
};

describe("assertion", function() {
it("can verify a ECDSA signature with non-standard r / s", async function() {
const key = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
const privateKey = key.privateKey.export({ type: "pkcs8", format:"pem" });
const publicKey = key.publicKey.export({ type: "spki", format:"pem" });
const input = generateInput(privateKey);
const fido2 = new Fido2Lib({
timeout: 42,
rpId: "localhost",
rpName: "localhost",
challengeSize: 128,
attestation: "direct",
cryptoParams: [-7, -257],
authenticatorRequireResidentKey: true,
authenticatorUserVerification: "required",
});

const challenge = sha256("SomeRandomChallenge");
const result = await fido2.assertionResult(
{
rawId: base64urlToArrayBuffer(input.credId),
response: {
clientDataJSON: input.clientData,
authenticatorData: base64urlToArrayBuffer(input.authenticatorData),
signature: input.signature,
userHandle: input.userHandle,
},
},
{
rpId: "localhost",
challenge: base64url(challenge),
origin: "http://localhost:3000",
factor: "first",
publicKey: publicKey,
prevCounter: 1,
userHandle: input.userHandle,
}
);
assert.strictEqual(result.audit.validRequest, true);
assert.strictEqual(result.audit.validExpectations, true);
});
});