Skip to Content

SAP Single Sign-On 3.0 – A Sample Secure Login REST Client

In a previous blog, I have announced that we plan to publish the REST API that allows you to implement your own Secure Login Clients. Here we go.

In general, Secure Login Server 3.0 responds with REST objects that need to be parsed and interpreted by your client.

Your client requests don´t contain REST or JSON, but HTTP headers and forms.


WORKFLOW

First, let´s get a better understanding of the required steps and the information that is exchanged between your Secure Login REST Client and Secure Login Server:


SERVER REST SPECIFICATION

The REST objects used by Secure Login Server 3.0 depend on the current status of the authentication:

object {               # Secure Login Server 3.0 SP01 REST API
 
    # session security properties
    object {
        string field["j_salt"];    # salt name for session
        string value;              # salt value for session
    } sessionSalt;                 # always sent until authentication is successful and the user has a session    
    
    # client profile properties
    object {
        # common client configuration
        integer keySize;
        # Secure Login Client configuration
        boolean autoLogOut;
        integer gracePeriod;
        integer inactivityTimeout;
        integer reAuthentication;
        boolean showSuccessMsg;
        integer autoReenrollTries;
    } clientConfig;    # client configuration, first response only
 
    # client UI messages properties
    object {
        string disclaimer;               # user language specific client disclaimer
        string messageText;              # user language specific informative text for authentication
        integer messageType[0,1,2,3];    # "0": "ok", "1": "warning", "2": "error", "3": "no icon"
        integer messageStatus;           # textual representation of messageType: "ok", "warning", "error", "none"
    } clientMessage;                     # always present
 
    # client UI fields properties
    array [
        object {
            string field;                       # field names for the response
            string preset;                      # predefined value for the input field as example a username
            string userprompt;                  # user language specific prompt text of this field
            string type["input", "password"];   # "input": visible text, "password": hidden/protected text
            string confirmField;                # [optional] field name for the input to be confirmed
        };
    ] userInput;    # always present, but may be empty
 
    # certificate requirements properties
    object {
        string subjectNameDER;    # base64 encoded ASN.1 DER blob with X.509 subjectName
        string subjectName;       # PrintableString encoded string with X.509 subjectName
        string extensionsDER;     # base64 encoded ASN.1 DER blob with X.509v3 extensions
           
        object {
            object {
                boolean cA;
                integer pathLenConstraint;
                boolean isCritical;
            } basicConstraints;
 
            object {
                boolean digitalSignature;
                boolean nonRepudiation;
                boolean keyEncipherment;
                boolean dataEncipherment;
                boolean keyAgreement;
                boolean keyCertSign;
                boolean cRLSign;
                boolean encipherOnly;
                boolean decipherOnly;
                boolean isCritical;
            } keyUsage;
 
            object {
                array [
                    object {
                        string name;
                        string objectId; # ASN.1 object ID of extended key usage
                    };
                ] extKeyUsage;
                boolean isCritical;
            } extKeyUsages;
 
            object {
                integer secureLoginClientPort;
                boolean isCritical;
            } secureLoginExtension;
 
            object {
                array [
                    object {
                        string type["rfc822Name", "dNSName", "upn", "iPAddress"]; # UPN (UserPrincipalName) is otherName with ObjectID "1.3.6.1.4.1.311.20.2.3" and UTF8String name value
                        string value;
                    };
                ] generalNames;
                boolean isCritical;
            } subjectAltNames;
     
        } extensions;
 
   } certificateTemplate;    # after successful authentication only

};


CLIENT FORM VALUES SPECIFICATION

Your client has to return all requested input fields with the specified names and user values in URL encoded form.

The syntax is:

name1=<URL-encoded-value1>&name2=<URL-encoded-value2>&...

Only in the initial request, your client may also give some desired certificate name properties which can be considered by Secure Login Server for the generation of the returned certificate template REST object. This is helpful for automated requests for servers or unattended clients, e.g. in a Certificate Lifecycle Management scenario.

