summaryrefslogtreecommitdiff
path: root/alarm/node_modules/node-forge/tests/nodejs-ws-webid.js
diff options
context:
space:
mode:
authorMinteck <contact@minteck.org>2022-10-18 08:59:09 +0200
committerMinteck <contact@minteck.org>2022-10-18 08:59:09 +0200
commit2c4ae43e688a9873e86211ea0e7aeb9ba770dd77 (patch)
tree17848d95522dab25d3cdeb9c4a6450e2a234861f /alarm/node_modules/node-forge/tests/nodejs-ws-webid.js
parent108525534c28013cfe1897c30e4565f9893f3766 (diff)
downloadpluralconnect-2c4ae43e688a9873e86211ea0e7aeb9ba770dd77.tar.gz
pluralconnect-2c4ae43e688a9873e86211ea0e7aeb9ba770dd77.tar.bz2
pluralconnect-2c4ae43e688a9873e86211ea0e7aeb9ba770dd77.zip
Update
Diffstat (limited to 'alarm/node_modules/node-forge/tests/nodejs-ws-webid.js')
-rw-r--r--alarm/node_modules/node-forge/tests/nodejs-ws-webid.js491
1 files changed, 491 insertions, 0 deletions
diff --git a/alarm/node_modules/node-forge/tests/nodejs-ws-webid.js b/alarm/node_modules/node-forge/tests/nodejs-ws-webid.js
new file mode 100644
index 0000000..fae0b82
--- /dev/null
+++ b/alarm/node_modules/node-forge/tests/nodejs-ws-webid.js
@@ -0,0 +1,491 @@
+var forge = require('../js/forge');
+var fs = require('fs');
+var http = require('http');
+//var rdf = require('./rdflib');
+var sys = require('sys');
+var urllib = require('url');
+var ws = require('./ws');
+
+// remove xmlns from input
+var normalizeNs = function(input, ns) {
+ var rval = null;
+
+ // primitive
+ if(typeof input === 'string' ||
+ typeof input === 'number' ||
+ typeof input === 'boolean') {
+ rval = input;
+ }
+ // array
+ else if(forge.util.isArray(input)) {
+ rval = [];
+ for(var i = 0; i < input.length; ++i) {
+ rval.push(normalizeNs(input[i], ns));
+ }
+ }
+ // object
+ else {
+ if('@' in input) {
+ // copy namespace map
+ var newNs = {};
+ for(var key in ns) {
+ newNs[key] = ns[key];
+ }
+ ns = newNs;
+
+ // update namespace map
+ for(var key in input['@']) {
+ if(key.indexOf('xmlns:') === 0) {
+ ns[key.substr(6)] = input['@'][key];
+ }
+ }
+ }
+
+ rval = {};
+ for(var key in input) {
+ if(key.indexOf('xmlns:') !== 0) {
+ var value = input[key];
+ var colon = key.indexOf(':');
+ if(colon !== -1) {
+ var prefix = key.substr(0, colon);
+ if(prefix in ns) {
+ key = ns[prefix] + key.substr(colon + 1);
+ }
+ }
+ rval[key] = normalizeNs(value, ns);
+ }
+ }
+ }
+
+ return rval;
+};
+
+// gets public key from WebID rdf
+var getPublicKey = function(data, uri, callback) {
+ // FIXME: use RDF library to simplify code below
+ //var kb = new rdf.RDFParser(rdf.IndexedFormula(), uri).loadBuf(data);
+ //var CERT = rdf.Namespace('http://www.w3.org/ns/auth/cert#');
+ //var RSA = rdf.Namespace('http://www.w3.org/ns/auth/rsa#');
+ var RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
+ var CERT = 'http://www.w3.org/ns/auth/cert#';
+ var RSA = 'http://www.w3.org/ns/auth/rsa#';
+ var desc = RDF + 'Description';
+ var about = RDF + 'about';
+ var type = RDF + 'type';
+ var resource = RDF + 'resource';
+ var publicKey = RSA + 'RSAPublicKey';
+ var modulus = RSA + 'modulus';
+ var exponent = RSA + 'public_exponent';
+ var identity = CERT + 'identity';
+ var hex = CERT + 'hex';
+ var decimal = CERT + 'decimal';
+
+ // gets a resource identifer from a node
+ var getResource = function(node, key) {
+ var rval = null;
+
+ // special case 'about'
+ if(key === about) {
+ if('@' in node && about in node['@']) {
+ rval = node['@'][about];
+ }
+ }
+ // any other resource
+ else if(
+ key in node &&
+ typeof node[key] === 'object' && !forge.util.isArray(node[key]) &&
+ '@' in node[key] && resource in node[key]['@']) {
+ rval = node[key]['@'][resource];
+ }
+
+ return rval;
+ };
+
+ // parse XML
+ uri = urllib.parse(uri);
+ var xml2js = require('./xml2js');
+ var parser = new xml2js.Parser();
+ parser.addListener('end', function(result) {
+ // normalize namespaces
+ result = normalizeNs(result, {});
+
+ // find grab all public keys whose identity matches hash from uri
+ var keys = [];
+ if(desc in result) {
+ // normalize RDF descriptions to array
+ if(!forge.util.isArray(result[desc])) {
+ desc = [result[desc]];
+ }
+ else {
+ desc = result[desc];
+ }
+
+ // collect properties for all resources
+ var graph = {};
+ for(var i = 0; i < desc.length; ++i) {
+ var node = desc[i];
+ var res = {};
+ for(var key in node) {
+ var obj = getResource(node, key);
+ res[key] = (obj === null) ? node[key] : obj;
+ }
+ graph[getResource(node, about) || ''] = res;
+ }
+
+ // for every public key w/identity that matches the uri hash
+ // save the public key modulus and exponent
+ for(var r in graph) {
+ var props = graph[r];
+ if(identity in props &&
+ type in props &&
+ props[type] === publicKey &&
+ props[identity] === uri.hash &&
+ modulus in props &&
+ exponent in props &&
+ props[modulus] in graph &&
+ props[exponent] in graph &&
+ hex in graph[props[modulus]] &&
+ decimal in graph[props[exponent]]) {
+ keys.push({
+ modulus: graph[props[modulus]][hex],
+ exponent: graph[props[exponent]][decimal]
+ });
+ }
+ }
+ }
+
+ sys.log('Public keys from RDF: ' + JSON.stringify(keys));
+ callback(keys);
+ });
+ parser.parseString(data);
+};
+
+// compares two public keys for equality
+var comparePublicKeys = function(key1, key2) {
+ return key1.modulus === key2.modulus && key1.exponent === key2.exponent;
+};
+
+// gets the RDF data from a URL
+var fetchUrl = function(url, callback, redirects) {
+ // allow 3 redirects by default
+ if(typeof(redirects) === 'undefined') {
+ redirects = 3;
+ }
+
+ sys.log('Fetching URL: \"' + url + '\"');
+
+ // parse URL
+ url = forge.util.parseUrl(url);
+ var client = http.createClient(
+ url.port, url.fullHost, url.scheme === 'https');
+ var request = client.request('GET', url.path, {
+ 'Host': url.host,
+ 'Accept': 'application/rdf+xml'
+ });
+ request.addListener('response', function(response) {
+ var body = '';
+
+ // error, return empty body
+ if(response.statusCode >= 400) {
+ callback(body);
+ }
+ // follow redirect
+ else if(response.statusCode === 302) {
+ if(redirects > 0) {
+ // follow redirect
+ fetchUrl(response.headers.location, callback, --redirects);
+ }
+ else {
+ // return empty body
+ callback(body);
+ }
+ }
+ // handle data
+ else {
+ response.setEncoding('utf8');
+ response.addListener('data', function(chunk) {
+ body += chunk;
+ });
+ response.addListener('end', function() {
+ callback(body);
+ });
+ }
+ });
+ request.end();
+};
+
+// does WebID authentication
+var authenticateWebId = function(c, state) {
+ var auth = false;
+
+ // get client-certificate
+ var cert = c.peerCertificate;
+
+ // get public key from certificate
+ var publicKey = {
+ modulus: cert.publicKey.n.toString(16).toLowerCase(),
+ exponent: cert.publicKey.e.toString(10)
+ };
+
+ sys.log(
+ 'Server verifying certificate w/CN: \"' +
+ cert.subject.getField('CN').value + '\"\n' +
+ 'Public Key: ' + JSON.stringify(publicKey));
+
+ // build queue of subject alternative names to authenticate with
+ var altNames = [];
+ var ext = cert.getExtension({name: 'subjectAltName'});
+ if(ext !== null && ext.altNames) {
+ for(var i = 0; i < ext.altNames.length; ++i) {
+ var altName = ext.altNames[i];
+ if(altName.type === 6) {
+ altNames.push(altName.value);
+ }
+ }
+ }
+
+ // create authentication processor
+ var authNext = function() {
+ if(!auth) {
+ // no more alt names, auth failed
+ if(altNames.length === 0) {
+ sys.log('WebID authentication FAILED.');
+ c.prepare(JSON.stringify({
+ success: false,
+ error: 'Not Authenticated'
+ }));
+ c.close();
+ }
+ // try next alt name
+ else {
+ // fetch URL
+ var url = altNames.shift();
+ fetchUrl(url, function(body) {
+ // get public key
+ getPublicKey(body, url, function(keys) {
+ // compare public keys from RDF until one matches
+ for(var i = 0; !auth && i < keys.length; ++i) {
+ auth = comparePublicKeys(keys[i], publicKey);
+ }
+ if(auth) {
+ // send authenticated notice to client
+ sys.log('WebID authentication PASSED.');
+ state.authenticated = true;
+ c.prepare(JSON.stringify({
+ success: true,
+ cert: forge.pki.certificateToPem(cert),
+ webID: url,
+ rdf: forge.util.encode64(body)
+ }));
+ }
+ else {
+ // try next alt name
+ authNext();
+ }
+ });
+ });
+ }
+ }
+ };
+
+ // do auth
+ authNext();
+};
+
+// creates credentials (private key + certificate)
+var createCredentials = function(cn, credentials) {
+ sys.log('Generating 512-bit key-pair and certificate for \"' + cn + '\".');
+ var keys = forge.pki.rsa.generateKeyPair(512);
+ sys.log('key-pair created.');
+
+ var cert = forge.pki.createCertificate();
+ cert.serialNumber = '01';
+ cert.validity.notBefore = new Date();
+ cert.validity.notAfter = new Date();
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
+ var attrs = [{
+ name: 'commonName',
+ value: cn
+ }, {
+ name: 'countryName',
+ value: 'US'
+ }, {
+ shortName: 'ST',
+ value: 'Virginia'
+ }, {
+ name: 'localityName',
+ value: 'Blacksburg'
+ }, {
+ name: 'organizationName',
+ value: 'Test'
+ }, {
+ shortName: 'OU',
+ value: 'Test'
+ }];
+ cert.setSubject(attrs);
+ cert.setIssuer(attrs);
+ cert.setExtensions([{
+ name: 'basicConstraints',
+ cA: true
+ }, {
+ name: 'keyUsage',
+ keyCertSign: true,
+ digitalSignature: true,
+ nonRepudiation: true,
+ keyEncipherment: true,
+ dataEncipherment: true
+ }, {
+ name: 'subjectAltName',
+ altNames: [{
+ type: 6, // URI
+ value: 'http://myuri.com/webid#me'
+ }]
+ }]);
+ // FIXME: add subjectKeyIdentifier extension
+ // FIXME: add authorityKeyIdentifier extension
+ cert.publicKey = keys.publicKey;
+
+ // self-sign certificate
+ cert.sign(keys.privateKey);
+
+ // save credentials
+ credentials.key = forge.pki.privateKeyToPem(keys.privateKey);
+ credentials.cert = forge.pki.certificateToPem(cert);
+
+ sys.log('Certificate created for \"' + cn + '\": \n' + credentials.cert);
+};
+
+// initialize credentials
+var credentials = {
+ key: null,
+ cert: null
+};
+
+// read private key from file
+var readPrivateKey = function(filename) {
+ credentials.key = fs.readFileSync(filename);
+ // try to parse from PEM as test
+ forge.pki.privateKeyFromPem(credentials.key);
+};
+
+// read certificate from file
+var readCertificate = function(filename) {
+ credentials.cert = fs.readFileSync(filename);
+ // try to parse from PEM as test
+ forge.pki.certificateFromPem(credentials.cert);
+};
+
+// parse command line options
+var opts = require('opts');
+var options = [
+{ short : 'v'
+, long : 'version'
+, description : 'Show version and exit'
+, callback : function() { console.log('v1.0'); process.exit(1); }
+},
+{ short : 'p'
+, long : 'port'
+, description : 'The port to listen for WebSocket connections on'
+, value : true
+},
+{ long : 'key'
+, description : 'The server private key file to use in PEM format'
+, value : true
+, callback : readPrivateKey
+},
+{ long : 'cert'
+, description : 'The server certificate file to use in PEM format'
+, value : true
+, callback : readCertificate
+}
+];
+opts.parse(options, true);
+
+// create credentials for server
+if(credentials.key === null || credentials.cert === null) {
+ createCredentials('server', credentials);
+}
+
+// function to create TLS server connection
+var createTls = function(websocket) {
+ var state = {
+ authenticated: false
+ };
+ return forge.tls.createConnection({
+ server: true,
+ caStore: [],
+ sessionCache: {},
+ // supported cipher suites in order of preference
+ cipherSuites: [
+ forge.tls.CipherSuites.TLS_RSA_WITH_AES_128_CBC_SHA,
+ forge.tls.CipherSuites.TLS_RSA_WITH_AES_256_CBC_SHA],
+ connected: function(c) {
+ sys.log('Server connected');
+
+ // do WebID authentication
+ try {
+ authenticateWebId(c, state);
+ }
+ catch(ex) {
+ c.close();
+ }
+ },
+ verifyClient: true,
+ verify: function(c, verified, depth, certs) {
+ // accept certs w/unknown-CA (48)
+ if(verified === 48) {
+ verified = true;
+ }
+ return verified;
+ },
+ getCertificate: function(c, hint) {
+ sys.log('Server using certificate for \"' + hint[0] + '\"');
+ return credentials.cert;
+ },
+ getPrivateKey: function(c, cert) {
+ return credentials.key;
+ },
+ tlsDataReady: function(c) {
+ // send base64-encoded TLS data over websocket
+ websocket.write(forge.util.encode64(c.tlsData.getBytes()));
+ },
+ dataReady: function(c) {
+ // ignore any data until connection is authenticated
+ if(state.authenticated) {
+ sys.log('Server received \"' + c.data.getBytes() + '\"');
+ }
+ },
+ closed: function(c) {
+ sys.log('Server disconnected');
+ websocket.end();
+ },
+ error: function(c, error) {
+ sys.log('Server error: ' + error.message);
+ }
+ });
+};
+
+// create websocket server
+var port = opts.get('port') || 8080;
+ws.createServer(function(websocket) {
+ // create TLS server connection
+ var tls = createTls(websocket);
+
+ websocket.addListener('connect', function(resource) {
+ sys.log('WebSocket connected: ' + resource);
+
+ // close connection after 30 second timeout
+ setTimeout(websocket.end, 30 * 1000);
+ });
+
+ websocket.addListener('data', function(data) {
+ // base64-decode data and process it
+ tls.process(forge.util.decode64(data));
+ });
+
+ websocket.addListener('close', function() {
+ sys.log('WebSocket closed');
+ });
+}).listen(port);
+
+sys.log('WebSocket WebID server running on port ' + port);