AJ ONeal
5 years ago
20 changed files with 466 additions and 874 deletions
@ -0,0 +1,174 @@ |
|||
'use strict'; |
|||
|
|||
var crypto = require('crypto'); |
|||
//var dnsjs = require('dns-suite');
|
|||
var dig = require('dig.js/dns-request'); |
|||
var request = require('util').promisify(require('@root/request')); |
|||
var express = require('express'); |
|||
var app = express(); |
|||
|
|||
var nameservers = require('dns').getServers(); |
|||
var index = crypto.randomBytes(2).readUInt16BE(0) % nameservers.length; |
|||
var nameserver = nameservers[index]; |
|||
|
|||
app.use('/', express.static(__dirname)); |
|||
app.use('/api', express.json()); |
|||
app.get('/api/dns/:domain', function(req, res, next) { |
|||
var domain = req.params.domain; |
|||
var casedDomain = domain |
|||
.toLowerCase() |
|||
.split('') |
|||
.map(function(ch) { |
|||
// dns0x20 takes advantage of the fact that the binary operation for toUpperCase is
|
|||
// ch = ch | 0x20;
|
|||
return Math.round(Math.random()) % 2 ? ch : ch.toUpperCase(); |
|||
}) |
|||
.join(''); |
|||
var typ = req.query.type; |
|||
var query = { |
|||
header: { |
|||
id: crypto.randomBytes(2).readUInt16BE(0), |
|||
qr: 0, |
|||
opcode: 0, |
|||
aa: 0, // Authoritative-Only
|
|||
tc: 0, // NA
|
|||
rd: 1, // Recurse
|
|||
ra: 0, // NA
|
|||
rcode: 0 // NA
|
|||
}, |
|||
question: [ |
|||
{ |
|||
name: casedDomain, |
|||
//, type: typ || 'A'
|
|||
typeName: typ || 'A', |
|||
className: 'IN' |
|||
} |
|||
] |
|||
}; |
|||
var opts = { |
|||
onError: function(err) { |
|||
next(err); |
|||
}, |
|||
onMessage: function(packet) { |
|||
var fail0x20; |
|||
|
|||
if (packet.id !== query.id) { |
|||
console.error( |
|||
"[SECURITY] ignoring packet for '" + |
|||
packet.question[0].name + |
|||
"' due to mismatched id" |
|||
); |
|||
console.error(packet); |
|||
return; |
|||
} |
|||
|
|||
packet.question.forEach(function(q) { |
|||
// if (-1 === q.name.lastIndexOf(cli.casedQuery))
|
|||
if (q.name !== casedDomain) { |
|||
fail0x20 = q.name; |
|||
} |
|||
}); |
|||
|
|||
['question', 'answer', 'authority', 'additional'].forEach(function( |
|||
group |
|||
) { |
|||
(packet[group] || []).forEach(function(a) { |
|||
var an = a.name; |
|||
var i = domain |
|||
.toLowerCase() |
|||
.lastIndexOf(a.name.toLowerCase()); // answer is something like ExAMPle.cOM and query was wWw.ExAMPle.cOM
|
|||
var j = a.name |
|||
.toLowerCase() |
|||
.lastIndexOf(domain.toLowerCase()); // answer is something like www.ExAMPle.cOM and query was ExAMPle.cOM
|
|||
|
|||
// it's important to note that these should only relpace changes in casing that we expected
|
|||
// any abnormalities should be left intact to go "huh?" about
|
|||
// TODO detect abnormalities?
|
|||
if (-1 !== i) { |
|||
// "EXamPLE.cOm".replace("wWw.EXamPLE.cOm".substr(4), "www.example.com".substr(4))
|
|||
a.name = a.name.replace( |
|||
casedDomain.substr(i), |
|||
domain.substr(i) |
|||
); |
|||
} else if (-1 !== j) { |
|||
// "www.example.com".replace("EXamPLE.cOm", "example.com")
|
|||
a.name = |
|||
a.name.substr(0, j) + |
|||
a.name.substr(j).replace(casedDomain, domain); |
|||
} |
|||
|
|||
// NOTE: right now this assumes that anything matching the query matches all the way to the end
|
|||
// it does not handle the case of a record for example.com.uk being returned in response to a query for www.example.com correctly
|
|||
// (but I don't think it should need to)
|
|||
if (a.name.length !== an.length) { |
|||
console.error( |
|||
"[ERROR] question / answer mismatch: '" + |
|||
an + |
|||
"' != '" + |
|||
a.length + |
|||
"'" |
|||
); |
|||
console.error(a); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
if (fail0x20) { |
|||
console.warn( |
|||
";; Warning: DNS 0x20 security not implemented (or packet spoofed). Queried '" + |
|||
casedDomain + |
|||
"' but got response for '" + |
|||
fail0x20 + |
|||
"'." |
|||
); |
|||
return; |
|||
} |
|||
|
|||
res.send({ |
|||
header: packet.header, |
|||
question: packet.question, |
|||
answer: packet.answer, |
|||
authority: packet.authority, |
|||
additional: packet.additional, |
|||
edns_options: packet.edns_options |
|||
}); |
|||
}, |
|||
onListening: function() {}, |
|||
onSent: function(/*res*/) {}, |
|||
onTimeout: function(res) { |
|||
console.error('dns timeout:', res); |
|||
next(new Error('DNS timeout - no response')); |
|||
}, |
|||
onClose: function() {}, |
|||
//, mdns: cli.mdns
|
|||
nameserver: nameserver, |
|||
port: 53, |
|||
timeout: 2000 |
|||
}; |
|||
|
|||
dig.resolveJson(query, opts); |
|||
}); |
|||
app.get('/api/http', function(req, res) { |
|||
var url = req.query.url; |
|||
return request({ method: 'GET', url: url }).then(function(resp) { |
|||
res.send(resp.body); |
|||
}); |
|||
}); |
|||
app.get('/api/_acme_api_', function(req, res) { |
|||
res.send({ success: true }); |
|||
}); |
|||
|
|||
module.exports = app; |
|||
if (require.main === module) { |
|||
// curl -L http://localhost:3000/api/dns/example.com?type=A
|
|||
console.info('Listening on localhost:3000'); |
|||
app.listen(3000); |
|||
console.info('Try this:'); |
|||
console.info("\tcurl -L 'http://localhost:3000/api/_acme_api_/'"); |
|||
console.info( |
|||
"\tcurl -L 'http://localhost:3000/api/dns/example.com?type=A'" |
|||
); |
|||
console.info( |
|||
"\tcurl -L 'http://localhost:3000/api/http/?url=https://example.com'" |
|||
); |
|||
} |
@ -1,151 +0,0 @@ |
|||
(function(exports) { |
|||
var Enc = (exports.Enc = {}); |
|||
|
|||
Enc.bufToBin = function(buf) { |
|||
var bin = ''; |
|||
// cannot use .map() because Uint8Array would return only 0s
|
|||
buf.forEach(function(ch) { |
|||
bin += String.fromCharCode(ch); |
|||
}); |
|||
return bin; |
|||
}; |
|||
|
|||
Enc.bufToHex = function toHex(u8) { |
|||
var hex = []; |
|||
var i, h; |
|||
var len = u8.byteLength || u8.length; |
|||
|
|||
for (i = 0; i < len; i += 1) { |
|||
h = u8[i].toString(16); |
|||
if (h.length % 2) { |
|||
h = '0' + h; |
|||
} |
|||
hex.push(h); |
|||
} |
|||
|
|||
return hex.join('').toLowerCase(); |
|||
}; |
|||
|
|||
Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) { |
|||
var r = str % 4; |
|||
if (2 === r) { |
|||
str += '=='; |
|||
} else if (3 === r) { |
|||
str += '='; |
|||
} |
|||
return str.replace(/-/g, '+').replace(/_/g, '/'); |
|||
}; |
|||
|
|||
Enc.base64ToBuf = function(b64) { |
|||
return Enc.binToBuf(atob(b64)); |
|||
}; |
|||
Enc.binToBuf = function(bin) { |
|||
var arr = bin.split('').map(function(ch) { |
|||
return ch.charCodeAt(0); |
|||
}); |
|||
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; |
|||
}; |
|||
Enc.bufToHex = function(u8) { |
|||
var hex = []; |
|||
var i, h; |
|||
var len = u8.byteLength || u8.length; |
|||
|
|||
for (i = 0; i < len; i += 1) { |
|||
h = u8[i].toString(16); |
|||
if (h.length % 2) { |
|||
h = '0' + h; |
|||
} |
|||
hex.push(h); |
|||
} |
|||
|
|||
return hex.join('').toLowerCase(); |
|||
}; |
|||
Enc.numToHex = function(d) { |
|||
d = d.toString(16); |
|||
if (d.length % 2) { |
|||
return '0' + d; |
|||
} |
|||
return d; |
|||
}; |
|||
|
|||
Enc.bufToUrlBase64 = function(u8) { |
|||
return Enc.base64ToUrlBase64(Enc.bufToBase64(u8)); |
|||
}; |
|||
|
|||
Enc.base64ToUrlBase64 = function(str) { |
|||
return str |
|||
.replace(/\+/g, '-') |
|||
.replace(/\//g, '_') |
|||
.replace(/=/g, ''); |
|||
}; |
|||
|
|||
Enc.bufToBase64 = function(u8) { |
|||
var bin = ''; |
|||
u8.forEach(function(i) { |
|||
bin += String.fromCharCode(i); |
|||
}); |
|||
return btoa(bin); |
|||
}; |
|||
|
|||
Enc.hexToBuf = function(hex) { |
|||
var arr = []; |
|||
hex.match(/.{2}/g).forEach(function(h) { |
|||
arr.push(parseInt(h, 16)); |
|||
}); |
|||
return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; |
|||
}; |
|||
|
|||
Enc.numToHex = function(d) { |
|||
d = d.toString(16); |
|||
if (d.length % 2) { |
|||
return '0' + d; |
|||
} |
|||
return d; |
|||
}; |
|||
|
|||
//
|
|||
// JWK to SSH (tested working)
|
|||
//
|
|||
Enc.base64ToHex = function(b64) { |
|||
var bin = atob(Enc.urlBase64ToBase64(b64)); |
|||
return Enc.binToHex(bin); |
|||
}; |
|||
|
|||
Enc.binToHex = function(bin) { |
|||
return bin |
|||
.split('') |
|||
.map(function(ch) { |
|||
var h = ch.charCodeAt(0).toString(16); |
|||
if (h.length % 2) { |
|||
h = '0' + h; |
|||
} |
|||
return h; |
|||
}) |
|||
.join(''); |
|||
}; |
|||
// TODO are there any nuance differences here?
|
|||
Enc.utf8ToHex = Enc.binToHex; |
|||
|
|||
Enc.hexToBase64 = function(hex) { |
|||
return btoa(Enc.hexToBin(hex)); |
|||
}; |
|||
|
|||
Enc.hexToBin = function(hex) { |
|||
return hex |
|||
.match(/.{2}/g) |
|||
.map(function(h) { |
|||
return String.fromCharCode(parseInt(h, 16)); |
|||
}) |
|||
.join(''); |
|||
}; |
|||
|
|||
Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) { |
|||
var r = str % 4; |
|||
if (2 === r) { |
|||
str += '=='; |
|||
} else if (3 === r) { |
|||
str += '='; |
|||
} |
|||
return str.replace(/-/g, '+').replace(/_/g, '/'); |
|||
}; |
|||
})('undefined' !== typeof exports ? module.exports : window); |
@ -1,139 +0,0 @@ |
|||
'use strict'; |
|||
|
|||
var crypto = require('crypto'); |
|||
//var dnsjs = require('dns-suite');
|
|||
var dig = require('dig.js/dns-request'); |
|||
var request = require('util').promisify(require('@root/request')); |
|||
var express = require('express'); |
|||
var app = express(); |
|||
|
|||
var nameservers = require('dns').getServers(); |
|||
var index = crypto.randomBytes(2).readUInt16BE(0) % nameservers.length; |
|||
var nameserver = nameservers[index]; |
|||
|
|||
app.use('/', express.static(__dirname)); |
|||
app.use('/api', express.json()); |
|||
app.get('/api/dns/:domain', function (req, res, next) { |
|||
var domain = req.params.domain; |
|||
var casedDomain = domain.toLowerCase().split('').map(function (ch) { |
|||
// dns0x20 takes advantage of the fact that the binary operation for toUpperCase is
|
|||
// ch = ch | 0x20;
|
|||
return Math.round(Math.random()) % 2 ? ch : ch.toUpperCase(); |
|||
}).join(''); |
|||
var typ = req.query.type; |
|||
var query = { |
|||
header: { |
|||
id: crypto.randomBytes(2).readUInt16BE(0) |
|||
, qr: 0 |
|||
, opcode: 0 |
|||
, aa: 0 // Authoritative-Only
|
|||
, tc: 0 // NA
|
|||
, rd: 1 // Recurse
|
|||
, ra: 0 // NA
|
|||
, rcode: 0 // NA
|
|||
} |
|||
, question: [ |
|||
{ name: casedDomain |
|||
//, type: typ || 'A'
|
|||
, typeName: typ || 'A' |
|||
, className: 'IN' |
|||
} |
|||
] |
|||
}; |
|||
var opts = { |
|||
onError: function (err) { |
|||
next(err); |
|||
} |
|||
, onMessage: function (packet) { |
|||
var fail0x20; |
|||
|
|||
if (packet.id !== query.id) { |
|||
console.error('[SECURITY] ignoring packet for \'' + packet.question[0].name + '\' due to mismatched id'); |
|||
console.error(packet); |
|||
return; |
|||
} |
|||
|
|||
packet.question.forEach(function (q) { |
|||
// if (-1 === q.name.lastIndexOf(cli.casedQuery))
|
|||
if (q.name !== casedDomain) { |
|||
fail0x20 = q.name; |
|||
} |
|||
}); |
|||
|
|||
[ 'question', 'answer', 'authority', 'additional' ].forEach(function (group) { |
|||
(packet[group]||[]).forEach(function (a) { |
|||
var an = a.name; |
|||
var i = domain.toLowerCase().lastIndexOf(a.name.toLowerCase()); // answer is something like ExAMPle.cOM and query was wWw.ExAMPle.cOM
|
|||
var j = a.name.toLowerCase().lastIndexOf(domain.toLowerCase()); // answer is something like www.ExAMPle.cOM and query was ExAMPle.cOM
|
|||
|
|||
// it's important to note that these should only relpace changes in casing that we expected
|
|||
// any abnormalities should be left intact to go "huh?" about
|
|||
// TODO detect abnormalities?
|
|||
if (-1 !== i) { |
|||
// "EXamPLE.cOm".replace("wWw.EXamPLE.cOm".substr(4), "www.example.com".substr(4))
|
|||
a.name = a.name.replace(casedDomain.substr(i), domain.substr(i)); |
|||
} else if (-1 !== j) { |
|||
// "www.example.com".replace("EXamPLE.cOm", "example.com")
|
|||
a.name = a.name.substr(0, j) + a.name.substr(j).replace(casedDomain, domain); |
|||
} |
|||
|
|||
// NOTE: right now this assumes that anything matching the query matches all the way to the end
|
|||
// it does not handle the case of a record for example.com.uk being returned in response to a query for www.example.com correctly
|
|||
// (but I don't think it should need to)
|
|||
if (a.name.length !== an.length) { |
|||
console.error("[ERROR] question / answer mismatch: '" + an + "' != '" + a.length + "'"); |
|||
console.error(a); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
if (fail0x20) { |
|||
console.warn(";; Warning: DNS 0x20 security not implemented (or packet spoofed). Queried '" |
|||
+ casedDomain + "' but got response for '" + fail0x20 + "'."); |
|||
return; |
|||
} |
|||
|
|||
res.send({ |
|||
header: packet.header |
|||
, question: packet.question |
|||
, answer: packet.answer |
|||
, authority: packet.authority |
|||
, additional: packet.additional |
|||
, edns_options: packet.edns_options |
|||
}); |
|||
} |
|||
, onListening: function () {} |
|||
, onSent: function (/*res*/) { } |
|||
, onTimeout: function (res) { |
|||
console.error('dns timeout:', res); |
|||
next(new Error("DNS timeout - no response")); |
|||
} |
|||
, onClose: function () { } |
|||
//, mdns: cli.mdns
|
|||
, nameserver: nameserver |
|||
, port: 53 |
|||
, timeout: 2000 |
|||
}; |
|||
|
|||
dig.resolveJson(query, opts); |
|||
}); |
|||
app.get('/api/http', function (req, res) { |
|||
var url = req.query.url; |
|||
return request({ method: 'GET', url: url }).then(function (resp) { |
|||
res.send(resp.body); |
|||
}); |
|||
}); |
|||
app.get('/api/_acme_api_', function (req, res) { |
|||
res.send({ success: true }); |
|||
}); |
|||
|
|||
module.exports = app; |
|||
if (require.main === module) { |
|||
// curl -L http://localhost:3000/api/dns/example.com?type=A
|
|||
console.info("Listening on localhost:3000"); |
|||
app.listen(3000); |
|||
console.info("Try this:"); |
|||
console.info("\tcurl -L 'http://localhost:3000/api/_acme_api_/'"); |
|||
console.info("\tcurl -L 'http://localhost:3000/api/dns/example.com?type=A'"); |
|||
console.info("\tcurl -L 'http://localhost:3000/api/http/?url=https://example.com'"); |
|||
} |
@ -0,0 +1,18 @@ |
|||
'use strict'; |
|||
|
|||
var path = require('path'); |
|||
|
|||
module.exports = { |
|||
entry: './lib/acme.js', |
|||
output: { |
|||
path: path.resolve(__dirname, 'dist'), |
|||
filename: 'acme.js', |
|||
library: 'acme', |
|||
libraryTarget: 'umd', |
|||
globalObject: "typeof self !== 'undefined' ? self : this" |
|||
}, |
|||
resolve: { |
|||
aliasFields: ['webpack', 'browser'], |
|||
mainFields: ['browser'] |
|||
} |
|||
}; |
Loading…
Reference in new issue