The following properties are supported:

subjectName             : the full X.509 Distinguished Name
subjectAltNamesRfc822   : one or more mail addresses (comma separated)
subjectAltNamesDNS      : one or more DNS names (comma separated)


CERTIFICATE TEMPLATE AND REQUEST

Secure Login Server has to receive a correct and complete PKCS#10 certificate signing request from your client. Especially if a Remote CA is used, Secure Login Server is unable to change and re-sign the request.

To allow your client to create such complete PKCS#10, Secure Login Server tells you which certificate attributes you have to add. This is done once the authentication loop is completed. Secure Login Server now sends a certificate template REST object.

You have two choices for your implementation:

  1. You take over the DER encoded ASN.1 blobs. This is only possible if you use a security or crypto toolkit for your own client that supports ASN.1 construction.
  2. You take over the string representatives and construct the respective X.509 certificate extensions. This also requires toolkit capabilities, but usually it´s a higher level API.

If your Secure Login Server version is lower than 3.0 SP01 PL01, your ASN.1 DER encoding must match exactly with the blob you have received.

Beginning with PL01, the comparison algorithm is more tolerant and accepts a different order of extensions as well as different string encodings in name attributes.

NODEJS SAMPLE REST CLIENT

This tiny NodeJS program is able to illustrate all kinds of Secure Login Server 3.0 authentication mechanisms, except SPNEGO. You should run Secure Login Server 3.0 SP01 or later.
If you like to implement your own client, try to get the sample running with your server first. You can learn a lot, with full access to all HTTP requests and responses.

// Basic Secure Login Client written in NodeJS
//
// Demo for interactive multi-step authentication with Secure Login Server 3.0 and REST protocol version SLC3.
//
// Copyright (c) SAP, All right reserved
//
// Author:
//   Stephan André 
//   Development Manager & Product Owner
//   SAP Single Sign-On & CommonCryptoLib
//   Products & Innovation Technology Security & Crypto 
//   SAP SE
//
// Version:
//   1.0, October 2016
//
// requires node-forge package for RSA key and PKCS#10 generation (https://github.com/digitalbazaar/forge)
// requires prompt package for nice user interaction (https://github.com/flatiron/prompt)

var fs = require('fs');
var url = require('url');
var crypto = require('crypto');
var https = require('https');
var forge = require('node-forge');
var prompt = require('prompt');

// add your own defaults here
var defUser = '';
var defPass = '';
var verbose = false;
var slsEnrollURL = 'https://sapsso-sls-test.mo.sap.corp/SecureLoginServer/slc3/doLogin?profile=for-my-rest-based-nodejs-client';
var slsTrustedRootURL = 'https://sapsso-sls-test.mo.sap.corp/SS0@Monsoon%20Root%20CA.crt';
var slsProfileID = '';

process.argv.forEach(function(val, index) {
	if (val == '-u' || val == '--username') { defUser = process.argv[index+1] };
	if (val == '-p' || val == '--password') { defPass = process.argv[index+1] };
	if (val == '-e' || val == '--enroll') { slsEnrollURL = process.argv[index+1] };
	if (val == '-i' || val == '--profile') { slsProfileID = process.argv[index+1] };
	if (val == '-t' || val == '--trust') { slsTrustedRootURL = process.argv[index+1] };
	if (val == '-v' || val == '--verbose') { verbose = true };
	if (val == '-h' || val == '--help') { 	
		console.log(process.argv);
		console.log('Options: [ -h | --help ]              print this help text');
		console.log('         [ -v | --verbose ]           print out all log information');
		console.log('         [ -u | --username username ] set default username for prompt and subjectName request');
		console.log('         [ -p | --password password ] set default password for prompt');
		console.log('         [ -e | --enroll url ]        SLS profile URL to enroll with');
		console.log('         [ -i | --profile ID ]        SLS profile ID (replacing default)');
		console.log('         [ -t | --trust url ]         URL to get trusted root for TLS (DER encoded, https:// or file)');
		process.exit(0);
	};
});
if (verbose == false) {
	console.log = function() {};	
}
if (slsProfileID != '') {
	var host = url.parse(slsEnrollURL).hostname;
	var	port = url.parse(slsEnrollURL).port;
	if (port == null) { port = 443 };
	slsEnrollURL =	'https://' + host + ':' + port + '/SecureLoginServer/slc3/getCertificate?profile=' + slsProfileID;
}

