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