~ubuntu-branches/ubuntu/trusty/enigmail/trusty-updates

« back to all changes in this revision

Viewing changes to services/sync/modules/jpakeclient.js

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2011-06-07 14:35:53 UTC
  • mfrom: (0.12.1 upstream)
  • Revision ID: package-import@ubuntu.com-20110607143553-fbgqhhvh8g8h6j1y
Tags: 2:1.2~a2~cvs20110606t2200-0ubuntu1
* Update to latest trunk snapshot for Thunderbird beta compat

* Remove build/pgo/profileserver.py from debian/clean. The new build
  system has a target depending on this
  - update debian/clean
* Drop debian/patches/autoconf.diff, just generate this at build time
* Refresh debian/patches/build_system_dont_link_libxul.diff
* libipc seems to be renamed to libipc-pipe. Fix genxpi and chrome.manifest
  to fix this 
  - add debian/patches/ipc-pipe_rename.diff
  - update debian/patches/series
* The makefiles in extensions/enigmail/ipc have an incorrect DEPTH
  attribute. Fix this so that they can find the rest of the build system
  - add debian/patches/makefile_depth.diff
  - update debian/patches/series
* Drop debian/patches/makefile-in-empty-xpcom-fix.diff - fixed in the
  current version
* Don't register a class ID multiple times, as this breaks enigmail entirely
  - add debian/patches/dont_register_cids_multiple_times.diff
  - update debian/patches/series
* Look for the Thunderbird 5 SDK
  - update debian/rules
  - update debian/control
* Run autoconf2.13 at build time
  - update debian/rules
  - update debian/control
* Add useless mesa-common-dev build-dep, just to satisfy the build system.
  We should just patch this out entirely really, but that's for another upload
  - update debian/control

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* ***** BEGIN LICENSE BLOCK *****
 
2
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
3
 *
 
4
 * The contents of this file are subject to the Mozilla Public License Version
 
5
 * 1.1 (the "License"); you may not use this file except in compliance with
 
6
 * the License. You may obtain a copy of the License at
 
7
 * http://www.mozilla.org/MPL/
 
8
 *
 
9
 * Software distributed under the License is distributed on an "AS IS" basis,
 
10
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
11
 * for the specific language governing rights and limitations under the
 
12
 * License.
 
13
 *
 
14
 * The Original Code is Firefox Sync.
 
15
 *
 
16
 * The Initial Developer of the Original Code is
 
17
 * Mozilla Foundation.
 
18
 * Portions created by the Initial Developer are Copyright (C) 2010
 
19
 * the Initial Developer. All Rights Reserved.
 
20
 *
 
21
 * Contributor(s):
 
22
 * Philipp von Weitershausen <philipp@weitershausen.de>
 
23
 *
 
24
 * Alternatively, the contents of this file may be used under the terms of
 
25
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 
26
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 
27
 * in which case the provisions of the GPL or the LGPL are applicable instead
 
28
 * of those above. If you wish to allow use of your version of this file only
 
29
 * under the terms of either the GPL or the LGPL, and not to allow others to
 
30
 * use your version of this file under the terms of the MPL, indicate your
 
31
 * decision by deleting the provisions above and replace them with the notice
 
32
 * and other provisions required by the GPL or the LGPL. If you do not delete
 
33
 * the provisions above, a recipient may use your version of this file under
 
34
 * the terms of any one of the MPL, the GPL or the LGPL.
 
35
 *
 
36
 * ***** END LICENSE BLOCK ***** */
 
37
 
 
38
const Cc = Components.classes;
 
39
const Ci = Components.interfaces;
 
40
const Cr = Components.results;
 
41
const Cu = Components.utils;
 
42
 
 
43
Cu.import("resource://services-sync/log4moz.js");
 
44
Cu.import("resource://services-sync/resource.js");
 
45
Cu.import("resource://services-sync/constants.js");
 
46
Cu.import("resource://services-sync/util.js");
 
47
 
 
48
const EXPORTED_SYMBOLS = ["JPAKEClient"];
 
49
 
 
50
const JPAKE_SIGNERID_SENDER   = "sender";
 