var SLC_USER_AGENT = 'SLC-NodeJS REST Blog 1.0';
var SLC_CLIENT_ID = 'Client=' + SLC_USER_AGENT;

console.log('\n----- ' + SLC_USER_AGENT + ' -----\n');

var x509 = new x509();
var cookies = new cookies();

function PSE() {
	this.newkey = null;
	this.keepkey = '';
	this.key = '';
	this.pkcs10 = '';
	this.pkcs7 = '';
	
	this.genPKCS10 = function(keySize, subjectName, subjectNameDER, extensions, extensionsDER) {

		console.log('genPKCS10: keySize = ' + keySize +
			'\n(Only multiples of 1024 supported by this NodeJS implementation, eventually next higher one is taken)\n');
		
		// sample REST response from SLS 3.0 SP01:
		// certificateTemplate:
		//  { subjectName: 'CN= ...',
		//    subjectNameDER: 'MFsxEDA ...',
		//    extensions:
		//     { keyUsage:
		//       { digitalSignature: true,
		//         nonRepudiation: false,
		//         keyEncipherment: true,
		//         dataEncipherment: true,
		//         encipherOnly: false,
		//         decipherOnly: false,
		//         keyAgreement: true,
		//         keyCertSign: false,
		//         isCritical: true },
		//       basicConstraints:
		//        { ca: false,
		//          isCritical: true },
		//       extKeyUsages:
		//        { extKeyUsage:
		//           [ { objectId: '1.3.6.1.5.5.7.3.1',
		//               name: 'serverAuth' } ],
		//          isCritical: false },
		//       subjectAltNames:
		//        { generalNames:
		//           [ { value: 'mail@domain.org',
		//               type: 'rfc822Name' },
		//             { value: 'server.domain.org',
		//               type: 'dNSName' } ],
		//           isCritical: false } },
		//    extensionsDER: 'MGMwDgY ...' }

		var csr = forge.pki.createCertificationRequest();
		this.newkey = forge.pki.rsa.generateKeyPair(keySize);
		csr.publicKey = this.newkey.publicKey;

		// get the subjectName
		// limitations in this sample:
		// - only PrintableStrings
		// - only trivial RDNs, no commatas allowed
		// important: ASN.1 encoding order is right to left
		var attrs = [];
		subjectName.toString().split(",").forEach(function(value) {
			var rdn = value.split("=");
			attrs.unshift( { shortName: rdn[0], value: rdn[1] } );
		});
		csr.setSubject(attrs);
		// get the extensions
		// important: ASN.1 encoding order is reverse JSON order in SLS 3.0 SP0
		var exts = [];
		// 1. get the keyUsages
		if (extensions['keyUsage']) {
			attrs = {};
			attrs['name'] = 'keyUsage';		
			for (var key in extensions['keyUsage']) {
				if (key == 'isCritical') { attrs['critical'] = extensions['keyUsage'].isCritical; }
				// let´s assume that the other aliases are the same in SLS and NodeJS
				else { attrs[key] = extensions['keyUsage'][key]; }
			};
			exts.push(attrs);
		}
		// 2. get the basicConstraints
		if (extensions['basicConstraints']) {
			attrs = {};
			attrs['name'] = 'basicConstraints';		
			attrs['cA'] = extensions['basicConstraints'].cA;	
			attrs['critical'] = extensions['basicConstraints'].isCritical;	
			if (extensions['basicConstraints'].pathLenConstraint >= 0) {
				attrs['pathLenConstraint'] = extensions['basicConstraints'].pathLenConstraint;
			}
			exts.push(attrs);
		}
		// 3. get the extendedKeyUsages
		if (extensions['extKeyUsages']) {
			attrs = {};
			attrs['name'] = 'extKeyUsage';		
			extensions['extKeyUsages'].extKeyUsage.forEach(function(value) {
				// let´s assume that the aliases are the same in SLS and NodeJS
				attrs[value.name] = true;	
			});
			exts.push(attrs);
		}
		// 4. get the subjectAltNames
		if (extensions['subjectAltNames']) {
			attrs = {};
			attrs['name'] = 'subjectAltName';
			var sans = [];	
			extensions['subjectAltNames'].generalNames.forEach(function(value) {
				console.log(value);
				if (value.type == 'upn') {
					console.error('\nERROR: Type userPrincipalName is not supported in this sample.');
				};
				if (value.type == 'rfc822Name') { sans.push( { type: 1, value: value.value } ) };
				if (value.type == 'dNSName') 	{ sans.push( { type: 2, value: value.value } ) };
				if (value.type == 'iPAddress') 	{ sans.push( { type: 7, value: value.value } ) };
			});
			attrs['altNames'] = sans;
			exts.push(attrs);
		}
		// set the extensions now
		csr.setAttributes( [ { name: 'extensionRequest', extensions: exts } ] );
		console.log(csr);
		
		csr.sign(this.newkey.privateKey);
		var verified = csr.verify();
		var pem = forge.pki.certificationRequestToPem(csr);
		this.pkcs10 = pem.split("\n").slice(1,-2).join("\n");
		this.key = forge.pki.privateKeyToPem(this.newkey.privateKey, 64);
		console.log(forge.asn1.prettyPrint(csr.certificationRequestInfo));
	};
	
	this.setPKCS7 = function(pkcs7) { this.pkcs7 = forge.pkcs7.messageFromPem('-----BEGIN PKCS7-----\r\n' + pkcs7 + '\r\n-----END PKCS7-----\r\n'); }
	// let´s assume that SLS always puts the user Cert on top, then CA, then Root
	this.getCert = function() { return forge.pki.certificateToPem(this.pkcs7.certificates[0]); }
	this.getCA = function() { return forge.pki.certificateToPem(this.pkcs7.certificates[1]); }
	this.getRoot = function() { return forge.pki.certificateToPem(this.pkcs7.certificates[2]); }
}

