Skip to content

Commit

Permalink
Add support for SSH U2F signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
wiktor-k committed Mar 7, 2024
1 parent 1cf4a52 commit 3342e88
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The following algorithms are supported at this time:
- RSA
- ed25519
- NIST P-256 and P-384
- SSH U2F signatures (ECDSA)

If you would like to see a different signing algorithm supported please
[file an issue](https://github.com/wiktor-k/ssh-sig/issues/new) attaching both
Expand Down
1 change: 1 addition & 0 deletions fixtures/ed25519_sk.txt.ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Not yet supported.
36 changes: 32 additions & 4 deletions formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export type Pubkey = {
curve: string;
point: Uint8Array;
toString(): string;
} | {
pk_algo: "sk-ecdsa-sha2-nistp256@openssh.com";
curve: string;
point: Uint8Array;
// typically "ssh:"
application: string;
toString(): string;
};

export function parsePubkey(
Expand Down Expand Up @@ -47,10 +54,24 @@ export function parsePubkey(
pk_algo === "ecdsa-sha2-nistp521"
) {
const curve = publickey.readString().toString();
const point = new Uint8Array(publickey.readString().bytes());
pubkey = {
pk_algo,
curve,
point: new Uint8Array(publickey.readString().bytes()),
point,
toString() {
return `${pk_algo} ${base64Encode(new Uint8Array(raw_publickey))}`;
},
};
} else if (pk_algo === "sk-ecdsa-sha2-nistp256@openssh.com") {
const curve = publickey.readString().toString();
const point = new Uint8Array(publickey.readString().bytes());
const application = publickey.readString().toString();
pubkey = {
pk_algo,
curve,
point,
application,
toString() {
return `${pk_algo} ${base64Encode(new Uint8Array(raw_publickey))}`;
},
Expand Down Expand Up @@ -96,7 +117,8 @@ export function convertPublicKey(publickey: Pubkey): {
};
} else if (
pk_algo === "ecdsa-sha2-nistp256" || pk_algo === "ecdsa-sha2-nistp384" ||
pk_algo === "ecdsa-sha2-nistp521"
pk_algo === "ecdsa-sha2-nistp521" ||
pk_algo === "sk-ecdsa-sha2-nistp256@openssh.com"
) {
if (publickey.point[0] !== 0x04) {
throw new Error("Only uncompressed (0x04) format is supported");
Expand All @@ -105,7 +127,10 @@ export function convertPublicKey(publickey: Pubkey): {
const point = publickey.point.slice(1);

let crv;
if (pk_algo === "ecdsa-sha2-nistp256") {
if (
pk_algo === "ecdsa-sha2-nistp256" ||
pk_algo === "sk-ecdsa-sha2-nistp256@openssh.com"
) {
crv = "P-256";
} else if (pk_algo === "ecdsa-sha2-nistp384") {
crv = "P-384";
Expand Down Expand Up @@ -138,7 +163,10 @@ export function convertAlgorithm(sig_algo: string) {
name: "Ed25519",
hash: { name: "SHA-512" },
};
} else if (sig_algo === "ecdsa-sha2-nistp256") {
} else if (
sig_algo === "ecdsa-sha2-nistp256" ||
sig_algo === "sk-ecdsa-sha2-nistp256@openssh.com"
) {
return {
name: "ECDSA",
namedCurve: "P-256",
Expand Down
2 changes: 2 additions & 0 deletions sig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ export interface Sig {
signature: {
sig_algo: string;
raw_signature: ArrayBuffer;
flags?: number;
counter?: number;
};
}
10 changes: 9 additions & 1 deletion sig_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export function parse(signature: DataView | string): Sig {
let bytes;
if (
sig_algo === "ecdsa-sha2-nistp256" || sig_algo === "ecdsa-sha2-nistp384" ||
sig_algo === "ecdsa-sha2-nistp512"
sig_algo === "ecdsa-sha2-nistp512" ||
sig_algo === "sk-ecdsa-sha2-nistp256@openssh.com"
) {
let r = new Uint8Array(sig_bytes.readString().bytes());
if (r[0] === 0x00 && r.length % 2 == 1) {
Expand All @@ -48,6 +49,11 @@ export function parse(signature: DataView | string): Sig {
} else {
bytes = sig_bytes.bytes();
}
let flags, counter;
if (sig_algo === "sk-ecdsa-sha2-nistp256@openssh.com") {
flags = new Uint8Array(raw_signature.readBytes(1).bytes())[0];
counter = raw_signature.readUint32();
}
return {
publickey: pubkey,
namespace,
Expand All @@ -56,6 +62,8 @@ export function parse(signature: DataView | string): Sig {
signature: {
sig_algo,
raw_signature: bytes,
flags,
counter,
},
};
}
35 changes: 35 additions & 0 deletions verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,41 @@ export async function verify(
);
data.push(...[0, 0, 0, digest.length]);
data.push(...digest);

if (signature.publickey.pk_algo === "sk-ecdsa-sha2-nistp256@openssh.com") {
// https://fuchsia.googlesource.com/third_party/openssh-portable/+/refs/heads/main/PROTOCOL.u2f#176
const u2f_data = [];
u2f_data.push(
...new Uint8Array(
await subtle.digest(
"SHA-256",
Uint8Array.from(
Array.prototype.map.call(
signature.publickey.application,
(x) => x.charCodeAt(0),
) as unknown as number[],
),
),
),
);
u2f_data.push(signature.signature.flags);
u2f_data.push(...[0, 0, 0, signature.signature.counter]);
u2f_data.push(
...new Uint8Array(
await subtle.digest(
"SHA-256",
Uint8Array.from(data as unknown as number[]),
),
),
);
return await subtle.verify(
algorithm,
key,
signature.signature.raw_signature,
new Uint8Array(u2f_data as unknown as number[]),
);
}

return await subtle.verify(
algorithm,
key,
Expand Down

0 comments on commit 3342e88

Please sign in to comment.