100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
/// SPDX-License-Identifier: GPL-3.0-or-later
|
|
/// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
|
|
// 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, decode, encode, is, neverEmbeddedType, Value, fromJS } from '@preserves/core';
|
|
import * as S from '../gen/sturdy.js';
|
|
import * as G from '../gen/gatekeeper.js';
|
|
export * from '../gen/sturdy.js';
|
|
|
|
export type SturdyValue = Value<S._embedded>;
|
|
|
|
export const KEY_LENGTH = 16; // 128 bits
|
|
|
|
export function embeddedNotAllowed(): never {
|
|
throw new Error("Embedded Ref not permitted in SturdyRef");
|
|
}
|
|
|
|
export function sturdyEncode(v: SturdyValue): Bytes {
|
|
return encode<S._embedded>(v, {
|
|
canonical: true,
|
|
includeAnnotations: false,
|
|
embeddedEncode: neverEmbeddedType,
|
|
});
|
|
}
|
|
|
|
export function sturdyDecode(bs: Bytes): SturdyValue {
|
|
return decode<S._embedded>(bs, {
|
|
includeAnnotations: false,
|
|
embeddedDecode: neverEmbeddedType,
|
|
});
|
|
}
|
|
|
|
export function sturdyBind(
|
|
oid: SturdyValue,
|
|
secretKey: Bytes,
|
|
target: S._embedded,
|
|
observer?: S._embedded,
|
|
): G.Bind {
|
|
return G.Bind({
|
|
description: G.Description({
|
|
stepType: fromJS(S.SturdyStepType()) as symbol,
|
|
detail: fromJS(S.SturdyDescriptionDetail({ oid, key: secretKey })),
|
|
}),
|
|
target,
|
|
observer: (observer === void 0) ? G.BindObserver.absent() : G.BindObserver.present(observer),
|
|
});
|
|
}
|
|
|
|
type RefAndBind = { ref: S.SturdyRef, bind: G.Bind };
|
|
export function mint(oid: SturdyValue, secretKey: Bytes): S.SturdyRef;
|
|
export function mint(oid: SturdyValue, secretKey: Bytes, target: S._embedded, observer?: S._embedded): RefAndBind;
|
|
export function mint(
|
|
oid: SturdyValue,
|
|
secretKey: Bytes,
|
|
target?: S._embedded,
|
|
observer?: S._embedded,
|
|
): S.SturdyRef | RefAndBind {
|
|
const ref = S.SturdyRef(S.Parameters({
|
|
oid,
|
|
sig: mac(secretKey, sturdyEncode(oid)),
|
|
caveats: S.CaveatsField.absent(),
|
|
}));
|
|
if (target === void 0) return ref;
|
|
return { ref, bind: sturdyBind(oid, secretKey, target, observer) };
|
|
}
|
|
|
|
function chainMac(key: Bytes, caveats: S.Caveat[]): Bytes {
|
|
return caveats.reduce((key, c) => mac(key, sturdyEncode(S.fromCaveat(c))), key);
|
|
}
|
|
|
|
export function caveatChain(r: S.SturdyRef): S.Caveat[] {
|
|
switch (r.parameters.caveats._variant) {
|
|
case "present": return r.parameters.caveats.caveats;
|
|
case "absent": return [];
|
|
case "invalid": throw new Error("Invalid caveats on sturdyref");
|
|
}
|
|
}
|
|
|
|
export function attenuate(r: S.SturdyRef, ... a: S.Caveat[]): S.SturdyRef {
|
|
if (a.length === 0) return r;
|
|
return S.SturdyRef(S.Parameters({
|
|
oid: r.parameters.oid,
|
|
caveats: S.CaveatsField.present([... caveatChain(r), ... a]),
|
|
sig: chainMac(r.parameters.sig, a),
|
|
}));
|
|
}
|
|
|
|
export function validate(r: S.SturdyRef, secretKey: Bytes): boolean {
|
|
const sig = chainMac(mac(secretKey, sturdyEncode(r.parameters.oid)), caveatChain(r));
|
|
return is(sig, r.parameters.sig);
|
|
}
|