function CLIENT() {
	this.tls_server_root = '';
	this.enroll_url = '';
	this.jsonStatus = {};
	this.pse = new PSE();
}

// begin main
{
	// get the TLS server root for trust -- do this with a local trust store in real life
	trust(slsTrustedRootURL, function(err, tls_server_root) {

		if (!tls_server_root) { console.error('ERROR: Cannot set TLS trust (trust)'); callback(new Error('No TLS trust')); return;}

		console.log(x509.parseCert(tls_server_root));

		var secureLoginClient = new CLIENT();
		secureLoginClient.tls_server_root = tls_server_root;
		secureLoginClient.enroll_url = slsEnrollURL;

		slc_Enroll(secureLoginClient, function(err, pkcs7) {
	
			if (err == null) {
			
				console.log('Client PKCS#7:\n' + pkcs7);
				secureLoginClient.pse.setPKCS7(pkcs7);
				console.info(x509.parseCert(secureLoginClient.pse.getCert()));
			}
		});	
	});
}
// end main

function slc_Enroll(client, callback) {
	
	console.log('[ ***** Entering slc_Enroll ***** ]');
			
	slc_InitialRequest(client, function(err, session) {
		
		if (!session) { console.error('ERROR: Cannot enroll new client (slc_InitialRequest)'); callback(new Error('Bad Request')); return;}
		if (typeof client.jsonStatus['userInput'] == 'undefined') { 
			console.error('ERROR in JSON: No userInput received (slc_InitialRequest)'); callback(new Error('Bad Request')); return;}
		
		console.log("[ *** jsonStatus: *** ]\n", client.jsonStatus);
			
		slc_LoginRequest(session, function(err, session) {

			if (!session) { console.error('ERROR: Cannot enroll new client (slc_LoginRequest)'); callback(new Error('Bad Request')); return;}
			if (typeof client.jsonStatus['certificateTemplate'] == 'undefined') { 
				console.error('ERROR in JSON: No certificateTemplate received (slc_LoginRequest)'); callback(new Error('Bad Request')); return;}
			
			slc_CertificateRequest(session, function(err, pkcs7) {

				if (!pkcs7) { console.error('ERROR: Cannot enroll new client (slc_CertificateRequest)'); callback(new Error('Bad Request')); return;}
		
				// we were successful
				callback(null, pkcs7);
			});
		});			
	});
}