51
const JPAKE_SIGNERID_RECEIVER = "receiver";
 
52
const JPAKE_LENGTH_SECRET     = 8;
 
53
const JPAKE_LENGTH_CLIENTID   = 256;
 
54
const JPAKE_VERIFY_VALUE      = "0123456789ABCDEF";
 
55
 
 
56
 
 
57
/*
 
58
 * Client to exchange encrypted data using the J-PAKE algorithm.
 
59
 * The exchange between two clients of this type looks like this:
 
60
 * 
 
61
 * 
 
62
 * Client A                      Server                      Client B
 
63
 * ==================================================================
 
64
 *                                  |
 
65
 * retrieve channel <---------------|
 
66
 * generate random secret           |
 
67
 * show PIN = secret + channel      |                ask user for PIN
 
68
 * upload A's message 1 ----------->|
 
69
 *                                  |--------> retrieve A's message 1
 
70
 *                                  |<---------- upload B's message 1
 
71
 * retrieve B's message 1 <---------|
 
72
 * upload A's message 2 ----------->|
 
73
 *                                  |--------> retrieve A's message 2
 
74
 *                                  |                     compute key
 
75
 *                                  |<---------- upload B's message 2
 
76
 * retrieve B's message 2 <---------|
 
77
 * compute key                      |
 
78
 * upload sha256d(key) ------------>|
 
79
 *                                  |---------> retrieve sha256d(key)
 
80
 *                                  |          verify against own key
 
81
 *                                  |                    encrypt data
 
82
 *                                  |<------------------- upload data
 
83
 * retrieve data <------------------|
 
84
 * verify HMAC                      |
 
85
 * decrypt data                     |
 
86
 * 
 
87
 * 
 
88
 * Create a client object like so:
 
89
 * 
 
90
 *   let client = new JPAKEClient(observer);
 
91
 * 
 
92
 * The 'observer' object must implement the following methods:
 
93
 * 
 
94
 *   displayPIN(pin) -- Display the PIN to the user, only called on the client
 
95
 *     that didn't provide the PIN.
 
96
 * 
 
97
 *   onComplete(data) -- Called after transfer has been completed. On
 
98
 *     the sending side this is called with no parameter and as soon as the
 
99
 *     data has been uploaded, which this doesn't mean the receiving side
 
100
 *     has actually retrieved them yet.
 
101
 *
 
102
 *   onAbort(error) -- Called whenever an error is encountered. All errors lead
 
103
 *     to an abort and the process has to be started again on both sides.
 
104
 * 
 
105
 * To start the data transfer on the receiving side, call
 
106
 * 
 
107
 *   client.receiveNoPIN();
 
108
 * 
 
109
 * This will allocate a new channel on the server, generate a PIN, have it
 
110
 * displayed and then do the transfer once the protocol has been completed
 
111
 * with the sending side.
 
112
 * 
 
113
 * To initiate the transfer from the sending side, call
 
114
 * 
 
115
 *   client.sendWithPIN(pin, data)
 
116
 * 
 
117
 * To abort the process, call
 
118
 * 
 
119
 *   client.abort();
 
120
 * 
 
121
 * Note that after completion or abort, the 'client' instance may not be reused.
 
122
 * You will have to create a new one in case you'd like to restart the process.
 
123
 */
 
124
function JPAKEClient(observer) {
 
125
  this.observer = observer;
 
126
 
 
127
  this._log = Log4Moz.repository.getLogger("Service.JPAKEClient");
 
128
  this._log.level = Log4Moz.Level[Svc.Prefs.get(
 
129
    "log.logger.service.jpakeclient", "Debug")];
 
130
 
 
131
  this._serverUrl = Svc.Prefs.get("jpake.serverURL");
 
132
  this._pollInterval = Svc.Prefs.get("jpake.pollInterval");
 
133
  this._maxTries = Svc.Prefs.get("jpake.maxTries");
 
134
  if (this._serverUrl.slice(-1) != "/")
 
135
    this._serverUrl += "/";
 
136
 
 
137
  this._jpake = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
 
138
                  .createInstance(Ci.nsISyncJPAKE);
 
139
  this._auth = new NoOpAuthenticator();
 
140
 
 
141
  this._setClientID();
 
142
}
 
