import APIs from "./APIs"; import { IChassisContext } from "../interfaces"; // import * as jwt from "jsonwebtoken"; export class KeycloakCerts { BEGIN_KEY = "-----BEGIN RSA PUBLIC KEY-----\n"; END_KEY = "\n-----END RSA PUBLIC KEY-----\n"; cert: any; constructor(protected context: IChassisContext, protected url: string) { this.reload(); } reload() { APIs.request(this.context, this.url) .then((found) => { this.context.log({ code: "plugin:jwt:keycloak:certs", found_keys: found.keys.length, }); this.cert = found; }) .catch((err) => { this.context.error({ code: "plugin:jwt:keycloak:certs:failed", message: err.message, url: this.url, }); }); } getKey(kid) { // console.log("keycloakCert.getKey: %j -> %j", this.cert, kid); return Object.hasOwnProperty.call(this.cert, "keys") ? this.cert.keys.find((k) => k.kid === kid) : undefined; } verify(key) { if (!(key.n && key.e)) { throw new Error("Can't find modulus or exponent in key."); } if (key.kty !== "RSA") { throw new Error("Key type (kty) must be RSA."); } if (key.alg !== "RS256") { throw new Error("Algorithm (alg) must be RS256."); } } // Based on tracker1's node-rsa-pem-from-mod-exp module. // See https://github.com/tracker1/node-rsa-pem-from-mod-exp getPublicKey(modulus, exponent) { const mod = this.convertToHex(modulus); const exp = this.convertToHex(exponent); const encModLen = this.encodeLength(mod.length / 2); const encExpLen = this.encodeLength(exp.length / 2); const part = [mod, exp, encModLen, encExpLen] .map((n) => n.length / 2) .reduce((a, b) => a + b); const bufferSource = `30${this.encodeLength( part + 2 )}02${encModLen}${mod}02${encExpLen}${exp}`; const pubkey = Buffer.from(bufferSource, "hex").toString("base64"); return ( this.BEGIN_KEY + pubkey.match(/.{1,64}/g).join("\n") + this.END_KEY ); } convertToHex(str) { const hex = Buffer.from(str, "base64").toString("hex"); return hex[0] < "0" || hex[0] > "7" ? `00${hex}` : hex; } encodeLength(n) { return n <= 127 ? this.toHex(n) : this.toLongHex(n); } toLongHex(number) { const str = this.toHex(number); const lengthByteLength = 128 + str.length / 2; return this.toHex(lengthByteLength) + str; } toHex(number) { const str = number.toString(16); return str.length % 2 ? `0${str}` : str; } }