function slc_InitialRequest(client, callback) {
	
	console.log('[ ***** Entering slc_InitialRequest ***** ]');

	var session = {};
	
	session.client = client;
	
	var init_body = SLC_CLIENT_ID;
	if (defUser != '') { init_body = 'subjectName=CN%3D' + encodeURIComponent(defUser) };

	console.log('init_body:\n', init_body);	
	
	var options = {
		host: url.parse(session.client.enroll_url).hostname,
		port: url.parse(session.client.enroll_url).port,
		path: '/SecureLoginServer/slc3/doLogin?profile=' + url.parse(session.client.enroll_url, true).query.profile,
		method: 'POST',
        headers: {
             'User-Agent': SLC_USER_AGENT,
             'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
             'Content-Length': init_body.length
        	},			
		ca: session.client.tls_server_root
	};
	
	var req = https.request(options, function(res) {
        console.log('HTTP STATUS: ' + res.statusCode + ' ' + res.statusMessage);
		var sls_status = '';
		if (res.statusCode != 200) {
			console.error('https.request to SLS profile failed:\n', session.client.enroll_url);
	        console.log('HEADERS: ' + JSON.stringify(res.headers));
			callback(new Error(res.statusCode));
			return;
		}
		res.on('data', function(d) {
			sls_status += d;
		});
		res.on('end', function() {
			var jsonStatus = JSON.parse(sls_status);
		    console.log('INIT answer:\n', jsonStatus);

			session.cookies = cookies.stripParms(res.headers['set-cookie']);
			session.client.jsonStatus = jsonStatus;

			callback(null, session);
	  	});
	});
	req.on('error', function(e) {
		console.error(e);
	});

	req.write(init_body);
}

