feat: [wip] BlahIdentity

This commit is contained in:
Shibo Lyu 2024-09-27 00:51:22 +08:00
parent 1e283829d8
commit 297425d336
6 changed files with 105 additions and 12 deletions

View file

@ -39,6 +39,7 @@ Deno.test("sign & verify payload with wrong key order but should still work", as
sig: signedPayload.sig,
signee: {
payload: { baz: 123, foo: "bar" },
act_key: signedPayload.signee.act_key,
id_key: signedPayload.signee.id_key,
nonce: signedPayload.signee.nonce,
timestamp: signedPayload.signee.timestamp,

View file

@ -80,10 +80,8 @@ export class BlahKeyPair {
payload,
timestamp,
id_key: identityKeyId ?? this.id,
act_key: this.id,
};
if (identityKeyId) {
signee.act_key = this.id;
}
const signeeBytes = new TextEncoder().encode(canonicalize(signee));

View file

@ -46,10 +46,10 @@ export class BlahPublicKey {
async verifyPayload<P>(signedPayload: BlahSignedPayload<P>): Promise<P> {
const { sig, signee } = signedPayload;
const signingKey = signee.act_key ?? signee.id_key;
const signingKey = signee.act_key;
if (signingKey !== this.id) {
throw new Error(
`Payload is not signed by this identity. Was signed by ${signingKey}.`,
`Payload is not signed by this public key. Was signed by ${signingKey}.`,
);
}
const signeeBytes = new TextEncoder().encode(canonicalize(signee));

View file

@ -6,7 +6,7 @@ export function blahPayloadSigneeSchemaOf<P extends z.ZodTypeAny>(schema: P) {
payload: schema,
timestamp: z.number().int(),
id_key: z.string(),
act_key: z.string().optional(),
act_key: z.string(),
});
}
@ -22,7 +22,7 @@ export type BlahPayloadSignee<P> = {
payload: P;
timestamp: number;
id_key: string;
act_key?: string;
act_key: string;
};
export type BlahSignedPayload<P> = {

View file

@ -17,5 +17,8 @@
"publish": {
"include": ["LICENSE", "README.md", "crypto", "identity"],
"exclude": ["**/*.test.ts"]
},
"test": {
"exclude": ["npm/"]
}
}

View file

@ -1,9 +1,100 @@
import type { BlahSignedPayload } from "../crypto/mod.ts";
import {
BlahKeyPair,
BlahPublicKey,
BlahSignedPayload,
} from "../crypto/mod.ts";
import type { BlahActKeyRecord } from "./actKey.ts";
import { blahIdentityFileSchema } from "./identityFile.ts";
import type { BlahProfile } from "./profile.ts";
export class BlahIdentity {
idKeyId: string;
actKeys: BlahSignedPayload<BlahActKeyRecord>[];
profile: BlahSignedPayload<BlahProfile>;
type ActKey = {
raw: BlahSignedPayload<BlahActKeyRecord>;
key: BlahPublicKey | BlahKeyPair;
expiresAt: Date;
sigValid: boolean;
};
async function constructActKeyFromRaw(
raw: BlahSignedPayload<BlahActKeyRecord>,
idKey: BlahPublicKey | BlahKeyPair,
): Promise<ActKey> {
const publicKey = idKey instanceof BlahKeyPair ? idKey.publicKey : idKey;
let sigValid = false;
try {
publicKey.verifyPayload(raw);
sigValid = true;
} catch {
sigValid = false;
}
const key = await BlahPublicKey.fromID(raw.signee.payload.act_key);
const expiresAt = new Date(raw.signee.payload.expire_time * 1000);
return { raw, key, expiresAt, sigValid };
}
export class BlahIdentity {
idKey: BlahPublicKey | BlahKeyPair;
actKeys: ActKey[];
rawProfile: BlahSignedPayload<BlahProfile>;
profileSigValid: boolean;
private constructor(
idKey: BlahPublicKey | BlahKeyPair,
actKeys: ActKey[],
rawProfile: BlahSignedPayload<BlahProfile>,
profileSigValid: boolean,
) {
this.idKey = idKey;
this.actKeys = actKeys;
this.rawProfile = rawProfile;
this.profileSigValid = profileSigValid;
}
get profile(): BlahProfile {
return this.rawProfile.signee.payload;
}
static async fromIdentityFile(
identityFile: unknown,
idKeyPair?: BlahKeyPair,
actingKeyPair?: BlahKeyPair,
): Promise<BlahIdentity> {
let identityFileJson = identityFile;
if (typeof identityFile === "string") {
identityFileJson = JSON.parse(identityFile);
}
const { id_key, act_keys, profile } = blahIdentityFileSchema.parse(
identityFileJson,
);
const idKey = idKeyPair ?? await BlahPublicKey.fromID(id_key);
if (idKey.id !== id_key) {
throw new Error("ID key pair does not match ID key in identity file.");
}
const actKeys: ActKey[] = await Promise.all(act_keys.map(async (raw) => {
const actKey = await constructActKeyFromRaw(raw, idKey);
if (actingKeyPair?.id === actKey.key.id) {
actKey.key = actingKeyPair;
}
return actKey;
}));
const rawProfile = profile;
const profileSigningKey = await BlahPublicKey.fromID(
rawProfile.signee.act_key,
);
if (actKeys.findIndex((k) => k.key.id === profileSigningKey.id) === -1) {
throw new Error("Profile is not signed by any of the act keys.");
}
let profileSigValid = false;
try {
profileSigningKey.verifyPayload(rawProfile);
profileSigValid = true;
} catch {
profileSigValid = false;
}
return new BlahIdentity(idKey, actKeys, rawProfile, profileSigValid);
}
}