143
JPAKEClient.prototype = {
 
144
 
 
145
  _chain: Utils.asyncChain,
 
146
 
 
147
  /*
 
148
   * Public API
 
149
   */
 
150
 
 
151
  receiveNoPIN: function receiveNoPIN() {
 
152
    this._my_signerid = JPAKE_SIGNERID_RECEIVER;
 
153
    this._their_signerid = JPAKE_SIGNERID_SENDER;
 
154
 
 
155
    this._secret = this._createSecret();
 
156
 
 
157
    // Allow a large number of tries first while we wait for the PIN
 
158
    // to be entered on the other device.
 
159
    this._maxTries = Svc.Prefs.get("jpake.firstMsgMaxTries");
 
160
    this._chain(this._getChannel,
 
161
                this._computeStepOne,
 
162
                this._putStep,
 
163
                this._getStep,
 
164
                function(callback) {
 
165
                  // Now we can switch back to the smaller timeout.
 
166
                  this._maxTries = Svc.Prefs.get("jpake.maxTries");
 
167
                  callback();
 
168
                },
 
169
                this._computeStepTwo,
 
170
                this._putStep,
 
171
                this._getStep,
 
172
                this._computeFinal,
 
173
                this._computeKeyVerification,
 
174
                this._putStep,
 
175
                this._getStep,
 
176
                this._decryptData,
 
177
                this._complete)();
 
178
  },
 
179
 
 
180
  sendWithPIN: function sendWithPIN(pin, obj) {
 
181
    this._my_signerid = JPAKE_SIGNERID_SENDER;
 
182
    this._their_signerid = JPAKE_SIGNERID_RECEIVER;
 
183
 
 
184
    this._channel = pin.slice(JPAKE_LENGTH_SECRET);
 
185
    this._channelUrl = this._serverUrl + this._channel;
 
186
    this._secret = pin.slice(0, JPAKE_LENGTH_SECRET);
 
187
    this._data = JSON.stringify(obj);
 
188
 
 
189
    this._chain(this._computeStepOne,
 
190
                this._getStep,
 
191
                this._putStep,
 
192
                this._computeStepTwo,
 
193
                this._getStep,
 
194
                this._putStep,
 
195
                this._computeFinal,
 
196
                this._getStep,
 
197
                this._encryptData,
 
198
                this._putStep,
 
199
                this._complete)();
 
200
  },
 
201
 
 
202
  abort: function abort(error) {
 
203
    this._log.debug("Aborting...");
 
204
    this._finished = true;
 
205
    let self = this;
 
206
    if (error == JPAKE_ERROR_CHANNEL
 
207
        || error == JPAKE_ERROR_NETWORK
 
208
        || error == JPAKE_ERROR_NODATA) {
 
209
      Utils.delay(function() { this.observer.onAbort(error); }, 0,
 
210
                  this, "_timer_onAbort");
 
211
    } else {
 
212
      this._reportFailure(error, function() { self.observer.onAbort(error); });
 
213
    }
 
214
  },
 
215
 
 
216
  /*
 
217
   * Utilities
 
218
   */
 
219
 
 
220
  _setClientID: function _setClientID() {
 
221
    let rng = Cc["@mozilla.org/security/random-generator;1"]
 
222
                .createInstance(Ci.nsIRandomGenerator);
 
223
    let bytes = rng.generateRandomBytes(JPAKE_LENGTH_CLIENTID / 2);
 
224
    this._clientID = [("0" + byte.toString(16)).slice(-2)
 
225
                      for each (byte in bytes)].join("");
 
226
  },
 
227
 
 
228
  _createSecret: function _createSecret() {
 
229
    // 0-9a-z without 1,l,o,0
 
230
    const key = "23456789abcdefghijkmnpqrstuvwxyz";
 
231
    let rng = Cc["@mozilla.org/security/random-generator;1"]
 
232
                .createInstance(Ci.nsIRandomGenerator);
 
233
    let bytes = rng.generateRandomBytes(JPAKE_LENGTH_SECRET);
 
234
    return [key[Math.floor(byte * key.length / 256)]
 
235
            for each (byte in bytes)].join("");
 
236
  },
 
237
 
 
238
  /*
 
239
   * Steps of J-PAKE procedure
 
240
   */
 
241
 
 
242
  _getChannel: function _getChannel(callback) {
 
243
    this._log.trace("Requesting channel.");
 
244
    let resource = new AsyncResource(this._serverUrl + "new_channel");
 
245
    resource.authenticator = this._auth;
 
246
    resource.setHeader("X-KeyExchange-Id", this._clientID);
 
247
    resource.get(Utils.bind2(this, function handleChannel(error, response) {
 
248
      if (this._finished)
 
249
        return;
 
250
 
 
251
      if (error) {
 
252
        this._log.error("Error acquiring channel ID. " + error);
 
253
        this.abort(JPAKE_ERROR_CHANNEL);
 
254
        return;
 
255
      }
 
256
      if (response.status != 200) {
 
257
        this._log.error("Error acquiring channel ID. Server responded with HTTP "
 
258
                        + response.status);
 
259
        this.abort(JPAKE_ERROR_CHANNEL);
 
260
        return;
 
261
      }
 
262
 
 
263
      let channel;
 
264
      try {
 
265
        this._channel = response.obj;
 
266
      } catch (ex) {
 
267
        this._log.error("Server responded with invalid JSON.");
 
268
        this.abort(JPAKE_ERROR_CHANNEL);
 
269
        return;
 
270
      }
 
271
      this._log.debug("Using channel " + this._channel);
 
272
      this._channelUrl = this._serverUrl + this._channel;
 
273
 
 
274
      // Don't block on UI code.
 
275
      let pin = this._secret + this._channel;
 
276
      Utils.delay(function() { this.observer.displayPIN(pin); }, 0,
 
277
                  this, "_timer_displayPIN");
 
278
      callback();
 
279
    }));
 
280
  },
 
281
 
 
282
  // Generic handler for uploading data.
 
283
  _putStep: function _putStep(callback) {
 
284
    this._log.trace("Uploading message " + this._outgoing.type);
 
285
    let resource = new AsyncResource(this._channelUrl);
 
286
    resource.authenticator = this._auth;
 
287
    resource.setHeader("X-KeyExchange-Id", this._clientID);
 
288
    resource.put(this._outgoing, Utils.bind2(this, function (error, response) {
 
289
      if (this._finished)
 
290
        return;
 
291
 
 
292
      if (error) {
 
293
        this._log.error("Error uploading data. " + error);
 
294
        this.abort(JPAKE_ERROR_NETWORK);
 
295
        return;
 
296
      }
 
297
      if (response.status != 200) {
 
298
        this._log.error("Could not upload data. Server responded with HTTP "
 
299
                        + response.status);
 
300
        this.abort(JPAKE_ERROR_SERVER);
 
301
        return;
 
302
      }
 
303
      // There's no point in returning early here since the next step will
 
304
      // always be a GET so let's pause for twice the poll interval.
 
305
      this._etag = response.headers["etag"];
 
306
      Utils.delay(function () { callback(); }, this._pollInterval * 2, this,
 
307
                  "_pollTimer");
 
308
    }));
 
309
  },
 
310
 
 
311
  // Generic handler for polling for and retrieving data.
 
312
  _pollTries: 0,
 
313
  _getStep: function _getStep(callback) {
 
314
    this._log.trace("Retrieving next message.");
 
315
    let resource = new AsyncResource(this._channelUrl);
 
316
    resource.authenticator = this._auth;
 
317
    resource.setHeader("X-KeyExchange-Id", this._clientID);
 
318
    if (this._etag)
 
319
      resource.setHeader("If-None-Match", this._etag);
 
320
 
 
321
    resource.get(Utils.bind2(this, function (error, response) {
 
322
      if (this._finished)
 
323
        return;
 
324
 
 
325
      if (error) {
 
326
        this._log.error("Error fetching data. " + error);
 
327
        this.abort(JPAKE_ERROR_NETWORK);
 
328
        return;
 
329
      }
 
330
 
 
331
      if (response.status == 304) {
 
332
        this._log.trace("Channel hasn't been updated yet. Will try again later.");
 
333
        if (this._pollTries >= this._maxTries) {
 
334
          this._log.error("Tried for " + this._pollTries + " times, aborting.");
 
335
          this.abort(JPAKE_ERROR_TIMEOUT);
 
336
          return;
 
337
        }
 
338
        this._pollTries += 1;
 
339
        Utils.delay(function() { this._getStep(callback); },
 
340
                    this._pollInterval, this, "_pollTimer");
 
341
        return;
 
342
      }
 
343
      this._pollTries = 0;
 
344
 
 
345
      if (response.status == 404) {
 
346
        this._log.error("No data found in the channel.");
 
347
        this.abort(JPAKE_ERROR_NODATA);
 
348
        return;
 
349
      }
 
350
      if (response.status != 200) {
 
351
        this._log.error("Could not retrieve data. Server responded with HTTP "
 
352
                        + response.status);
 
353
        this.abort(JPAKE_ERROR_SERVER);
 
354
        return;
 
355
      }
 
356
 
 
357
      try {
 
358
        this._incoming = response.obj;
 
359
      } catch (ex) {
 
360
        this._log.error("Server responded with invalid JSON.");
 
361
        this.abort(JPAKE_ERROR_INVALID);
 
362
        return;
 
363
      }
 
364
      this._log.trace("Fetched message " + this._incoming.type);
 
365
      callback();
 
366
    }));
 
367
  },
 
368
 
 
369
  _reportFailure: function _reportFailure(reason, callback) {
 
370
    this._log.debug("Reporting failure to server.");
 
371
    let resource = new AsyncResource(this._serverUrl + "report");
 
372
    resource.authenticator = this._auth;
 
373
    resource.setHeader("X-KeyExchange-Id", this._clientID);
 
374
    resource.setHeader("X-KeyExchange-Cid", this._channel);
 
375
    resource.setHeader("X-KeyExchange-Log", reason);
 
376
    resource.post("", Utils.bind2(this, function (error, response) {
 
377
      if (error)
 
378
        this._log.warn("Report failed: " + error);
 
379
      else if (response.status != 200)
 
380
        this._log.warn("Report failed. Server responded with HTTP "
 
381
                       + response.status);
 
382
 
 
383
      // Do not block on errors, we're done or aborted by now anyway.
 
384
      callback();
 
385
    }));
 
386
  },
 
387
 
 
388
  _computeStepOne: function _computeStepOne(callback) {
 
389
    this._log.trace("Computing round 1.");
 
390
    let gx1 = {};
 
391
    let gv1 = {};
 
392
    let r1 = {};
 
393
    let gx2 = {};
 
394
    let gv2 = {};
 
395
    let r2 = {};
 
396
    try {
 
397
      this._jpake.round1(this._my_signerid, gx1, gv1, r1, gx2, gv2, r2);
 
398
    } catch (ex) {
 
399
      this._log.error("JPAKE round 1 threw: " + ex);
 
400
      this.abort(JPAKE_ERROR_INTERNAL);
 
401
      return;
 
402
    }
 
403
    let one = {gx1: gx1.value,
 
404
               gx2: gx2.value,
 
405
               zkp_x1: {gr: gv1.value, b: r1.value, id: this._my_signerid},
 
406
               zkp_x2: {gr: gv2.value, b: r2.value, id: this._my_signerid}};
 
407
    this._outgoing = {type: this._my_signerid + "1", payload: one};
 
408
    this._log.trace("Generated message " + this._outgoing.type);
 
409
    callback();
 
410
  },
 
411
 
 
412
  _computeStepTwo: function _computeStepTwo(callback) {
 
413
    this._log.trace("Computing round 2.");
 
414
    if (this._incoming.type != this._their_signerid + "1") {
 
415
      this._log.error("Invalid round 1 message: "
 
416
                      + JSON.stringify(this._incoming));
 
417
      this.abort(JPAKE_ERROR_WRONGMESSAGE);
 
418
      return;
 
419
    }
 
420
 
 
421
    let step1 = this._incoming.payload;
 
422
    if (!step1 || !step1.zkp_x1 || step1.zkp_x1.id != this._their_signerid
 
423
        || !step1.zkp_x2 || step1.zkp_x2.id != this._their_signerid) {
 
424
      this._log.error("Invalid round 1 payload: " + JSON.stringify(step1));
 
425
      this.abort(JPAKE_ERROR_WRONGMESSAGE);
 
426
      return;
 
427
    }
 
428
 
 
429
    let A = {};
 
430
    let gvA = {};
 
431
    let rA = {};
 
432
 
 
433
    try {
 
434
      this._jpake.round2(this._their_signerid, this._secret,
 
435
                         step1.gx1, step1.zkp_x1.gr, step1.zkp_x1.b,
 
436
                         step1.gx2, step1.zkp_x2.gr, step1.zkp_x2.b,
 
437
                         A, gvA, rA);
 
438
    } catch (ex) {
 
439
      this._log.error("JPAKE round 2 threw: " + ex);
 
440
      this.abort(JPAKE_ERROR_INTERNAL);
 
441
      return;
 
442
    }
 
443
    let two = {A: A.value,
 
444
               zkp_A: {gr: gvA.value, b: rA.value, id: this._my_signerid}};
 
445
    this._outgoing = {type: this._my_signerid + "2", payload: two};
 
446
    this._log.trace("Generated message " + this._outgoing.type);
 
447
    callback();
 
448
  },
 
449
 
 
450
  _computeFinal: function _computeFinal(callback) {
 
451
    if (this._incoming.type != this._their_signerid + "2") {
 
452
      this._log.error("Invalid round 2 message: "
 
453
                      + JSON.stringify(this._incoming));
 
454
      this.abort(JPAKE_ERROR_WRONGMESSAGE);
 
455
      return;
 
456
    }
 
457
 
 
458
    let step2 = this._incoming.payload;
 
459
    if (!step2 || !step2.zkp_A || step2.zkp_A.id != this._their_signerid) {
 
460
      this._log.error("Invalid round 2 payload: " + JSON.stringify(step1));
 
461
      this.abort(JPAKE_ERROR_WRONGMESSAGE);
 
462
      return;
 
463
    }
 
464
 
 
465
    let aes256Key = {};
 
466
    let hmac256Key = {};
 
467
 
 
468
    try {
 
469
      this._jpake.final(step2.A, step2.zkp_A.gr, step2.zkp_A.b, HMAC_INPUT,
 
470
                        aes256Key, hmac256Key);
 
471
    } catch (ex) {
 
472
      this._log.error("JPAKE final round threw: " + ex);
 
473
      this.abort(JPAKE_ERROR_INTERNAL);
 
474
      return;
 
475
    }
 
476
 
 
477
    this._crypto_key = aes256Key.value;
 
478
    let hmac_key = Utils.makeHMACKey(Utils.safeAtoB(hmac256Key.value));
 
479
    this._hmac_hasher = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, hmac_key);
 
480
 
 
481
    callback();
 
482
  },
 