function slc_LoginRequest(session, callback) {
	
	console.log('[ ***** Entering slc_LoginRequest ***** ]');
    console.log('session: ', session);
    console.log('userInput:\n', session.client.jsonStatus['userInput']);
	
	var schema = {};
	var properties = {};
	session.client.jsonStatus['userInput'].forEach(function(value) {
		if (value.type == 'input') {
			properties['j_username'] = { description: value.userprompt, hidden: false, required: true, default: defUser };
		}
		if (value.type == 'password') {
			properties['j_password'] = { description: value.userprompt, hidden: true, replace: '*', required: true, default: defPass };
		}
	});
	schema['properties'] = properties;
    console.log('schema:\n', schema);
	
	var clientMessage = session.client.jsonStatus['clientMessage'];
	var message = '--------------------------------------------------------------------------------------';
	if (clientMessage.messageType == 0 || clientMessage.messageType == 3 || clientMessage.messageStatus == 'ok' || clientMessage.messageStatus == 'none') {
	    message += '\n[ > ] ';
	}
	if (clientMessage.messageType == 1 || clientMessage.messageStatus == 'warning') {
	    message += '\n[ ? ] ';
	}
	if (clientMessage.messageType == 2 || clientMessage.messageStatus == 'error') {
	    message += '\n[ ! ] ';
	}
	if (clientMessage.messageText != '') {
	    message += clientMessage.messageText;
		if (message[message.length-1] != '.') { message += '.' };
		message += ' ';
	}
    message += 'Enter your credentials:';
    console.info(message);
	prompt.get(schema, function (err, result) {

	    if (err == 'Error: canceled') { console.info(''); process.exit(0) };
	    console.log(result);
		
		if (session.client.jsonStatus['sessionSalt'] == null) {
			console.error('ERROR: Server did not send a session salt. Obviously, the security session was invalidated.'); 
			process.exit(0)
		};

		var auth_body = 'j_username=' + encodeURIComponent(result.j_username) +
						'&j_password=' + encodeURIComponent(result.j_password) +
						'&j_salt=' + encodeURIComponent(session.client.jsonStatus['sessionSalt'].value);

		console.log('auth_body:\n', auth_body);

		var options = {
			host: url.parse(session.client.enroll_url).hostname,
			port: url.parse(session.client.enroll_url).port,
			path: '/SecureLoginServer/slc3/doLogin?profile=' + url.parse(session.client.enroll_url, true).query.profile,
			method: 'POST',
		    headers: {
		         'User-Agent': SLC_USER_AGENT,
		         'Cookie': session.cookies,
		         'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
		         'Content-Length': auth_body.length
		    	},			
			ca: session.client.tls_server_root
		};	

		var req = https.request(options, function(res) {
		    console.log('HTTP STATUS: ' + res.statusCode + ' ' + res.statusMessage);
			var sls_status = '';
			if (res.statusCode != 200) {
		        console.log('HEADERS: ' + JSON.stringify(res.headers));
				callback(new Error(res.statusCode));
				return;
			}
			res.on('data', function(d) {
				sls_status += d;
			});
			res.on('end', function() {
				var jsonStatus = JSON.parse(sls_status);
			    console.log('AUTH answer:\n', jsonStatus);

				session.cookies.push(cookies.stripParms(res.headers['set-cookie']));
				session.client.jsonStatus = jsonStatus;
				
				if (typeof session.client.jsonStatus['certificateTemplate'] == 'undefined') {

				    console.log('[ ***** Recursively calling slc_LoginRequest ... ***** ]');

					slc_LoginRequest(session, function(err, session) {
						// yet another authentication request
						callback(null, session);
					});					
				}
				else {
					// no more authentication requests
					callback(null, session);
				}
		  	});
		});
		req.on('error', function(e) {
			console.error(e);
		});

		req.write(auth_body);
	});
}

function slc_CertificateRequest(session, callback) {
	
	console.log('[ ***** Entering slc_CertificateRequest ***** ]');
	
    console.log('certificateTemplate:\n', session.client.jsonStatus['certificateTemplate']);
    console.log('subjectAltNames:\n', session.client.jsonStatus['certificateTemplate'].extensions['subjectAltNames']);
    console.log('extendedKeyUsage:\n', session.client.jsonStatus['certificateTemplate'].extensions['extKeyUsages']);
	
	session.client.pse.genPKCS10(	session.client.jsonStatus['clientConfig'].keySize,
									session.client.jsonStatus['certificateTemplate'].subjectName,
									session.client.jsonStatus['certificateTemplate'].subjectNameDER,
									session.client.jsonStatus['certificateTemplate'].extensions,
									session.client.jsonStatus['certificateTemplate'].extensionsDER);
	console.log('Client PKCS#10:\n' + session.client.pse.pkcs10);

	var options = {
		host: url.parse(session.client.enroll_url).hostname,
		port: url.parse(session.client.enroll_url).port,
		path: '/SecureLoginServer/slc3/getCertificate?profile=' + url.parse(session.client.enroll_url, true).query.profile,
		method: 'POST',
        headers: {
             'User-Agent': SLC_USER_AGENT,
             'Cookie': session.cookies,
             'Content-Type': 'application/pkcs10; name=certreq.p10',
             'Content-Length': session.client.pse.pkcs10.length
        	},			
		ca: session.client.tls_server_root
	};

	var req = https.request(options, function(res) {
        console.log('HTTP STATUS: ' + res.statusCode + ' ' + res.statusMessage);
		var pkcs7 = '';
		res.on('data', function(d) {
			pkcs7 += d;
		});
		res.on('end', function() {
			if (res.statusCode != 200) {
		        console.log('HEADERS: ' + JSON.stringify(res.headers));
				callback(new Error(res.statusCode));
				return;
			}
			callback(null, pkcs7);
	  	});
	});
	req.on('error', function(e) {
		console.error(e);
	});

	req.write(session.client.pse.pkcs10);
}

