From 32a731dcf166a28aa3760de5ca567d1e89da1f61 Mon Sep 17 00:00:00 2001 From: Bruno Peralva Date: Thu, 15 Oct 2026 10:50:44 -0300 Subject: [PATCH] fix(verify): surface key parsing error for malformed PEM public keys A malformed PEM key passed to verify() fell back to being treated as an HMAC secret, which then failed with a misleading "invalid algorithm" error. Now, when the key material looks like PEM and createPublicKey() fails, verify() returns a JsonWebTokenError carrying the crypto error. Fixes #882 --- test/verify.tests.js | 31 +++++++++++++++++++++++++++++++ verify.js | 9 ++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/test/verify.tests.js b/test/verify.tests.js index 88500756..59467b4e 100644 --- a/test/verify.tests.js +++ b/test/verify.tests.js @@ -30,6 +30,37 @@ describe('verify', function() { }); }); + describe('with a malformed PEM public key', function () { + const token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' }); + const malformedPem = pub.toString().replace(/\n[A-Za-z0-9+/]{10}/, '\n'); + + it('should report the key parsing error instead of "invalid algorithm"', function () { + let error; + try { + jwt.verify(token, malformedPem); + } catch (e) { + error = e; + } + expect(error).to.be.instanceOf(JsonWebTokenError); + expect(error.message).to.match(/^secretOrPublicKey is not a valid PEM key: /); + expect(error.inner).to.be.instanceOf(Error); + }); + + it('should report the key parsing error when the key is a Buffer', function () { + expect(function () { + jwt.verify(token, Buffer.from(malformedPem)); + }).to.throw(JsonWebTokenError, /secretOrPublicKey is not a valid PEM key/); + }); + + it('should report the key parsing error to the callback', function (done) { + jwt.verify(token, malformedPem, function (err) { + expect(err).to.be.instanceOf(JsonWebTokenError); + expect(err.message).to.match(/secretOrPublicKey is not a valid PEM key/); + done(); + }); + }); + }); + it('should not be able to verify unsigned token', function () { const header = { alg: 'none' }; const payload = { iat: Math.floor(Date.now() / 1000 ) }; diff --git a/verify.js b/verify.js index cdbfdc45..77e6db14 100644 --- a/verify.js +++ b/verify.js @@ -18,6 +18,10 @@ if (PS_SUPPORTED) { RSA_KEY_ALGS.splice(RSA_KEY_ALGS.length, 0, 'PS256', 'PS384', 'PS512'); } +function looksLikePem(key) { + return (typeof key === 'string' || Buffer.isBuffer(key)) && key.includes('-----BEGIN'); +} + module.exports = function (jwtString, secretOrPublicKey, options, callback) { if ((typeof options === 'function') && !callback) { callback = options; @@ -120,7 +124,10 @@ module.exports = function (jwtString, secretOrPublicKey, options, callback) { if (secretOrPublicKey != null && !(secretOrPublicKey instanceof KeyObject)) { try { secretOrPublicKey = createPublicKey(secretOrPublicKey); - } catch (_) { + } catch (err) { + if (looksLikePem(secretOrPublicKey)) { + return done(new JsonWebTokenError('secretOrPublicKey is not a valid PEM key: ' + err.message, err)); + } try { secretOrPublicKey = createSecretKey(typeof secretOrPublicKey === 'string' ? Buffer.from(secretOrPublicKey) : secretOrPublicKey); } catch (_) {