483
 
 
484
  _computeKeyVerification: function _computeKeyVerification(callback) {
 
485
    this._log.trace("Encrypting key verification value.");
 
486
    let iv, ciphertext;
 
487
    try {
 
488
      iv = Svc.Crypto.generateRandomIV();
 
489
      ciphertext = Svc.Crypto.encrypt(JPAKE_VERIFY_VALUE,
 
490
                                      this._crypto_key, iv);
 
491
    } catch (ex) {
 
492
      this._log.error("Failed to encrypt key verification value.");
 
493
      this.abort(JPAKE_ERROR_INTERNAL);
 
494
      return;
 
495
    }
 
496
    this._outgoing = {type: this._my_signerid + "3",
 
497
                      payload: {ciphertext: ciphertext, IV: iv}};
 
498
    this._log.trace("Generated message " + this._outgoing.type);
 
499
    callback();
 
500
  },
 
501
 
 
502
  _encryptData: function _encryptData(callback) {
 
503
    this._log.trace("Verifying their key.");
 
504
    if (this._incoming.type != this._their_signerid + "3") {
 
505
      this._log.error("Invalid round 3 data: " +
 
506
                      JSON.stringify(this._incoming));
 
507
      this.abort(JPAKE_ERROR_WRONGMESSAGE);
 
508
      return;
 
509
    }
 
510
    let step3 = this._incoming.payload;
 
511
    try {
 
512
      ciphertext = Svc.Crypto.encrypt(JPAKE_VERIFY_VALUE,
 
513
                                      this._crypto_key, step3.IV);
 
514
      if (ciphertext != step3.ciphertext)
 
515
        throw "Key mismatch!";
 
516
    } catch (ex) {
 
517
      this._log.error("Keys don't match!");
 
518
      this.abort(JPAKE_ERROR_KEYMISMATCH);
 
519
      return;
 
520
    }
 
521
 
 
522
    this._log.trace("Encrypting data.");
 
523
    let iv, ciphertext, hmac;
 
524
    try {
 
525
      iv = Svc.Crypto.generateRandomIV();
 
526
      ciphertext = Svc.Crypto.encrypt(this._data, this._crypto_key, iv);
 
527
      hmac = Utils.bytesAsHex(Utils.digestUTF8(ciphertext, this._hmac_hasher));
 
528
    } catch (ex) {
 
529
      this._log.error("Failed to encrypt data.");
 
530
      this.abort(JPAKE_ERROR_INTERNAL);
 
531
      return;
 
532
    }
 
533
    this._outgoing = {type: this._my_signerid + "3",
 
534
                      payload: {ciphertext: ciphertext, IV: iv, hmac: hmac}};
 
535
    this._log.trace("Generated message " + this._outgoing.type);
 
536
    callback();
 
537
  },
 
