UNPKG

@dfinity/identity

Version:

JavaScript and TypeScript library to manage identity with the Internet Computer

298 lines 13.4 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var _PartialDelegationIdentity_delegation; import { fromHex, requestIdOf, SignIdentity, toHex, } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; import * as cbor from 'simple-cbor'; import { PartialIdentity } from './partial'; import { bufFromBufLike } from '@dfinity/candid'; const domainSeparator = new TextEncoder().encode('\x1Aic-request-auth-delegation'); const requestDomainSeparator = new TextEncoder().encode('\x0Aic-request'); function _parseBlob(value) { if (typeof value !== 'string' || value.length < 64) { throw new Error('Invalid public key.'); } return fromHex(value); } /** * A single delegation object that is signed by a private key. This is constructed by * `DelegationChain.create()`. * * {@see DelegationChain} */ export class Delegation { constructor(pubkey, expiration, targets) { this.pubkey = pubkey; this.expiration = expiration; this.targets = targets; } toCBOR() { // Expiration field needs to be encoded as a u64 specifically. return cbor.value.map(Object.assign({ pubkey: cbor.value.bytes(this.pubkey), expiration: cbor.value.u64(this.expiration.toString(16), 16) }, (this.targets && { targets: cbor.value.array(this.targets.map(t => cbor.value.bytes(bufFromBufLike(t.toUint8Array())))), }))); } toJSON() { // every string should be hex and once-de-hexed, // discoverable what it is (e.g. de-hex to get JSON with a 'type' property, or de-hex to DER // with an OID). After de-hex, if it's not obvious what it is, it's an ArrayBuffer. return Object.assign({ expiration: this.expiration.toString(16), pubkey: toHex(this.pubkey) }, (this.targets && { targets: this.targets.map(p => p.toHex()) })); } } /** * Sign a single delegation object for a period of time. * @param from The identity that lends its delegation. * @param to The identity that receives the delegation. * @param expiration An expiration date for this delegation. * @param targets Limit this delegation to the target principals. */ async function _createSingleDelegation(from, to, expiration, targets) { const delegation = new Delegation(to.toDer(), BigInt(+expiration) * BigInt(1000000), // In nanoseconds. targets); // The signature is calculated by signing the concatenation of the domain separator // and the message. // Note: To ensure Safari treats this as a user gesture, ensure to not use async methods // besides the actualy webauthn functionality (such as `sign`). Safari will de-register // a user gesture if you await an async call thats not fetch, xhr, or setTimeout. const challenge = new Uint8Array([ ...domainSeparator, ...new Uint8Array(requestIdOf(Object.assign({}, delegation))), ]); const signature = await from.sign(bufFromBufLike(challenge)); return { delegation, signature, }; } /** * A chain of delegations. This is JSON Serializable. * This is the object to serialize and pass to a DelegationIdentity. It does not keep any * private keys. */ export class DelegationChain { constructor(delegations, publicKey) { this.delegations = delegations; this.publicKey = publicKey; } /** * Create a delegation chain between two (or more) keys. By default, the expiration time * will be very short (15 minutes). * * To build a chain of more than 2 identities, this function needs to be called multiple times, * passing the previous delegation chain into the options argument. For example: * @example * const rootKey = createKey(); * const middleKey = createKey(); * const bottomeKey = createKey(); * * const rootToMiddle = await DelegationChain.create( * root, middle.getPublicKey(), Date.parse('2100-01-01'), * ); * const middleToBottom = await DelegationChain.create( * middle, bottom.getPublicKey(), Date.parse('2100-01-01'), { previous: rootToMiddle }, * ); * * // We can now use a delegation identity that uses the delegation above: * const identity = DelegationIdentity.fromDelegation(bottomKey, middleToBottom); * @param from The identity that will delegate. * @param to The identity that gets delegated. It can now sign messages as if it was the * identity above. * @param expiration The length the delegation is valid. By default, 15 minutes from calling * this function. * @param options A set of options for this delegation. expiration and previous * @param options.previous - Another DelegationChain that this chain should start with. * @param options.targets - targets that scope the delegation (e.g. Canister Principals) */ static async create(from, to, expiration = new Date(Date.now() + 15 * 60 * 1000), options = {}) { var _a, _b; const delegation = await _createSingleDelegation(from, to, expiration, options.targets); return new DelegationChain([...(((_a = options.previous) === null || _a === void 0 ? void 0 : _a.delegations) || []), delegation], ((_b = options.previous) === null || _b === void 0 ? void 0 : _b.publicKey) || from.getPublicKey().toDer()); } /** * Creates a DelegationChain object from a JSON string. * @param json The JSON string to parse. */ static fromJSON(json) { const { publicKey, delegations } = typeof json === 'string' ? JSON.parse(json) : json; if (!Array.isArray(delegations)) { throw new Error('Invalid delegations.'); } const parsedDelegations = delegations.map(signedDelegation => { const { delegation, signature } = signedDelegation; const { pubkey, expiration, targets } = delegation; if (targets !== undefined && !Array.isArray(targets)) { throw new Error('Invalid targets.'); } return { delegation: new Delegation(_parseBlob(pubkey), BigInt('0x' + expiration), // expiration in JSON is an hexa string (See toJSON() below). targets && targets.map((t) => { if (typeof t !== 'string') { throw new Error('Invalid target.'); } return Principal.fromHex(t); })), signature: _parseBlob(signature), }; }); return new this(parsedDelegations, _parseBlob(publicKey)); } /** * Creates a DelegationChain object from a list of delegations and a DER-encoded public key. * @param delegations The list of delegations. * @param publicKey The DER-encoded public key of the key-pair signing the first delegation. */ static fromDelegations(delegations, publicKey) { return new this(delegations, publicKey); } toJSON() { return { delegations: this.delegations.map(signedDelegation => { const { delegation, signature } = signedDelegation; const { targets } = delegation; return { delegation: Object.assign({ expiration: delegation.expiration.toString(16), pubkey: toHex(delegation.pubkey) }, (targets && { targets: targets.map(t => t.toHex()), })), signature: toHex(signature), }; }), publicKey: toHex(this.publicKey), }; } } /** * An Identity that adds delegation to a request. Everywhere in this class, the name * innerKey refers to the SignIdentity that is being used to sign the requests, while * originalKey is the identity that is being borrowed. More identities can be used * in the middle to delegate. */ export class DelegationIdentity extends SignIdentity { constructor(_inner, _delegation) { super(); this._inner = _inner; this._delegation = _delegation; } /** * Create a delegation without having access to delegateKey. * @param key The key used to sign the requests. * @param delegation A delegation object created using `createDelegation`. */ static fromDelegation(key, delegation) { return new this(key, delegation); } getDelegation() { return this._delegation; } getPublicKey() { return { derKey: this._delegation.publicKey, toDer: () => this._delegation.publicKey, }; } sign(blob) { return this._inner.sign(blob); } async transformRequest(request) { const { body } = request, fields = __rest(request, ["body"]); const requestId = await requestIdOf(body); return Object.assign(Object.assign({}, fields), { body: { content: body, sender_sig: await this.sign(bufFromBufLike(new Uint8Array([...requestDomainSeparator, ...new Uint8Array(requestId)]))), sender_delegation: this._delegation.delegations, sender_pubkey: this._delegation.publicKey, } }); } } /** * A partial delegated identity, representing a delegation chain and the public key that it targets */ export class PartialDelegationIdentity extends PartialIdentity { constructor(inner, delegation) { super(inner); _PartialDelegationIdentity_delegation.set(this, void 0); __classPrivateFieldSet(this, _PartialDelegationIdentity_delegation, delegation, "f"); } /** * The Delegation Chain of this identity. */ get delegation() { return __classPrivateFieldGet(this, _PartialDelegationIdentity_delegation, "f"); } /** * Create a {@link PartialDelegationIdentity} from a {@link PublicKey} and a {@link DelegationChain}. * @param key The {@link PublicKey} to delegate to. * @param delegation a {@link DelegationChain} targeting the inner key. * @constructs PartialDelegationIdentity */ static fromDelegation(key, delegation) { return new PartialDelegationIdentity(key, delegation); } } _PartialDelegationIdentity_delegation = new WeakMap(); /** * Analyze a DelegationChain and validate that it's valid, ie. not expired and apply to the * scope. * @param chain The chain to validate. * @param checks Various checks to validate on the chain. */ export function isDelegationValid(chain, checks) { // Verify that the no delegation is expired. If any are in the chain, returns false. for (const { delegation } of chain.delegations) { // prettier-ignore if (+new Date(Number(delegation.expiration / BigInt(1000000))) <= +Date.now()) { return false; } } // Check the scopes. const scopes = []; const maybeScope = checks === null || checks === void 0 ? void 0 : checks.scope; if (maybeScope) { if (Array.isArray(maybeScope)) { scopes.push(...maybeScope.map(s => (typeof s === 'string' ? Principal.fromText(s) : s))); } else { scopes.push(typeof maybeScope === 'string' ? Principal.fromText(maybeScope) : maybeScope); } } for (const s of scopes) { const scope = s.toText(); for (const { delegation } of chain.delegations) { if (delegation.targets === undefined) { continue; } let none = true; for (const target of delegation.targets) { if (target.toText() === scope) { none = false; break; } } if (none) { return false; } } } return true; } //# sourceMappingURL=delegation.js.map