1 /* jwsjs-2.2.0 (c) 2010-2017 Kenji Urushima | kjur.github.com/jsrsasign/license
  2  */
  3 /*
  4  * jwsjs.js - JSON Web Signature JSON Serialization (JWSJS) Class
  5  *
  6  * Copyright (c) 2010-2017 Kenji Urushima (kenji.urushima@gmail.com)
  7  *
  8  * This software is licensed under the terms of the MIT License.
  9  * https://kjur.github.io/jsrsasign/license/
 10  *
 11  * The above copyright and license notice shall be 
 12  * included in all copies or substantial portions of the Software.
 13  */
 14 
 15 /**
 16  * @fileOverview
 17  * @name jwsjs-2.0.js
 18  * @author Kenji Urushima kenji.urushima@gmail.com
 19  * @version jsrsasign 8.0.0 jwsjs 2.2.0 (2017-Jun-25)
 20  * @since jsjws 1.2, jsrsasign 4.8.0
 21  * @license <a href="https://kjur.github.io/jsrsasign/license/">MIT License</a>
 22  */
 23 
 24 if (typeof KJUR == "undefined" || !KJUR) KJUR = {};
 25 if (typeof KJUR.jws == "undefined" || !KJUR.jws) KJUR.jws = {};
 26 
 27 /**
 28  * JSON Web Signature JSON Serialization (JWSJS) class.<br/>
 29  * @class JSON Web Signature JSON Serialization (JWSJS) class
 30  * @name KJUR.jws.JWSJS
 31  * @property {array of String} aHeader array of Encoded JWS Headers
 32  * @property {String} sPayload Encoded JWS payload
 33  * @property {array of String} aSignature array of Encoded JWS signature value
 34  * @author Kenji Urushima
 35  * @version 2.1.0 (2016 Sep 6)
 36  * @see <a href="https://kjur.github.io/jsjws/">old jwjws home page https://kjur.github.io/jsjws/</a>
 37  * @see <a href="https://kjur.github.io/jsrsasigns/">'jwrsasign'(RSA Sign JavaScript Library) home page https://kjur.github.io/jsrsasign/</a>
 38  * @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>
 39  *
 40  * @description
 41  * This class generates and verfies "JSON Web Signature JSON Serialization (JWSJS)" of
 42  * <a href="http://tools.ietf.org/html/draft-jones-json-web-signature-json-serialization-01">
 43  * IETF Internet Draft</a>. Here is major methods of this class:
 44  * <ul>
 45  * <li>{@link KJUR.jws.JWSJS#readJWSJS} - initialize with string or JSON object of JWSJS.</li>
 46  * <li>{@link KJUR.jws.JWSJS#initWithJWS} - initialize with JWS as first signature.</li>
 47  * <li>{@link KJUR.jws.JWSJS#addSignature} - append signature to JWSJS object.</li>
 48  * <li>{@link KJUR.jws.JWSJS#verifyAll} - verify all signatures in JWSJS object.</li>
 49  * <li>{@link KJUR.jws.JWSJS#getJSON} - get result of JWSJS object as JSON object.</li>
 50  * </ul>
 51  *
 52  * @example
 53  * // initialize
 54  * jwsjs1 = new KJUR.jws.JWSJS();
 55  * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
 56  * 
 57  * // add PS256 signature with RSA private key object
 58  * prvKeyObj = KEYUTIL.getKey("-----BEGIN PRIVATE KEY...");
 59  * jwsjs1.addSignature("PS256", {alg: "PS256"}, prvKeyObj);
 60  * // add HS256 signature with HMAC password "secret"
 61  * jwsjs1.addSignature(null, {alg: "HS256"}, {utf8: "secret"});
 62  * 
 63  * // get result finally
 64  * jwsjsObj1 = jwsjs1.getJSON();
 65  *
 66  * // verify all signatures
 67  * isValid = jwsjs1.verifyAll([["-----BEGIN CERT...", ["RS256"]],
 68  *                             [{utf8: "secret"}, ["HS256"]]]); 
 69  * 
 70  */
 71 KJUR.jws.JWSJS = function() {
 72     var _KJUR = KJUR,
 73 	_KJUR_jws = _KJUR.jws,
 74 	_KJUR_jws_JWS = _KJUR_jws.JWS,
 75 	_readSafeJSONString = _KJUR_jws_JWS.readSafeJSONString;
 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     // == verify signature ====================================================
165     /**
166      * verify all signature of JWS-JS object by array of key and acceptAlgs.<br/>
167      * @name verifyAll
168      * @memberOf KJUR.jws.JWSJS#
169      * @function
170      * @param {array of key and acceptAlgs} aKeyAlg a array of key and acceptAlgs
171      * @return true if all signatures are valid otherwise false
172      * @since jwsjs 2.1.0 jsrsasign 5.1.0
173      * @example
174      * jwsjs1 = new KJUR.jws.JWSJS();
175      * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
176      * isValid = jwsjs1.verifyAll([["-----BEGIN CERT...", ["RS256"]],
177      *                             [{utf8: "secret"}, ["HS256"]]]); 
178      */
179     this.verifyAll = function(aKeyAlg) {
180 	if (this.aHeader.length !== aKeyAlg.length ||
181 	    this.aSignature.length !== aKeyAlg.length)
182 	    return false;
183 
184 	for (var i = 0; i < aKeyAlg.length; i++) {
185 	    var keyAlg = aKeyAlg[i];
186 	    if (keyAlg.length  !== 2) return false;
187 	    var result = this.verifyNth(i, keyAlg[0], keyAlg[1]);
188 	    if (result === false) return false;
189 	}
190 	return true;
191     };
192 
193     /**
194      * verify Nth signature of JWS-JS object by key and acceptAlgs.<br/>
195      * @name verifyNth
196      * @memberOf KJUR.jws.JWSJS#
197      * @function
198      * @param {Integer} idx nth index of JWS-JS signature to verify
199      * @param {Object} key key to verify
200      * @param {array of String} acceptAlgs array of acceptable signature algorithms
201      * @return true if signature is valid otherwise false
202      * @since jwsjs 2.1.0 jsrsasign 5.1.0
203      * @example
204      * jwsjs1 = new KJUR.jws.JWSJS();
205      * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
206      * isValid1 = jwsjs1.verifyNth(0, "-----BEGIN CERT...", ["RS256"]);
207      * isValid2 = jwsjs1.verifyNth(1, {utf8: "secret"}, ["HS256"]);
208      */
209     this.verifyNth = function(idx, key, acceptAlgs) {
210 	if (this.aHeader.length <= idx || this.aSignature.length <= idx)
211 	    return false;
212 	var sHeader = this.aHeader[idx];
213 	var sSignature = this.aSignature[idx];
214 	var sJWS = sHeader + "." + this.sPayload + "." + sSignature;
215 	var result = false;
216 	try {
217 	    result = _KJUR_jws_JWS.verify(sJWS, key, acceptAlgs);
218 	} catch (ex) {
219 	    return false;
220 	}
221 	return result;
222     };
223 
224     /**
225      * read JWS-JS string or object<br/>
226      * @name readJWSJS
227      * @memberOf KJUR.jws.JWSJS#
228      * @function
229      * @param {Object} spJWSJS string or JSON object of JWS-JS to load.
230      * @throw if sJWSJS is malformed or not JSON string.
231      * @description
232      * NOTE: Loading from JSON object is suppored from 
233      * jsjws 2.1.0 jsrsasign 5.1.0 (2016-Sep-06).
234      * @example
235      * // load JWSJS from string
236      * jwsjs1 = new KJUR.jws.JWSJS();
237      * jwsjs1.readJWSJS("{headers: [...], payload: "eyJ...", signatures: [...]}");
238      *
239      * // load JWSJS from JSON object
240      * jwsjs1 = new KJUR.jws.JWSJS();
241      * jwsjs1.readJWSJS({headers: [...], payload: "eyJ...", signatures: [...]});
242      */
243     this.readJWSJS = function(spJWSJS) {
244 	if (typeof spJWSJS === "string") {
245 	    var oJWSJS = _readSafeJSONString(spJWSJS);
246 	    if (oJWSJS == null) throw "argument is not safe JSON object string";
247 
248 	    this.aHeader = oJWSJS.headers;
249 	    this.sPayload = oJWSJS.payload;
250 	    this.aSignature = oJWSJS.signatures;
251 	} else {
252 	    try {
253 		if (spJWSJS.headers.length > 0) {
254 		    this.aHeader = spJWSJS.headers;
255 		} else {
256 		    throw "malformed header";
257 		}
258 		if (typeof spJWSJS.payload === "string") {
259 		    this.sPayload = spJWSJS.payload;
260 		} else {
261 		    throw "malformed signatures";
262 		}
263 		if (spJWSJS.signatures.length > 0) {
264 		    this.signatures = spJWSJS.signatures;
265 		} else {
266 		    throw "malformed signatures";
267 		}
268 	    } catch (ex) {
269 		throw "malformed JWS-JS JSON object: " + ex;
270 	    }
271 	}
272     };
273 
274     // == utility =============================================================
275     /**
276      * get JSON object for this JWS-JS object.<br/>
277      * @name getJSON
278      * @memberOf KJUR.jws.JWSJS#
279      * @function
280      * @example
281      * jwsj1 = new KJUR.jws.JWSJS();
282      * // do some jwsj1 operation then get result by getJSON()
283      * jwsjsObj1 = jwsjs1.getJSON();
284      * // jwsjsObj1 → { headers: [...], payload: "ey...", signatures: [...] }
285      */
286     this.getJSON = function() {
287 	return { "headers": this.aHeader,
288 		 "payload": this.sPayload,
289 		 "signatures": this.aSignature }; 
290     };
291 
292     /**
293      * check if this JWS-JS object is empty.<br/>
294      * @name isEmpty
295      * @memberOf KJUR.jws.JWSJS#
296      * @function
297      * @return 1 if there is no signatures in this object, otherwise 0.
298      */
299     this.isEmpty = function() {
300 	if (this.aHeader.length == 0) return 1; 
301 	return 0;
302     };
303 };
304 
305