SturdyRef prototype
This commit is contained in:
parent
e23b93e913
commit
50a563210c
|
@ -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<EmbeddedRef>;
|
||||||
|
|
||||||
|
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<Caveat>; // chain, evaluated left-to-right
|
||||||
|
|
||||||
|
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 async function newKey(): Promise<Bytes> {
|
||||||
|
const bs = new Bytes(KEY_LENGTH);
|
||||||
|
window.crypto.getRandomValues(underlying(bs));
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sturdyEncode(v: SturdyValue): Bytes {
|
||||||
|
return encode<EmbeddedRef>(v, {
|
||||||
|
canonical: true,
|
||||||
|
includeAnnotations: false,
|
||||||
|
encodePointer(v) { return v },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mac(secretKey: Bytes, term: SturdyValue): Promise<Bytes> {
|
||||||
|
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<SturdyRef> {
|
||||||
|
return SturdyRef(oid, [], await mac(secretKey, oid));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function attenuate(r: SturdyRef, ... a: Attenuation): Promise<SturdyRef> {
|
||||||
|
return SturdyRef(
|
||||||
|
SturdyRef._.oid(r),
|
||||||
|
[... SturdyRef._.attenuations(r), a],
|
||||||
|
await mac(SturdyRef._.sig(r), a)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validate(r: SturdyRef, secretKey: Bytes): Promise<boolean> {
|
||||||
|
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<Pattern, never>([
|
||||||
|
[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();
|
Loading…
Reference in New Issue