1 /*! jws-3.3.4 (c) 2013-2016 Kenji Urushima | kjur.github.com/jsrsasign/license 2 */ 3 /* 4 * jws.js - JSON Web Signature(JWS) and JSON Web Token(JWT) Class 5 * 6 * version: 3.3.4 (2016 May 17) 7 * 8 * Copyright (c) 2010-2016 Kenji Urushima (kenji.urushima@gmail.com) 9 * 10 * This software is licensed under the terms of the MIT License. 11 * http://kjur.github.com/jsrsasign/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.3.js 20 * @author Kenji Urushima kenji.urushima@gmail.com 21 * @version 3.3.4 (2016-May-17) 22 * @since jsjws 1.0, jsrsasign 4.8.0 23 * @license <a href="http://kjur.github.io/jsrsasign/license/">MIT License</a> 24 */ 25 26 if (typeof KJUR == "undefined" || !KJUR) KJUR = {}; 27 28 /** 29 * kjur's JSON Web Signature/Token(JWS/JWT) library name space 30 * <p> 31 * This namespace privides following JWS/JWS related classes. 32 * <ul> 33 * <li>{@link KJUR.jws.JWS} - JSON Web Signature/Token(JWS/JWT) class</li> 34 * <li>{@link KJUR.jws.JWSJS} - JWS JSON Serialization(JWSJS) class</li> 35 * <li>{@link KJUR.jws.IntDate} - UNIX origin time utility class</li> 36 * </ul> 37 * NOTE: Please ignore method summary and document of this namespace. This caused by a bug of jsdoc2. 38 * </p> 39 * @name KJUR.jws 40 * @namespace 41 */ 42 if (typeof KJUR.jws == "undefined" || !KJUR.jws) KJUR.jws = {}; 43 44 /** 45 * JSON Web Signature(JWS) class.<br/> 46 * @name KJUR.jws.JWS 47 * @class JSON Web Signature(JWS) class 48 * @see <a href="http://kjur.github.com/jsjws/">'jwjws'(JWS JavaScript Library) home page http://kjur.github.com/jsjws/</a> 49 * @see <a href="http://kjur.github.com/jsrsasigns/">'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/</a> 50 * @see <a href="http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-14">IETF I-D JSON Web Algorithms (JWA)</a> 51 * @since jsjws 1.0 52 * @description 53 * This class provides JSON Web Signature(JWS)/JSON Web Token(JWT) signing and validation. 54 * <h4>Supported Algorithms</h4> 55 * Here is supported algorithm names for {@link KJUR.jws.JWS.sign} and {@link KJUR.jws.JWS.verify} 56 * methods. 57 * <table> 58 * <tr><th>alg value</th><th>spec requirement</th><th>jsjws support</th></tr> 59 * <tr><td>HS256</td><td>REQUIRED</td><td>SUPPORTED</td></tr> 60 * <tr><td>HS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 61 * <tr><td>HS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 62 * <tr><td>RS256</td><td>RECOMMENDED</td><td>SUPPORTED</td></tr> 63 * <tr><td>RS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 64 * <tr><td>RS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 65 * <tr><td>ES256</td><td>RECOMMENDED+</td><td>SUPPORTED</td></tr> 66 * <tr><td>ES384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 67 * <tr><td>ES512</td><td>OPTIONAL</td><td>-</td></tr> 68 * <tr><td>PS256</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 69 * <tr><td>PS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 70 * <tr><td>PS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 71 * <tr><td>none</td><td>REQUIRED</td><td>SUPPORTED(signature generation only)</td></tr> 72 * </table> 73 * <dl> 74 * <dt><b>NOTE1</b> 75 * <dd>HS384 is supported since jsjws 3.0.2 with jsrsasign 4.1.4. 76 * <dt><b>NOTE2</b> 77 * <dd>Some deprecated methods have been removed since jws 3.3 of jsrsasign 4.10.0. 78 * Removed methods are following: 79 * <ul> 80 * <li>JWS.verifyJWSByNE</li> 81 * <li>JWS.verifyJWSByKey</li> 82 * <li>JWS.generateJWSByNED</li> 83 * <li>JWS.generateJWSByKey</li> 84 * <li>JWS.generateJWSByP1PrvKey</li> 85 * </ul> 86 * </dl> 87 * <b>EXAMPLE</b><br/> 88 * @example 89 * // JWS signing 90 * sJWS = KJUR.jws.JWS.sign(null, '{"alg":"HS256", "cty":"JWT"}', '{"age": 21}', "password"); 91 * // JWS validation 92 * isValid = KJUR.jws.JWS.verify('eyJjdHkiOiJKV1QiLCJhbGc...', "password"); 93 * // JWT validation 94 * isValid = KJUR.jws.JWS.verifyJWT('eyJh...', "password", { 95 * alg: ['HS256', 'HS384'], 96 * iss: ['http://foo.com'] 97 * }); 98 */ 99 KJUR.jws.JWS = function() { 100 var ns1 = KJUR.jws.JWS; 101 102 // === utility ============================================================= 103 104 /** 105 * parse JWS string and set public property 'parsedJWS' dictionary.<br/> 106 * @name parseJWS 107 * @memberOf KJUR.jws.JWS 108 * @function 109 * @param {String} sJWS JWS signature string to be parsed. 110 * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". 111 * @throws if JWS Header is a malformed JSON string. 112 * @since jws 1.1 113 */ 114 this.parseJWS = function(sJWS, sigValNotNeeded) { 115 if ((this.parsedJWS !== undefined) && 116 (sigValNotNeeded || (this.parsedJWS.sigvalH !== undefined))) { 117 return; 118 } 119 if (sJWS.match(/^([^.]+)\.([^.]+)\.([^.]+)$/) == null) { 120 throw "JWS signature is not a form of 'Head.Payload.SigValue'."; 121 } 122 var b6Head = RegExp.$1; 123 var b6Payload = RegExp.$2; 124 var b6SigVal = RegExp.$3; 125 var sSI = b6Head + "." + b6Payload; 126 this.parsedJWS = {}; 127 this.parsedJWS.headB64U = b6Head; 128 this.parsedJWS.payloadB64U = b6Payload; 129 this.parsedJWS.sigvalB64U = b6SigVal; 130 this.parsedJWS.si = sSI; 131 132 if (!sigValNotNeeded) { 133 var hSigVal = b64utohex(b6SigVal); 134 var biSigVal = parseBigInt(hSigVal, 16); 135 this.parsedJWS.sigvalH = hSigVal; 136 this.parsedJWS.sigvalBI = biSigVal; 137 } 138 139 var sHead = b64utoutf8(b6Head); 140 var sPayload = b64utoutf8(b6Payload); 141 this.parsedJWS.headS = sHead; 142 this.parsedJWS.payloadS = sPayload; 143 144 if (! ns1.isSafeJSONString(sHead, this.parsedJWS, 'headP')) 145 throw "malformed JSON string for JWS Head: " + sHead; 146 }; 147 }; 148 149 // === major static method ======================================================== 150 151 /** 152 * generate JWS signature by specified key<br/> 153 * @name sign 154 * @memberOf KJUR.jws.JWS 155 * @function 156 * @static 157 * @param {String} alg JWS algorithm name to sign and force set to sHead or null 158 * @param {String} spHead string or object of JWS Header 159 * @param {String} spPayload string or object of JWS Payload 160 * @param {String} key string of private key or mac key object to sign 161 * @param {String} pass (OPTION)passcode to use encrypted asymmetric private key 162 * @return {String} JWS signature string 163 * @since jws 3.0.0 164 * @see <a href="http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html">jsrsasign KJUR.crypto.Signature method</a> 165 * @see <a href="http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Mac.html">jsrsasign KJUR.crypto.Mac method</a> 166 * @description 167 * This method supports following algorithms. 168 * <table> 169 * <tr><th>alg value</th><th>spec requirement</th><th>jsjws support</th></tr> 170 * <tr><td>HS256</td><td>REQUIRED</td><td>SUPPORTED</td></tr> 171 * <tr><td>HS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 172 * <tr><td>HS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 173 * <tr><td>RS256</td><td>RECOMMENDED</td><td>SUPPORTED</td></tr> 174 * <tr><td>RS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 175 * <tr><td>RS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 176 * <tr><td>ES256</td><td>RECOMMENDED+</td><td>SUPPORTED</td></tr> 177 * <tr><td>ES384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 178 * <tr><td>ES512</td><td>OPTIONAL</td><td>-</td></tr> 179 * <tr><td>PS256</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 180 * <tr><td>PS384</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 181 * <tr><td>PS512</td><td>OPTIONAL</td><td>SUPPORTED</td></tr> 182 * <tr><td>none</td><td>REQUIRED</td><td>SUPPORTED(signature generation only)</td></tr> 183 * </table> 184 * <dl> 185 * <dt>NOTE1: 186 * <dd>salt length of RSAPSS signature is the same as the hash algorithm length 187 * because of <a href="http://www.ietf.org/mail-archive/web/jose/current/msg02901.html">IETF JOSE ML discussion</a>. 188 * <dt>NOTE2: 189 * <dd>To support HS384, patched version of CryptoJS is used. 190 * <a href="https://code.google.com/p/crypto-js/issues/detail?id=84">See here for detail</a>. 191 * <dt>NOTE3: 192 * From jsrsasign 4.10.0 jws 3.3.0, Way to provide password 193 * for HS* algorithm is changed. The 'key' attribute value is 194 * passed to {@link KJUR.crypto.Mac.setPassword} so please see 195 * {@link KJUR.crypto.Mac.setPassword} for detail. 196 * As for backword compatibility, if key is a string, has even length and 197 * 0..9, A-F or a-f characters, key string is treated as a hexadecimal 198 * otherwise it is treated as a raw string. 199 * <dd> 200 * </dl> 201 * <b>EXAMPLE</b><br/> 202 * @example 203 * // sign HS256 signature with password "aaa" implicitly handled as string 204 * sJWS = KJUR.jws.JWS.sign(null, {alg: "HS256", cty: "JWT"}, {age: 21}, "aaa"); 205 * // sign HS256 signature with password "6161" implicitly handled as hex 206 * sJWS = KJUR.jws.JWS.sign(null, {alg: "HS256", cty: "JWT"}, {age: 21}, "6161"); 207 * // sign HS256 signature with base64 password 208 * sJWS = KJUR.jws.JWS.sign(null, {alg: "HS256"}, {age: 21}, {b64: "Mi/8..a="}); 209 * // sign RS256 signature with PKCS#8 PEM RSA private key 210 * sJWS = KJUR.jws.JWS.sign(null, {alg: "RS256"}, {age: 21}, "-----BEGIN PRIVATE KEY..."); 211 * // sign RS256 signature with PKCS#8 PEM ECC private key with passcode 212 * sJWS = KJUR.jws.JWS.sign(null, {alg: "ES256"}, {age: 21}, 213 * "-----BEGIN PRIVATE KEY...", "keypass"); 214 * // header and payload can be passed by both string and object 215 * sJWS = KJUR.jws.JWS.sign(null, '{alg:"HS256",cty:"JWT"}', '{age:21}', "aaa"); 216 */ 217 KJUR.jws.JWS.sign = function(alg, spHeader, spPayload, key, pass) { 218 var ns1 = KJUR.jws.JWS; 219 var sHeader, pHeader, sPayload; 220 221 // 1. check signatureInput(Header, Payload) is string or object 222 if (typeof spHeader != 'string' && typeof spHeader != 'object') 223 throw "spHeader must be JSON string or object: " + spHeader; 224 225 if (typeof spHeader == 'object') { 226 pHeader = spHeader; 227 sHeader = JSON.stringify(pHeader); 228 } 229 230 if (typeof spHeader == 'string') { 231 sHeader = spHeader; 232 if (! ns1.isSafeJSONString(sHeader)) 233 throw "JWS Head is not safe JSON string: " + sHeader; 234 pHeader = ns1.readSafeJSONString(sHeader); 235 236 } 237 238 sPayload = spPayload; 239 if (typeof spPayload == 'object') sPayload = JSON.stringify(spPayload); 240 241 // 2. use alg if defined in sHeader 242 if ((alg == '' || alg == null) && 243 pHeader['alg'] !== undefined) { 244 alg = pHeader['alg']; 245 } 246 247 // 3. update sHeader to add alg if alg undefined 248 if ((alg != '' && alg != null) && 249 pHeader['alg'] === undefined) { 250 pHeader['alg'] = alg; 251 sHeader = JSON.stringify(pHeader); 252 } 253 254 // 4. check explicit algorithm doesn't match with JWS header. 255 if (alg !== pHeader.alg) 256 throw "alg and sHeader.alg doesn't match: " + alg + "!=" + pHeader.alg; 257 258 // 5. set signature algorithm like SHA1withRSA 259 var sigAlg = null; 260 if (ns1.jwsalg2sigalg[alg] === undefined) { 261 throw "unsupported alg name: " + alg; 262 } else { 263 sigAlg = ns1.jwsalg2sigalg[alg]; 264 } 265 266 var uHeader = utf8tob64u(sHeader); 267 var uPayload = utf8tob64u(sPayload); 268 var uSignatureInput = uHeader + "." + uPayload 269 // 6. sign 270 var hSig = ""; 271 if (sigAlg.substr(0, 4) == "Hmac") { 272 if (key === undefined) 273 throw "mac key shall be specified for HS* alg"; 274 //alert("sigAlg=" + sigAlg); 275 var mac = new KJUR.crypto.Mac({'alg': sigAlg, 'prov': 'cryptojs', 'pass': key}); 276 mac.updateString(uSignatureInput); 277 hSig = mac.doFinal(); 278 } else if (sigAlg.indexOf("withECDSA") != -1) { 279 var sig = new KJUR.crypto.Signature({'alg': sigAlg}); 280 sig.init(key, pass); 281 sig.updateString(uSignatureInput); 282 hASN1Sig = sig.sign(); 283 hSig = KJUR.crypto.ECDSA.asn1SigToConcatSig(hASN1Sig); 284 } else if (sigAlg != "none") { 285 var sig = new KJUR.crypto.Signature({'alg': sigAlg}); 286 sig.init(key, pass); 287 sig.updateString(uSignatureInput); 288 hSig = sig.sign(); 289 } 290 291 var uSig = hextob64u(hSig); 292 return uSignatureInput + "." + uSig; 293 }; 294 295 /** 296 * verify JWS signature by specified key or certificate<br/> 297 * @name verify 298 * @memberOf KJUR.jws.JWS 299 * @function 300 * @static 301 * @param {String} sJWS string of JWS signature to verify 302 * @param {Object} key string of public key, certificate or key object to verify 303 * @param {String} acceptAlgs array of algorithm name strings (OPTION) 304 * @return {Boolean} true if the signature is valid otherwise false 305 * @since jws 3.0.0 306 * @see <a href="http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Signature.html">jsrsasign KJUR.crypto.Signature method</a> 307 * @see <a href="http://kjur.github.io/jsrsasign/api/symbols/KJUR.crypto.Mac.html">jsrsasign KJUR.crypto.Mac method</a> 308 * @description 309 * <p> 310 * This method verifies a JSON Web Signature Compact Serialization string by the validation 311 * algorithm as described in 312 * <a href="http://self-issued.info/docs/draft-jones-json-web-signature-04.html#anchor5"> 313 * the section 5 of Internet Draft draft-jones-json-web-signature-04.</a> 314 * </p> 315 * <p> 316 * Since 3.2.0 strict key checking has been provided against a JWS algorithm 317 * in a JWS header. 318 * <ul> 319 * <li>In case 'alg' is 'HS*' in the JWS header, 320 * 'key' shall be hexadecimal string for Hmac{256,384,512} shared secret key. 321 * Otherwise it raise an error.</li> 322 * <li>In case 'alg' is 'RS*' or 'PS*' in the JWS header, 323 * 'key' shall be a RSAKey object or a PEM string of 324 * X.509 RSA public key certificate or PKCS#8 RSA public key. 325 * Otherwise it raise an error.</li> 326 * <li>In case 'alg' is 'ES*' in the JWS header, 327 * 'key' shall be a KJUR.crypto.ECDSA object or a PEM string of 328 * X.509 ECC public key certificate or PKCS#8 ECC public key. 329 * Otherwise it raise an error.</li> 330 * <li>In case 'alg' is 'none' in the JWS header, 331 * validation not supported after jsjws 3.1.0.</li> 332 * </ul> 333 * </p> 334 * <p> 335 * NOTE1: The argument 'acceptAlgs' is supported since 3.2.0. 336 * Strongly recommended to provide acceptAlgs to mitigate 337 * signature replacement attacks.<br/> 338 * </p> 339 * <p> 340 * NOTE2: From jsrsasign 4.9.0 jws 3.2.5, Way to provide password 341 * for HS* algorithm is changed. The 'key' attribute value is 342 * passed to {@link KJUR.crypto.Mac.setPassword} so please see 343 * {@link KJUR.crypto.Mac.setPassword} for detail. 344 * As for backword compatibility, if key is a string, has even length and 345 * 0..9, A-F or a-f characters, key string is treated as a hexadecimal 346 * otherwise it is treated as a raw string. 347 * </p> 348 * @example 349 * // 1) verify a RS256 JWS signature by a certificate string. 350 * isValid = KJUR.jws.JWS.verify('eyJh...', '-----BEGIN...', ['RS256']); 351 * 352 * // 2) verify a HS256 JWS signature by a certificate string. 353 * isValid = KJUR.jws.JWS.verify('eyJh...', {hex: '6f62ad...'}, ['HS256']); 354 * isValid = KJUR.jws.JWS.verify('eyJh...', {b64: 'Mi/ab8...a=='}, ['HS256']); 355 * isValid = KJUR.jws.JWS.verify('eyJh...', {utf8: 'Secret秘密'}, ['HS256']); 356 * isValid = KJUR.jws.JWS.verify('eyJh...', '6f62ad', ['HS256']); // implicit hex 357 * isValid = KJUR.jws.JWS.verify('eyJh...', '6f62ada', ['HS256']); // implicit raw string 358 * 359 * // 3) verify a ES256 JWS signature by a KJUR.crypto.ECDSA key object. 360 * var pubkey = KEYUTIL.getKey('-----BEGIN CERT...'); 361 * var isValid = KJUR.jws.JWS.verify('eyJh...', pubkey); 362 */ 363 KJUR.jws.JWS.verify = function(sJWS, key, acceptAlgs) { 364 var jws = KJUR.jws.JWS; 365 var a = sJWS.split("."); 366 var uHeader = a[0]; 367 var uPayload = a[1]; 368 var uSignatureInput = uHeader + "." + uPayload; 369 var hSig = b64utohex(a[2]); 370 371 // 1. parse JWS header 372 var pHeader = jws.readSafeJSONString(b64utoutf8(a[0])); 373 var alg = null; 374 var algType = null; // HS|RS|PS|ES|no 375 if (pHeader.alg === undefined) { 376 throw "algorithm not specified in header"; 377 } else { 378 alg = pHeader.alg; 379 algType = alg.substr(0, 2); 380 } 381 382 // 2. check whether alg is acceptable algorithms 383 if (acceptAlgs != null && 384 Object.prototype.toString.call(acceptAlgs) === '[object Array]' && 385 acceptAlgs.length > 0) { 386 var acceptAlgStr = ":" + acceptAlgs.join(":") + ":"; 387 if (acceptAlgStr.indexOf(":" + alg + ":") == -1) { 388 throw "algorithm '" + alg + "' not accepted in the list"; 389 } 390 } 391 392 // 3. check whether key is a proper key for alg. 393 if (alg != "none" && key === null) { 394 throw "key shall be specified to verify."; 395 } 396 397 // 3.1. There is no key check for HS* because Mac will check it. 398 // since jsrsasign 5.0.0. 399 400 // 3.2. convert key object if key is a public key or cert PEM string 401 if (typeof key == "string" && 402 key.indexOf("-----BEGIN ") != -1) { 403 key = KEYUTIL.getKey(key); 404 } 405 406 // 3.3. check whether key is RSAKey obj if alg is RS* or PS*. 407 if (algType == "RS" || algType == "PS") { 408 if (!(key instanceof RSAKey)) { 409 throw "key shall be a RSAKey obj for RS* and PS* algs"; 410 } 411 } 412 413 // 3.4. check whether key is ECDSA obj if alg is ES*. 414 if (algType == "ES") { 415 if (!(key instanceof KJUR.crypto.ECDSA)) { 416 throw "key shall be a ECDSA obj for ES* algs"; 417 } 418 } 419 420 // 3.5. check when alg is 'none' 421 if (alg == "none") { 422 } 423 424 // 4. check whether alg is supported alg in jsjws. 425 var sigAlg = null; 426 if (jws.jwsalg2sigalg[pHeader.alg] === undefined) { 427 throw "unsupported alg name: " + alg; 428 } else { 429 sigAlg = jws.jwsalg2sigalg[alg]; 430 } 431 432 // 5. verify 433 if (sigAlg == "none") { 434 throw "not supported"; 435 } else if (sigAlg.substr(0, 4) == "Hmac") { 436 var hSig2 = null; 437 if (key === undefined) 438 throw "hexadecimal key shall be specified for HMAC"; 439 //try { 440 var mac = new KJUR.crypto.Mac({'alg': sigAlg, 'pass': key}); 441 mac.updateString(uSignatureInput); 442 hSig2 = mac.doFinal(); 443 //} catch(ex) {}; 444 return hSig == hSig2; 445 } else if (sigAlg.indexOf("withECDSA") != -1) { 446 var hASN1Sig = null; 447 try { 448 hASN1Sig = KJUR.crypto.ECDSA.concatSigToASN1Sig(hSig); 449 } catch (ex) { 450 return false; 451 } 452 var sig = new KJUR.crypto.Signature({'alg': sigAlg}); 453 sig.init(key) 454 sig.updateString(uSignatureInput); 455 return sig.verify(hASN1Sig); 456 } else { 457 var sig = new KJUR.crypto.Signature({'alg': sigAlg}); 458 sig.init(key) 459 sig.updateString(uSignatureInput); 460 return sig.verify(hSig); 461 } 462 }; 463 464 /** 465 * parse header and payload of JWS signature<br/> 466 * @name parse 467 * @memberOf KJUR.jws.JWS 468 * @function 469 * @static 470 * @param {String} sJWS string of JWS signature to parse 471 * @return {Array} associative array of parsed header and payload. See below. 472 * @throws if sJWS is malformed JWS signature 473 * @since jws 3.3.3 474 * @description 475 * This method parses JWS signature string. 476 * Resulted associative array has following properties: 477 * <ul> 478 * <li>headerObj - JSON object of header</li> 479 * <li>payloadObj - JSON object of payload if payload is JSON string otherwise undefined</li> 480 * <li>headerPP - pretty printed JSON header by stringify</li> 481 * <li>payloadPP - pretty printed JSON payload by stringify if payload is JSON otherwise Base64URL decoded raw string of payload</li> 482 * <li>sigHex - hexadecimal string of signature</li> 483 * </ul> 484 * @example 485 * KJUR.jws.JWS.parse(sJWS) -> 486 * { 487 * headerObj: {"alg": "RS256", "typ": "JWS"}, 488 * payloadObj: {"product": "orange", "quantity": 100}, 489 * headerPP: 490 * '{ 491 * "alg": "RS256", 492 * "typ": "JWS" 493 * }', 494 * payloadPP: 495 * '{ 496 * "product": "orange", 497 * "quantity": 100 498 * }', 499 * sigHex: "91f3cd..." 500 * } 501 */ 502 KJUR.jws.JWS.parse = function(sJWS) { 503 var a = sJWS.split("."); 504 var result = {}; 505 var uHeader, uPayload, uSig; 506 if (a.length != 2 && a.length != 3) 507 throw "malformed sJWS: wrong number of '.' splitted elements"; 508 509 uHeader = a[0]; 510 uPayload = a[1]; 511 if (a.length == 3) uSig = a[2]; 512 513 result.headerObj = KJUR.jws.JWS.readSafeJSONString(b64utoutf8(uHeader)); 514 result.payloadObj = KJUR.jws.JWS.readSafeJSONString(b64utoutf8(uPayload)); 515 516 result.headerPP = JSON.stringify(result.headerObj, null, " "); 517 if (result.payloadObj == null) { 518 result.payloadPP = b64utoutf8(uPayload); 519 } else { 520 result.payloadPP = JSON.stringify(result.payloadObj, null, " "); 521 } 522 523 if (uSig !== undefined) { 524 result.sigHex = b64utohex(uSig); 525 } 526 527 return result; 528 }; 529 530 /** 531 * @name verifyJWT 532 * @memberOf KJUR.jws.JWS 533 * @function 534 * @static 535 * @param {String} sJWT string of JSON Web Token(JWT) to verify 536 * @param {Object} key string of public key, certificate or key object to verify 537 * @param {Array} acceptField associative array of acceptable fields (OPTION) 538 * @return {Boolean} true if the JWT token is valid otherwise false 539 * @since jws 3.2.3 jsrsasign 4.8.0 540 * 541 * @description 542 * This method verifies a 543 * <a href="https://tools.ietf.org/html/rfc7519">RFC 7519</a> 544 * JSON Web Token(JWT). 545 * It will verify following: 546 * <ul> 547 * <li>Header.alg 548 * <ul> 549 * <li>alg is specified in JWT header.</li> 550 * <li>alg is included in acceptField.alg array. (MANDATORY)</li> 551 * <li>alg is proper for key.</li> 552 * </ul> 553 * </li> 554 * <li>Payload.iss (issuer) - Payload.iss is included in acceptField.iss array if specified. (OPTION)</li> 555 * <li>Payload.sub (subject) - Payload.sub is included in acceptField.sub array if specified. (OPTION)</li> 556 * <li>Payload.aud (audience) - Payload.aud is included in acceptField.aud array or 557 * the same as value if specified. (OPTION)</li> 558 * <li>Time validity 559 * <ul> 560 * <li> 561 * If acceptField.verifyAt as number of UNIX origin time is specifed for validation time, 562 * this method will verify at the time for it, otherwise current time will be used to verify. 563 * </li> 564 * <li> 565 * Clock of JWT generator or verifier can be fast or slow. If these clocks are 566 * very different, JWT validation may fail. To avoid such case, 'jsrsasign' supports 567 * 'acceptField.gracePeriod' parameter which specifies acceptable time difference 568 * of those clocks in seconds. So if you want to accept slow or fast in 2 hours, 569 * you can specify <code>acceptField.gracePeriod = 2 * 60 * 60;</code>. 570 * "gracePeriod" is zero by default. 571 * "gracePeriod" is supported since jsrsasign 5.0.12. 572 * </li> 573 * <li>Payload.exp (expire) - Validation time is smaller than Payload.exp + gracePeriod.</li> 574 * <li>Payload.nbf (not before) - Validation time is greater than Payload.nbf - gracePeriod.</li> 575 * <li>Payload.iat (issued at) - Validation time is greater than Payload.iat - gracePeriod.</li> 576 * </ul> 577 * </li> 578 * <li>Payload.jti (JWT id) - Payload.jti is included in acceptField.jti if specified. (OPTION)</li> 579 * <li>JWS signature of JWS is valid for specified key.</li> 580 * </ul> 581 * 582 * @example 583 * // simple validation for HS256 584 * isValid = KJUR.jws.JWS.verifyJWT("eyJhbG...", "616161", {alg: ["HS256"]}), 585 * 586 * // full validation for RS or PS 587 * pubkey = KEYUTIL.getKey('-----BEGIN CERT...'); 588 * isValid = KJUR.jws.JWS.verifyJWT('eyJh...', pubkey, { 589 * alg: ['RS256', 'RS512', 'PS256', 'PS512'], 590 * iss: ['http://foo.com'], 591 * sub: ['mailto:john@foo.com', 'mailto:alice@foo.com'], 592 * verifyAt: KJUR.jws.IntDate.get('20150520235959Z'), 593 * aud: ['http://foo.com'], // aud: 'http://foo.com' is fine too. 594 * jti: 'id123456', 595 * gracePeriod: 1 * 60 * 60 // accept 1 hour slow or fast 596 * }); 597 */ 598 KJUR.jws.JWS.verifyJWT = function(sJWT, key, acceptField) { 599 var ns1 = KJUR.jws.JWS; 600 601 // 1. parse JWT 602 var a = sJWT.split("."); 603 var uHeader = a[0]; 604 var uPayload = a[1]; 605 var uSignatureInput = uHeader + "." + uPayload; 606 var hSig = b64utohex(a[2]); 607 608 // 2. parse JWS header 609 var pHeader = ns1.readSafeJSONString(b64utoutf8(uHeader)); 610 611 // 3. parse JWS payload 612 var pPayload = ns1.readSafeJSONString(b64utoutf8(uPayload)); 613 614 // 4. algorithm ('alg' in header) check 615 if (pHeader.alg === undefined) return false; 616 if (acceptField.alg === undefined) 617 throw "acceptField.alg shall be specified"; 618 if (! ns1.inArray(pHeader.alg, acceptField.alg)) return false; 619 620 // 5. issuer ('iss' in payload) check 621 if (pPayload.iss !== undefined && typeof acceptField.iss === "object") { 622 if (! ns1.inArray(pPayload.iss, acceptField.iss)) return false; 623 } 624 625 // 6. subject ('sub' in payload) check 626 if (pPayload.sub !== undefined && typeof acceptField.sub === "object") { 627 if (! ns1.inArray(pPayload.sub, acceptField.sub)) return false; 628 } 629 630 // 7. audience ('aud' in payload) check 631 if (pPayload.aud !== undefined && typeof acceptField.aud === "object") { 632 if (typeof pPayload.aud == "string") { 633 if (! ns1.inArray(pPayload.aud, acceptField.aud)) 634 return false; 635 } else if (typeof pPayload.aud == "object") { 636 if (! ns1.includedArray(pPayload.aud, acceptField.aud)) 637 return false; 638 } 639 } 640 641 // 8. time validity 642 // (nbf - gracePeriod < now < exp + gracePeriod) && (iat - gracePeriod < now) 643 var now = KJUR.jws.IntDate.getNow(); 644 if (acceptField.verifyAt !== undefined && typeof acceptField.verifyAt === "number") { 645 now = acceptField.verifyAt; 646 } 647 if (acceptField.gracePeriod === undefined || 648 typeof acceptField.gracePeriod !== "number") { 649 acceptField.gracePeriod = 0; 650 } 651 652 // 8.1 expired time 'exp' check 653 if (pPayload.exp !== undefined && typeof pPayload.exp == "number") { 654 if (pPayload.exp + acceptField.gracePeriod < now) return false; 655 } 656 657 // 8.2 not before time 'nbf' check 658 if (pPayload.nbf !== undefined && typeof pPayload.nbf == "number") { 659 if (now < pPayload.nbf - acceptField.gracePeriod) return false; 660 } 661 662 // 8.3 issued at time 'iat' check 663 if (pPayload.iat !== undefined && typeof pPayload.iat == "number") { 664 if (now < pPayload.iat - acceptField.gracePeriod) return false; 665 } 666 667 // 9 JWT id 'jti' check 668 if (pPayload.jti !== undefined && acceptField.jti !== undefined) { 669 if (pPayload.jti !== acceptField.jti) return false; 670 } 671 672 // 10 JWS signature check 673 if (! KJUR.jws.JWS.verify(sJWT, key, acceptField.alg)) return false; 674 675 // 11 passed all check 676 return true; 677 }; 678 679 /** 680 * check whether array is included by another array 681 * @name includedArray 682 * @memberOf KJUR.jws.JWS 683 * @function 684 * @static 685 * @param {Array} a1 check whether set a1 is included by a2 686 * @param {Array} a2 check whether set a1 is included by a2 687 * @return {Boolean} check whether set a1 is included by a2 688 * @since jws 3.2.3 689 * This method verifies whether an array is included by another array. 690 * It doesn't care about item ordering in a array. 691 * @example 692 * KJUR.jws.JWS.includedArray(['b'], ['b', 'c', 'a']) => true 693 * KJUR.jws.JWS.includedArray(['a', 'b'], ['b', 'c', 'a']) => true 694 * KJUR.jws.JWS.includedArray(['a', 'b'], ['b', 'c']) => false 695 */ 696 KJUR.jws.JWS.includedArray = function(a1, a2) { 697 var inArray = KJUR.jws.JWS.inArray; 698 if (a1 === null) return false; 699 if (typeof a1 !== "object") return false; 700 if (typeof a1.length !== "number") return false; 701 702 for (var i = 0; i < a1.length; i++) { 703 if (! inArray(a1[i], a2)) return false; 704 } 705 return true; 706 }; 707 708 /** 709 * check whether item is included by array 710 * @name inArray 711 * @memberOf KJUR.jws.JWS 712 * @function 713 * @static 714 * @param {String} item check whether item is included by array 715 * @param {Array} a check whether item is included by array 716 * @return {Boolean} check whether item is included by array 717 * @since jws 3.2.3 718 * This method verifies whether an item is included by an array. 719 * It doesn't care about item ordering in an array. 720 * @example 721 * KJUR.jws.JWS.inArray('b', ['b', 'c', 'a']) => true 722 * KJUR.jws.JWS.inArray('a', ['b', 'c', 'a']) => true 723 * KJUR.jws.JWS.inArray('a', ['b', 'c']) => false 724 */ 725 KJUR.jws.JWS.inArray = function(item, a) { 726 if (a === null) return false; 727 if (typeof a !== "object") return false; 728 if (typeof a.length !== "number") return false; 729 for (var i = 0; i < a.length; i++) { 730 if (a[i] == item) return true; 731 } 732 return false; 733 }; 734 735 /** 736 * static associative array of general signature algorithm name from JWS algorithm name 737 * @since jws 3.0.0 738 */ 739 KJUR.jws.JWS.jwsalg2sigalg = { 740 "HS256": "HmacSHA256", 741 "HS384": "HmacSHA384", 742 "HS512": "HmacSHA512", 743 "RS256": "SHA256withRSA", 744 "RS384": "SHA384withRSA", 745 "RS512": "SHA512withRSA", 746 "ES256": "SHA256withECDSA", 747 "ES384": "SHA384withECDSA", 748 //"ES512": "SHA512withECDSA", // unsupported because of jsrsasign's bug 749 "PS256": "SHA256withRSAandMGF1", 750 "PS384": "SHA384withRSAandMGF1", 751 "PS512": "SHA512withRSAandMGF1", 752 "none": "none", 753 }; 754 755 // === utility static method ================================================== 756 757 /** 758 * check whether a String "s" is a safe JSON string or not.<br/> 759 * If a String "s" is a malformed JSON string or an other object type 760 * this returns 0, otherwise this returns 1. 761 * @name isSafeJSONString 762 * @memberOf KJUR.jws.JWS 763 * @function 764 * @static 765 * @param {String} s JSON string 766 * @return {Number} 1 or 0 767 */ 768 KJUR.jws.JWS.isSafeJSONString = function(s, h, p) { 769 var o = null; 770 try { 771 o = jsonParse(s); 772 if (typeof o != "object") return 0; 773 if (o.constructor === Array) return 0; 774 if (h) h[p] = o; 775 return 1; 776 } catch (ex) { 777 return 0; 778 } 779 }; 780 781 /** 782 * read a String "s" as JSON object if it is safe.<br/> 783 * If a String "s" is a malformed JSON string or not JSON string, 784 * this returns null, otherwise returns JSON object. 785 * @name readSafeJSONString 786 * @memberOf KJUR.jws.JWS 787 * @function 788 * @static 789 * @param {String} s JSON string 790 * @return {Object} JSON object or null 791 * @since 1.1.1 792 */ 793 KJUR.jws.JWS.readSafeJSONString = function(s) { 794 var o = null; 795 try { 796 o = jsonParse(s); 797 if (typeof o != "object") return null; 798 if (o.constructor === Array) return null; 799 return o; 800 } catch (ex) { 801 return null; 802 } 803 }; 804 805 /** 806 * get Encoed Signature Value from JWS string.<br/> 807 * @name getEncodedSignatureValueFromJWS 808 * @memberOf KJUR.jws.JWS 809 * @function 810 * @static 811 * @param {String} sJWS JWS signature string to be verified 812 * @return {String} string of Encoded Signature Value 813 * @throws if sJWS is not comma separated string such like "Header.Payload.Signature". 814 */ 815 KJUR.jws.JWS.getEncodedSignatureValueFromJWS = function(sJWS) { 816 if (sJWS.match(/^[^.]+\.[^.]+\.([^.]+)$/) == null) { 817 throw "JWS signature is not a form of 'Head.Payload.SigValue'."; 818 } 819 return RegExp.$1; 820 }; 821 822 /** 823 * get RFC 7638 JWK thumbprint from JWK object 824 * @name getJWKthumbprint 825 * @memberOf KJUR.jws.JWS 826 * @function 827 * @static 828 * @param {String} o JWK object to be calculated thumbprint 829 * @return {String} Base64 URL encoded JWK thumbprint value 830 * @since jsrsasign 5.0.2 jws 3.3.2 831 * @description 832 * This method calculates JWK thmubprint for specified JWK object 833 * as described in 834 * <a href="https://tools.ietf.org/html/rfc7638">RFC 7638</a>. 835 * It supports all type of "kty". (i.e. "RSA", "EC" and "oct" 836 * (for symmetric key)) 837 * Working sample is 838 * <a href="https://kjur.github.io/jsrsasign/sample/tool_jwktp.html">here</a>. 839 * @example 840 * jwk = {"kty":"RSA", "n":"0vx...", "e":"AQAB", ...}; 841 * thumbprint = KJUR.jws.JWS.getJWKthumbprint(jwk); 842 */ 843 KJUR.jws.JWS.getJWKthumbprint = function(o) { 844 if (o.kty !== "RSA" && 845 o.kty !== "EC" && 846 o.kty !== "oct") 847 throw "unsupported algorithm for JWK Thumprint"; 848 849 // 1. get canonically ordered json string 850 var s = '{'; 851 if (o.kty === "RSA") { 852 if (typeof o.n != "string" || typeof o.e != "string") 853 throw "wrong n and e value for RSA key"; 854 s += '"' + 'e' + '":"' + o.e + '",'; 855 s += '"' + 'kty' + '":"' + o.kty + '",'; 856 s += '"' + 'n' + '":"' + o.n + '"}'; 857 } else if (o.kty === "EC") { 858 if (typeof o.crv != "string" || 859 typeof o.x != "string" || 860 typeof o.y != "string") 861 throw "wrong crv, x and y value for EC key"; 862 s += '"' + 'crv' + '":"' + o.crv + '",'; 863 s += '"' + 'kty' + '":"' + o.kty + '",'; 864 s += '"' + 'x' + '":"' + o.x + '",'; 865 s += '"' + 'y' + '":"' + o.y + '"}'; 866 } else if (o.kty === "oct") { 867 if (typeof o.k != "string") 868 throw "wrong k value for oct(symmetric) key"; 869 s += '"' + 'kty' + '":"' + o.kty + '",'; 870 s += '"' + 'k' + '":"' + o.k + '"}'; 871 } 872 //alert(s); 873 874 // 2. get thumb print 875 var hJWK = rstrtohex(s); 876 var hash = KJUR.crypto.Util.hashHex(hJWK, "sha256"); 877 var hashB64U = hextob64u(hash); 878 879 return hashB64U; 880 }; 881 882 /** 883 * IntDate class for time representation for JSON Web Token(JWT) 884 * @class KJUR.jws.IntDate class 885 * @name KJUR.jws.IntDate 886 * @since jws 3.0.1 887 * @description 888 * Utility class for IntDate which is integer representation of UNIX origin time 889 * used in JSON Web Token(JWT). 890 */ 891 KJUR.jws.IntDate = {}; 892 893 /** 894 * get UNIX origin time from by string 895 * @name get 896 * @memberOf KJUR.jws.IntDate 897 * @function 898 * @static 899 * @param {String} s string of time representation 900 * @return {Integer} UNIX origin time in seconds for argument 's' 901 * @since jws 3.0.1 902 * @throws "unsupported format: s" when malformed format 903 * @description 904 * This method will accept following representation of time. 905 * <ul> 906 * <li>now - current time</li> 907 * <li>now + 1hour - after 1 hour from now</li> 908 * <li>now + 1day - after 1 day from now</li> 909 * <li>now + 1month - after 30 days from now</li> 910 * <li>now + 1year - after 365 days from now</li> 911 * <li>YYYYmmDDHHMMSSZ - UTC time (ex. 20130828235959Z)</li> 912 * <li>number - UNIX origin time (seconds from 1970-01-01 00:00:00) (ex. 1377714748)</li> 913 * </ul> 914 */ 915 KJUR.jws.IntDate.get = function(s) { 916 if (s == "now") { 917 return KJUR.jws.IntDate.getNow(); 918 } else if (s == "now + 1hour") { 919 return KJUR.jws.IntDate.getNow() + 60 * 60; 920 } else if (s == "now + 1day") { 921 return KJUR.jws.IntDate.getNow() + 60 * 60 * 24; 922 } else if (s == "now + 1month") { 923 return KJUR.jws.IntDate.getNow() + 60 * 60 * 24 * 30; 924 } else if (s == "now + 1year") { 925 return KJUR.jws.IntDate.getNow() + 60 * 60 * 24 * 365; 926 } else if (s.match(/Z$/)) { 927 return KJUR.jws.IntDate.getZulu(s); 928 } else if (s.match(/^[0-9]+$/)) { 929 return parseInt(s); 930 } 931 throw "unsupported format: " + s; 932 }; 933 934 /** 935 * get UNIX origin time from Zulu time representation string 936 * @name getZulu 937 * @memberOf KJUR.jws.IntDate 938 * @function 939 * @static 940 * @param {String} s string of Zulu time representation (ex. 20151012125959Z) 941 * @return {Integer} UNIX origin time in seconds for argument 's' 942 * @since jws 3.0.1 943 * @throws "unsupported format: s" when malformed format 944 * @description 945 * This method provides UNIX origin time from Zulu time. 946 * Following representations are supported: 947 * <ul> 948 * <li>YYYYMMDDHHmmSSZ - GeneralizedTime format</li> 949 * <li>YYMMDDHHmmSSZ - UTCTime format. If YY is greater or equal to 950 * 50 then it represents 19YY otherwise 20YY.</li> 951 * </ul> 952 * @example 953 * KJUR.jws.IntDate.getZulu("20151012125959Z") => 1478... 954 * KJUR.jws.IntDate.getZulu("151012125959Z") => 1478... 955 */ 956 KJUR.jws.IntDate.getZulu = function(s) { 957 var a; 958 if (a = s.match(/(\d+)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)Z/)) { 959 var sYear = RegExp.$1; 960 var year = parseInt(sYear); 961 if (sYear.length == 4) { 962 } else if (sYear.length == 2) { 963 if (50 <= year && year < 100) { 964 year = 1900 + year; 965 } else if (0 <= year && year < 50) { 966 year = 2000 + year; 967 } else { 968 throw "malformed year string for UTCTime"; 969 } 970 } else { 971 throw "malformed year string"; 972 } 973 var month = parseInt(RegExp.$2) - 1; 974 var day = parseInt(RegExp.$3); 975 var hour = parseInt(RegExp.$4); 976 var min = parseInt(RegExp.$5); 977 var sec = parseInt(RegExp.$6); 978 var d = new Date(Date.UTC(year, month, day, hour, min, sec)); 979 return ~~(d / 1000); 980 } 981 throw "unsupported format: " + s; 982 }; 983 984 /** 985 * get UNIX origin time of current time 986 * @name getNow 987 * @memberOf KJUR.jws.IntDate 988 * @function 989 * @static 990 * @return {Integer} UNIX origin time for current time 991 * @since jws 3.0.1 992 * @description 993 * This method provides UNIX origin time for current time 994 * @example 995 * KJUR.jws.IntDate.getNow() => 1478... 996 */ 997 KJUR.jws.IntDate.getNow = function() { 998 var d = ~~(new Date() / 1000); 999 return d; 1000 }; 1001 1002 /** 1003 * get UTC time string from UNIX origin time value 1004 * @name intDate2UTCString 1005 * @memberOf KJUR.jws.IntDate 1006 * @function 1007 * @static 1008 * @param {Integer} intDate UNIX origin time value (ex. 1478...) 1009 * @return {String} UTC time string 1010 * @since jws 3.0.1 1011 * @description 1012 * This method provides UTC time string for UNIX origin time value. 1013 * @example 1014 * KJUR.jws.IntDate.intDate2UTCString(1478...) => "2015 Oct ..." 1015 */ 1016 KJUR.jws.IntDate.intDate2UTCString = function(intDate) { 1017 var d = new Date(intDate * 1000); 1018 return d.toUTCString(); 1019 }; 1020 1021 /** 1022 * get UTC time string from UNIX origin time value 1023 * @name intDate2Zulu 1024 * @memberOf KJUR.jws.IntDate 1025 * @function 1026 * @static 1027 * @param {Integer} intDate UNIX origin time value (ex. 1478...) 1028 * @return {String} Zulu time string 1029 * @since jws 3.0.1 1030 * @description 1031 * This method provides Zulu time string for UNIX origin time value. 1032 * @example 1033 * KJUR.jws.IntDate.intDate2UTCString(1478...) => "20151012...Z" 1034 */ 1035 KJUR.jws.IntDate.intDate2Zulu = function(intDate) { 1036 var d = new Date(intDate * 1000); 1037 var year = ("0000" + d.getUTCFullYear()).slice(-4); 1038 var mon = ("00" + (d.getUTCMonth() + 1)).slice(-2); 1039 var day = ("00" + d.getUTCDate()).slice(-2); 1040 var hour = ("00" + d.getUTCHours()).slice(-2); 1041 var min = ("00" + d.getUTCMinutes()).slice(-2); 1042 var sec = ("00" + d.getUTCSeconds()).slice(-2); 1043 return year + mon + day + hour + min + sec + "Z"; 1044 }; 1045 1046