refactor: id / act key support

This commit is contained in:
Shibo Lyu 2024-09-23 02:31:23 +08:00
parent 74f1c59566
commit c5f6659d8f
6 changed files with 41 additions and 26 deletions

9
.zed/tasks.json Normal file
View file

@ -0,0 +1,9 @@
// Static tasks configuration.
//
// Example:
[
{
"label": "Deno Test",
"command": "deno test -A"
}
]

View file

@ -17,7 +17,7 @@ Deno.test("encode & decode keypair", async () => {
Deno.test("sign & verify payload", async () => {
const payload = { foo: "bar", baz: 123 };
const signedPayload = await keypair.signPayload(payload);
const verifiedPayload = await keypair.publicIdentity.verifyPayload(
const verifiedPayload = await keypair.publicKey.verifyPayload(
signedPayload,
);
@ -28,7 +28,7 @@ Deno.test("sign & verify payload with wrong keypair", async () => {
const keypair2 = await BlahKeyPair.generate();
const payload = { foo: "bar", baz: 123 };
const signedPayload = await keypair.signPayload(payload);
expect(keypair2.publicIdentity.verifyPayload(signedPayload))
expect(keypair2.publicKey.verifyPayload(signedPayload))
.rejects.toMatch(/sign/);
});
@ -39,12 +39,12 @@ Deno.test("sign & verify payload with wrong key order but should still work", as
sig: signedPayload.sig,
signee: {
payload: { baz: 123, foo: "bar" },
user: signedPayload.signee.user,
id_key: signedPayload.signee.id_key,
nonce: signedPayload.signee.nonce,
timestamp: signedPayload.signee.timestamp,
},
};
const verifiedPayload = await keypair.publicIdentity.verifyPayload(
const verifiedPayload = await keypair.publicKey.verifyPayload(
signedPayload2,
);
expect(verifiedPayload).toEqual(payload);

View file

@ -1,5 +1,5 @@
import canonicalize from "./canonicalize.ts";
import { BlahPublicIdentity } from "./publicIdentity.ts";
import { BlahPublicKey } from "./publicKey.ts";
import type { BlahPayloadSignee, BlahSignedPayload } from "./signedPayload.ts";
import { bufToHex } from "./utils.ts";
@ -10,21 +10,21 @@ export type EncodedBlahKeyPair = {
};
export class BlahKeyPair {
publicIdentity: BlahPublicIdentity;
publicKey: BlahPublicKey;
private privateKey: CryptoKey;
get id() {
return this.publicIdentity.id;
return this.publicKey.id;
}
get name() {
return this.publicIdentity.name;
return this.publicKey.name;
}
private constructor(
publicIdentity: BlahPublicIdentity,
publicIdentity: BlahPublicKey,
privateKey: CryptoKey,
) {
this.publicIdentity = publicIdentity;
this.publicKey = publicIdentity;
this.privateKey = privateKey;
}
@ -37,7 +37,7 @@ export class BlahKeyPair {
"verify",
],
) as CryptoKeyPair;
const publicIdentity = await BlahPublicIdentity.fromPublicKey(publicKey);
const publicIdentity = await BlahPublicKey.fromPublicKey(publicKey);
return new BlahKeyPair(publicIdentity, privateKey);
}
@ -45,7 +45,7 @@ export class BlahKeyPair {
if (encoded.v !== "0") {
throw new Error("Unsupported version");
}
const publicIdentity = await BlahPublicIdentity.fromID(encoded.id);
const publicIdentity = await BlahPublicKey.fromID(encoded.id);
const privateKey = await crypto.subtle.importKey(
"jwk",
encoded.privateKey,
@ -60,7 +60,7 @@ export class BlahKeyPair {
async encode(): Promise<EncodedBlahKeyPair> {
return {
v: "0",
id: this.publicIdentity.id,
id: this.publicKey.id,
privateKey: await crypto.subtle.exportKey("jwk", this.privateKey),
};
}
@ -68,6 +68,7 @@ export class BlahKeyPair {
async signPayload<P>(
payload: P,
date: Date = new Date(),
identityKeyId?: string,
): Promise<BlahSignedPayload<P>> {
const nonceBuf = new Uint32Array(1);
crypto.getRandomValues(nonceBuf);
@ -78,8 +79,12 @@ export class BlahKeyPair {
nonce: nonceBuf[0],
payload,
timestamp,
user: this.id,
id_key: identityKeyId ?? this.id,
};
if (identityKeyId) {
signee.act_key = this.id;
}
const signeeBytes = new TextEncoder().encode(canonicalize(signee));
const rawSig = await crypto.subtle.sign(

View file

@ -1,4 +1,4 @@
export * from "./keypair.ts";
export * from "./publicIdentity.ts";
export * from "./publicKey.ts";
export * from "./signedPayload.ts";
export * from "./utils.ts";

View file

@ -2,7 +2,7 @@ import canonicalize from "./canonicalize.ts";
import type { BlahSignedPayload } from "./signedPayload.ts";
import { bufToHex, hexToBuf } from "./utils.ts";
export class BlahPublicIdentity {
export class BlahPublicKey {
private publicKey: CryptoKey;
id: string;
name: string;
@ -16,13 +16,13 @@ export class BlahPublicIdentity {
static async fromPublicKey(
publicKey: CryptoKey,
): Promise<BlahPublicIdentity> {
): Promise<BlahPublicKey> {
const rawKey = await crypto.subtle.exportKey("raw", publicKey);
const id = bufToHex(rawKey);
return new BlahPublicIdentity(publicKey, id);
return new BlahPublicKey(publicKey, id);
}
static async fromID(id: string): Promise<BlahPublicIdentity> {
static async fromID(id: string): Promise<BlahPublicKey> {
const rawKey = hexToBuf(id);
const publicKey = await crypto.subtle.importKey(
"raw",
@ -33,22 +33,23 @@ export class BlahPublicIdentity {
"verify",
],
);
return new BlahPublicIdentity(publicKey, id);
return new BlahPublicKey(publicKey, id);
}
static async verifyPayload<P>(
signedPayload: BlahSignedPayload<P>,
): Promise<{ payload: P; identity: BlahPublicIdentity }> {
): Promise<{ payload: P; key: BlahPublicKey }> {
const { signee } = signedPayload;
const identity = await BlahPublicIdentity.fromID(signee.user);
return { payload: await identity.verifyPayload(signedPayload), identity };
const key = await BlahPublicKey.fromID(signee.act_key ?? signee.id_key);
return { payload: await key.verifyPayload(signedPayload), key };
}
async verifyPayload<P>(signedPayload: BlahSignedPayload<P>): Promise<P> {
const { sig, signee } = signedPayload;
if (signee.user !== this.id) {
const signingKey = signee.act_key ?? signee.id_key;
if (signingKey !== this.id) {
throw new Error(
`Payload is not signed by this identity. Was signed by ${signee.user}.`,
`Payload is not signed by this identity. Was signed by ${signingKey}.`,
);
}
const signeeBytes = new TextEncoder().encode(canonicalize(signee));

View file

@ -2,7 +2,7 @@ export type BlahPayloadSignee<P> = {
nonce: number;
payload: P;
timestamp: number;
user: string;
id_key: string;
act_key?: string;
};