First pass at making a GSSWeb object
authorMark Donnelly <mark@painless-security.com>
Wed, 22 Oct 2014 20:54:44 +0000 (16:54 -0400)
committerMark Donnelly <mark@painless-security.com>
Wed, 22 Oct 2014 20:54:44 +0000 (16:54 -0400)
navigator.gssweb.js [new file with mode: 0644]

diff --git a/navigator.gssweb.js b/navigator.gssweb.js
new file mode 100644 (file)
index 0000000..a72fa87
--- /dev/null
@@ -0,0 +1,204 @@
+console.log("Loading navigator.gssweb.js - #2");
+
+
+var GSSWeb = (function () {
+  function GSSWeb(config) {
+    this.serverPath = config.serverPath;
+    this.credential = config.credential;
+    this.appTag = config.appTag || "GSSWeb-" + navigator.generateNonce();
+    this.error = config.error || function (err) {
+      console.warn(err);
+    };
+    this.success = config.success;
+
+    this.version = "0.0.1";
+    this.authenticationState = false;
+    this.context = "";
+    this.serverToken = "";
+    this.clientCred = "";
+    this.xhr = new XMLHttpRequest();
+    this.xhr.open("POST", this.serverPath, true);
+    this.xhr.setRequestHeader(
+      'Content-Type', 
+      'application/x-www-form-urlencoded'
+    );
+    this.xhr.onreadystatechange = this.recvTokenFromServer.bind(this);
+
+    this.gss = new navigator.gss_eap({
+      appTag: this.appTag,
+      error: this.handleGSSError.bind(this)
+    });
+  }
+  /* What to invoke when the underlying GSS library 
+   * has a problem.
+   */
+  GSSWeb.prototype.handleGSSError = 
+    function (major, minor, errMsg, appTag) {
+      this.error(errMsg, appTag);
+    };
+
+  /* The basic authenticate function.
+   * Takes no arguments, as all should be supplied
+   * on the base object.
+   */
+  GSSWeb.prototype.authenticate = function () {
+    /* Error checking */
+    // Ensure that the callbacks exist well
+    if ( "function" != typeof(this.error) )
+    {
+      // I can't even call my error function!  All that can be done
+      // is throw an error.
+      throw("Error function not supplied to navigator.gssweb object!");
+    }
+    if ( "function" != typeof(this.success) )
+    {
+      // OK, so we have an error function; use it.
+      this.error(
+        "Success function not supplied to navigator.gssweb object!"
+      );
+    }
+    if ( ! this.presentString(this.serverPath) )
+    {
+      this.error("Server path not supplied to navigator.gssweb object!");
+    }
+
+    
+    /* Setup */
+    this.nonce = navigator.generateNonce();
+
+    // Start off the cascade by getting the
+    // GSS name of the server
+    this.authGetServerName();
+  };
+
+  GSSWeb.prototype.authGetServerName = function () {
+    this.gss.import_name({
+      name: "HTTP@" + window.location.host,
+      success: this.authReceiveServerName.bind(this)
+    });
+  };
+  GSSWeb.prototype.authReceiveServerName = function (data, appTag) {
+    this.serverName = data.gss_name;
+
+    /* Either move on to acquire_cred because we have been
+    * supplied a credential, or move on to init_sec_context
+    * when we have not.
+    */
+    if ( this.presentString(this.credential) )
+      this.authGetClientName();
+    else
+      this.authInitSecContext();
+  };
+
+  GSSWeb.prototype.authGetClientName = function () {
+    this.gss.import_name({
+      name: this.credential,
+      success: this.authReceiveClientName.bind(this)
+    });
+  };
+  GSSWeb.prototype.authReceiveClientName = function (data, appTag) {
+    this.clientName = data.gss_name;
+
+    // Next up: Get the local credential
+    this.authAcquireCred();
+  };
+
+  GSSWeb.prototype.authAcquireCred = function () {
+    this.gss.acquire_cred({
+      desired_name: this.clientName,
+      cred_usage: 1,
+      success: this.authReceiveClientCred.bind(this)
+    });
+  };
+  GSSWeb.prototype.authReceiveClientCred =
+    function (cred, actual_mechs, lifetime_rec) {
+      this.clientCred = cred;
+
+      this.authInitSecContext();
+    };
+
+  GSSWeb.prototype.authInitSecContext = function () {
+    var params = {
+      target_name: this.serverName,
+      success: this.sendTokenToServer.bind(this)
+    };
+
+    if ("" != this.clientCred) {
+      params.claimant_cred_handle = this.clientCred;
+    }
+    if ("" != this.serverToken) {
+      params.input_token = this.serverToken;
+    }
+    if ("" != this.context) {
+      params.context_handle = this.context;
+    }
+
+    this.gss.init_sec_context(params);
+  };
+  GSSWeb.prototype.sendTokenToServer = 
+    function (ctxt, 
+              mech_type, 
+              output_token, 
+              ret_flags, 
+              time_rec,
+              app_tag) {
+    this.clientToken = output_token;
+    this.context = ctxt;
+
+    var data = "nonce=" + this.nonce +
+               "&token=" + encodeURIComponent(this.clientToken);
+    this.xhr.send(data);
+  };
+  GSSWeb.prototype.recvTokenFromServer = function () {
+    // Only care when we're ready
+    if (this.xhr.readyState != 4) {
+      return;
+    }
+
+    var serverResponse = JSON.parse(this.xhr.responseText);
+    this.serverToken = serverResponse.gssweb.token;
+    switch (this.xhr.status) {
+      case 200:
+        // Finished!
+        var decoded = window.atob(serverResponse.application.data);
+        this.authenticationState = true;
+        this.success(
+          decoded,
+          serverResponse.application["content-type"],
+          this.appTag
+        );
+        break;
+      case 401:
+        // Continue needed
+        this.authInitSecContext();
+        break;
+      default:
+        // We have some server-reported error
+        this.error(
+          window.location.host + 
+          " reported an error; aborting",
+          this.appTag
+        );
+
+        // Destroy the GSS context.
+        this.context = undefined;
+        return;
+    }
+  };
+  
+  /*************************************
+   * Utility methods
+   *************************************/
+  // return true if the variable is a non-empty string
+  GSSWeb.prototype.presentString = function(str)
+  {
+    return(
+      "string" == typeof(str) &&
+      "" != str
+    );
+  }
+  
+  return GSSWeb;
+})();
+
+navigator.gssweb = GSSWeb;