|
|
@ -236,6 +236,22 @@ keyfetch.verify = function (opts) { |
|
|
|
throw new Error("token's 'nbf' has not been reached or could not parsed: '" + nbf + "'"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (opts.jwks || opts.jwk) { |
|
|
|
return overrideLookup(opts.jwks || [opts.jwk]); |
|
|
|
} |
|
|
|
|
|
|
|
function overrideLookup(jwks) { |
|
|
|
return Promise.all(jwks.map(function (jwk) { |
|
|
|
var Keypairs = jwk.x ? Eckles : Rasha; |
|
|
|
return Keypairs.export({ jwk: jwk }).then(function (pem) { |
|
|
|
return Keypairs.thumbprint({ jwk: jwk }).then(function (thumb) { |
|
|
|
return { jwk: jwk, pem: pem, thumbprint: thumb }; |
|
|
|
}); |
|
|
|
}); |
|
|
|
})).then(verifyAny); |
|
|
|
} |
|
|
|
|
|
|
|
var kid = decoded.header.kid; |
|
|
|
var iss; |
|
|
|
var fetcher; |
|
|
@ -254,17 +270,48 @@ keyfetch.verify = function (opts) { |
|
|
|
fetchOne = keyfetch.jwk; |
|
|
|
} |
|
|
|
|
|
|
|
function verify(jwk, payload) { |
|
|
|
var payload = jwt.split('.')[1]; // as string, as it was signed
|
|
|
|
if (kid) { |
|
|
|
return fetchOne(kid, iss).then(verifyOne); //.catch(fetchAny);
|
|
|
|
} else { |
|
|
|
return fetcher(iss).then(verifyAny); |
|
|
|
} |
|
|
|
|
|
|
|
function verify(hit, payload) { |
|
|
|
var alg = 'SHA' + decoded.header.alg.replace(/[^\d]+/i, ''); |
|
|
|
var sig = convertIfEcdsa(decoded.header, decoded.signature); |
|
|
|
var sig = ecdsaAsn1SigToJwtSig(decoded.header, decoded.signature); |
|
|
|
return require('crypto') |
|
|
|
.createVerify(alg) |
|
|
|
.update(jwt.split('.')[0] + '.' + payload) |
|
|
|
.verify(jwk.pem, sig, 'base64') |
|
|
|
.verify(hit.pem, sig, 'base64') |
|
|
|
; |
|
|
|
} |
|
|
|
|
|
|
|
function convertIfEcdsa(header, b64sig) { |
|
|
|
function verifyOne(hit) { |
|
|
|
if (true === verify(hit, payload)) { |
|
|
|
return decoded; |
|
|
|
} |
|
|
|
throw new Error('token signature verification was unsuccessful'); |
|
|
|
} |
|
|
|
|
|
|
|
function verifyAny(hits) { |
|
|
|
if (hits.some(function (hit) { |
|
|
|
if (kid) { |
|
|
|
if (kid !== hit.jwk.kid && kid !== hit.thumbprint) { return; } |
|
|
|
if (true === verify(hit, payload)) { return true; } |
|
|
|
throw new Error('token signature verification was unsuccessful'); |
|
|
|
} else { |
|
|
|
if (true === verify(hit, payload)) { return true; } |
|
|
|
} |
|
|
|
})) { |
|
|
|
return decoded; |
|
|
|
} |
|
|
|
throw new Error("Retrieved a list of keys, but none of them matched the 'kid' (key id) of the token."); |
|
|
|
} |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
function ecdsaAsn1SigToJwtSig(header, b64sig) { |
|
|
|
// ECDSA JWT signatures differ from "normal" ECDSA signatures
|
|
|
|
// https://tools.ietf.org/html/rfc7518#section-3.4
|
|
|
|
if (!/^ES/i.test(header.alg)) { return b64sig; } |
|
|
@ -299,36 +346,3 @@ keyfetch.verify = function (opts) { |
|
|
|
.replace(/=/g, '') |
|
|
|
; |
|
|
|
} |
|
|
|
|
|
|
|
var payload = jwt.split('.')[1]; // as string, as it was signed
|
|
|
|
if (kid) { |
|
|
|
return fetchOne(kid, iss).then(verifyOne); //.catch(fetchAny);
|
|
|
|
} else { |
|
|
|
return fetchAny(); |
|
|
|
} |
|
|
|
|
|
|
|
function verifyOne(jwk) { |
|
|
|
if (true === verify(jwk, payload)) { |
|
|
|
return decoded; |
|
|
|
} |
|
|
|
throw new Error('token signature verification was unsuccessful'); |
|
|
|
} |
|
|
|
|
|
|
|
function fetchAny() { |
|
|
|
return fetcher(iss).then(function (jwks) { |
|
|
|
if (jwks.some(function (jwk) { |
|
|
|
if (kid) { |
|
|
|
if (kid !== jwk.kid && kid !== jwk.thumbprint) { return; } |
|
|
|
if (true === verify(jwk, payload)) { return true; } |
|
|
|
throw new Error('token signature verification was unsuccessful'); |
|
|
|
} else { |
|
|
|
if (true === verify(jwk, payload)) { return true; } |
|
|
|
} |
|
|
|
})) { |
|
|
|
return decoded; |
|
|
|
} |
|
|
|
throw new Error("Retrieved a list of keys, but none of them matched the 'kid' (key id) of the token."); |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
}; |
|
|
|