UNPKG

@sap/xssec

Version:

XS Advanced Container Security API for node.js

115 lines (97 loc) 3.82 kB
const crypto = require("crypto"); const UnsupportedAlgorithmError = require('../error/validation/UnsupportedAlgorithmError'); const InvalidTokenSignatureError = require('../error/validation/InvalidTokenSignatureError'); /** * @typedef {import("../token/Token")} Token */ const availableHashes = crypto.getHashes(); class Jwk { key; // key information from JWKS response pubKey; // Node.Js crypto public key nodeAlg; // alg as Node.Js crypto name, e.g. RSA-SHA256 /** * Creates a JWK based on a jwk-formatted public key * @param {Object} key key information in jwk format */ constructor(key) { this.key = key; this.pubKey = crypto.createPublicKey({ key: key, format: "jwk" }); } /** * Creates a JWK based on a PEM-formatted public key * @param {String} pem public key in PEM format * @returns Jwk */ static fromPEM(pem) { const cleanedUpPem = Jwk.cleanUpPemKey(pem); const pubKey = crypto.createPublicKey({ key: cleanedUpPem, format: "pem" }); const jwk = pubKey.export({ format: "jwk" }); return new Jwk(jwk); } /** * Validates if the token was signed with the private key that belongs to this public key. * If available, uses the signature cache to avoid validating the same signature again on subsequent requests. * @param {Token} token * @param {import("../util/Types").Cache} [signatureCache] an optional cache for signature validation results * @returns void if the signature is valid * @throws {InvalidTokenSignatureError} when signature is invalid */ validateSignature(token, signatureCache) { let validationResult = signatureCache?.get(token.jwt); if (validationResult == null) { validationResult = this.validateSignatureWithoutCache(token); signatureCache?.set(token.jwt, validationResult); } if (validationResult !== true) { throw new InvalidTokenSignatureError(token); } } /** * Validates if the token was signed with the private key that belongs to this public key without using a cache. * @param {Token} token * @returns {boolean} true if the signature is valid, false otherwise */ validateSignatureWithoutCache(token) { const nodeAlg = Jwk.mapAlgToNodeAlg(token.header.alg); if (!availableHashes.includes(nodeAlg)) { throw new UnsupportedAlgorithmError(token, token.header.alg); } const [header, payload, signature] = token.jwt.split("."); const verifier = crypto.createVerify(nodeAlg); verifier.update(`${header}.${payload}`); return verifier.verify(this.pubKey, signature, 'base64'); } static mapAlgToNodeAlg(alg) { switch (alg?.toUpperCase()) { case "RS256": return "RSA-SHA256"; case "RS384": return "RSA-SHA384"; case "RS512": return "RSA-SHA512"; default: return null; } } /* * Adds missing line breaks to malformed PEM keys. * For backward-compatibility, a specific kind of malformed PEM needs to be supported that is lacking line breaks around the header and footer. * This kind of PEM input can occur, for example, in old service bindings of XSA and is not always fixable by consumers of this library. */ static cleanUpPemKey(pem = "") { if (!pem.includes("KEY-----\n")) { pem = pem.replace("KEY-----", "KEY-----\n"); } if (!pem.includes("\n-----END")) { pem = pem.replace("-----END", "\n-----END"); } return pem; } } module.exports = Jwk;