Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

prelimiary support for 'fingerprint' fields in Namecoin records

  • Loading branch information...
commit 5aa9da17b0bd777461bf67ddcc47e332218560dc 1 parent fd10e3b
@itsnotlupus authored
View
17 README.md
@@ -77,6 +77,22 @@ If you are using the source version, you will also need:
- node ( http://nodejs.org/ )
- optimist and binary ( `npm install optimist binary `)
+If you are building from the top of the tree, you will also need:
+
+- dcrypt . `npm install dcrypt` would normally work, but I rely on
+ a couple of patches that aren't in it yet, so you'll need to grab it from
+ https://github.com/itsnotlupus/dcrypt . Put that under nmcsocks/node_modules/dcrypt,
+ and build it with `node-waf configure build`. That ought to do it.
+
+## new stuff to pay attention to:
+
+If you're trying to use the new "fingerprint" support code, you'll need to add the
+X.509 CA certificate NmcSocks creates locally into your browser. The procedure varies
+for each browser. The CA certificate is located under <NmcSocksDataDir>/namecoin_root.crt
+On unix, <NmcSocksDataDir> is ~/.nmcsocks/
+On windows, it's %APP_DATA%/NmcSocks/
+Mac Users, look under ~/Library/Application\ Support/NmcSocks/
+
## todo
- ipv6 support
@@ -88,3 +104,4 @@ If you are using the source version, you will also need:
v0.2: better draft spec support (delegate/import/alias stuff), infinite loop mitigation
v0.3: embedded DNS server to resolve .bit domains
v0.4: better DNS server support, various bug fixes
+ v0.5: support for the "fingerprint" record field, enabled decentralized TLS certificate trust.
View
5 lib/name_scan.js
@@ -75,6 +75,9 @@ module.exports = {
"ftp": { "delegate": [ "d/domain", "eu" ] },
"us": { "ip": "192.168.0.0" }
}
+ },
+ "d/tlstest": {
+ "ip": "192.168.1.90",
+ "fingerprint": [ "07:3B:39:56:0A:A8:80:76:8A:B1:34:9C:B6:36:F7:56:5B:4C:13:E0" ]
}
-
};
View
5 lib/nmcdns.js
@@ -38,8 +38,9 @@ function startServer(port, host, callback) {
if (err) {
return console.log("Resolver error:", err);
}
- for (var i=0,l=data.length;i<l;i++) {
- var type, answer=data[i];
+ var answers = data.answer;
+ for (var i=0,l=answers.length;i<l;i++) {
+ var type, answer=answers[i];
switch (net.isIP(answer)) {
case 0: type = ndns.ns_t.cname; break;
case 4: type = ndns.ns_t.a; break;
View
36 lib/nmcresolver.js
@@ -66,28 +66,32 @@ function resolveHostWithNamecoin(host, type, counter, callback) {
case types.HOST:
case types.TOR:
case types.I2P:
- callback(null, host);
+ callback(null, resolverAnswer(host));
break;
case types.IPV4:
if (net.isIP(host)==4) {
- callback(null, host);
+ callback(null, resolverAnswer(host));
} else {
// take our chances with a resolver, if we can.
if (private_mode) {
callback(new Error("Cannot resolve this host in private mode."));
} else {
- dns.resolve4(host, callback);
+ dns.resolve4(host, function(err, data){
+ callback(err, resolverAnswer(data));
+ });
}
}
break;
case types.IPV6:
if (net.isIP(host)==6) {
- callback(null, host);
+ callback(null, resolverAnswer(host));
} else {
if (private_mode) {
callback(new Error("Cannot resolve this host in private mode."));
} else {
- dns.resolve6(host, callback);
+ dns.resolve6(host, function(err, data){
+ callback(err, resolverAnswer(data));
+ });
}
}
break;
@@ -274,7 +278,7 @@ function resolveFromValue(host, type, parent, chunks, value, counter, callback)
if (err) {
postDNSprocessing();
} else {
- callback(null, data);
+ callback(null, resolverAnswer(data, value.fingerprint));
}
});
} else {
@@ -305,16 +309,16 @@ function resolveFromValue(host, type, parent, chunks, value, counter, callback)
// 1. legacy crap: if value a string?
if (typeof value == "string") {
- return callback(null, value);
+ return callback(null, resolverAnswer(value)); // no fingerprints evar in this case
}
// enforce tor_mode
if ((tor_mode || type==types.TOR) && value.tor) {
- return callback(null, value.tor);
+ return callback(null, resolverAnswer(value.tor, value.fingerprint)); // can .onion sites use https?
}
// enforce i2p_mode
if ((i2p_mode || type==types.I2P) && value.i2p) {
- return callback(null, value.i2p.b32);
+ return callback(null, resolverAnswer(value.i2p.b32, value.fingerprint)); // I2P over SSL?
}
// 2. else, find some other hardcoded value to use.
@@ -323,14 +327,14 @@ function resolveFromValue(host, type, parent, chunks, value, counter, callback)
ip = value.ip;
// if it's not an ipv4 address, we ignore it.
if (net.isIP(oneOf(ip))==4) {
- return callback(null, ip);
+ return callback(null, resolverAnswer(ip, value.fingerprint));
}
}
if (value.ip6 && (type==types.ANY || type==types.IPV6)) {
ip = value.ip6;
// if it's not an ipv6 address, we ignore it.
if (net.isIP(oneOf(ip))==6) {
- return callback(null, ip);
+ return callback(null, resolverAnswer(ip, value.fingerprint));
}
}
@@ -371,6 +375,12 @@ function resolveWithDNS(host, server, callback) {
});
}
+function resolverAnswer(answer, fingerprint) {
+ return {
+ answer: arrayOf(answer),
+ fingerprint: arrayOf(fingerprint)
+ };
+}
function testMode() {
testing = true;
@@ -397,7 +407,7 @@ module.exports = {
types: types,
resolve: function(host, callback) {
return resolveHostWithNamecoin(host, types.ANY, 0, function(err, value){
- callback(err, oneOf(value));
+ callback(err, value);
});
},
/**
@@ -405,7 +415,7 @@ module.exports = {
*/
resolveFull: function(host, type, callback) {
return resolveHostWithNamecoin(host, type, 0, function(err,value) {
- callback(err, arrayOf(value));
+ callback(err, value);
});
},
setPrivateMode: function(flag) { private_mode = !!flag; },
View
95 lib/nmcsocks.js
@@ -5,6 +5,7 @@ var net = require("net");
var binary = require("binary");
var resolver = require("./nmcresolver");
var rpc = require("./nmcrpc");
+var nmctls = require("./nmctls");
var argv = require("optimist")
.usage("Start a NameCoin Socks 5 Proxy.\nUsage: $0")
@@ -56,9 +57,11 @@ var argv = require("optimist")
describe: "IP address for the DNS server to listen on"
})
.options("dir", {
- alias: "d",
describe: "Namecoin configuration directory"
})
+ .options("test", {
+ describe: "Test mode. Enable some static namecoin records. Not for general use."
+ })
.options("help", {
alias: "h",
describe: "Display this help message"
@@ -81,6 +84,13 @@ switch(true){
// namecoin config directory handling
if (argv.dir) {
+ try {
+ fs.statSync(argv.dir);
+ if (!fs.isDirectory) { throw "no"; }
+ } catch (e) {
+ console.log("Error: namecoin config --dir value is not a valid directory.");
+ process.exit(1);
+ }
rpc.setDataDir(argv.dir);
console.log("Using namecoin config directory: ", rpc.getDataDir());
}
@@ -105,6 +115,11 @@ if (argv.private) {
console.log("Private mode is enabled.");
}
+// test mode, mostly for dev stuff. don't use this, mm'kay?
+if (argv.test) {
+ resolver.testMode();
+}
+
function namecoinRpcTester(next) {
rpc.call("getinfo",[],function(err,data) {
if (err) {
@@ -256,10 +271,10 @@ function new_client(client) {
});
}
-function socks_do(t, client, host, port, response) {
+function socks_do(t, client, hostname, port, response) {
// resolve "host" through NameCoin, if applicable
- resolver.resolve(host, function(err, host) {
+ resolver.resolve(hostname, function(err, data) {
if (err) {
response[1] = 4;
@@ -268,9 +283,38 @@ function socks_do(t, client, host, port, response) {
return;
}
+ var host = data.answer[0]; // XXX for extra point, loop through the answer if the previous ones fail.
+
switch (t) {
case 1:
- socks_connect(client, host, port, response); break;
+ if (data.fingerprint && data.fingerprint.length && port == 443) { // hardcoded port.. this is not awesome, design-wise.
+ // take the SSL MITM path instead.
+ nmctls.clientConnect(port, host, hostname, data.fingerprint, function(err, socket) {
+
+ if(err) {
+ console.log("10: NmcSocks frowns upon your puny SSL server.",err);
+ return;
+ }
+ console.log("10: NmcSocks approves of your SSL connection.");
+ // needed to allow the other side of the TLS setup to work
+ client.on("data", function(data){
+ console.log("browser sent: ",data);
+ });
+ nmctls.serverConnect(client, hostname, function(client) {
+ console.log("30: It seems to have worked. piping the two ends now.");
+ client.setMaxListeners(30);
+ socket.setMaxListeners(30);
+
+ client.pipe(socket);
+ socket.pipe(client);
+ });
+ client.write(new Buffer(response));
+ console.log("20: ok, waiting for tls initiation to be done on the client side now.");
+ });
+ } else {
+ socks_connect(client, host, port, response);
+ }
+ break;
case 2:
socks_bind(client, host, port); break;
case 3:
@@ -331,7 +375,9 @@ function socks_connect(client, host, port, response) {
if (argv.chain == "never") {
socket = new net.Socket({type: 'tcp4'});
- socket.connect(port, host, socketHandler);
+ socket.connect(port, host, function() {
+ socketHandler(client, socket, response);
+ });
socket.once("error", socketErrorHandler);
} else {
openSocksSocket(argv.shost, argv.sport, host, port, function(err, data){
@@ -342,32 +388,18 @@ function socks_connect(client, host, port, response) {
} else {
socket = new net.Socket({type: 'tcp4'});
socket.once("error", socketErrorHandler);
- socket.connect(port, host, socketHandler);
+ socket.connect(port, host, function() {
+ socketHandler(client, socket, response);
+ });
}
} else {
socket = data;
socket.once("error", socketErrorHandler);
- socketHandler();
+ socketHandler(client, socket, response);
}
});
}
- function socketHandler() {
-
- client.setMaxListeners(30);
- socket.setMaxListeners(30);
-
- client.pipe(socket);
- socket.pipe(client);
-
- // everything is setup. let the client know about it.
- try {
- client.write(new Buffer(response));
- } catch (e) {
- // the client hang up on me? lame.
- }
-
- };
function socketErrorHandler(e) {
response[1]=4; // XXX may not be the right error..
client.end(new Buffer(response));
@@ -375,6 +407,23 @@ function socks_connect(client, host, port, response) {
}
}
+function socketHandler(client, socket, response) {
+
+ client.setMaxListeners(30);
+ socket.setMaxListeners(30);
+
+ client.pipe(socket);
+ socket.pipe(client);
+
+ // everything is setup. let the client know about it.
+ try {
+ client.write(new Buffer(response));
+ } catch (e) {
+ // the client hang up on me? lame.
+ }
+
+};
+
function socks_bind(client, host, port) {
throw new Error("socks_bind not implemented.");
}
View
100 lib/nmctls.js
@@ -72,12 +72,28 @@ function writeFile(filename, data, callback) {
fs.writeFile(file, data, callback);
}
+var serial=null;
+function getSerial() {
+ initDataDir();
+ var file = getDataDir() + "/serial.txt";
+ if (serial==null) {
+ try {
+ serial = fs.readFileSync(file)+0||0;
+ } catch(e) {
+ serial = 0;
+ }
+ }
+ serial+=1;
+ fs.writeFile(file, ""+serial); // don't need to block on that part.
+ return serial;
+}
+
function clientConnect(port, host, hostname, fingerprints, callback) {
var socket = tls.connect(port, host, {}, function() {
- var cert = s.pair.ssl.getPeerCertificate();
+ var cert = socket.pair.ssl.getPeerCertificate();
var subject = qs.parse(cert.subject,"/");
var common_name = subject.CN.toLowerCase();
- var fingerprint = cert.fingerprint;
+ var fingerprint = cert.fingerprint.toLowerCase();
if (cert.subject !== cert.issuer) {
socket.destroy();
return callback(new Error("Certificate is NOT self-signed."));
@@ -93,7 +109,7 @@ function clientConnect(port, host, hostname, fingerprints, callback) {
} else {
return callback(new Error("Certificate Common Name did not match hostname."));
}
- if (fingerprints.indexOf(fingerprint)==-1) {
+ if (fingerprints.map(function(v){return v.toLowerCase()}).indexOf(fingerprint)==-1) {
return callback(new Error("Certificate fingerprint did not match record."));
}
// still here? the certificate is legit.
@@ -110,13 +126,26 @@ function serverConnect(socket, hostname, callback) {
var crt, pkey;
if (err) {
// we don't have a root CA? We must be new here.
- var cert = dcrypt.x509.createCert("XX", "NameCoin Internet Authoritah"); // respect it.
+ var cert = dcrypt.x509.createCert( 1024, 365*20, getSerial(), {// a bit under 20 years.
+ "C": "US",
+ "CN": "NameCoin Internet Authoritah", // respect it.
+ "O": "NameCoin",
+ "OU": "NmcSocks"
+ }, {
+ "basicConstraints": "critical,CA:TRUE",
+ "keyUsage": "critical,keyCertSign,cRLSign",
+ "subjectKeyIdentifier": "hash",
+ "nsCertType": "sslCA",
+ "nsComment": "This certificate was generated locally and is used by NmcSocks to allow TLS to be usable with Namecoin domains without the need for a central authority."
+ });
// sign yourself.
cert.x509 = dcrypt.x509.signCert(cert.x509, cert.x509, cert.pkey);
writeFile("namecoin_root.crt", cert.x509, function(){
writeFile("namecoin_root.key", cert.pkey, function(){
crt = cert.x509;
pkey = cert.pkey;
+ // XXX now would be a great time to blow away the certs/ folder, or we might end up with bad host certs
+ console.log("Root CA created. proceeding to generate a cert for "+hostname);
generateCertFor(crt, pkey, hostname);
});
});
@@ -128,6 +157,7 @@ function serverConnect(socket, hostname, callback) {
throw(err);
} else {
pkey = data.toString();
+ console.log("Root CA loaded. proceeding to generate a cert for "+hostname);
generateCertFor(crt, pkey, hostname);
}
});
@@ -135,12 +165,18 @@ function serverConnect(socket, hostname, callback) {
});
} else {
var cert = JSON.parse(data.toString());
+ console.log("certificate for "+hostname+" loaded.");
startTls(socket, {cert:cert.x509, key:cert.pkey}, callback);
}
});
// 2. if not, find the root CA, generate a new cert for it.
function generateCertFor(crt, pkey, hostname) {
- var cert = dcrypt.x509.createCert("XX", hostname);
+ var cert = dcrypt.x509.createCert( 1024, 365*10, getSerial(), {
+ "C": "US",
+ "CN": hostname
+ }, {
+ "nsComment": "This certificate was generated locally by NmcSocks."
+ });
cert.x509 = dcrypt.x509.signCert(cert.x509, crt, pkey);
writeFile("certs/"+hostname+".cert", JSON.stringify(cert), function(){});
startTls(socket, {cert:cert.x509, key:cert.pkey}, callback);
@@ -149,47 +185,26 @@ function serverConnect(socket, hostname, callback) {
// 3. use cert to startTLS on the socket. ( https://gist.github.com/848444 )
function startTls(socket, options, callback) {
+ console.log("STARTTLS: begin");
var sslcontext = crypto.createCredentials(options);
- var pair = tls.createSecurePair(sslcontext, false);
+ var pair = tls.createSecurePair(sslcontext, true); // this "true" is important.
var cleartext = pipe(pair, socket);
+ cleartext._controlReleased = false;
pair.on("secure", function() {
- var verifyError = pair.ssl.verifyError();
-
- if (verifyError) {
- cleartext.authorized = false;
- cleartext.authorizationError = verifyError;
- } else {
- cleartext.authorized = true;
- }
+ console.log("startTls::on_secure!");
+ pair.cleartext.authorized = false;
+ pair.cleartext.npnProtocol = pair.npnProtocol;
+
+ cleartext._controlReleased = true;
- callback && callback();
+ callback && callback(pair.cleartext);
});
- cleartext._controlReleased = true;
+ console.log("STARTTLS: end. waiting for 'secure' event!");
return cleartext;
}
-function forwardEvents(events,emitterSource,emitterDestination) {
- var map = {}
- for(var i = 0; i < events.length; i++) {
- var name = events[i];
- var handler = (function generateForwardEvent(){
- return function forwardEvent(name) {
- return emitterDestination.emit.apply(emitterDestination,arguments)
- }
- })(name);
- map[name] = handler;
- emitterSource.on(name,handler);
- }
- return map;
-}
-function removeEvents(map,emitterSource) {
- for(var k in map) {
- emitter.removeListener(k,map[k])
- }
-}
-
function pipe(pair, socket) {
pair.encrypted.pipe(socket);
socket.pipe(pair.encrypted);
@@ -201,27 +216,36 @@ function pipe(pair, socket) {
cleartext.authorized = false;
function onerror(e) {
+ console.log("pipe::error",e);
if (cleartext._controlReleased) {
cleartext.emit('error', e);
}
}
- var map = forwardEvents(["timeout", "end", "close"],socket,cleartext);
function onclose() {
+ console.log("pipe::close");
socket.removeListener('error', onerror);
socket.removeListener('close', onclose);
- removeEvents(map,socket);
+ socket.removeListener('timeout', ontimeout);
+ }
+
+ function ontimeout() {
+ console.log("pipe::timeout");
+ cleartext.emit('timeout');
}
socket.on('error', onerror);
socket.on('close', onclose);
+ socket.on('timeout', ontimeout);
return cleartext;
}
+
// do a little bit of setup automatically. XXX
initDataDir();
+
module.exports = {
clientConnect: clientConnect,
serverConnect: serverConnect
View
7 lib/tester.js
@@ -77,9 +77,10 @@ function runTest(test, done) {
if (err&&test.error) { return done(); } // automatic success in preordained failure. :-/
assert(!err&&!test.error, test, (err&&err.message)||"expected error, but got data: "+data); // the prophesized failure did not come to pass!
assert(data, test, "data is null or undefined");
- assert(data.length==test.answer.length, test, "data length didn't match: "+data.length);
- for (var i=0,l=data.length;i<l;i++) {
- assert(data[i]==test.answer[i], test, "data didn't match: "+data);
+ assert(data.answer, test, "data is null or undefined "+JSON.stringify(data));
+ assert(data.answer.length==test.answer.length, test, "data length didn't match: "+data.length);
+ for (var i=0,l=data.answer.length;i<l;i++) {
+ assert(data.answer[i]==test.answer[i], test, "data didn't match: "+data);
}
done();
});
View
2  package.json
@@ -2,7 +2,7 @@
"name" : "nmcsocks",
"version" : "0.4",
"description" : "A socks proxy and DNS resolver for Namecoin.",
- "main" : "./nmcsocks.js",
+ "main" : "./nmcsocks",
"repository" : {
"type": "git",
"url": "http://github.com/itsnotlupus/nmcsocks.git"
Please sign in to comment.
Something went wrong with that request. Please try again.