135 lines
3.0 KiB
JavaScript
135 lines
3.0 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
const path = require("path");
|
||
|
const fs = require("fs");
|
||
|
const crypto = require("crypto");
|
||
|
const {md, pki} = require("node-forge");
|
||
|
const log = require("../log");
|
||
|
const Helper = require("../helper");
|
||
|
|
||
|
module.exports = {
|
||
|
get,
|
||
|
remove,
|
||
|
};
|
||
|
|
||
|
function get(uuid) {
|
||
|
if (Helper.config.public) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const folderPath = Helper.getClientCertificatesPath();
|
||
|
const paths = getPaths(folderPath, uuid);
|
||
|
|
||
|
if (!fs.existsSync(paths.privateKeyPath) || !fs.existsSync(paths.certificatePath)) {
|
||
|
return generateAndWrite(folderPath, paths);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
return {
|
||
|
private_key: fs.readFileSync(paths.privateKeyPath, "utf-8"),
|
||
|
certificate: fs.readFileSync(paths.certificatePath, "utf-8"),
|
||
|
};
|
||
|
} catch (e) {
|
||
|
log.error("Unable to remove certificate", e);
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function remove(uuid) {
|
||
|
if (Helper.config.public) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const paths = getPaths(Helper.getClientCertificatesPath(), uuid);
|
||
|
|
||
|
try {
|
||
|
if (fs.existsSync(paths.privateKeyPath)) {
|
||
|
fs.unlinkSync(paths.privateKeyPath);
|
||
|
}
|
||
|
|
||
|
if (fs.existsSync(paths.certificatePath)) {
|
||
|
fs.unlinkSync(paths.certificatePath);
|
||
|
}
|
||
|
} catch (e) {
|
||
|
log.error("Unable to remove certificate", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function generateAndWrite(folderPath, paths) {
|
||
|
const certificate = generate();
|
||
|
|
||
|
try {
|
||
|
fs.mkdirSync(folderPath, {recursive: true});
|
||
|
|
||
|
fs.writeFileSync(paths.privateKeyPath, certificate.private_key, {
|
||
|
mode: 0o600,
|
||
|
});
|
||
|
fs.writeFileSync(paths.certificatePath, certificate.certificate, {
|
||
|
mode: 0o600,
|
||
|
});
|
||
|
|
||
|
return certificate;
|
||
|
} catch (e) {
|
||
|
log.error("Unable to write certificate", e);
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
function generate() {
|
||
|
const keys = pki.rsa.generateKeyPair(2048);
|
||
|
const cert = pki.createCertificate();
|
||
|
|
||
|
cert.publicKey = keys.publicKey;
|
||
|
cert.serialNumber = crypto.randomBytes(16).toString("hex").toUpperCase();
|
||
|
|
||
|
// Set notBefore a day earlier just in case the time between
|
||
|
// the client and server is not perfectly in sync
|
||
|
cert.validity.notBefore = new Date();
|
||
|
cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1);
|
||
|
|
||
|
// Set notAfter 100 years into the future just in case
|
||
|
// the server actually validates this field
|
||
|
cert.validity.notAfter = new Date();
|
||
|
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 100);
|
||
|
|
||
|
const attrs = [
|
||
|
{
|
||
|
name: "commonName",
|
||
|
value: "The Lounge IRC Client",
|
||
|
},
|
||
|
];
|
||
|
cert.setSubject(attrs);
|
||
|
cert.setIssuer(attrs);
|
||
|
|
||
|
// Set extensions that indicate this is a client authentication certificate
|
||
|
cert.setExtensions([
|
||
|
{
|
||
|
name: "extKeyUsage",
|
||
|
clientAuth: true,
|
||
|
},
|
||
|
{
|
||
|
name: "nsCertType",
|
||
|
client: true,
|
||
|
},
|
||
|
]);
|
||
|
|
||
|
// Sign this certificate with a SHA256 signature
|
||
|
cert.sign(keys.privateKey, md.sha256.create());
|
||
|
|
||
|
const pem = {
|
||
|
private_key: pki.privateKeyToPem(keys.privateKey),
|
||
|
certificate: pki.certificateToPem(cert),
|
||
|
};
|
||
|
|
||
|
return pem;
|
||
|
}
|
||
|
|
||
|
function getPaths(folderPath, uuid) {
|
||
|
return {
|
||
|
privateKeyPath: path.join(folderPath, `${uuid}.pem`),
|
||
|
certificatePath: path.join(folderPath, `${uuid}.crt`),
|
||
|
};
|
||
|
}
|