Please use docs.servicenow.com for the latest documentation.

This site is for reference purposes only and may not be accurate for the latest ServiceNow version

Implementing a Nonce

From Wiki Archive
Jump to: navigation, search
Note
Note: This article applies to Fuji and earlier releases. For more current information, see Implement a Nonce at http://docs.servicenow.com

The ServiceNow Wiki is no longer being updated. Visit http://docs.servicenow.com for the latest product documentation.

The latest release this documentation applies to is Fuji. For the Geneva release, see Platform security. Documentation for later releases is also on docs.servicenow.com.


Overview

Admins can implement a nonce to be used with single sign-on digest authentication. To use a nonce with the unencrypted token or encrypted token methods of single sign on, the steps in this article will still apply with only a few minor changes.

Note
Note: The nonce is used only for login requests, not for any other type of request. If the system receives a nonce value after login, the nonce is not consumed.


Benefits

The usage of a nonce prohibits a malicious user from performing a replay attack in order to log into your system. 

High-level Overview

When a customer has implemented the digested token Single Sign-on and wishes to add the security of a nonce, the process flows as follows:

  1. A user logs into the customer's portal.
  2. The customer generates the required SSO parameters and appends a random nonce  to the end.  For example, if the customer were forwarding the authentication response via the query string, it may look something like this:
SM_USER=itil&DE_USER=V1QuWMmxSfBgfRS099X0cAjKo5Q=&NONCE=1407743018 

ServiceNow receives this request and retrieves the authentication variables.  Before attempting to verify the integrity of the authentication response, ServiceNow will check the nonce against an internal table (u_authentication_nonce) to verify that it does not yet exist.  If the nonce does not exist within that table, the nonce is then added to the table and the authentication process is allowed to continue.  However, if that nonce value already exists within the table, the authentication attempt is cancelled and an error code of failed_missing_requirement is returned, which will typically take the user back to the login page.

Implementation

Implementing a nonce is fairly straightforward when these steps are followed:

  • Create a system property called glide.authenticate.header.nonce_key and set its value to whatever variable name you're using for the nonce, such as NONCE or NCE.
  • Create a new table called u_authentication_nonce. Add a field to the table called u_nonce.
  • Goto System Properties -> Installation Exits and create a new item called DigestSingleSignOnNonce which overrides ExternalAuthentication (see image below).
Image:nonce

  • Add the following code to the script portion of the newly created DigestSingleSignOnNonce.


Note
Note: These API calls changed in the Calgary release:
  • GlideProperties replaces Packages.com.glide.util.GlideProperties
  • GlideCookieMan replaces Packages.com.glide.ui.CookieMan
  • GlideLog replaces Packages.com.glide.util.Log
  • GlideUser replaces Packages.com.glide.sys.User
  • SncAuthentication replaces Packages.com.snc.authentication.digest.Authentication

The new script object calls apply to the Calgary release and beyond. For releases prior to Calgary, substitute the packages calls as described above. Packages calls are not valid beginning with the Calgary release. For more information, see Scripting API Changes.


<source lang="javascript">gs.include("PrototypeServer");

var DigestSingleSignOnNonce = Class.create(); DigestSingleSignOnNonce.prototype = {

process : function() {

var headerKey = GlideProperties.get("glide.authenticate.header.key", "SM_USER"); var headerDigestKey = GlideProperties.get("glide.authenticate.header.encrypted_key", "DIGEST"); var headerNonceKey = GlideProperties.get("glide.authenticate.header.nonce_key", "NCE"); var fieldName = GlideProperties.get("glide.authenticate.header.value", "user_name"); var fkey = GlideProperties.get("glide.authenticate.secret_key");

// Look in the Headers var data = request.getHeader(headerKey); var encdata = request.getHeader(headerDigestKey); var nonce = request.getHeader(headerNonceKey);

// If not, then check the URL Parameters if (data == null || encdata == null || nonce == null) { data = request.getParameter(headerKey); encdata = request.getParameter(headerDigestKey); nonce = request.getParameter(headerNonceKey); }

// then maybe its a cookie if (data == null || encdata == null || nonce == null) { var cookies = request.getCookies(); data = GlideCookieMan.getCookieValue(cookies, headerKey); encdata = GlideCookieMan.getCookieValue(cookies, headerDigestKey); nonce = GlideCookieMan.getCookieValue(cookies, headerNonceKey); }

// if found run encryption if (data != null && encdata != null && nonce != null) { try {

// Replace all spaces with plus(+)'s, converted in url encdata = encdata.replaceAll(' ', '+');

// ----- Encrypt the username var key = this.getDigest( data + "|" + nonce, fkey);

// Check for match of received encoded data // and your encoding of user name if (encdata == key) { var ugr = new GlideRecord("sys_user"); ugr.initialize(); if (!ugr.isValidField(fieldName)) { GlideLog.warn("External authorization is set to use field: '"+ fieldName + "' which doesn't exist");

return "failed_missing_requirement"; } ugr.addQuery(fieldName, data); ugr.query(); if (!ugr.next()) { var userLoad = GlideUser.getUser(data); if (userLoad == null) return "failed_authentication"; ugr.initialize(); ugr.addQuery(fieldName, data); ugr.query(); if (!ugr.next()) return "failed_authentication"; } if (this.processNonce(nonce)){ var userName = ugr.getValue("user_name"); return userName; } else return "failed_missing_requirement"; } else { return "failed_authentication"; } } catch(e) { gs.log(e); return "failed_authentication"; } // Encoded data didn't match recieved Encoded data } else { return "failed_missing_requirement"; } },

getDigest : function( data, fkey ) { try { // default to something JDK 1.4 has var MAC_ALG = "HmacSHA1"; return SncAuthentication.encode(data, fkey, MAC_ALG);

} catch (e) { gs.log(e.toString()); throw 'failed_missing_requirement'; } } ,

processNonce : function( sentNonce ) { var ngr = new GlideRecord("u_authentication_nonce");

ngr.addQuery("u_nonce", sentNonce); ngr.query(); if (ngr.next()) { gs.log("This SSO entry has already been processed! (Nonce: " + sentNonce + ")"); return false; } var ngrNew = new GlideRecord("u_authentication_nonce"); ngrNew.initialize(); ngrNew.u_nonce = sentNonce; ngrNew.insert(); gs.log("Inserted new nonce: " + sentNonce); return true; } };

</source>

  • Once you've saved your new installation exit, goto the DigestSingleSignOn installation exit and make sure that it is set Active=false.


Your instance should now be configured to implement a nonce.