mirror of
https://github.com/Blah-IM/typescript-core.git
synced 2025-04-30 16:21:10 +00:00
feat: [wip] BlahIdentity
This commit is contained in:
parent
1e283829d8
commit
297425d336
6 changed files with 105 additions and 12 deletions
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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> = {
|
||||
|
|
|
@ -17,5 +17,8 @@
|
|||
"publish": {
|
||||
"include": ["LICENSE", "README.md", "crypto", "identity"],
|
||||
"exclude": ["**/*.test.ts"]
|
||||
},
|
||||
"test": {
|
||||
"exclude": ["npm/"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue