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