Bug 1020859 Part 1 - Make HawkClient return all the response details for a request, and make deriveHawkCredentials common code. r=jparsons
authorMark Banner <mbanner@mozilla.com>
Wed, 18 Jun 2014 10:42:15 +0100
changeset 189317 82d9d828e62acde165e5e3a4a76a187437e5bfcf
parent 189316 45dd001f865a8135939c05b3d1ce5b13c3209e30
child 189318 cc7732b638a3b2c8a098bc84bc18dadf967469f4
push id7349
push userryanvm@gmail.com
push dateWed, 18 Jun 2014 20:18:07 +0000
treeherderfx-team@815c4fab188b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjparsons
bugs1020859
milestone33.0a1
Bug 1020859 Part 1 - Make HawkClient return all the response details for a request, and make deriveHawkCredentials common code. r=jparsons
services/common/hawkclient.js
services/common/hawkrequest.js
services/common/tests/unit/test_hawkclient.js
services/common/tests/unit/test_hawkrequest.js
services/fxaccounts/FxAccountsClient.jsm
services/fxaccounts/tests/xpcshell/test_client.js
services/mobileid/MobileIdentityClient.jsm
services/mobileid/MobileIdentityCommon.jsm
--- a/services/common/hawkclient.js
+++ b/services/common/hawkclient.js
@@ -171,17 +171,17 @@ this.HawkClient.prototype = {
    * @param method
    *        The HTTP request method
    * @param credentials
    *        Hawk credentials
    * @param payloadObj
    *        An object that can be encodable as JSON as the payload of the
    *        request
    * @return Promise
-   *        Returns a promise that resolves to the text response of the API call,
+   *        Returns a promise that resolves to the response of the API call,
    *        or is rejected with an error.  If the server response can be parsed
    *        as JSON and contains an 'error' property, the promise will be
    *        rejected with this JSON-parsed response.
    */
   request: function(path, method, credentials=null, payloadObj={}, retryOK=true) {
     method = method.toLowerCase();
 
     let deferred = Promise.defer();
@@ -234,18 +234,18 @@ this.HawkClient.prototype = {
       let okResponse = (200 <= status && status < 300);
       if (!okResponse || jsonResponse.error) {
         if (jsonResponse.error) {
           return deferred.reject(jsonResponse);
         }
         return deferred.reject(self._constructError(restResponse, "Request failed"));
       }
       // It's up to the caller to know how to decode the response.
-      // We just return the raw text.
-      deferred.resolve(this.response.body);
+      // We just return the whole response.
+      deferred.resolve(this.response);
     };
 
     function onComplete(error) {
       try {
         // |this| is the RESTRequest object and we need to ensure _onComplete
         // gets the same one.
         _onComplete.call(this, error);
       } catch (ex) {
--- a/services/common/hawkrequest.js
+++ b/services/common/hawkrequest.js
@@ -3,23 +3,26 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 this.EXPORTED_SYMBOLS = [
   "HAWKAuthenticatedRESTRequest",
+  "deriveHawkCredentials"
 ];
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/rest.js");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/Credentials.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils",
                                   "resource://services-crypto/utils.js");
 
 const Prefs = new Preferences("services.common.rest.");
 
 /**
  * Single-use HAWK-authenticated HTTP requests to RESTish resources.
@@ -85,16 +88,56 @@ HAWKAuthenticatedRESTRequest.prototype =
     this.setHeader("Accept-Language", this._intl.accept_languages);
 
     return RESTRequest.prototype.dispatch.call(
       this, method, data, onComplete, onProgress
     );
   }
 };
 
+
+/**
+  * Generic function to derive Hawk credentials.
+  *
+  * Hawk credentials are derived using shared secrets, which depend on the token
+  * in use.
+  *
+  * @param tokenHex
+  *        The current session token encoded in hex
+  * @param context
+  *        A context for the credentials. A protocol version will be prepended
+  *        to the context, see Credentials.keyWord for more information.
+  * @param size
+  *        The size in bytes of the expected derived buffer,
+  *        defaults to 3 * 32.
+  * @return credentials
+  *        Returns an object:
+  *        {
+  *          algorithm: sha256
+  *          id: the Hawk id (from the first 32 bytes derived)
+  *          key: the Hawk key (from bytes 32 to 64)
+  *          extra: size - 64 extra bytes (if size > 64)
+  *        }
+  */
+function deriveHawkCredentials(tokenHex, context, size=96) {
+  let token = CommonUtils.hexToBytes(tokenHex);
+  let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size);
+
+  let result = {
+    algorithm: "sha256",
+    key: out.slice(32, 64),
+    id: CommonUtils.bytesAsHex(out.slice(0, 32))
+  };
+  if (size > 64) {
+    result.extra = out.slice(64);
+  }
+
+  return result;
+}
+
 // With hawk request, we send the user's accepted-languages with each request.
 // To keep the number of times we read this pref at a minimum, maintain the
 // preference in a stateful object that notices and updates itself when the
 // pref is changed.
 this.Intl = function Intl() {
   // We won't actually query the pref until the first time we need it
   this._accepted = "";
   this._everRead = false;
--- a/services/common/tests/unit/test_hawkclient.js
+++ b/services/common/tests/unit/test_hawkclient.js
@@ -54,17 +54,17 @@ add_task(function test_authenticated_get
       response.setStatusLine(request.httpVersion, 200, "OK");
       response.bodyOutputStream.write(message, message.length);
     }
   });
 
   let client = new HawkClient(server.baseURI);
 
   let response = yield client.request("/foo", method, TEST_CREDS);
-  let result = JSON.parse(response);
+  let result = JSON.parse(response.body);
 
   do_check_eq("Great Success!", result.msg);
 
   yield deferredStop(server);
 });
 
 add_task(function test_authenticated_post_request() {
   let method = "POST";
@@ -76,17 +76,17 @@ add_task(function test_authenticated_pos
       response.setHeader("Content-Type", "application/json");
       response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available());
     }
   });
 
   let client = new HawkClient(server.baseURI);
 
   let response = yield client.request("/foo", method, TEST_CREDS, {foo: "bar"});
