Implement a nonce

  • Release version: Washingtondc
  • Updated February 1, 2024
  • 2 minutes to read
  • Summarize
    Summarized using AI
    This content was generated using new OpenAI-powered functionality. Results are provided on an as is basis and are not guaranteed to be accurate or complete.

    Summary of Implement a Nonce

    This guide details the steps to implement a cryptographic nonce in the authentication header of your ServiceNow instance, ensuring that each nonce can be used only once for enhanced security during authentication processes.

    Show full answer Show less

    Key Features

    • Create a system property named glide.authenticate.header.noncekey to define your nonce variable (e.g., NONCE or NCE).
    • Establish a new table called uauthenticationnonce with a field named unonce to store nonce values.
    • Override the ExternalAuthentication method by creating an item called DigestSingleSignOnNonce within System Properties > Installation Exits.
    • Implement a script to handle nonce processing, encompassing data retrieval, encryption, and nonce validation.

    Key Outcomes

    Following these steps will enable your ServiceNow instance to effectively utilize nonces for authentication, preventing replay attacks and ensuring that each authentication request is unique. You will also be able to log and manage nonce entries, providing a secure authentication environment.

    Add a cryptographic nonce to the authentication header to ensure that it can only be used once.

    • 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.
    • Go to System Properties > Installation Exits and create an item called DigestSingleSignOnNonce which overrides ExternalAuthentication (glide.authenticate.external_property).
    • Add the following code to the script portion of the newly created DigestSingleSignOnNonce.
      
      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|nonce
      				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;
      	}
      };      
           
    • Once you've saved your new installation exit, go to the DigestSingleSignOn installation exit and make sure that it is set Active=false.

    Your instance should now be configured to implement a nonce.