1 /*! jwsjs-2.1.0 (c) 2010-2016 Kenji Urushima | kjur.github.com/jsrsasign/license
  2  */
  3 /*
  4  * jwsjs.js - JSON Web Signature JSON Serialization (JWSJS) Class
  5  *
  6  * version: 2.1.0 (2016 Sep 6)
  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 jwsjs-2.0.js
 20  * @author Kenji Urushima kenji.urushima@gmail.com
 21  * @version 2.1.0 (2016 Sep 6)
 22  * @since jsjws 1.2, 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 if (typeof KJUR.jws == "undefined" || !KJUR.jws) KJUR.jws = {};
 28 
 29 /**
 30  * JSON Web Signature JSON Serialization (JWSJS) class.<br/>
 31  * @class JSON Web Signature JSON Serialization (JWSJS) class
 32  * @name KJUR.jws.JWSJS
 33  * @property {array of String} aHeader array of Encoded JWS Headers
 34  * @property {String} sPayload Encoded JWS payload
 35  * @property {array of String} aSignature array of Encoded JWS signature value
 36  * @author Kenji Urushima
 37  * @version 2.1.0 (2016 Sep 6)
 38  * @see <a href="http://kjur.github.com/jsjws/">old jwjws home page http://kjur.github.com/jsjws/</a>
 39  * @see <a href="http://kjur.github.com/jsrsasigns/">'jwrsasign'(RSA Sign JavaScript Library) home page http://kjur.github.com/jsrsasign/</a>
 40  * @see <a href="http://tools.ietf.org/html/draft-jones-json-web-signature-json-serialization-01">IETF I-D JSON Web Signature JSON Serialization (JWS-JS) specification</a>
 41  *
 42  * @description
 43  * This class generates and verfies "JSON Web Signature JSON Serialization (JWSJS)" of
 44  * <a href="http://tools.ietf.org/html/draft-jones-json-web-signature-json-serialization-01">
 45  * IETF Internet Draft</a>. Here is major methods of this class:
 46  * <ul>
 47  * <li>{@link KJUR.jws.JWSJS#readJWSJS} - initialize with string or JSON object of JWSJS.</li>
 48  * <li>{@link KJUR.jws.JWSJS#initWithJWS} - initialize with JWS as first signature.</li>
 49  * <li>{@link KJUR.jws.JWSJS#addSignature} - append signature to JWSJS object.</li>
 50  * <li>{@link KJUR.jws.JWSJS#verifyAll} - verify all signatures in JWSJS object.</li>
 51  * <li>{@link KJUR.jws.JWSJS#getJSON} - get result of JWSJS object as JSON object.</li>
 52  * </ul>
 53  *
 54  * @example
 55  * // initialize
 56  * jwsjs1 = new KJUR.jws.JWSJS();
 57  * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
 58  * 
 59  * // add PS256 signature with RSA private key object
 60  * prvKeyObj = KEYUTIL.getKey("-----BEGIN PRIVATE KEY...");
 61  * jwsjs1.addSignature("PS256", {alg: "PS256"}, prvKeyObj);
 62  * // add HS256 signature with HMAC password "secret"
 63  * jwsjs1.addSignature(null, {alg: "HS256"}, {utf8: "secret"});
 64  * 
 65  * // get result finally
 66  * jwsjsObj1 = jwsjs1.getJSON();
 67  *
 68  * // verify all signatures
 69  * isValid = jwsjs1.verifyAll([["-----BEGIN CERT...", ["RS256"]],
 70  *                             [{utf8: "secret"}, ["HS256"]]]); 
 71  * 
 72  */
 73 KJUR.jws.JWSJS = function() {
 74     var ns1 = KJUR.jws.JWS;
 75     var nJWS = KJUR.jws.JWS;
 76 
 77     this.aHeader = [];
 78     this.sPayload = "";
 79     this.aSignature = [];
 80 
 81     // == initialize ===================================================================
 82     /**
 83      * (re-)initialize this object.<br/>
 84      * @name init
 85      * @memberOf KJUR.jws.JWSJS#
 86      * @function
 87      */
 88     this.init = function() {
 89 	this.aHeader = [];
 90 	this.sPayload = undefined;
 91 	this.aSignature = [];
 92     };
 93 
 94     /**
 95      * (re-)initialize and set first signature with JWS.<br/>
 96      * @name initWithJWS
 97      * @memberOf KJUR.jws.JWSJS#
 98      * @param {String} sJWS JWS signature to set
 99      * @function
100      * @example
101      * jwsjs1 = new KJUR.jws.JWSJWS();
102      * jwsjs1.initWithJWS("eyJ...");
103      */
104     this.initWithJWS = function(sJWS) {
105 	this.init();
106 
107 	var a = sJWS.split(".");
108 	if (a.length != 3)
109 	    throw "malformed input JWS";
110 
111 	this.aHeader.push(a[0]);
112 	this.sPayload = a[1];
113 	this.aSignature.push(a[2]);
114     };
115 
116     // == add signature ===================================================================
117     /**
118      * add a signature to existing JWS-JS by algorithm, header and key.<br/>
119      * @name addSignature
120      * @memberOf KJUR.jws.JWSJS#
121      * @function
122      * @param {String} alg JWS algorithm. If null, alg in header will be used.
123      * @param {Object} spHead string or object of JWS Header to add.
124      * @param {Object} key JWS key to sign. key object, PEM private key or HMAC key
125      * @param {String} pass optional password for encrypted PEM private key
126      * @throw if signature append failed.
127      * @example
128      * // initialize
129      * jwsjs1 = new KJUR.jws.JWSJS();
130      * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
131      *
132      * // add PS256 signature with RSA private key object
133      * prvKeyObj = KEYUTIL.getKey("-----BEGIN PRIVATE KEY...");
134      * jwsjs1.addSignature("PS256", {alg: "PS256"}, prvKeyObj);
135      *
136      * // add HS256 signature with HMAC password "secret"
137      * jwsjs1.addSignature(null, {alg: "HS256"}, {utf8: "secret"});
138      *
139      * // get result finally
140      * jwsjsObj1 = jwsjs1.getJSON();
141      */
142     this.addSignature = function(alg, spHead, key, pass) {
143 	if (this.sPayload === undefined || this.sPayload === null)
144 	    throw "there's no JSON-JS signature to add.";
145 
146 	var sigLen = this.aHeader.length;
147 	if (this.aHeader.length != this.aSignature.length)
148 	    throw "aHeader.length != aSignature.length";
149 
150 	try {
151 	    var sJWS = KJUR.jws.JWS.sign(alg, spHead, this.sPayload, key, pass);
152 	    var a = sJWS.split(".");
153 	    var sHeader2 = a[0];
154 	    var sSignature2 = a[2];
155 	    this.aHeader.push(a[0]);
156 	    this.aSignature.push(a[2]);
157 	} catch(ex) {
158 	    if (this.aHeader.length > sigLen) this.aHeader.pop();
159 	    if (this.aSignature.length > sigLen) this.aSignature.pop();
160 	    throw "addSignature failed: " + ex;
161 	}
162     };
163 
164     /**
165      * (DEPRECATED) add a signature to existing JWS-JS by Header and PKCS1 private key.<br/>
166      * @name addSignatureByHeaderKey
167      * @memberOf KJUR.jws.JWSJS#
168      * @function
169      * @param {String} sHead JSON string of JWS Header for adding signature.
170      * @param {String} sPemPrvKey string of PKCS1 private key
171      * @deprecated from jwsjs 2.1.0 jsrsasign 5.1.0
172      */
173     this.addSignatureByHeaderKey = function(sHead, sPemPrvKey) {
174 	var sPayload = b64utoutf8(this.sPayload);
175 
176 	var jws = new KJUR.jws.JWS();
177 	var sJWS = jws.generateJWSByP1PrvKey(sHead, sPayload, sPemPrvKey);
178   
179 	this.aHeader.push(jws.parsedJWS.headB64U);
180 	this.aSignature.push(jws.parsedJWS.sigvalB64U);
181     };
182 
183     /**
184      * (DEPRECATED) add a signature to existing JWS-JS by Header, Payload and PKCS1 private key.<br/>
185      * This is to add first signature to JWS-JS object.
186      * @name addSignatureByHeaderPayloadKey
187      * @memberOf KJUR.jws.JWSJS#
188      * @function
189      * @param {String} sHead JSON string of JWS Header for adding signature.
190      * @param {String} sPayload string of JWS Payload for adding signature.
191      * @param {String} sPemPrvKey string of PKCS1 private key
192      * @deprecated from jwsjs 2.1.0 jsrsasign 5.1.0
193      */
194     this.addSignatureByHeaderPayloadKey = function(sHead, sPayload, sPemPrvKey) {
195 	var jws = new KJUR.jws.JWS();
196 	var sJWS = jws.generateJWSByP1PrvKey(sHead, sPayload, sPemPrvKey);
197   
198 	this.aHeader.push(jws.parsedJWS.headB64U);
199 	this.sPayload = jws.parsedJWS.payloadB64U;
200 	this.aSignature.push(jws.parsedJWS.sigvalB64U);
201     };
202 
203     // == verify signature ===================================================================
204     /**
205      * verify all signature of JWS-JS object by array of key and acceptAlgs.<br/>
206      * @name verifyAll
207      * @memberOf KJUR.jws.JWSJS#
208      * @function
209      * @param {array of key and acceptAlgs} aKeyAlg a array of key and acceptAlgs
210      * @return true if all signatures are valid otherwise false
211      * @since jwsjs 2.1.0 jsrsasign 5.1.0
212      * @example
213      * jwsjs1 = new KJUR.jws.JWSJS();
214      * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
215      * isValid = jwsjs1.verifyAll([["-----BEGIN CERT...", ["RS256"]],
216      *                             [{utf8: "secret"}, ["HS256"]]]); 
217      */
218     this.verifyAll = function(aKeyAlg) {
219 	if (this.aHeader.length !== aKeyAlg.length ||
220 	    this.aSignature.length !== aKeyAlg.length)
221 	    return false;
222 
223 	for (var i = 0; i < aKeyAlg.length; i++) {
224 	    var keyAlg = aKeyAlg[i];
225 	    if (keyAlg.length  !== 2) return false;
226 	    var result = this.verifyNth(i, keyAlg[0], keyAlg[1]);
227 	    if (result === false) return false;
228 	}
229 	return true;
230     };
231 
232     /**
233      * verify Nth signature of JWS-JS object by key and acceptAlgs.<br/>
234      * @name verifyNth
235      * @memberOf KJUR.jws.JWSJS#
236      * @function
237      * @param {Integer} idx nth index of JWS-JS signature to verify
238      * @param {Object} key key to verify
239      * @param {array of String} acceptAlgs array of acceptable signature algorithms
240      * @return true if signature is valid otherwise false
241      * @since jwsjs 2.1.0 jsrsasign 5.1.0
242      * @example
243      * jwsjs1 = new KJUR.jws.JWSJS();
244      * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
245      * isValid1 = jwsjs1.verifyNth(0, "-----BEGIN CERT...", ["RS256"]);
246      * isValid2 = jwsjs1.verifyNth(1, {utf8: "secret"}, ["HS256"]);
247      */
248     this.verifyNth = function(idx, key, acceptAlgs) {
249 	if (this.aHeader.length <= idx || this.aSignature.length <= idx)
250 	    return false;
251 	var sHeader = this.aHeader[idx];
252 	var sSignature = this.aSignature[idx];
253 	var sJWS = sHeader + "." + this.sPayload + "." + sSignature;
254 	var result = false;
255 	try {
256 	    result = nJWS.verify(sJWS, key, acceptAlgs);
257 	} catch (ex) {
258 	    return false;
259 	}
260 	return result;
261     };
262 
263     /**
264      * (DEPRECATED) verify JWS-JS object with array of certificate string.<br/>
265      * @name verifyWithCerts
266      * @memberOf KJUR.jws.JWSJS#
267      * @function
268      * @param {array of String} aCert array of string for X.509 PEM certificate.
269      * @return 1 if signature is valid.
270      * @throw if JWS-JS signature is invalid.
271      * @deprecated from jwsjs 2.1.0 jsrsasign 5.1.0
272      */
273     this.verifyWithCerts = function(aCert) {
274 	if (this.aHeader.length != aCert.length) 
275 	    throw "num headers does not match with num certs";
276 	if (this.aSignature.length != aCert.length) 
277 	    throw "num signatures does not match with num certs";
278 
279 	var payload = this.sPayload;
280 	var errMsg = "";
281 	for (var i = 0; i < aCert.length; i++) {
282 	    var cert = aCert[i];
283 	    var header = this.aHeader[i];
284 	    var sig = this.aSignature[i];
285 	    var sJWS = header + "." + payload + "." + sig;
286 
287 	    var jws = new KJUR.jws.JWS();
288 	    try {
289 		var result = jws.verifyJWSByPemX509Cert(sJWS, cert);
290 		if (result != 1) {
291 		    errMsg += (i + 1) + "th signature unmatch. ";
292 		}
293 	    } catch (ex) {
294 		errMsg += (i + 1) + "th signature fail(" + ex + "). ";
295 	    }
296 	}
297 
298 	if (errMsg == "") {
299 	    return 1;
300 	} else {
301 	    throw errMsg;
302 	}
303     };
304 
305     /**
306      * read JWS-JS string or object<br/>
307      * @name readJWSJS
308      * @memberOf KJUR.jws.JWSJS#
309      * @function
310      * @param {Object} spJWSJS string or JSON object of JWS-JS to load.
311      * @throw if sJWSJS is malformed or not JSON string.
312      * @description
313      * NOTE: Loading from JSON object is suppored from 
314      * jsjws 2.1.0 jsrsasign 5.1.0 (2016-Sep-06).
315      * @example
316      * // load JWSJS from string
317      * jwsjs1 = new KJUR.jws.JWSJS();
318      * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
319      *
320      * // load JWSJS from JSON object
321      * jwsjs1 = new KJUR.jws.JWSJS();
322      * jwsjs1.readJWSJS({headers: [...], payload: "eyJ...", signatures: [...]});
323      */
324     this.readJWSJS = function(spJWSJS) {
325 	if (typeof spJWSJS === "string") {
326 	    var oJWSJS = ns1.readSafeJSONString(spJWSJS);
327 	    if (oJWSJS == null) throw "argument is not safe JSON object string";
328 
329 	    this.aHeader = oJWSJS.headers;
330 	    this.sPayload = oJWSJS.payload;
331 	    this.aSignature = oJWSJS.signatures;
332 	} else {
333 	    try {
334 		if (spJWSJS.headers.length > 0) {
335 		    this.aHeader = spJWSJS.headers;
336 		} else {
337 		    throw "malformed header";
338 		}
339 		if (typeof spJWSJS.payload === "string") {
340 		    this.sPayload = spJWSJS.payload;
341 		} else {
342 		    throw "malformed signatures";
343 		}
344 		if (spJWSJS.signatures.length > 0) {
345 		    this.signatures = spJWSJS.signatures;
346 		} else {
347 		    throw "malformed signatures";
348 		}
349 	    } catch (ex) {
350 		throw "malformed JWS-JS JSON object: " + ex;
351 	    }
352 	}
353     };
354 
355     // == utility ===================================================================
356     /**
357      * get JSON object for this JWS-JS object.<br/>
358      * @name getJSON
359      * @memberOf KJUR.jws.JWSJS#
360      * @function
361      * @example
362      * jwsj1 = new KJUR.jws.JWSJS();
363      * // do some jwsj1 operation then get result by getJSON()
364      * jwsjsObj1 = jwsjs1.getJSON();
365      * // jwsjsObj1 → { headers: [...], payload: "ey...", signatures: [...] }
366      */
367     this.getJSON = function() {
368 	return { "headers": this.aHeader,
369 		 "payload": this.sPayload,
370 		 "signatures": this.aSignature }; 
371     };
372 
373     /**
374      * check if this JWS-JS object is empty.<br/>
375      * @name isEmpty
376      * @memberOf KJUR.jws.JWSJS#
377      * @function
378      * @return 1 if there is no signatures in this object, otherwise 0.
379      */
380     this.isEmpty = function() {
381 	if (this.aHeader.length == 0) return 1; 
382 	return 0;
383     };
384 };
385 
386