1 /*! jws-3.2.0 (c) 2013-2015 Kenji Urushima | kjur.github.com/jsjws/license 2 */ 3 /* 4 * jws.js - JSON Web Signature Class 5 * 6 * version: 3.2.0 (2015 Apr 3) 7 * 8 * Copyright (c) 2010-2015 Kenji Urushima (kenji.urushima@gmail.com) 9 * 10 * This software is licensed under the terms of the MIT License. 11 * http://kjur.github.com/jsjws/license/ 12 * 13 * The above copyright and license notice shall be 14 * included in all copies or substantial portions of the Software. 15 */ 16 17 /** 18 * @fileOverview 19 * @name jws-3.2.js 20 * @author Kenji Urushima kenji.urushima@gmail.com 21 * @version 3.2.0 (2015-Apr-18) 22 * @since jsjws 1.0 23 * @license <a href="http://kjur.github.io/jsjws/license/">MIT License</a> 24 */ 25 26 if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; 27 if (typeof KJUR.jws == "undefined" || !KJUR.jws) KJUR.jws = {}; 28 29 /** 30 * JSON Web Signature(JWS) class.<br/> 31 * @name KJUR.jws.JWS 32 * @class JSON Web Signature(JWS) class 33 * @property {Dictionary} parsedJWS This property is set after JWS signature verification. <br/> 34 * Following "parsedJWS_*" properties can be accessed as "parsedJWS.*" because of 35 * JsDoc restriction. 36 * @property {String} parsedJWS_headB64U string of Encrypted JWS Header 37 * @property {String} parsedJWS_payloadB64U string of Encrypted JWS Payload 38 * @property {String} parsedJWS_sigvalB64U string of Encrypted JWS signature value 39 * @property {String} parsedJWS_si string of Signature Input 40 * @property {String} parsedJWS_sigvalH hexadecimal string of JWS signature value 41 * @property {String} parsedJWS_sigvalBI BigInteger(defined in jsbn.js) object of JWS signature value 42 * @property {String} parsedJWS_headS string of decoded JWS Header 43 * @property {String} parsedJWS_headS string of decoded JWS Payload 44 * @requires base64x.js, json-sans-eval.js and jsrsasign library 45 * @see <a href="http://kjur.github.com/jsjws/">'jwjws'(JWS JavaScript Library) home page http://kjur.github.com/jsjws/</a> 46 * @see <a href="http://kjur.github.com/jsrsasigns/">'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/</a> 47 * @see <a href="http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14">IETF I-D JSON Web Algorithms (JWA)</a> 48 * @since jsjws 1.0 49 * @description 50 * <h4>Supported Algorithms</h4> 51 * Here is supported algorithm names for {@link KJUR.jws.JWS.sign} and {@link KJUR.jws.JWS.verify} 52 * methods. 53 * <table> 54 * <tr><th>alg value</th><th>spec requirement</th><th>jsjws support</th></tr> 55 * <tr><td>HS256</td><td>REQUIRED</td><td>SUPPORTED</td></tr> 56 * <tr><td>HS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 57 * <tr><td>HS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 58 * <tr><td>RS256</td><td>RECOMMENDED</td><td>SUPPORTED</td></tr> 59 * <tr><td>RS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 60 * <tr><td>RS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 61 * <tr><td>ES256</td><td>RECOMMENDED+</td><td>SUPPORTED</td></tr> 62 * <tr><td>ES384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 63 * <tr><td>ES512</td><td>OPTIONAL</td><td>-</td></tr> 64 * <tr><td>PS256</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 65 * <tr><td>PS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 66 * <tr><td>PS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 67 * <tr><td>none</td><td>REQUIRED</td><td>SUPPORTED(signature generation only)</td></tr> 68 * </table> 69 * NOTE1: HS384 is supported since jsjws 3.0.2 with jsrsasign 4.1.4.<br/> 70 */ 71 KJUR.jws.JWS = function() { 72 73 // === utility ============================================================= 74 75 /** 76 * parse JWS string and set public property 'parsedJWS' dictionary.<br/> 77 * @name parseJWS 78 * @memberOf KJUR.jws.JWS 79 * @function 80 * @param {String} sJWS JWS signature string to be parsed. 81 * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". 82 * @throws if JWS Header is a malformed JSON string. 83 * @since jws 1.1 84 */ 85 this.parseJWS = function(sJWS, sigValNotNeeded) { 86 if ((this.parsedJWS !== undefined) && 87 (sigValNotNeeded || (this.parsedJWS.sigvalH !== undefined))) { 88 return; 89 } 90 if (sJWS.match(/^([^.]+)\.([^.]+)\.([^.]+)$/) == null) { 91 throw "JWS signature is not a form of 'Head.Payload.SigValue'."; 92 } 93 var b6Head = RegExp.$1; 94 var b6Payload = RegExp.$2; 95 var b6SigVal = RegExp.$3; 96 var sSI = b6Head + "." + b6Payload; 97 this.parsedJWS = {}; 98 this.parsedJWS.headB64U = b6Head; 99 this.parsedJWS.payloadB64U = b6Payload; 100 this.parsedJWS.sigvalB64U = b6SigVal; 101 this.parsedJWS.si = sSI; 102 103 if (!sigValNotNeeded) { 104 var hSigVal = b64utohex(b6SigVal); 105 var biSigVal = parseBigInt(hSigVal, 16); 106 this.parsedJWS.sigvalH = hSigVal; 107 this.parsedJWS.sigvalBI = biSigVal; 108 } 109 110 var sHead = b64utoutf8(b6Head); 111 var sPayload = b64utoutf8(b6Payload); 112 this.parsedJWS.headS = sHead; 113 this.parsedJWS.payloadS = sPayload; 114 115 if (! this.isSafeJSONString(sHead, this.parsedJWS, 'headP')) 116 throw "malformed JSON string for JWS Head: " + sHead; 117 }; 118 119 // ==== JWS Validation ========================================================= 120 function _getSignatureInputByString(sHead, sPayload) { 121 return utf8tob64u(sHead) + "." + utf8tob64u(sPayload); 122 }; 123 124 function _getHashBySignatureInput(sSignatureInput, sHashAlg) { 125 var hashfunc = function(s) { return KJUR.crypto.Util.hashString(s, sHashAlg); }; 126 if (hashfunc == null) throw "hash function not defined in jsrsasign: " + sHashAlg; 127 return hashfunc(sSignatureInput); 128 }; 129 130 function _jws_verifySignature(sHead, sPayload, hSig, hN, hE) { 131 var sSignatureInput = _getSignatureInputByString(sHead, sPayload); 132 var biSig = parseBigInt(hSig, 16); 133 return _rsasign_verifySignatureWithArgs(sSignatureInput, biSig, hN, hE); 134 }; 135 136 /** 137 * verify JWS signature with naked RSA public key.<br/> 138 * This only supports "RS256" and "RS512" algorithm. 139 * @name verifyJWSByNE 140 * @memberOf KJUR.jws.JWS 141 * @function 142 * @param {String} sJWS JWS signature string to be verified 143 * @param {String} hN hexadecimal string for modulus of RSA public key 144 * @param {String} hE hexadecimal string for public exponent of RSA public key 145 * @return {String} returns 1 when JWS signature is valid, otherwise returns 0 146 * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". 147 * @throws if JWS Header is a malformed JSON string. 148 * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} 149 */ 150 this.verifyJWSByNE = function(sJWS, hN, hE) { 151 this.parseJWS(sJWS); 152 return _rsasign_verifySignatureWithArgs(this.parsedJWS.si, this.parsedJWS.sigvalBI, hN, hE); 153 }; 154 155 /** 156 * verify JWS signature with RSA public key.<br/> 157 * This only supports "RS256", "RS512", "PS256" and "PS512" algorithms. 158 * @name verifyJWSByKey 159 * @memberOf KJUR.jws.JWS 160 * @function 161 * @param {String} sJWS JWS signature string to be verified 162 * @param {RSAKey} key RSA public key 163 * @return {Boolean} returns true when JWS signature is valid, otherwise returns false 164 * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". 165 * @throws if JWS Header is a malformed JSON string. 166 * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} 167 */ 168 this.verifyJWSByKey = function(sJWS, key) { 169 this.parseJWS(sJWS); 170 var hashAlg = _jws_getHashAlgFromParsedHead(this.parsedJWS.headP); 171 var isPSS = this.parsedJWS.headP['alg'].substr(0, 2) == "PS"; 172 173 if (key.hashAndVerify) { 174 return key.hashAndVerify(hashAlg, 175 new Buffer(this.parsedJWS.si, 'utf8').toString('base64'), 176 b64utob64(this.parsedJWS.sigvalB64U), 177 'base64', 178 isPSS); 179 } else if (isPSS) { 180 return key.verifyStringPSS(this.parsedJWS.si, 181 this.parsedJWS.sigvalH, hashAlg); 182 } else { 183 return key.verifyString(this.parsedJWS.si, 184 this.parsedJWS.sigvalH); 185 } 186 }; 187 188 /** 189 * verify JWS signature by PEM formatted X.509 certificate.<br/> 190 * This only supports "RS256" and "RS512" algorithm. 191 * @name verifyJWSByPemX509Cert 192 * @memberOf KJUR.jws.JWS 193 * @function 194 * @param {String} sJWS JWS signature string to be verified 195 * @param {String} sPemX509Cert string of PEM formatted X.509 certificate 196 * @return {String} returns 1 when JWS signature is valid, otherwise returns 0 197 * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". 198 * @throws if JWS Header is a malformed JSON string. 199 * @since 1.1 200 * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.verify} 201 */ 202 this.verifyJWSByPemX509Cert = function(sJWS, sPemX509Cert) { 203 this.parseJWS(sJWS); 204 var x509 = new X509(); 205 x509.readCertPEM(sPemX509Cert); 206 return x509.subjectPublicKeyRSA.verifyString(this.parsedJWS.si, this.parsedJWS.sigvalH); 207 }; 208 209 // ==== JWS Generation ========================================================= 210 function _jws_getHashAlgFromParsedHead(head) { 211 var sigAlg = head["alg"]; 212 var hashAlg = ""; 213 214 if (sigAlg != "RS256" && sigAlg != "RS512" && 215 sigAlg != "PS256" && sigAlg != "PS512") 216 throw "JWS signature algorithm not supported: " + sigAlg; 217 if (sigAlg.substr(2) == "256") hashAlg = "sha256"; 218 if (sigAlg.substr(2) == "512") hashAlg = "sha512"; 219 return hashAlg; 220 }; 221 222 function _jws_getHashAlgFromHead(sHead) { 223 return _jws_getHashAlgFromParsedHead(jsonParse(sHead)); 224 }; 225 226 function _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD) { 227 var rsa = new RSAKey(); 228 rsa.setPrivate(hN, hE, hD); 229 230 var hashAlg = _jws_getHashAlgFromHead(sHead); 231 var sigValue = rsa.signString(sSI, hashAlg); 232 return sigValue; 233 }; 234 235 function _jws_generateSignatureValueBySI_Key(sHead, sPayload, sSI, key, head) { 236 var hashAlg = null; 237 if (typeof head == "undefined") { 238 hashAlg = _jws_getHashAlgFromHead(sHead); 239 } else { 240 hashAlg = _jws_getHashAlgFromParsedHead(head); 241 } 242 243 var isPSS = head['alg'].substr(0, 2) == "PS"; 244 245 if (key.hashAndSign) { 246 return b64tob64u(key.hashAndSign(hashAlg, sSI, 'binary', 'base64', isPSS)); 247 } else if (isPSS) { 248 return hextob64u(key.signStringPSS(sSI, hashAlg)); 249 } else { 250 return hextob64u(key.signString(sSI, hashAlg)); 251 } 252 }; 253 254 function _jws_generateSignatureValueByNED(sHead, sPayload, hN, hE, hD) { 255 var sSI = _getSignatureInputByString(sHead, sPayload); 256 return _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD); 257 }; 258 259 /** 260 * generate JWS signature by Header, Payload and a naked RSA private key.<br/> 261 * This only supports "RS256" and "RS512" algorithm. 262 * @name generateJWSByNED 263 * @memberOf KJUR.jws.JWS 264 * @function 265 * @param {String} sHead string of JWS Header 266 * @param {String} sPayload string of JWS Payload 267 * @param {String} hN hexadecimal string for modulus of RSA public key 268 * @param {String} hE hexadecimal string for public exponent of RSA public key 269 * @param {String} hD hexadecimal string for private exponent of RSA private key 270 * @return {String} JWS signature string 271 * @throws if sHead is a malformed JSON string. 272 * @throws if supported signature algorithm was not specified in JSON Header. 273 * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} 274 */ 275 this.generateJWSByNED = function(sHead, sPayload, hN, hE, hD) { 276 if (! this.isSafeJSONString(sHead)) throw "JWS Head is not safe JSON string: " + sHead; 277 var sSI = _getSignatureInputByString(sHead, sPayload); 278 var hSigValue = _jws_generateSignatureValueBySI_NED(sHead, sPayload, sSI, hN, hE, hD); 279 var b64SigValue = hextob64u(hSigValue); 280 281 this.parsedJWS = {}; 282 this.parsedJWS.headB64U = sSI.split(".")[0]; 283 this.parsedJWS.payloadB64U = sSI.split(".")[1]; 284 this.parsedJWS.sigvalB64U = b64SigValue; 285 286 return sSI + "." + b64SigValue; 287 }; 288 289 /** 290 * generate JWS signature by Header, Payload and a RSA private key.<br/> 291 * This only supports "RS256", "RS512", "PS256" and "PS512" algorithms. 292 * @name generateJWSByKey 293 * @memberOf KJUR.jws.JWS 294 * @function 295 * @param {String} sHead string of JWS Header 296 * @param {String} sPayload string of JWS Payload 297 * @param {RSAKey} RSA private key 298 * @return {String} JWS signature string 299 * @throws if sHead is a malformed JSON string. 300 * @throws if supported signature algorithm was not specified in JSON Header. 301 * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} 302 */ 303 this.generateJWSByKey = function(sHead, sPayload, key) { 304 var obj = {}; 305 if (!this.isSafeJSONString(sHead, obj, 'headP')) 306 throw "JWS Head is not safe JSON string: " + sHead; 307 var sSI = _getSignatureInputByString(sHead, sPayload); 308 var b64SigValue = _jws_generateSignatureValueBySI_Key(sHead, sPayload, sSI, key, obj.headP); 309 310 this.parsedJWS = {}; 311 this.parsedJWS.headB64U = sSI.split(".")[0]; 312 this.parsedJWS.payloadB64U = sSI.split(".")[1]; 313 this.parsedJWS.sigvalB64U = b64SigValue; 314 315 return sSI + "." + b64SigValue; 316 }; 317 318 // === sign with PKCS#1 RSA private key ===================================================== 319 function _jws_generateSignatureValueBySI_PemPrvKey(sHead, sPayload, sSI, sPemPrvKey) { 320 var rsa = new RSAKey(); 321 rsa.readPrivateKeyFromPEMString(sPemPrvKey); 322 var hashAlg = _jws_getHashAlgFromHead(sHead); 323 var sigValue = rsa.signString(sSI, hashAlg); 324 return sigValue; 325 }; 326 327 /** 328 * generate JWS signature by Header, Payload and a PEM formatted PKCS#1 RSA private key.<br/> 329 * This only supports "RS256" and "RS512" algorithm. 330 * @name generateJWSByP1PrvKey 331 * @memberOf KJUR.jws.JWS 332 * @function 333 * @param {String} sHead string of JWS Header 334 * @param {String} sPayload string of JWS Payload 335 * @param {String} string for sPemPrvKey PEM formatted PKCS#1 RSA private key<br/> 336 * Heading and trailing space characters in PEM key will be ignored. 337 * @return {String} JWS signature string 338 * @throws if sHead is a malformed JSON string. 339 * @throws if supported signature algorithm was not specified in JSON Header. 340 * @since 1.1 341 * @deprecated from 3.0.0 please move to {@link KJUR.jws.JWS.sign} 342 */ 343 this.generateJWSByP1PrvKey = function(sHead, sPayload, sPemPrvKey) { 344 if (! this.isSafeJSONString(sHead)) throw "JWS Head is not safe JSON string: " + sHead; 345 var sSI = _getSignatureInputByString(sHead, sPayload); 346 var hSigValue = _jws_generateSignatureValueBySI_PemPrvKey(sHead, sPayload, sSI, sPemPrvKey); 347 var b64SigValue = hextob64u(hSigValue); 348 349 this.parsedJWS = {}; 350 this.parsedJWS.headB64U = sSI.split(".")[0]; 351 this.parsedJWS.payloadB64U = sSI.split(".")[1]; 352 this.parsedJWS.sigvalB64U = b64SigValue; 353 354 return sSI + "." + b64SigValue; 355 }; 356 }; 357 358 // === major static method ======================================================== 359 360 /** 361 * generate JWS signature by specified key<br/> 362 * @name sign 363 * @memberOf KJUR.jws.JWS 364 * @function 365 * @static 366 * @param {String} alg JWS algorithm name to sign and force set to sHead or null 367 * @param {String} sHead string of JWS Header 368 * @param {String} sPayload string of JWS Payload 369 * @param {String} key string of private key or key object to sign 370 * @param {String} pass (OPTION)passcode to use encrypted private key 371 * @return {String} JWS signature string 372 * @since jws 3.0.0 373 * @see <a href="http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html">jsrsasign KJUR.crypto.Signature method</a> 374 * @see <a href="http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Mac.html">jsrsasign KJUR.crypto.Mac method</a> 375 * @description 376 * This method supports following algorithms. 377 * <table> 378 * <tr><th>alg value</th><th>spec requirement</th><th>jsjws support</th></tr> 379 * <tr><td>HS256</td><td>REQUIRED</td><td>SUPPORTED</td></tr> 380 * <tr><td>HS384</td><td>OPTIONAL</td><td>-</td></tr> 381 * <tr><td>HS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 382 * <tr><td>RS256</td><td>RECOMMENDED</td><td>SUPPORTED</td></tr> 383 * <tr><td>RS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 384 * <tr><td>RS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 385 * <tr><td>ES256</td><td>RECOMMENDED+</td><td>SUPPORTED</td></tr> 386 * <tr><td>ES384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 387 * <tr><td>ES512</td><td>OPTIONAL</td><td>-</td></tr> 388 * <tr><td>PS256</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 389 * <tr><td>PS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 390 * <tr><td>PS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 391 * <tr><td>none</td><td>REQUIRED</td><td>SUPPORTED(signature generation only)</td></tr> 392 * </table> 393 * <dl> 394 * <dt>NOTE1: 395 * <dd>salt length of RSAPSS signature is the same as the hash algorithm length 396 * because of <a href="http://www.ietf.org/mail-archive/web/jose/current/msg02901.html">IETF JOSE ML discussion</a>. 397 * <dt>NOTE2: 398 * <dd>The reason of HS384 unsupport is 399 * <a href="https://code.google.com/p/crypto-js/issues/detail?id=84">CryptoJS HmacSHA384 bug</a>. 400 * </dl> 401 */ 402 KJUR.jws.JWS.sign = function(alg, sHeader, sPayload, key, pass) { 403 var ns1 = KJUR.jws.JWS; 404 405 if (! ns1.isSafeJSONString(sHeader)) 406 throw "JWS Head is not safe JSON string: " + sHead; 407 408 var pHeader = ns1.readSafeJSONString(sHeader); 409 410 // 1. use alg if defined in sHeader 411 if ((alg == '' || alg == null) && 412 pHeader['alg'] !== undefined) { 413 alg = pHeader['alg']; 414 } 415 416 // 2. set alg in sHeader if undefined 417 if ((alg != '' && alg != null) && 418 pHeader['alg'] === undefined) { 419 pHeader['alg'] = alg; 420 sHeader = JSON.stringify(pHeader); 421 } 422 423 // 3. set signature algorithm like SHA1withRSA 424 var sigAlg = null; 425 if (ns1.jwsalg2sigalg[alg] === undefined) { 426 throw "unsupported alg name: " + alg; 427 } else { 428 sigAlg = ns1.jwsalg2sigalg[alg]; 429 } 430 431 var uHeader = utf8tob64u(sHeader); 432 var uPayload = utf8tob64u(sPayload); 433 var uSignatureInput = uHeader + "." + uPayload 434 435 // 4. sign 436 var hSig = ""; 437 if (sigAlg.substr(0, 4) == "Hmac") { 438 if (key === undefined) 439 throw "hexadecimal key shall be specified for HMAC"; 440 var mac = new KJUR.crypto.Mac({'alg': sigAlg, 'pass': hextorstr(key)}); 441 mac.updateString(uSignatureInput); 442 hSig = mac.doFinal(); 443 } else if (sigAlg.indexOf("withECDSA") != -1) { 444 var sig = new KJUR.crypto.Signature({'alg': sigAlg}); 445 sig.init(key, pass); 446 sig.updateString(uSignatureInput); 447 hASN1Sig = sig.sign(); 448 hSig = KJUR.crypto.ECDSA.asn1SigToConcatSig(hASN1Sig); 449 } else if (sigAlg != "none") { 450 var sig = new KJUR.crypto.Signature({'alg': sigAlg}); 451 sig.init(key, pass); 452 sig.updateString(uSignatureInput); 453 hSig = sig.sign(); 454 } 455 456 var uSig = hextob64u(hSig); 457 return uSignatureInput + "." + uSig; 458 }; 459 460 /** 461 * verify JWS signature by specified key or certificate<br/> 462 * @name verify 463 * @memberOf KJUR.jws.JWS 464 * @function 465 * @static 466 * @param {String} sJWS string of JWS signature to verify 467 * @param {Object} key string of public key, certificate or key object to verify 468 * @param {String} acceptAlgs array of algorithm name strings (OPTION) 469 * @return {Boolean} true if the signature is valid otherwise false 470 * @since jws 3.0.0 471 * @see <a href="http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html">jsrsasign KJUR.crypto.Signature method</a> 472 * @see <a href="http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Mac.html">jsrsasign KJUR.crypto.Mac method</a> 473 * @description 474 * <p> 475 * This method verifies a JSON Web Signature Compact Serialization string by the validation 476 * algorithm as described in 477 * <a href="http://self-issued.info/docs/draft-jones-json-web-signature-04.html#anchor5"> 478 * the section 5 of Internet Draft draft-jones-json-web-signature-04.</a> 479 * </p> 480 * <p> 481 * Since 3.2.0 strict key checking has been provided against a JWS algorithm 482 * in a JWS header. 483 * <ul> 484 * <li>In case 'alg' is 'HS*' in the JWS header, 485 * 'key' shall be hexadecimal string for Hmac{256,384,512} shared secret key. 486 * Otherwise it raise an error.</li> 487 * <li>In case 'alg' is 'RS*' or 'PS*' in the JWS header, 488 * 'key' shall be a RSAKey object or a PEM string of 489 * X.509 RSA public key certificate or PKCS#8 RSA public key. 490 * Otherwise it raise an error.</li> 491 * <li>In case 'alg' is 'ES*' in the JWS header, 492 * 'key' shall be a KJUR.crypto.ECDSA object or a PEM string of 493 * X.509 ECC public key certificate or PKCS#8 ECC public key. 494 * Otherwise it raise an error.</li> 495 * <li>In case 'alg' is 'none' in the JWS header, 496 * validation not supported after jsjws 3.1.0.</li> 497 * </ul> 498 * </p> 499 * <p> 500 * NOTE1: The argument 'acceptAlgs' is supported since 3.2.0. 501 * Strongly recommended to provide acceptAlgs to mitigate 502 * signature replacement attacks.<br/> 503 * </p> 504 * @example 505 * // 1) verify a RS256 JWS signature by a certificate string. 506 * var isValid = KJUR.jws.JWS.verify('eyJh...', '-----BEGIN...', ['RS256']); 507 * 508 * // 2) verify a HS256 JWS signature by a certificate string. 509 * var isValid = KJUR.jws.JWS.verify('eyJh...', '6f62ad...', ['HS256']); 510 * 511 * // 3) verify a ES256 JWS signature by a KJUR.crypto.ECDSA key object. 512 * var pubkey = KEYUTIL.getKey('-----BEGIN CERT...'); 513 * var isValid = KJUR.jws.JWS.verify('eyJh...', pubkey); 514 */ 515 KJUR.jws.JWS.verify = function(sJWS, key, acceptAlgs) { 516 var jws = KJUR.jws.JWS; 517 var a = sJWS.split("."); 518 var uHeader = a[0]; 519 var uPayload = a[1]; 520 var uSignatureInput = uHeader + "." + uPayload; 521 var hSig = b64utohex(a[2]); 522 523 // 1. parse JWS header 524 var pHeader = jws.readSafeJSONString(b64utoutf8(a[0])); 525 var alg = null; 526 var algType = null; // HS|RS|PS|ES|no 527 if (pHeader.alg === undefined) { 528 throw "algorithm not specified in header"; 529 } else { 530 alg = pHeader.alg; 531 algType = alg.substr(0, 2); 532 } 533 534 // 2. check whether alg is acceptable algorithms 535 if (acceptAlgs != null && 536 Object.prototype.toString.call(acceptAlgs) === '[object Array]' && 537 acceptAlgs.length > 0) { 538 var acceptAlgStr = ":" + acceptAlgs.join(":") + ":"; 539 if (acceptAlgStr.indexOf(":" + alg + ":") == -1) { 540 throw "algorithm '" + alg + "' not accepted in the list"; 541 } 542 } 543 544 // 3. check whether key is a proper key for alg. 545 if (alg != "none" && key === null) { 546 throw "key shall be specified to verify."; 547 } 548 549 // 3.1. check whether key is hexstr if alg is HS*. 550 if (algType == "HS") { 551 if (typeof key != "string" && 552 key.length != 0 && 553 key.length % 2 != 0 && 554 ! key.match(/^[0-9A-Fa-f]+/)) { 555 throw "key shall be a hexadecimal str for HS* algs"; 556 } 557 } 558 559 // 3.2. convert key object if key is a public key or cert PEM string 560 if (typeof key == "string" && 561 key.indexOf("-----BEGIN ") != -1) { 562 key = KEYUTIL.getKey(key); 563 } 564 565 // 3.3. check whether key is RSAKey obj if alg is RS* or PS*. 566 if (algType == "RS" || algType == "PS") { 567 if (!(key instanceof RSAKey)) { 568 throw "key shall be a RSAKey obj for RS* and PS* algs"; 569 } 570 } 571 572 // 3.4. check whether key is ECDSA obj if alg is ES*. 573 if (algType == "ES") { 574 if (!(key instanceof KJUR.crypto.ECDSA)) { 575 throw "key shall be a ECDSA obj for ES* algs"; 576 } 577 } 578 579 // 3.5. check when alg is 'none' 580 if (alg == "none") { 581 } 582 583 // 4. check whether alg is supported alg in jsjws. 584 var sigAlg = null; 585 if (jws.jwsalg2sigalg[pHeader.alg] === undefined) { 586 throw "unsupported alg name: " + alg; 587 } else { 588 sigAlg = jws.jwsalg2sigalg[alg]; 589 } 590 591 // 5. verify 592 if (sigAlg == "none") { 593 throw "not supported"; 594 } else if (sigAlg.substr(0, 4) == "Hmac") { 595 if (key === undefined) 596 throw "hexadecimal key shall be specified for HMAC"; 597 var mac = new KJUR.crypto.Mac({'alg': sigAlg, 'pass': hextorstr(key)}); 598 mac.updateString(uSignatureInput); 599 hSig2 = mac.doFinal(); 600 return hSig == hSig2; 601 } else if (sigAlg.indexOf("withECDSA") != -1) { 602 var hASN1Sig = null; 603 try { 604 hASN1Sig = KJUR.crypto.ECDSA.concatSigToASN1Sig(hSig); 605 } catch (ex) { 606 return false; 607 } 608 var sig = new KJUR.crypto.Signature({'alg': sigAlg}); 609 sig.init(key) 610 sig.updateString(uSignatureInput); 611 return sig.verify(hASN1Sig); 612 } else { 613 var sig = new KJUR.crypto.Signature({'alg': sigAlg}); 614 sig.init(key) 615 sig.updateString(uSignatureInput); 616 return sig.verify(hSig); 617 } 618 }; 619 620 /* 621 * @since jws 3.0.0 622 */ 623 KJUR.jws.JWS.jwsalg2sigalg = { 624 "HS256": "HmacSHA256", 625 "HS384": "HmacSHA384", 626 "HS512": "HmacSHA512", 627 "RS256": "SHA256withRSA", 628 "RS384": "SHA384withRSA", 629 "RS512": "SHA512withRSA", 630 "ES256": "SHA256withECDSA", 631 "ES384": "SHA384withECDSA", 632 //"ES512": "SHA512withECDSA", // unsupported because of jsrsasign's bug 633 "PS256": "SHA256withRSAandMGF1", 634 "PS384": "SHA384withRSAandMGF1", 635 "PS512": "SHA512withRSAandMGF1", 636 "none": "none", 637 }; 638 639 // === utility static method ====================================================== 640 641 /** 642 * check whether a String "s" is a safe JSON string or not.<br/> 643 * If a String "s" is a malformed JSON string or an other object type 644 * this returns 0, otherwise this returns 1. 645 * @name isSafeJSONString 646 * @memberOf KJUR.jws.JWS 647 * @function 648 * @static 649 * @param {String} s JSON string 650 * @return {Number} 1 or 0 651 */ 652 KJUR.jws.JWS.isSafeJSONString = function(s, h, p) { 653 var o = null; 654 try { 655 o = jsonParse(s); 656 if (typeof o != "object") return 0; 657 if (o.constructor === Array) return 0; 658 if (h) h[p] = o; 659 return 1; 660 } catch (ex) { 661 return 0; 662 } 663 }; 664 665 /** 666 * read a String "s" as JSON object if it is safe.<br/> 667 * If a String "s" is a malformed JSON string or not JSON string, 668 * this returns null, otherwise returns JSON object. 669 * @name readSafeJSONString 670 * @memberOf KJUR.jws.JWS 671 * @function 672 * @static 673 * @param {String} s JSON string 674 * @return {Object} JSON object or null 675 * @since 1.1.1 676 */ 677 KJUR.jws.JWS.readSafeJSONString = function(s) { 678 var o = null; 679 try { 680 o = jsonParse(s); 681 if (typeof o != "object") return null; 682 if (o.constructor === Array) return null; 683 return o; 684 } catch (ex) { 685 return null; 686 } 687 }; 688 689 /** 690 * get Encoed Signature Value from JWS string.<br/> 691 * @name getEncodedSignatureValueFromJWS 692 * @memberOf KJUR.jws.JWS 693 * @function 694 * @static 695 * @param {String} sJWS JWS signature string to be verified 696 * @return {String} string of Encoded Signature Value 697 * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". 698 */ 699 KJUR.jws.JWS.getEncodedSignatureValueFromJWS = function(sJWS) { 700 if (sJWS.match(/^[^.]+\.[^.]+\.([^.]+)$/) == null) { 701 throw "JWS signature is not a form of 'Head.Payload.SigValue'."; 702 } 703 return RegExp.$1; 704 }; 705 706 /** 707 * IntDate class for time representation for JSON Web Token(JWT) 708 * @class KJUR.jws.IntDate class 709 * @name KJUR.jws.IntDate 710 * @since jws 3.0.1 711 * @description 712 * Utility class for IntDate which is integer representation of UNIX origin time 713 * used in JSON Web Token(JWT). 714 */ 715 KJUR.jws.IntDate = function() { 716 }; 717 718 /** 719 * @name get 720 * @memberOf KJUR.jws.IntDate 721 * @function 722 * @static 723 * @param {String} s string of time representation 724 * @return {Integer} UNIX origin time in seconds for argument 's' 725 * @since jws 3.0.1 726 * @throws "unsupported format: s" when malformed format 727 * @description 728 * This method will accept following representation of time. 729 * <ul> 730 * <li>now - current time</li> 731 * <li>now + 1hour - after 1 hour from now</li> 732 * <li>now + 1day - after 1 day from now</li> 733 * <li>now + 1month - after 30 days from now</li> 734 * <li>now + 1year - after 365 days from now</li> 735 * <li>YYYYmmDDHHMMSSZ - UTC time (ex. 20130828235959Z)</li> 736 * <li>number - UNIX origin time (seconds from 1970-01-01 00:00:00) (ex. 1377714748)</li> 737 * </ul> 738 */ 739 KJUR.jws.IntDate.get = function(s) { 740 if (s == "now") { 741 return KJUR.jws.IntDate.getNow(); 742 } else if (s == "now + 1hour") { 743 return KJUR.jws.IntDate.getNow() + 60 * 60; 744 } else if (s == "now + 1day") { 745 return KJUR.jws.IntDate.getNow() + 60 * 60 * 24; 746 } else if (s == "now + 1month") { 747 return KJUR.jws.IntDate.getNow() + 60 * 60 * 24 * 30; 748 } else if (s == "now + 1year") { 749 return KJUR.jws.IntDate.getNow() + 60 * 60 * 24 * 365; 750 } else if (s.match(/Z$/)) { 751 return KJUR.jws.IntDate.getZulu(s); 752 } else if (s.match(/^[0-9]+$/)) { 753 return parseInt(s); 754 } 755 throw "unsupported format: " + s; 756 }; 757 758 KJUR.jws.IntDate.getZulu = function(s) { 759 if (a = s.match(/(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)Z/)) { 760 var year = parseInt(RegExp.$1); 761 var month = parseInt(RegExp.$2) - 1; 762 var day = parseInt(RegExp.$3); 763 var hour = parseInt(RegExp.$4); 764 var min = parseInt(RegExp.$5); 765 var sec = parseInt(RegExp.$6); 766 var d = new Date(Date.UTC(year, month, day, hour, min, sec)); 767 return ~~(d / 1000); 768 } 769 throw "unsupported format: " + s; 770 }; 771 772 /* 773 * @since jws 3.0.1 774 */ 775 KJUR.jws.IntDate.getNow = function() { 776 var d = ~~(new Date() / 1000); 777 return d; 778 }; 779 780 /* 781 * @since jws 3.0.1 782 */ 783 KJUR.jws.IntDate.intDate2UTCString = function(intDate) { 784 var d = new Date(intDate * 1000); 785 return d.toUTCString(); 786 }; 787 788 /* 789 * @since jws 3.0.1 790 */ 791 KJUR.jws.IntDate.intDate2Zulu = function(intDate) { 792 var d = new Date(intDate * 1000); 793 var year = ("0000" + d.getUTCFullYear()).slice(-4); 794 var mon = ("00" + (d.getUTCMonth() + 1)).slice(-2); 795 var day = ("00" + d.getUTCDate()).slice(-2); 796 var hour = ("00" + d.getUTCHours()).slice(-2); 797 var min = ("00" + d.getUTCMinutes()).slice(-2); 798 var sec = ("00" + d.getUTCSeconds()).slice(-2); 799 return year + mon + day + hour + min + sec + "Z"; 800 }; 801