NodeJS STARTTLS Use SNI - node.js

I'm building a simple, STARTTLS capable POP3 Proxy in Node.JS and I'm having quite a hard time.
The proxy serves as a front-end for many back-end servers, so it must load their certificates dynamically, depending on the Client's connection.
I'm trying to use the SNICallback, which brings me the servername the client uses, but I can't set the right certificate after this, because I need one certificate before I have this call, when I create the secure context.
The code is as bellow:
// Load libraries
var net = require('net');
var tls = require('tls');
var fs = require('fs');
// Load certificates (created with openssl)
var certs = [];
for (var i = 1; i <= 8; i++) {
var hostName = 'localhost' + i;
certs[hostName] = {
key : fs.readFileSync('./private-key.pem'),
cert : fs.readFileSync('./public-cert' + i + '.pem'),
}
}
var server = net.createServer(function(socket) {
socket.write('+OK localhost POP3 Proxy Ready\r\n');
socket.on('data', function(data) {
if (data == "STLS\r\n") {
socket.write("+OK begin TLS negotiation\r\n");
upgradeSocket(socket);
} else if (data == "QUIT\r\n") {
socket.write("+OK Logging out.\r\n");
socket.end();
} else {
socket.write("-ERR unknown command.\r\n");
}
});
}).listen(10110);
and upgradeSocket() is as follows:
function upgradeSocket(socket) {
// I need this 'details' or handshake will fail with a message:
// SSL routines:ssl3_get_client_hello:no shared cipher
var details = {
key : fs.readFileSync('./private-key.pem'),
cert : fs.readFileSync('./public-cert1.pem'),
}
var options = {
isServer : true,
server : server,
SNICallback : function(serverName) {
return tls.createSecureContext(certs[serverName]);
},
}
sslcontext = tls.createSecureContext(details);
pair = tls.createSecurePair(sslcontext, true, false, false, options);
pair.encrypted.pipe(socket);
socket.pipe(pair.encrypted);
pair.fd = socket.fd;
pair.on("secure", function() {
console.log("TLS connection secured");
});
}
It handshakes correctly but the certificate I use is the static one in 'details', not the one I get in the SNICallback.
To test it I'm running the server and using gnutls-cli as a Client:
~$ gnutls-cli -V -s -p 10110 --crlf --insecure -d 5 localhost3
STLS
^D (Control+D)
The above command is supposed to get me the 'localhost3' certificate but it's getting the 'localhost1' because it's defined in 'details' var;
There are just too many examples throughout the internet with HTTPS or for TLS Clients, which it's a lot different from what I have here, and even for Servers as well but they're not using SNI. Any help will be appreciated.
Thanks in advance.

