diff options
author | Minteck <contact@minteck.org> | 2022-10-18 08:59:09 +0200 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2022-10-18 08:59:09 +0200 |
commit | 2c4ae43e688a9873e86211ea0e7aeb9ba770dd77 (patch) | |
tree | 17848d95522dab25d3cdeb9c4a6450e2a234861f /alarm/node_modules/node-forge/tests/nodejs-ws-webid.js | |
parent | 108525534c28013cfe1897c30e4565f9893f3766 (diff) | |
download | pluralconnect-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.js | 491 |
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); |