538
 
 
539
  _decryptData: function _decryptData(callback) {
 
540
    this._log.trace("Verifying their key.");
 
541
    if (this._incoming.type != this._their_signerid + "3") {
 
542
      this._log.error("Invalid round 3 data: "
 
543
                      + JSON.stringify(this._incoming));
 
544
      this.abort(JPAKE_ERROR_WRONGMESSAGE);
 
545
      return;
 
546
    }
 
547
    let step3 = this._incoming.payload;
 
548
    try {
 
549
      let hmac = Utils.bytesAsHex(
 
550
        Utils.digestUTF8(step3.ciphertext, this._hmac_hasher));
 
551
      if (hmac != step3.hmac)
 
552
        throw "HMAC validation failed!";
 
553
    } catch (ex) {
 
554
      this._log.error("HMAC validation failed.");
 
555
      this.abort(JPAKE_ERROR_KEYMISMATCH);
 
556
      return;
 
557
    }
 
558
 
 
559
    this._log.trace("Decrypting data.");
 
560
    let cleartext;
 
561
    try {      
 
562
      cleartext = Svc.Crypto.decrypt(step3.ciphertext, this._crypto_key,
 
563
                                     step3.IV);
 
564
    } catch (ex) {
 
565
      this._log.error("Failed to decrypt data.");
 
566
      this.abort(JPAKE_ERROR_INTERNAL);
 
567
      return;
 
568
    }
 
569
 
 
570
    try {
 
571
      this._newData = JSON.parse(cleartext);
 
572
    } catch (ex) {
 
573
      this._log.error("Invalid data data: " + JSON.stringify(cleartext));
 
574
      this.abort(JPAKE_ERROR_INVALID);
 
575
      return;
 
576
    }
 
577
 
 
578
    this._log.trace("Decrypted data.");
 
579
    callback();
 
580
  },
 
581
 
 
582
  _complete: function _complete() {
 
583
    this._log.debug("Exchange completed.");
 
584
    this._finished = true;
 
585
    Utils.delay(function () { this.observer.onComplete(this._newData); },
 
586
                0, this, "_timer_onComplete");
 
587
  }
 
588
 
 
589
};