function trust(slsTrustedRootURL, callback) {
	
	console.log('[ ***** Entering slc_GetRootCert ***** ]');
	
	if (slsTrustedRootURL.substr(0,3) == 'http') {
		
		var options = {
			host: url.parse(slsTrustedRootURL).hostname,
			port: url.parse(slsTrustedRootURL).port,
			path: url.parse(slsTrustedRootURL).path,
			method: 'GET',
			rejectUnauthorized: false,	// download of file without trusted root
	        headers: { 'User-Agent': SLC_USER_AGENT	}
		};
	
		var req = https.request(options, function(res) {
			res.setEncoding('binary');
	        console.log('HTTP STATUS: ' + res.statusCode + ' ' + res.statusMessage);
	        console.log('HEADERS: ' + JSON.stringify(res.headers));
			if (res.statusCode != 200) {
		        console.error('HEADERS: ' + JSON.stringify(res.headers));
			    process.exit(1);
			}
			var data = '';
			res.on('data', function(d) {
		        data += d;
			});
			res.on('end', function() {
				var rootPEM = forge.pem.encode({type: 'CERTIFICATE', body: data}, 64);
		        console.log('HTTP GET returned data (now PEM encoded):\n', rootPEM);
				callback(null, rootPEM);
		  	});
		});
		req.on('error', function(e) {
			console.error(e);
		});
	
		req.end();
	}
	else {

		console.error('readFile ', slsTrustedRootURL);
		
		fs.readFile(slsTrustedRootURL, 'binary', function (err, data) {
			if (err) {
				console.error(err);
		    	process.exit(1);
		  	}
			var rootPEM = forge.pem.encode({type: 'CERTIFICATE', body: data}, 64);
	        console.log('readFile returned data (now PEM encoded):\n', rootPEM);
			callback(null, rootPEM);
		});		
	}
}

function cookies() {
	
	this.stripParms = function(object) {
		
		var cookies = new Array();
		for (var key in object) {
			var cookie = object[key].split(";").slice(0,1);
			if (cookie[0].slice(-1) != "=")
				cookies.push(cookie.join(","));
		}
		return cookies;
	};
}

