// 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 { mac, newKey } from './cryptography.js'; import { Bytes, Dictionary, encode, is, Record, 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 function sturdyEncode(v: SturdyValue): Bytes { return encode(v, { canonical: true, includeAnnotations: false, encodePointer(v) { return v }, }); } export async function mint(oid: SturdyValue, secretKey: Bytes): Promise { return SturdyRef(oid, [], await mac(secretKey, sturdyEncode(oid))); } export async function attenuate(r: SturdyRef, ... a: Attenuation): Promise { return SturdyRef( SturdyRef._.oid(r), [... SturdyRef._.attenuations(r), a], await mac(SturdyRef._.sig(r), sturdyEncode(a)) ); } export async function validate(r: SturdyRef, secretKey: Bytes): Promise { const sig = await SturdyRef._.attenuations(r).reduce( async (sig, a) => mac(await sig, sturdyEncode(a)), mac(secretKey, sturdyEncode(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('should be true:', await validate(m1, new Bytes(KEY_LENGTH))); console.log('should be true:', await validate(m2, new Bytes(KEY_LENGTH))); console.log('should be false:', 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('should be false:', await validate(m2, new Bytes(KEY_LENGTH))); m2[0] = 'hello world'; console.log(m2.asPreservesText()); console.log('should be true:', await validate(m2, new Bytes(KEY_LENGTH))); console.log('should be false:', await validate(m2, await newKey())); console.log((await newKey()).asPreservesText()); console.log((await newKey()).asPreservesText()); console.log(sturdyEncode(m2).asPreservesText()); } main();