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
};
name1=<URL-encoded-value1>&name2=<URL-encoded-value2>&...
subjectName : the full X.509 Distinguished Name
subjectAltNamesRfc822 : one or more mail addresses (comma separated)
subjectAltNamesDNS : one or more DNS names (comma separated)
// 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);
};
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
20 | |
14 | |
11 | |
9 | |
8 | |
8 | |
7 | |
6 | |
6 | |
6 |