UNPKG

@m-ld/gateway

Version:

m-ld gateway for services

179 lines 13.5 kB
import { Optional, propertyValue } from '@m-ld/m-ld'; import { createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify } from 'crypto'; import { AuthKey, domainRelativeIri } from '../lib/index.js'; import { signJwt, verifyJwt } from '@m-ld/io-web-runtime/dist/server/jwt'; /** * User Key details, appears in: * 1. Gateway domain, with all details * 2. Timesheet domains, without private key (for sig verify) * 3. Client configuration, without revocation (assumed true) */ export class UserKey { /** * From m-ld subject representation */ static fromJSON(src) { // noinspection JSCheckFunctionSignatures return new UserKey({ keyid: this.keyidFromRef(src), name: propertyValue(src, 'name', Optional, String), publicKey: propertyValue(src, 'public', Uint8Array), privateKey: propertyValue(src, 'private', Optional, Uint8Array), revoked: propertyValue(src, 'revoked', Optional, Boolean) }); } /** * From client config – no name or revocation */ static fromConfig(config) { return new UserKey({ keyid: AuthKey.fromString(config.auth.key).keyid, publicKey: Buffer.from(config.key.public, 'base64'), privateKey: config.key.private ? Buffer.from(config.key.private, 'base64') : undefined }); } /** * @throws {TypeError} if the reference is not to a user key */ static keyidFromRef(ref) { // noinspection JSCheckFunctionSignatures const id = ref['@id'].includes('//') ? new URL(ref['@id']).pathname.slice(1) : ref['@id']; if (!/^\.\w{5,}$/.test(id)) throw new TypeError(`Unexpected user key identity format "${id}"`); return id.slice(1); } /** @returns Reference */ static refFromKeyid(keyid, domain) { const id = `.${keyid}`; return { '@id': domain ? domainRelativeIri(id, domain) : id }; } static splitSignature(data) { const buf = Buffer.from(data); const delim = buf.indexOf(':'); if (delim < 5) return [undefined, data]; return [buf.subarray(0, delim).toString(), buf.subarray(delim + 1)]; } static generate(authKey) { if (typeof authKey == 'string') authKey = AuthKey.fromString(authKey); // noinspection JSCheckFunctionSignatures return new UserKey({ keyid: authKey.keyid, ...generateKeyPairSync('rsa', { modulusLength: 1024, publicKeyEncoding: this.encoding.public, privateKeyEncoding: this.encoding.private(authKey) }) }); } constructor({ keyid, name, publicKey, privateKey, revoked = false }) { this.keyid = keyid; this.name = name; this.publicKey = Buffer.from(publicKey); this.privateKey = privateKey ? Buffer.from(privateKey) : undefined; this.revoked = revoked; } /** * @returns {boolean} `false` if the auth key does not correspond to this user key */ matches(authKey) { if (authKey.keyid !== this.keyid) return false; // Shortcut try { return !!this.getCryptoPrivateKey(authKey); } catch (e) { // ERR_OSSL_EVP_BAD_DECRYPT if the secret is wrong return false; } } sign(data, authKey) { return Buffer.concat([ Buffer.from(`${this.keyid}:`), sign('RSA-SHA256', data, this.getCryptoPrivateKey(authKey)) ]); } verify(sig, data) { const [keyid, cryptoSig] = UserKey.splitSignature(sig); if (keyid !== this.keyid) return false; return verify('RSA-SHA256', data, this.getCryptoPublicKey(), cryptoSig); } /** * @returns {Promise<string>} JWT */ signJwt(payload, authKey, options) { // noinspection JSCheckFunctionSignatures return signJwt(payload, this.getCryptoPrivateKey(authKey), { ...options, algorithm: 'RS256', keyid: this.keyid }); } static verifyJwt(jwt, getUserKey) { return verifyJwt(jwt, async (header) => (await getUserKey(header)).getCryptoPublicKey(), { algorithms: ['RS256'] }); } /** * @param {AuthKey} authKey * @returns {[string, KeyObject]} Arguments for HTTP signing * @see https://httpwg.org/http-extensions/draft-ietf-httpbis-message-signatures.html */ getSignHttpArgs(authKey) { return ['rsa-v1_5-sha256', this.getCryptoPrivateKey(authKey)]; } getCryptoPrivateKey(authKey) { if (this.privateKey == null) throw new RangeError('Private key unavailable'); // noinspection JSCheckFunctionSignatures return createPrivateKey({ key: this.privateKey, ...UserKey.encoding.private(authKey) }); } getCryptoPublicKey() { return createPublicKey({ key: this.publicKey, ...UserKey.encoding.public }); } /** * @param {boolean} excludePrivate `true` to exclude the private key * @returns {GraphSubject} */ toJSON(excludePrivate = false) { // noinspection JSValidateTypes return { ...UserKey.refFromKeyid(this.keyid), '@type': 'UserKey', name: this.name, public: this.publicKey, private: excludePrivate ? undefined : this.privateKey, revoked: this.revoked }; } /** * Note this is only a partial inverse of {@link fromConfig}: * - the user and domain are not included */ toConfig(authKey) { return Object.assign(authKey.toConfig(), { key: { type: 'rsa', public: this.publicKey.toString('base64'), private: this.privateKey?.toString('base64') // revoked assumed false } }); } } UserKey.encoding = { public: { type: 'spki', format: 'der' }, private: (authKey) => ({ type: 'pkcs8', format: 'der', cipher: 'aes-256-cbc', passphrase: authKey.secret }) }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXNlcktleS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kYXRhL1VzZXJLZXkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFnQixRQUFRLEVBQUUsYUFBYSxFQUFhLE1BQU0sWUFBWSxDQUFDO0FBQzlFLE9BQU8sRUFDTCxnQkFBZ0IsRUFBRSxlQUFlLEVBQUUsbUJBQW1CLEVBQ25CLElBQUksRUFBRSxNQUFNLEVBQ2hELE1BQU0sUUFBUSxDQUFDO0FBQ2hCLE9BQU8sRUFBRSxPQUFPLEVBQWlCLGlCQUFpQixFQUFPLE1BQU0saUJBQWlCLENBQUM7QUFFakYsT0FBTyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQVUxRTs7Ozs7R0FLRztBQUNILE1BQU0sT0FBTyxPQUFPO0lBQ2xCOztPQUVHO0lBQ0gsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFpQjtRQUMvQix5Q0FBeUM7UUFDekMsT0FBTyxJQUFJLE9BQU8sQ0FBQztZQUNqQixLQUFLLEVBQUUsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUM7WUFDN0IsSUFBSSxFQUFFLGFBQWEsQ0FBQyxHQUFHLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxNQUFNLENBQUM7WUFDbEQsU0FBUyxFQUFFLGFBQWEsQ0FBQyxHQUFHLEVBQUUsUUFBUSxFQUFFLFVBQVUsQ0FBQztZQUNuRCxVQUFVLEVBQUUsYUFBYSxDQUFDLEdBQUcsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLFVBQVUsQ0FBQztZQUMvRCxPQUFPLEVBQUUsYUFBYSxDQUFDLEdBQUcsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQztTQUMxRCxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsVUFBVSxDQUFDLE1BQXFCO1FBQ3JDLE9BQU8sSUFBSSxPQUFPLENBQUM7WUFDakIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLO1lBQ2hELFNBQVMsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQztZQUNuRCxVQUFVLEVBQUUsTUFBTSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztnQkFDOUIsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLE9BQU8sRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUztTQUN4RCxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsWUFBWSxDQUFDLEdBQWM7UUFDaEMseUNBQXlDO1FBQ3pDLE1BQU0sRUFBRSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUNwQyxJQUFJLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDckQsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ3hCLE1BQU0sSUFBSSxTQUFTLENBQUMsd0NBQXdDLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDckUsT0FBTyxFQUFFLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3JCLENBQUM7SUFFRCx5QkFBeUI7SUFDekIsTUFBTSxDQUFDLFlBQVksQ0FBQyxLQUFhLEVBQUUsTUFBZTtRQUNoRCxNQUFNLEVBQUUsR0FBRyxJQUFJLEtBQUssRUFBRSxDQUFDO1FBQ3ZCLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxFQUFFLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDO0lBQ2hFLENBQUM7SUFFRCxNQUFNLENBQUMsY0FBYyxDQUFDLElBQWdCO1FBQ3BDLE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDOUIsTUFBTSxLQUFLLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMvQixJQUFJLEtBQUssR0FBRyxDQUFDO1lBQ1gsT0FBTyxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUMzQixPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUUsR0FBRyxDQUFDLFFBQVEsQ0FBQyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN0RSxDQUFDO0lBRUQsTUFBTSxDQUFDLFFBQVEsQ0FBQyxPQUF5QjtRQUN2QyxJQUFJLE9BQU8sT0FBTyxJQUFJLFFBQVE7WUFDNUIsT0FBTyxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDeEMseUNBQXlDO1FBQ3pDLE9BQU8sSUFBSSxPQUFPLENBQUM7WUFDakIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLO1lBQ3BCLEdBQUcsbUJBQW1CLENBQUMsS0FBSyxFQUFtQztnQkFDN0QsYUFBYSxFQUFFLElBQUk7Z0JBQ25CLGlCQUFpQixFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTTtnQkFDdkMsa0JBQWtCLEVBQUUsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDO2FBQ25ELENBQUM7U0FDSCxDQUFDLENBQUM7SUFDTCxDQUFDO0lBa0JELFlBQVksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxVQUFVLEVBQUUsT0FBTyxHQUFHLEtBQUssRUFNaEU7UUFDQyxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztRQUNuQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztRQUNqQixJQUFJLENBQUMsU0FBUyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDeEMsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztRQUNuRSxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztJQUN6QixDQUFDO0lBRUQ7O09BRUc7SUFDSCxPQUFPLENBQUMsT0FBZ0I7UUFDdEIsSUFBSSxPQUFPLENBQUMsS0FBSyxLQUFLLElBQUksQ0FBQyxLQUFLO1lBQzlCLE9BQU8sS0FBSyxDQUFDLENBQUMsV0FBVztRQUMzQixJQUFJO1lBQ0YsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLG1CQUFtQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1NBQzVDO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixrREFBa0Q7WUFDbEQsT0FBTyxLQUFLLENBQUM7U0FDZDtJQUNILENBQUM7SUFFRCxJQUFJLENBQUMsSUFBZ0IsRUFBRSxPQUFnQjtRQUNyQyxPQUFPLE1BQU0sQ0FBQyxNQUFNLENBQUM7WUFDbkIsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxLQUFLLEdBQUcsQ0FBQztZQUM3QixJQUFJLENBQUMsWUFBWSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsbUJBQW1CLENBQUMsT0FBTyxDQUFDLENBQUM7U0FDNUQsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELE1BQU0sQ0FBQyxHQUFlLEVBQUUsSUFBZ0I7UUFDdEMsTUFBTSxDQUFDLEtBQUssRUFBRSxTQUFTLENBQUMsR0FBRyxPQUFPLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZELElBQUksS0FBSyxLQUFLLElBQUksQ0FBQyxLQUFLO1lBQ3RCLE9BQU8sS0FBSyxDQUFDO1FBQ2YsT0FBTyxNQUFNLENBQUMsWUFBWSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsa0JBQWtCLEVBQUUsRUFBRSxTQUFTLENBQUMsQ0FBQztJQUMxRSxDQUFDO0lBRUQ7O09BRUc7SUFDSCxPQUFPLENBQUMsT0FBaUMsRUFBRSxPQUFnQixFQUFFLE9BQXFCO1FBQ2hGLHlDQUF5QztRQUN6QyxPQUFPLE9BQU8sQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLG1CQUFtQixDQUFDLE9BQU8sQ0FBc0IsRUFBRTtZQUM5RSxHQUFHLE9BQU8sRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSztTQUNsRCxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQsTUFBTSxDQUFDLFNBQVMsQ0FBQyxHQUFXLEVBQUUsVUFBbUQ7UUFDL0UsT0FBTyxTQUFTLENBQUMsR0FBRyxFQUFFLEtBQUssRUFBQyxNQUFNLEVBQUMsRUFBRSxDQUNqQyxDQUFDLE1BQU0sVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsa0JBQWtCLEVBQXVCLEVBQ3RFLEVBQUUsVUFBVSxFQUFFLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQy9CLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsZUFBZSxDQUFDLE9BQWdCO1FBQzlCLE9BQU8sQ0FBQyxpQkFBaUIsRUFBRSxJQUFJLENBQUMsbUJBQW1CLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUNoRSxDQUFDO0lBRU8sbUJBQW1CLENBQUMsT0FBZ0I7UUFDMUMsSUFBSSxJQUFJLENBQUMsVUFBVSxJQUFJLElBQUk7WUFDekIsTUFBTSxJQUFJLFVBQVUsQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO1FBQ2xELHlDQUF5QztRQUN6QyxPQUFPLGdCQUFnQixDQUFrQjtZQUN2QyxHQUFHLEVBQUUsSUFBSSxDQUFDLFVBQVU7WUFDcEIsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUM7U0FDckMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELGtCQUFrQjtRQUNoQixPQUFPLGVBQWUsQ0FBaUI7WUFDckMsR0FBRyxFQUFFLElBQUksQ0FBQyxTQUFTO1lBQ25CLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNO1NBQzNCLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7O09BR0c7SUFDSCxNQUFNLENBQUMsY0FBYyxHQUFHLEtBQUs7UUFDM0IsK0JBQStCO1FBQy9CLE9BQU87WUFDTCxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQztZQUNuQyxPQUFPLEVBQUUsU0FBUztZQUNsQixJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUk7WUFDZixNQUFNLEVBQUUsSUFBSSxDQUFDLFNBQVM7WUFDdEIsT0FBTyxFQUFFLGNBQWMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsVUFBVTtZQUNyRCxPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87U0FDdEIsQ0FBQztJQUNKLENBQUM7SUFFRDs7O09BR0c7SUFDSCxRQUFRLENBQUMsT0FBZ0I7UUFDdkIsT0FBTyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsRUFBRTtZQUN2QyxHQUFHLEVBQUU7Z0JBQ0gsSUFBSSxFQUFFLEtBQWM7Z0JBQ3BCLE1BQU0sRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUM7Z0JBQ3pDLE9BQU8sRUFBRSxJQUFJLENBQUMsVUFBVSxFQUFFLFFBQVEsQ0FBQyxRQUFRLENBQUM7Z0JBQzVDLHdCQUF3QjthQUN6QjtTQUNGLENBQUMsQ0FBQztJQUNMLENBQUM7O0FBaklNLGdCQUFRLEdBQUc7SUFDaEIsTUFBTSxFQUFFLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFO0lBQ3ZDLE9BQU8sRUFBRSxDQUFDLE9BQWdCLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDOUIsSUFBSSxFQUFFLE9BQU87UUFDYixNQUFNLEVBQUUsS0FBSztRQUNiLE1BQU0sRUFBRSxhQUFhO1FBQ3JCLFVBQVUsRUFBRSxPQUFPLENBQUMsTUFBTTtLQUMzQixDQUFDO0NBQ0gsQ0FBQyJ9