novy-syndicate/src/sturdy.ts

69 lines
2.8 KiB
TypeScript

// 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 } from './cryptography.js';
import { Bytes, encode, is, Record, Value } from 'preserves';
import type { Pattern, Template } from './rewrite.js';
export type EmbeddedRef = { ref: SturdyRef };
export type SturdyValue = Value<EmbeddedRef>;
export const _SturdyRef = Symbol.for('sturdyref');
export const SturdyRef = Record.makeConstructor<{
oid: SturdyValue, // (arbitrary) name of the ultimate target of the ref
caveatChain: Array<Caveat>[],
// ^ caveats/rewrites. Evaluated RIGHT-TO-LEFT; each Array<Caveat> is evaluated LEFT-TO-RIGHT
sig: Bytes, // *keyed* signature of canonicalEncode of rightmost item in [oid, ... caveatChain]
}, EmbeddedRef>()(_SturdyRef, ['oid', 'caveatChain', 'sig']);
export type SturdyRef = [SturdyValue, Array<Caveat>[], Bytes] & { label: typeof _SturdyRef };
export type Caveat = Or<Rewrite>;
// ^ 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 = <T extends SturdyValue>() =>
Record.makeConstructor<{ alternatives: T[] }, EmbeddedRef>()(_Or, ['alternatives']);
export type Or<T extends SturdyValue> = T | Record<typeof _Or, [T[]], EmbeddedRef>;
export const _Rewrite = Symbol.for('rewrite');
export const Rewrite = Record.makeConstructor<{
pattern: Pattern,
template: Template,
}, EmbeddedRef>()(_Rewrite, ['pattern', 'template']);
export type Rewrite = ReturnType<typeof Rewrite>;
export const KEY_LENGTH = 16; // 128 bits
export function sturdyEncode(v: SturdyValue): Bytes {
return encode<EmbeddedRef>(v, {
canonical: true,
includeAnnotations: false,
encodePointer(v) { return v },
});
}
export async function mint(oid: SturdyValue, secretKey: Bytes): Promise<SturdyRef> {
return SturdyRef(oid, [], await mac(secretKey, sturdyEncode(oid)));
}
export async function attenuate(r: SturdyRef, ... a: Array<Caveat>): Promise<SturdyRef> {
return SturdyRef(
SturdyRef._.oid(r),
[... SturdyRef._.caveatChain(r), a],
await mac(SturdyRef._.sig(r), sturdyEncode(a))
);
}
export async function validate(r: SturdyRef, secretKey: Bytes): Promise<boolean> {
const sig = await SturdyRef._.caveatChain(r).reduce(
async (sig, a) => mac(await sig, sturdyEncode(a)),
mac(secretKey, sturdyEncode(SturdyRef._.oid(r))));
return is(sig, SturdyRef._.sig(r));
}