Enforce STS policies

This commit is contained in:
Pavel Djundik 2020-02-19 13:20:22 +02:00
parent 9b9db35e3c
commit d9985e7318
3 changed files with 110 additions and 3 deletions

View file

@ -6,6 +6,7 @@ const IrcFramework = require("irc-framework");
const Chan = require("./chan");
const Msg = require("./msg");
const Helper = require("../helper");
const STSPolicies = require("../plugins/sts");
module.exports = Network;
@ -78,7 +79,7 @@ Network.prototype.validate = function(client) {
this.username = cleanString(this.username) || "thelounge";
this.realname = cleanString(this.realname) || "The Lounge User";
this.password = cleanString(this.password);
this.host = cleanString(this.host);
this.host = cleanString(this.host).toLowerCase();
this.name = cleanString(this.name);
if (!this.port) {
@ -124,6 +125,23 @@ Network.prototype.validate = function(client) {
return false;
}
const stsPolicy = STSPolicies.get(this.host);
if (stsPolicy && !this.tls) {
this.channels[0].pushMessage(
client,
new Msg({
type: Msg.Type.ERROR,
text: `${this.host} has an active strict transport security policy, will connect to port ${stsPolicy.port} over a secure connection.`,
}),
true
);
this.port = stsPolicy.port;
this.tls = true;
this.rejectUnauthorized = true;
}
return true;
};

View file

@ -1,6 +1,7 @@
"use strict";
const Msg = require("../../models/msg");
const STSPolicies = require("../sts");
module.exports = function(irc, network) {
const client = this;
@ -27,7 +28,13 @@ module.exports = function(irc, network) {
});
if (isSecure) {
// TODO: store and update duration
const duration = parseInt(values.duration, 10);
if (isNaN(duration)) {
return;
}
STSPolicies.update(network.host, network.port, duration);
} else {
const port = parseInt(values.port, 10);
@ -38,7 +45,7 @@ module.exports = function(irc, network) {
network.channels[0].pushMessage(
client,
new Msg({
text: `Server sent a strict transport security policy, reconnecting to port ${port}`,
text: `Server sent a strict transport security policy, reconnecting to ${network.host}:${port}`,
}),
true
);

82
src/plugins/sts.js Normal file
View file

@ -0,0 +1,82 @@
"use strict";
const _ = require("lodash");
const fs = require("fs");
const path = require("path");
const log = require("../log");
const Helper = require("../helper");
class STSPolicies {
constructor() {
this.stsFile = path.join(Helper.getHomePath(), "sts-policies.json");
this.policies = new Map();
this.refresh = _.debounce(this.saveFile, 10000, {maxWait: 60000});
if (!fs.existsSync(this.stsFile)) {
return;
}
const storedPolicies = JSON.parse(fs.readFileSync(this.stsFile, "utf-8"));
const now = Date.now();
storedPolicies.forEach((value) => {
if (value.expires > now) {
this.policies.set(value.host, {
port: value.port,
expires: value.expires,
});
}
});
}
get(host) {
const policy = this.policies.get(host);
if (typeof policy === "undefined") {
return null;
}
if (policy.expires <= Date.now()) {
this.policies.delete(host);
this.refresh();
return null;
}
return policy;
}
update(host, port, duration) {
if (duration > 0) {
this.policies.set(host, {
port: port,
expires: Date.now() + duration * 1000,
});
} else {
this.policies.delete(host);
}
this.refresh();
}
saveFile() {
const policiesToStore = [];
this.policies.forEach((value, key) => {
policiesToStore.push({
host: key,
port: value.port,
expires: value.expires,
});
});
const file = JSON.stringify(policiesToStore, null, "\t");
fs.writeFile(this.stsFile, file, {flag: "w+"}, (err) => {
if (err) {
log.error("Failed to update STS policies file!", err);
}
});
}
}
module.exports = new STSPolicies();