diff --git a/examples/tcp-tls.js b/examples/tcp-tls.js new file mode 100644 index 0000000..7576d36 --- /dev/null +++ b/examples/tcp-tls.js @@ -0,0 +1,20 @@ +var hl7 = require('../lib'); + +// openssl genrsa -out ../test/test-key.pem 1024 +// openssl req -new -key ../test/test-key.pem -out csr.pem -subj '/CN=test.localhost/O=Test/C=XX' +// openssl x509 -req -in csr.pem -signkey ../test/test-key.pem -out ../test/test-cert.pem +// rm csr.pem + +////////////////////SERVER/////////////////// +var app = hl7.tcp(); +app.use(function(req, res, next) { + console.log('Message Recieved From ' + req.facility); + console.log('Message Event: ' + req.event); + console.log('Message Type: ' + req.type); + next(); +}); + +app.start(7778,undefined,{key:'../test/test-key.pem',cert:'../test/test-cert.pem'}); +console.log('TLS tcp interface listening on ' + 7778); +////////////////////SERVER/////////////////// + diff --git a/lib/client/tcp-client.js b/lib/client/tcp-client.js index da1c86b..7a4a13a 100644 --- a/lib/client/tcp-client.js +++ b/lib/client/tcp-client.js @@ -1,5 +1,6 @@ var Parser = require('../hl7/parser.js'); var net = require('net') +var tls = require('tls') var VT = String.fromCharCode(0x0b); var FS = String.fromCharCode(0x1c); @@ -18,17 +19,42 @@ function TcpClient() { this.port = this.options.port; this.callback = this.options.callback; this.keepalive = this.options.keepalive; + this.key = this.options.key; + this.cert = this.options.cert; + this.tls = this.options.tls; + this.ca = this.options.ca; + this.rejectUnauthorized = this.options.rejectUnauthorized; + this.checkServerIdentity = this.options.checkServerIdentity; + this.responseBuffer = ""; this.awaitingResponse = false; this.parser = new Parser({ segmentSeperator: '\r' }); + + this.timeout = this.options.timeout; } TcpClient.prototype.connect = function(callback) { var self = this; - self.client = net.connect({host: self.host, port: self.port}, function() { + + const cb = function() { self.client.on('data', function(data) { self.responseBuffer += data.toString(); - if (self.responseBuffer.substring(self.responseBuffer.length - 2, self.responseBuffer.length) == FS + CR) { + + /* + 'Sometimes' the responseBuffer contains things like FS or CR, and so + self.responseBuffer.substring() stops copying early, and the compare to FS+CR fails + So try a real parse, see if it's a response. + */ + var _parsed = false; + var _ackish = false; + try{ + const ackish = self.parser.parse(self.responseBuffer.substring(1, self.responseBuffer.length - 1)); + _parsed = true; + }catch(e){ + console.log('ackish',e) + } + + if (self.responseBuffer.substring(self.responseBuffer.length - 2, self.responseBuffer.length) == FS + CR || _parsed) { var ack = self.parser.parse(self.responseBuffer.substring(1, self.responseBuffer.length - 2)); self.callback(null, ack); self.responseBuffer = ""; @@ -39,10 +65,33 @@ TcpClient.prototype.connect = function(callback) { } }); callback(); - }); + } + + if( (self.key && self.cert) || self.tls ){ + var opts = {host: self.host, port: self.port} // FUTURE client certificate support via .key/.cert + if( self.ca ){ + opts.ca = self.ca; + } + if( typeof self.rejectUnauthorized != 'undefined' ){ // can be true or false, if false still add to opts + opts.rejectUnauthorized = self.rejectUnauthorized; + } + if( self.checkServerIdentity ){ + opts.checkServerIdentity = self.checkServerIdentity; + } + self.client = tls.connect(opts, cb); + if( self.timeout ){ + self.client.setTimeout( self.timeout ) + } + }else{ + self.client = net.connect({host: self.host, port: self.port}, cb); + } + self.client.on('error', function(err) { callback(err); }) + self.client.on('timeout', function(err) { + callback('timeout'); + }) } TcpClient.prototype.send = function(msg, callback) { diff --git a/lib/server/tcp-server.js b/lib/server/tcp-server.js index 161cdc9..ce7840a 100644 --- a/lib/server/tcp-server.js +++ b/lib/server/tcp-server.js @@ -2,6 +2,8 @@ var EventEmitter = require('events').EventEmitter; var Message = require('../hl7/message'); var moment = require('moment'); var net = require('net'); +var tls = require('tls'); +var fs = require('fs'); var Parser = require('../hl7/parser'); var util = require('util'); @@ -52,9 +54,9 @@ function Res(socket, ack) { TcpServer.prototype.start = function(port, encoding, options) { var self = this; options = options || {} - this.server = net.createServer(function(socket) { - var message = ""; + const cb = function(socket) { + var message = ""; socket.on('data', function(data) { try { message += data.toString(); @@ -76,7 +78,17 @@ TcpServer.prototype.start = function(port, encoding, options) { message = ""; self.handler(err); }) - }); + } + + if( options.key && options.cert ){ + this.server = tls.createServer({ + key : fs.readFileSync(options.key), + cert : fs.readFileSync(options.cert) + }, cb ); + }else{ + this.server = net.createServer( cb ); + } + this.server.listen(port); } diff --git a/test/test-cert.pem b/test/test-cert.pem new file mode 100644 index 0000000..3bd5728 --- /dev/null +++ b/test/test-cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7DCCAVUCFB6xGp/ZQKVbdy8cCDPgaRhzBT3nMA0GCSqGSIb3DQEBCwUAMDUx +FzAVBgNVBAMMDnRlc3QubG9jYWxob3N0MQ0wCwYDVQQKDARUZXN0MQswCQYDVQQG +EwJYWDAeFw0yMTA4MjUxMDU0MzhaFw0yMTA5MjQxMDU0MzhaMDUxFzAVBgNVBAMM +DnRlc3QubG9jYWxob3N0MQ0wCwYDVQQKDARUZXN0MQswCQYDVQQGEwJYWDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApWwraVQpDueC1vTc5GnS4YMaibpuCWnq +UioCGEgkS7C6PDqWcOv/isFb0fC4NIzoMfC4PM/Mne2OSVb/9g72Wh8+qFht/qTI +9l7ZwBORxt2f/w4RvL06KFZV+bU4CFqdYE5arSLvRTfgXVKt84KNjeLU2nxdAHDF +aNaAMbBprNsCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCDN28IkRMSCWZmR8YpEtwd +Zwu5pUuG0btRIAE8ysR7hxRoDsfOsOAedg2f+nJFeva47vIf+J3lCx7gcmhVMB/j +R+eQ7PpZY+Sg7E4/h1JNnlrXGlrEpbk+QzzPyNmz9VWsl+kWQH7nqo/xHeF1r7PG +8UvN1jIGGF4TZgqYHoP61Q== +-----END CERTIFICATE----- diff --git a/test/test-key.pem b/test/test-key.pem new file mode 100644 index 0000000..1254c03 --- /dev/null +++ b/test/test-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQClbCtpVCkO54LW9NzkadLhgxqJum4JaepSKgIYSCRLsLo8OpZw +6/+KwVvR8Lg0jOgx8Lg8z8yd7Y5JVv/2DvZaHz6oWG3+pMj2XtnAE5HG3Z//DhG8 +vTooVlX5tTgIWp1gTlqtIu9FN+BdUq3zgo2N4tTafF0AcMVo1oAxsGms2wIDAQAB +AoGAPyko/iN7NcyhgW+m87OQKDjFpja/kuqU5GBTwm+xJBFLWnnIPT9up80iQOuX +ecEfWwodXmK9LsOoY0pLmuilmLAv5p4Ae0WBWFLx4KETicRft250VMwP2siAjRzZ +YHu0c+fU8LVvPJXOyFtyzWOcAGcD+03KmKPm3Iz5y/ER1fkCQQDW5jbspXyYEJ4x +2NFbDqFxrk62n/ysUkLpeZLBPLWwnoQGTZCcHnjIrIuUuX8LMFYEiHmVGDLuKqQ/ +AEmkHAPnAkEAxQ+APwNfPDGmd69T1IL+i9vMIEiPMMR4INLkQ3AM7ngzANPTKMQx +vLned2xw4aAQlS0pZ59rwHa7LmrvQHNw7QJBAIn+4x4rVkq/wRX5SNbUGk/Jwrn7 +GmH9oQIUE20K8xEreVQi0s2ts5oJUsh6JV6l91aJ3KklFO9jan0aTi+X3dkCQC6Y +I7LCkVoiQ0I5apDtCFL5faKEhZb6XY0lUGPBss0QDr87Vspk2X3OVRPXyDkRJ9qd ++xRNVaUEbe3jyv5qTWECQDNhiAZaz0DCSPLwEmsl8kD+MWbd/roEi1NdKnw3po0j +2LnwkpFOWo23tREndDim8zBrNxzX17iXLmjemcb5gKw= +-----END RSA PRIVATE KEY----- diff --git a/test/test-server.js b/test/test-server.js index 47c0a75..644cb0e 100644 --- a/test/test-server.js +++ b/test/test-server.js @@ -151,5 +151,42 @@ describe('TcpServer', function() { tcpServer.stop() }); }); + + describe('TLS support', function() { + it('should start and stop TLS enabled tcp server listenting on specified port', function(done) { + var parser = new hl7.Parser(); + var adt = parser.parse(fs.readFileSync('test/samples/adt.hl7').toString()); + + tcpServer = server.createTcpServer(function(err, req, res) { + }); + + tcpServer.start(8686,undefined,{key:'test/test-key.pem',cert:'test/test-cert.pem'}); + + tcpServer.stop(); + + done(); + }); + it('should respond to messages sent over TLS', function(done) { + var parser = new hl7.Parser(); + var adt = parser.parse(fs.readFileSync('test/samples/adt.hl7').toString()); + + tcpServer = server.createTcpServer(function(err, req, res) { + res.end(); + }); + + tcpServer.start(8686,undefined,{key:'test/test-key.pem',cert:'test/test-cert.pem'}); + + setTimeout(function() { + var tcpClient = server.createTcpClient({ host: '127.0.0.1', port: 8686, tls: true }); + + tcpClient.send(adt, function(ack) { + done(); + }); + }, 1000); + + tcpServer.stop(); + }); + + }) });