/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones // 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; 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(v, { canonical: true, includeAnnotations: false, embeddedEncode: neverEmbeddedType, }); } export function sturdyDecode(bs: Bytes): SturdyValue { return decode(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); }