function x509() {
	
	this.getSubjectCN = function(s) {
		
		var cert = forge.pki.certificateFromPem(s);
		var object = cert.subject.attributes;
		
		for (var key in object) {
			if (object[key].shortName == 'CN') {
				return object[key].value;
			}
		}
		return '';
	}
	this.parseDistinguishedName = function(object) {
		
		var dn = '';
		for (var key in object) {
			dn = object[key].shortName + '=' +	object[key].value + ', ' + dn;
		}
		return dn.substring(0, dn.length - 2);
	}
	this.parseBasicConstraints = function(object) {
		
		var bc = '';
		for (var key in object) {
			if (object[key].name == 'basicConstraints') {
				bc += '\tCRITICAL: ' + object[key].critical + 			
				'\n\t\t\t\t      cA: ' + object[key].cA;
			}
		}
		return bc;
	}
	this.parseExtendedKeyUsage = function(object) {
		
		var bc = '';
		for (var key in object) {
			if (object[key].name == 'extKeyUsage' && object[key].clientAuth ) {
				bc += '\tCRITICAL: ' + object[key].critical + 			
				'\n\t\t\t      clientAuth: ' + object[key].clientAuth;
			}
			if (object[key].name == 'extKeyUsage' && object[key].serverAuth ) {
				bc += '\tCRITICAL: ' + object[key].critical + 			
				'\n\t\t\t      serverAuth: ' + object[key].serverAuth;
			}
		}
		return bc;
	}
	this.parseKeyUsage = function(object) {
		
		var ku = '';
		for (var key in object) {
			if (object[key].name == 'keyUsage') {
				ku += '\tCRITICAL: ' + object[key].critical + 			
					'\n\t\t\tdigitalSignature: ' + object[key].digitalSignature +
					'\n\t\t\t  nonRepudiation: ' + object[key].nonRepudiation +
					'\n\t\t\t keyEncipherment: ' + object[key].keyEncipherment +
					'\n\t\t\tdataEncipherment: ' + object[key].dataEncipherment +
					'\n\t\t\t    keyAgreement: ' + object[key].keyAgreement +
					'\n\t\t\t     keyCertSign: ' + object[key].keyCertSign +
					'\n\t\t\t         cRLSign: ' + object[key].digitalSignature +
					'\n\t\t\t    encipherOnly: ' + object[key].encipherOnly +
					'\n\t\t\t    decipherOnly: ' + object[key].decipherOnly;
			}
		}
		return ku;
	}	
	this.parseSubjAltName = function(object) {
		
        var subjAlt = '';
        for (var key in object) {               
            if (object[key].name == 'subjectAltName') {                                                   
                for (i=0; i<object[key].altNames.length; i++) {
                	subjAlt += '\n\t\t\t   ';
					switch (object[key].altNames[i].type) {
						case 0: subjAlt += '    otherName: '; break;
						case 1: subjAlt += '   rfc822Name: '; break;
						case 2: subjAlt += '      dNSName: '; break;
						case 3: subjAlt += '  x400Address: '; break;
						case 4: subjAlt += 'directoryName: '; break;
						case 5: subjAlt += ' ediPartyName: '; break;
						case 6: subjAlt += ' uniformResId: '; break;
						case 7: subjAlt += '    IPAddress: '; break;
						case 8: subjAlt += ' registeredID: '; break;
					}
                	subjAlt += object[key].altNames[i].value;
                }
            }
        }
        return subjAlt;
	};
    this.parseCert = function(s) {
			
		var cert = forge.pki.certificateFromPem(s);
		return 'Certificate:' + 
		'\n\tSubject:         ' + x509.parseDistinguishedName(cert.subject.attributes) +
		'\n\tIssuer:          ' + x509.parseDistinguishedName(cert.issuer.attributes) +
		'\n\tSerial number:   ' + cert.serialNumber +
		'\n\tKey size:        ' + Math.ceil(cert.publicKey.n.bitLength()) +
		'\n\tValidity:' +
		'\n\t\tNot before:    ' + cert.validity.notBefore +
		'\n\t\tNot after:     ' + cert.validity.notAfter +
		'\n\tExtensions:' +
		'\n\t\tBasic constr.: ' + x509.parseBasicConstraints(cert.extensions) +
		'\n\t\tKey usage:     ' + x509.parseKeyUsage(cert.extensions) +
		'\n\t\tExt. key usage:' + x509.parseExtendedKeyUsage(cert.extensions) +
		'\n\t\tAltern. names: ' + x509.parseSubjAltName(cert.extensions);
	};
}

2 Comments
You must be Logged on to comment or reply to a post.
  • A change was made in the REST specification:

    The subjectName must be PrintableString encoded, not UTF-8.

    The sample code was already considering this.

  • Another change was made:

    If your Secure Login Server version is lower than 3.0 SP01 PL01, your ASN.1 DER encoding must match exactly with the blob you have received.

    Beginning with PL01, the comparison algorithm is more tolerant and accepts a different order of extensions as well as different string encodings in name attributes.