From 50a563210ceab8063b21bc4eb53990b26598c1c8 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 4 Mar 2021 19:54:22 +0100 Subject: [PATCH] SturdyRef prototype --- src/sturdy.ts | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/sturdy.ts diff --git a/src/sturdy.ts b/src/sturdy.ts new file mode 100644 index 0000000..8ce76de --- /dev/null +++ b/src/sturdy.ts @@ -0,0 +1,116 @@ +// Basically Macaroons [1] in a Dataspace context +// +// [1]: Birgisson, Arnar, Joe Gibbs Politz, Úlfar Erlingsson, Ankur +// Taly, Michael Vrable, and Mark Lentczner. “Macaroons: Cookies with +// Contextual Caveats for Decentralized Authorization in the Cloud.” +// In Network and Distributed System Security Symposium. San Diego, +// California: Internet Society, 2014. + +import { Bytes, Dictionary, encode, is, Record, underlying, Value } from 'preserves'; +import type { Pattern, Template } from './rewrite.js'; +import * as RW from './rewrite.js'; + +export type EmbeddedRef = { ref: SturdyRef }; +export type SturdyValue = Value; + +export const _SturdyRef = Symbol.for('sturdyref'); +export const SturdyRef = Record.makeConstructor<{ + oid: SturdyValue, // (arbitrary) name of the ultimate target of the ref + attenuations: Attenuation[], // caveats/rewrites, evaluated RIGHT-TO-LEFT + sig: Bytes, // *keyed* signature of canonicalEncode of rightmost item in [oid, ... attenuations] +}, EmbeddedRef>()(_SturdyRef, ['oid', 'attenuations', 'sig']); +export type SturdyRef = [SturdyValue, Attenuation[], Bytes] & { label: typeof _SturdyRef }; + +export type Attenuation = Array; // chain, evaluated left-to-right + +export type Caveat = Or; +// ^ embodies 1st-party caveats over assertion structure, but nothing else +// can add 3rd-party caveats and richer predicates later + +export const _Or = Symbol.for('or'); +export const Or = () => + Record.makeConstructor<{ alternatives: T[] }, EmbeddedRef>()(_Or, ['alternatives']); +export type Or = T | Record; + +export const _Rewrite = Symbol.for('rewrite'); +export const Rewrite = Record.makeConstructor<{ + pattern: Pattern, + template: Template, +}, EmbeddedRef>()(_Rewrite, ['pattern', 'template']); +export type Rewrite = ReturnType; + +export const KEY_LENGTH = 16; // 128 bits + +export async function newKey(): Promise { + const bs = new Bytes(KEY_LENGTH); + window.crypto.getRandomValues(underlying(bs)); + return bs; +} + +export function sturdyEncode(v: SturdyValue): Bytes { + return encode(v, { + canonical: true, + includeAnnotations: false, + encodePointer(v) { return v }, + }); +} + +export async function mac(secretKey: Bytes, term: SturdyValue): Promise { + if (secretKey.length !== KEY_LENGTH) throw new Error("Invalid key length"); + const k = await window.crypto.subtle.importKey( + "raw", + underlying(secretKey), + { + hash: 'SHA-256', + name: 'HMAC', + }, + false, + ['sign']); + const bs = await window.crypto.subtle.sign({ name: 'HMAC' }, k, underlying(sturdyEncode(term))); + return Bytes.from(new Uint8Array(bs, 0, KEY_LENGTH)); +} + +export async function mint(oid: SturdyValue, secretKey: Bytes): Promise { + return SturdyRef(oid, [], await mac(secretKey, oid)); +} + +export async function attenuate(r: SturdyRef, ... a: Attenuation): Promise { + return SturdyRef( + SturdyRef._.oid(r), + [... SturdyRef._.attenuations(r), a], + await mac(SturdyRef._.sig(r), a) + ); +} + +export async function validate(r: SturdyRef, secretKey: Bytes): Promise { + const sig = await SturdyRef._.attenuations(r).reduce( + async (sig, a) => mac(await sig, a), + mac(secretKey, SturdyRef._.oid(r))); + return is(sig, SturdyRef._.sig(r)); +} + +async function main() { + const m1 = await mint('hello world', new Bytes(KEY_LENGTH)); + console.log(m1.asPreservesText()); + const m2 = await attenuate(m1, Rewrite(RW.PBind('a', + RW.PCompound(RW.CRec(Symbol.for('says'), 2), + new Dictionary([ + [0, RW.Lit('Tony')]]))), + RW.TRef('a'))); + console.log(m2.asPreservesText()); + console.log(await validate(m1, new Bytes(KEY_LENGTH))); + console.log(await validate(m2, new Bytes(KEY_LENGTH))); + console.log(await validate(m2, Bytes.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1))); + m2[0] = 'hello world2'; + console.log(m2.asPreservesText()); + console.log(await validate(m2, new Bytes(KEY_LENGTH))); + m2[0] = 'hello world'; + console.log(m2.asPreservesText()); + console.log(await validate(m2, new Bytes(KEY_LENGTH))); + console.log(await validate(m2, await newKey())); + console.log((await newKey()).asPreservesText()); + console.log((await newKey()).asPreservesText()); + console.log(sturdyEncode(m2).asPreservesText()); +} + +main();