@m-ld/gateway
Version:
m-ld gateway for services
179 lines • 13.5 kB
JavaScript
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