The answer is quite simple using tls.TLSSocket, though there is a gotcha with the listeners.
You have to remove all the listeners from the regular net.Socket you have, instantiate a new tls.TLSSocket using your net.Socket and put the listeners back on the tls.TLSSocket.
To achieve this easily, use a wrapper like Haraka's tls_socket pluggableStream over the regular net.Socket and replace the "upgrade"
function to something like:
pluggableStream.prototype.upgrade = function(options) {
var self = this;
var socket = self;
var netSocket = self.targetsocket;
socket.clean();
var secureContext = tls.createSecureContext(options)
var tlsSocket = new tls.TLSSocket(netSocket, {
// ...
secureContext : secureContext,
SNICallback : options.SNICallback
// ...
});
self.attach(tlsSocket);
}
and your options object would have the SNICallback defined as:
var options {
// ...
SNICallback : function(serverName, callback){
callback(null, tls.createSecureContext(getCertificateFor(serverName));
// ...
}
}

Related

How to configure the user_token of Damn Vulnerable Web Application within CSRF field while Script based authentication using ZAP?

I had been following the documentation of Script Based Authentication for Damn Vulnerable Web Application using ZAP. I have navigated to http://localhost/dvwa/login.php through Manual Explore which opens up the DVWA application on my localhost as follows:
and adds the URL to the Default Context.
I've also created the dvwa script with the following configuration:
and modified the dvwa script:
Now when I try Configure Context Authentication, dvwa script does gets loaded but the CSRF field doesn't shows up.
Additionally, POST Data doesn't even shows up but Extra POST Data is shown.
Am I missing something in the steps? Can someone help me out?
The modified script within the documentation of Script Based Authentication section for Damn Vulnerable Web Application using ZAP
seems incomplete.
The complete script is available at Setting up ZAP to Test Damn Vulnerable Web App (DVWA) which is as follows:
function authenticate(helper, paramsValues, credentials) {
var loginUrl = paramsValues.get("Login URL");
var csrfTokenName = paramsValues.get("CSRF Field");
var csrfTokenValue = extractInputFieldValue(getPageContent(helper, loginUrl), csrfTokenName);
var postData = paramsValues.get("POST Data");
postData = postData.replace('{%username%}', encodeURIComponent(credentials.getParam("Username")));
postData = postData.replace('{%password%}', encodeURIComponent(credentials.getParam("Password")));
postData = postData.replace('{%' + csrfTokenName + '%}', encodeURIComponent(csrfTokenValue));
var msg = sendAndReceive(helper, loginUrl, postData);
return msg;
}
function getRequiredParamsNames() {
return [ "Login URL", "CSRF Field", "POST Data" ];
}
function getOptionalParamsNames() {
return [];
}
function getCredentialsParamsNames() {
return [ "Username", "Password" ];
}
function getPageContent(helper, url) {
var msg = sendAndReceive(helper, url);
return msg.getResponseBody().toString();
}
function sendAndReceive(helper, url, postData) {
var msg = helper.prepareMessage();
var method = "GET";
if (postData) {
method = "POST";
msg.setRequestBody(postData);
}
var requestUri = new org.apache.commons.httpclient.URI(url, true);
var requestHeader = new org.parosproxy.paros.network.HttpRequestHeader(method, requestUri, "HTTP/1.0");
msg.setRequestHeader(requestHeader);
helper.sendAndReceive(msg);
return msg;
}
function extractInputFieldValue(page, fieldName) {
// Rhino:
var src = new net.htmlparser.jericho.Source(page);
// Nashorn:
// var Source = Java.type("net.htmlparser.jericho.Source");
// var src = new Source(page);
var it = src.getAllElements('input').iterator();
while (it.hasNext()) {
var element = it.next();
if (element.getAttributeValue('name') == fieldName) {
return element.getAttributeValue('value');
}
}
return '';
}
Using this script, CSRF Field and POST Data field shows up just perfect.

ServiceStack minimum configuration to get Redis Pub/Sub working between multiple Web sites/services

Let's say for sake of argument I have 3 web service hosts running, and only one of them has registered any handlers (which I think equates to subscribing to the channel/topic) e.g.
var mqService = new RedisMqServer(container.Resolve<IRedisClientsManager>())
{
DisablePriorityQueues = true
};
container.Register<IMessageService>(mqService);
container.Register(mqService.MessageFactory);
mqService.RegisterHandler<OutboundInitiateCallInfo>(ServiceController.ExecuteMessage);
mqService.RegisterHandler<DirectMailAssignmentInfo>(ServiceController.ExecuteMessage);
mqService.Start();
Now my question is, "Do I need to construct the other app hosts in the same fashion if they only publish??" e.g.
var mqService = new RedisMqServer(container.Resolve<IRedisClientsManager>())
{
DisablePriorityQueues = true
};
container.Register<IMessageService>(mqService);
container.Register(mqService.MessageFactory);
mqService.Start(); <=== Do I need to start the service, or is the MessageFactory registration enough?
Thank you,
Stephen
The minimum code for a publisher is just:
var redisManager = container.Resolve<IRedisClientsManager>();
using (var mqProducer = new RedisMessageProducer(redisManager))
{
mqProducer.Publish(new Msg { ... });
}
You could also use a MessageFactory:
var msgFactory = new RedisMessageFactory(redisMangager);
using (var mqClient = msgFactory.CreateMessageQueueClient())
{
mqClient.Publish(new Msg { ... });
}

Getting TProtocolException: Invalid data with node.js thrift client on server side

I am trying to use node.js thrift client. I am getting error on server side
TSimpleServer exception: N6apache6thrift8protocol18TProtocolExceptionE: TProtocolException: Invalid data
How to fix this issue?
My sample .thrift file is:
struct Person{
1: required string name_;
2: required map<i64,string> attribute1_;
3: required map<i64,i64> attribute2_;
4: required map<i64,string> attribute3_;
}
service ProcessPerson {
void DoPerson(
1: required list<Person> person_array
)
}
node.js client code is:
var thrift = require('thrift');
var ttransport = require('./node_modules/thrift/lib/thrift/transport.js');
var tprotocol = require('./node_modules/thrift/lib/thrift/protocol.js');
var b_conn = thrift.createConnection('localhost', 9090, {transport: ttransport.TBufferedTransport ,protocol: tprotocol.TBinaryProtocol});
var ServicePerson = require('./person_js/ProcessPerson.js');
var type = require('./person_js/person_types');
b_conn.on('error', function(err) {
console.error("error");
console.error(err);
});
b_conn.on('connect', function(data) {
console.log('on conect');
var client = thrift.createClient(ServicePerson, b_conn);
var person_list = new Array();
var person_obj = new type.Person({name_:"aa", attribute1_:"",attribute2_:"",attribute3_: "" });
console.log(person_obj);
person_list.push(person_obj);
client.DoPerson(person_list, function() {
console.log("Hi");
});
});
I am using skeleton file at server side.
I have seen this problem before.
The cause is that the struct message that received is not valid because it doesn't set the field which is "required" in thrift!
Please be sure that the client set all the required fields.
PS: I don't know how to check the node js code. I write code in C++ which I can check with __isset mechanism

Cannot get node-apn to connect

I'm trying to make a connection to APNs. It simply won't connect. I get variations of:
apn Socket error occurred +609ms { [Error: socket hang up] code: 'ECONNRESET' }
and
apn Connection error occurred before TLS Handshake +0ms
This is for a Passbook pass. Not an app. I'm using Passbook certificates.
My code is:
var apns = require('apn');
var root = process.cwd();
var fs = require('fs');
var options = {
cert: root + '/certs/new/cert.pem', /* Certificate file path */
certData: null, /* String or Buffer containing certificate data, if supplied uses this instead of cert file path */
key: root + '/certs/new/key.pem', /* Key file path */
keyData: null, /* String or Buffer containing key data, as certData */
passphrase: 'secret', /* A passphrase for the Key file */
ca: null, /* String or Buffer of CA data to use for the TLS connection */
gateway: 'gateway.sandbox.push.apple.com',/* gateway address */
port: 2195, /* gateway port */
enhanced: true, /* enable enhanced format */
errorCallback: undefined, /* Callback when error occurs function(err,notification) */
cacheLength: 100 /* Number of notifications to cache for error purposes */
};
var apnsConnection = new apns.Connection(options);
var myDevice = new apns.Device('token');
var note = new apns.Notification();
note.payload = {};
note.device = myDevice;
apnsConnection.sendNotification(note);
It appears that I mixed up my certificates. I'm sure I tried swapping them earlier but obviously didn't.
cert: Your app cert.
key: Apple's WWDR
are you behind a proxy? that could be the issue (at least it is often in my case)
Try the following structure : Read the .cert and .key files manually and set them as certData and keyData property, respectivelly. Here is the core :
var key = root + '/certs/new/key.pem'
var cert = root + '/certs/new/cert.pem';
var certData = fs.readFileSync(cert, encoding='ascii');
var keyData = fs.readFileSync(key, encoding='ascii');
var apnsConnection = new apns.Connection({
certData: certData,
keyData: keyData,
gateway: 'gateway.sandbox.push.apple.com',
port: 2195,
... /* other configs of course */
});

direct (non-tcp) connection to redis from nodejs

hello all
I looked at the at the redis-node-client source (relevant part is shown bellow) and I see that it connects to redis via the 'net' package, which is TCP based.
line 370
exports.createClient = function (port, host, options) {
var port = port || exports.DEFAULT_PORT;
var host = host || exports.DEFAULT_HOST;
var client = new Client(net.createConnection(port, host), options);
client.port = port;
client.host = host;
return client;
};
I was wondering if there's a more direct client for redis, preferably via domain-sockets or something of that sort. Im using redis localy, as cache, without going over the wire so its unnecessary to encode/decode messages with TCP headers...
thank you
Unix Domain Socket support appears to have landed in Redis as of Nov 4th.
http://code.google.com/p/redis/issues/detail?id=231
To connect to a Unix Domain Socket, you need to supply the pathname to net.createConnection. Maybe something like this in redis-node-client:
exports.createSocketClient = function (path, options) {
var client = new Client(net.createConnection(path), options);
client.path = path;
return client;
};

Resources