1 /* jwsjs-2.2.1 (c) 2010-2018 Kenji Urushima | kjur.github.io/jsrsasign/license 2 */ 3 /* 4 * jwsjs.js - JSON Web Signature JSON Serialization (JWSJS) Class 5 * 6 * Copyright (c) 2010-2018 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.1 (2018-Mar-24) 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.aSignature = 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