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