-  let result = JSON.parse(response);
+  let result = JSON.parse(response.body);
 
   do_check_eq("bar", result.foo);
 
   yield deferredStop(server);
 });
 
 add_task(function test_credentials_optional() {
   let method = "GET";
@@ -98,17 +98,17 @@ add_task(function test_credentials_optio
       response.setStatusLine(request.httpVersion, 200, "OK");
       response.setHeader("Content-Type", "application/json");
       response.bodyOutputStream.write(message, message.length);
     }
   });
 
   let client = new HawkClient(server.baseURI);
   let result = yield client.request("/foo", method); // credentials undefined
-  do_check_eq(JSON.parse(result).msg, "you're in the friend zone");
+  do_check_eq(JSON.parse(result.body).msg, "you're in the friend zone");
 
   yield deferredStop(server);
 });
 
 add_task(function test_server_error() {
   let message = "Ohai!";
   let method = "GET";
 
@@ -237,17 +237,17 @@ add_task(function test_2xx_success() {
     }
   });
 
   let client = new HawkClient(server.baseURI);
 
   let response = yield client.request("/foo", method, credentials);
 
   // Shouldn't be any content in a 202
-  do_check_eq(response, "");
+  do_check_eq(response.body, "");
 
   yield deferredStop(server);
 });
 
 add_task(function test_retry_request_on_fail() {
   let attempts = 0;
   let credentials = {
     id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
@@ -292,17 +292,17 @@ add_task(function test_retry_request_on_
     return Date.now() + 12 * HOUR_MS;
   };
 
   // We begin with no offset
   do_check_eq(client.localtimeOffsetMsec, 0);
 
   // Request will have bad timestamp; client will retry once
   let response = yield client.request("/maybe", method, credentials);
-  do_check_eq(response, "i love you!!!");
+  do_check_eq(response.body, "i love you!!!");
 
   yield deferredStop(server);
 });
 
 add_task(function test_multiple_401_retry_once() {
   // Like test_retry_request_on_fail, but always return a 401
   // and ensure that the client only retries once.
   let attempts = 0;
--- a/services/common/tests/unit/test_hawkrequest.js
+++ b/services/common/tests/unit/test_hawkrequest.js
@@ -2,16 +2,28 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-common/hawkrequest.js");
 
+// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc
+let SESSION_KEYS = {
+  sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+
+                  "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"),
+
+  tokenID:      h("c0a29dcf46174973 da1378696e4c82ae"+
+                  "10f723cf4f4d9f75 e39f4ae3851595ab"),
+
+  reqHMACkey:   h("9d8f22998ee7f579 8b887042466b72d5"+
+                  "3e56ab0c094388bf 65831f702d2febc0"),
+};
+
 function do_register_cleanup() {
   Services.prefs.resetUserPrefs();
 
   // remove the pref change listener
   let hawk = new HAWKAuthenticatedRESTRequest("https://example.com");
   hawk._intl.uninit();
 }
 
@@ -196,8 +208,21 @@ add_test(function test_hawk_language_pre
 
       Services.prefs.resetUserPrefs();
 
       server.stop(run_next_test);
     });
   }
 });
 
+add_task(function test_deriveHawkCredentials() {
+  let credentials = deriveHawkCredentials(
+    SESSION_KEYS.sessionToken, "sessionToken");
+
+  do_check_eq(credentials.algorithm, "sha256");
+  do_check_eq(credentials.id, SESSION_KEYS.tokenID);
+  do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
+});
+
+// turn formatted test vectors into normal hex strings
+function h(hexStr) {
+  return hexStr.replace(/\s+/g, "");
+}
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -6,16 +6,17 @@ this.EXPORTED_SYMBOLS = ["FxAccountsClie
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-common/hawkclient.js");
+Cu.import("resource://services-common/hawkrequest.js");
 Cu.import("resource://services-crypto/utils.js");
 Cu.import("resource://gre/modules/FxAccountsCommon.js");
 Cu.import("resource://gre/modules/Credentials.jsm");
 
 const HOST = Services.prefs.getCharPref("identity.fxaccounts.auth.uri");
 
 this.FxAccountsClient = function(host = HOST) {
   this.host = host;
@@ -152,58 +153,58 @@ this.FxAccountsClient.prototype = {
    * Destroy the current session with the Firefox Account API server
    *
    * @param sessionTokenHex
    *        The session token encoded in hex
    * @return Promise
    */
   signOut: function (sessionTokenHex) {
     return this._request("/session/destroy", "POST",
-      this._deriveHawkCredentials(sessionTokenHex, "sessionToken"));
+      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
   },
 
   /**
    * Check the verification status of the user's FxA email address
    *
    * @param sessionTokenHex
    *        The current session token encoded in hex
    * @return Promise
    */
   recoveryEmailStatus: function (sessionTokenHex) {
     return this._request("/recovery_email/status", "GET",
-      this._deriveHawkCredentials(sessionTokenHex, "sessionToken"));
+      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
   },
 
   /**
    * Resend the verification email for the user
    *
    * @param sessionTokenHex
    *        The current token encoded in hex
    * @return Promise
    */
   resendVerificationEmail: function(sessionTokenHex) {
     return this._request("/recovery_email/resend_code", "POST",
-      this._deriveHawkCredentials(sessionTokenHex, "sessionToken"));
+      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
   },
 
   /**
    * Retrieve encryption keys
    *
    * @param keyFetchTokenHex
    *        A one-time use key fetch token encoded in hex
    * @return Promise
    *        Returns a promise that resolves to an object:
    *        {
    *          kA: an encryption key for recevorable data (bytes)
    *          wrapKB: an encryption key that requires knowledge of the
    *                  user's password (bytes)
    *        }
    */
   accountKeys: function (keyFetchTokenHex) {
-    let creds = this._deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
+    let creds = deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
     let keyRequestKey = creds.extra.slice(0, 32);
     let morecreds = CryptoUtils.hkdf(keyRequestKey, undefined,
                                      Credentials.keyWord("account/keys"), 3 * 32);
     let respHMACKey = morecreds.slice(0, 32);
     let respXORKey = morecreds.slice(32, 96);
 
     return this._request("/account/keys", "GET", creds).then(resp => {
       if (!resp.bundle) {
@@ -242,17 +243,17 @@ this.FxAccountsClient.prototype = {
    * @return Promise
    *        Returns a promise that resolves to the signed certificate.
    *        The certificate can be used to generate a Persona assertion.
    * @throws a new Error
    *         wrapping any of these HTTP code/errno pairs:
    *           https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-12
    */
   signCertificate: function (sessionTokenHex, serializedPublicKey, lifetime) {
-    let creds = this._deriveHawkCredentials(sessionTokenHex, "sessionToken");
+    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
 
     let body = { publicKey: serializedPublicKey,
                  duration: lifetime };
     return Promise.resolve()
       .then(_ => this._request("/certificate/sign", "POST", creds, body))
       .then(resp => resp.cert,
             err => {
               log.error("HAWK.signCertificate error: " + JSON.stringify(err));
@@ -304,48 +305,16 @@ this.FxAccountsClient.prototype = {
       },
       (error) => {
         log.error("accountStatus failed with: " + error);
         return Promise.reject(error);
       }
     );
   },
 
-  /**
-   * The FxA auth server expects requests to certain endpoints to be authorized using Hawk.
-   * Hawk credentials are derived using shared secrets, which depend on the context
-   * (e.g. sessionToken vs. keyFetchToken).
-   *
-   * @param tokenHex
-   *        The current session token encoded in hex
-   * @param context
-   *        A context for the credentials
-   * @param size
-   *        The size in bytes of the expected derived buffer
-   * @return credentials
-   *        Returns an object:
-   *        {
-   *          algorithm: sha256
-   *          id: the Hawk id (from the first 32 bytes derived)
-   *          key: the Hawk key (from bytes 32 to 64)
-   *          extra: size - 64 extra bytes
-   *        }
-   */
-  _deriveHawkCredentials: function (tokenHex, context, size) {
-    let token = CommonUtils.hexToBytes(tokenHex);
-    let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size || 3 * 32);
-
-    return {
-      algorithm: "sha256",
-      key: out.slice(32, 64),
-      extra: out.slice(64),
-      id: CommonUtils.bytesAsHex(out.slice(0, 32))
-    };
-  },
-
   _clearBackoff: function() {
       this.backoffError = null;
   },
 
   /**
    * A general method for sending raw API calls to the FxA auth server.
    * All request bodies and responses are JSON.
    *
@@ -374,22 +343,22 @@ this.FxAccountsClient.prototype = {
     // We were asked to back off.
     if (this.backoffError) {
       log.debug("Received new request during backoff, re-rejecting.");
       deferred.reject(this.backoffError);
       return deferred.promise;
     }
 
     this.hawk.request(path, method, credentials, jsonPayload).then(
-      (responseText) => {
+      (response) => {
         try {
-          let response = JSON.parse(responseText);
-          deferred.resolve(response);
+          let responseObj = JSON.parse(response.body);
+          deferred.resolve(responseObj);
         } catch (err) {
-          log.error("json parse error on response: " + responseText);
+          log.error("json parse error on response: " + response.body);
           deferred.reject({error: err});
         }
       },
 
       (error) => {
         log.error("error " + method + "ing " + path + ": " + JSON.stringify(error));
         if (error.retryAfter) {
           log.debug("Received backoff response; caching error as flag.");
--- a/services/fxaccounts/tests/xpcshell/test_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_client.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://gre/modules/FxAccountsClient.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-common/hawkrequest.js");
 Cu.import("resource://services-crypto/utils.js");
 
 const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
 
 function run_test() {
   run_next_test();
 }
 
@@ -28,28 +29,16 @@ let ACCOUNT_KEYS = {
 
   kA:           h("2021222324252627 28292a2b2c2d2e2f"+
                   "3031323334353637 38393a3b3c3d3e3f"),
 
   wrapKB:       h("4041424344454647 48494a4b4c4d4e4f"+
                   "5051525354555657 58595a5b5c5d5e5f"),
 };
 
-// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-use-session-certificatesign-etc
-let SESSION_KEYS = {
-  sessionToken: h("a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf"+
-                  "b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf"),
-
-  tokenID:      h("c0a29dcf46174973 da1378696e4c82ae"+
-                  "10f723cf4f4d9f75 e39f4ae3851595ab"),
-
-  reqHMACkey:   h("9d8f22998ee7f579 8b887042466b72d5"+
-                  "3e56ab0c094388bf 65831f702d2febc0"),
-};
-
 function deferredStop(server) {
   let deferred = Promise.defer();
   server.stop(deferred.resolve);
   return deferred.promise;
 }
 
 add_task(function test_authenticated_get_request() {
   let message = "{\"msg\": \"Great Success!\"}";
@@ -665,23 +654,12 @@ add_task(function test_email_case() {
 
   let result = yield client.signIn(clientEmail, "123456");
   do_check_eq(result.areWeHappy, "yes");
   do_check_eq(attempts, 2);
 
   yield deferredStop(server);
 });
 
-add_task(function test__deriveHawkCredentials() {
-  let client = new FxAccountsClient("https://example.org");
-
-  let credentials = client._deriveHawkCredentials(
-    SESSION_KEYS.sessionToken, "sessionToken");
-
-  do_check_eq(credentials.algorithm, "sha256");
-  do_check_eq(credentials.id, SESSION_KEYS.tokenID);
-  do_check_eq(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
-});
-
 // turn formatted test vectors into normal hex strings
 function h(hexStr) {
   return hexStr.replace(/\s+/g, "");
 }
--- a/services/mobileid/MobileIdentityClient.jsm
+++ b/services/mobileid/MobileIdentityClient.jsm
@@ -7,16 +7,17 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["MobileIdentityClient"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://services-common/hawkclient.js");
+Cu.import("resource://services-common/hawkrequest.js");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-crypto/utils.js");
 Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 this.MobileIdentityClient = function(aServerUrl) {
   let serverUrl = aServerUrl || SERVER_URL;
@@ -99,25 +100,18 @@ this.MobileIdentityClient.prototype = {
    *        Returns an object:
    *        {
    *          algorithm: sha256
    *          id: the Hawk id (from the first 32 bytes derived)
    *          key: the Hawk key (from bytes 32 to 64)
    *        }
    */
   _deriveHawkCredentials: function(aSessionToken) {
-    let token = CommonUtils.hexToBytes(aSessionToken);
-    let out = CryptoUtils.hkdf(token, undefined,
-                               CREDENTIALS_DERIVATION_INFO,
-                               CREDENTIALS_DERIVATION_SIZE);
-    return {
-      algorithm: "sha256",
-      key: CommonUtils.bytesAsHex(out.slice(32, 64)),
-      id: CommonUtils.bytesAsHex(out.slice(0, 32))
-    };
+    return deriveHawkCredentials(aSessionToken, CREDENTIALS_DERIVATION_INFO,
+                          CREDENTIALS_DERIVATION_SIZE);
   },
 
   /**
    * A general method for sending raw API calls to the mobile id verification
    * server.
    * All request bodies and responses are JSON.
    *
    * @param path
@@ -131,21 +125,21 @@ this.MobileIdentityClient.prototype = {
    * @return Promise
    *        Returns a promise that resolves to the JSON response of the API
    *        call, or is rejected with an error.
    */
   _request: function(path, method, credentials, jsonPayload) {
     let deferred = Promise.defer();
 
     this.hawk.request(path, method, credentials, jsonPayload).then(
-      (responseText) => {
-        log.debug("MobileIdentityClient -> responseText " + responseText);
+      (response) => {
+        log.debug("MobileIdentityClient -> response.body " + response.body);
         try {
-          let response = JSON.parse(responseText);
-          deferred.resolve(response);
+          let responseObj = JSON.parse(response.body);
+          deferred.resolve(responseObj);
         } catch (err) {
           deferred.reject({error: err});
         }
       },
 
       (error) => {
         log.error("MobileIdentityClient -> Error ${}", error);
         deferred.reject(SERVER_ERRNO_TO_ERROR[error.errno] || ERROR_UNKNOWN);
--- a/services/mobileid/MobileIdentityCommon.jsm
+++ b/services/mobileid/MobileIdentityCommon.jsm
@@ -47,17 +47,17 @@ this.REGISTER         = "/register";
 this.SMS_MT_VERIFY    = "/" + this.SMS_MT + "/verify";
 this.SMS_MO_MT_VERIFY = "/" + this.SMS_MO_MT + "/verify";
 this.SMS_VERIFY_CODE  = "/sms/verify_code";
 this.SIGN             = "/certificate/sign";
 this.UNREGISTER       = "/unregister";
 
 // Server consts.
 this.SERVER_URL = Services.prefs.getCharPref("services.mobileid.server.uri");
-this.CREDENTIALS_DERIVATION_INFO = "identity.mozilla.com/picl/v1/sessionToken";
+this.CREDENTIALS_DERIVATION_INFO = "sessionToken";
 this.CREDENTIALS_DERIVATION_SIZE = 2 * 32;
 
 this.SILENT_SMS_RECEIVED_TOPIC = "silent-sms-received";
 
 this.ASSERTION_LIFETIME   = 1000 * 60 * 5;   // 5 minutes.
 this.CERTIFICATE_LIFETIME = 1000 * 3600 * 6; // 6 hours.
 this.KEY_LIFETIME         = 1000 * 3600 * 12; // 12 hours.