@adp-psych/container-tools
Version:
Tools for using containers for psychology experiments
213 lines (198 loc) • 5.07 kB
JavaScript
/*
* Copyright (C) 2021 Anthony Di Pietro
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* @file Generates a self-signed TLS certificate.
*
* @module module:generate-selfsigned-certificate
* @author Anthony Di Pietro <anthony.dipietro@research.uwa.edu.au>
* @copyright © 2021 Anthony Di Pietro
* @license AGPL-3.0-or-later
*/
/* eslint-disable security/detect-non-literal-fs-filename -- Trusted source. */
const fs = require('node:fs/promises');
const path = require('node:path');
const {
'pki': {
certificateToPem,
createCertificate,
privateKeyToPem,
'rsa': {generateKeyPair},
},
} = require('node-forge');
/**
* The RSA key size.
*
* @constant {Number}
* @static
* @access protected
*/
const KEY_SIZE = 4096;
/**
* The certificate subject.
*
* @constant {Object}
* @static
* @access protected
*/
const SUBJECT = [{
'name': 'commonName',
'value': 'localhost',
}];
/**
* The certificate extensions.
*
* @constant {Array.<Object>}
* @static
* @access protected
*/
const EXTENSIONS = [
{
'cA': true,
'name': 'basicConstraints',
},
{
'dataEncipherment': true,
'digitalSignature': true,
'keyCertSign': true,
'keyEncipherment': true,
'name': 'keyUsage',
'nonRepudiation': true,
},
{
'clientAuth': true,
'codeSigning': true,
'emailProtection': true,
'name': 'extKeyUsage',
'serverAuth': true,
'timeStamping': true,
},
{
'client': true,
'email': true,
'emailCA': true,
'name': 'nsCertType',
'objCA': true,
'objsign': true,
'server': true,
'sslCA': true,
},
{
'altNames': [{
'ip': '127.0.0.1',
'type': 7,
}],
'name': 'subjectAltName',
},
{
'name': 'subjectKeyIdentifier',
},
];
/**
* Gets the validity dates for a certificate that lasts one year starting now.
*
* @static
* @access protected
* @returns {Object} The validity dates.
* @example
* validityDates();
*/
const validityDates = () => {
const notBefore = new Date();
const notAfter = new Date();
notAfter.setFullYear(notBefore.getFullYear() + 1);
return {notAfter, notBefore};
};
/**
* Makes a key pair.
*
* @static
* @access protected
* @returns {Object} The key pair.
* @example
* makeKeyPair();
*/
const makeKeyPair = () => generateKeyPair(KEY_SIZE);
/**
* Makes a certificate.
*
* @static
* @access protected
* @param {Object} keyPair - The key pair for the certificate.
* @returns {Object} The certificate.
* @example
* makeCertificate(makeKeyPair());
*/
const makeCertificate = (keyPair) => {
const {notAfter, notBefore} = validityDates();
const certificate = createCertificate();
certificate.publicKey = keyPair.publicKey;
certificate.serialNumber = '01';
certificate.validity.notBefore = notBefore;
certificate.validity.notAfter = notAfter;
certificate.setSubject(SUBJECT);
certificate.setIssuer(SUBJECT);
certificate.setExtensions(EXTENSIONS);
certificate.sign(keyPair.privateKey);
return certificate;
};
/**
* Writes a private key.
*
* @static
* @access protected
* @param {Object} privateKey - The private key.
* @param {String} filename - The filename.
* @example
* writePrivateKey(makeKeyPair().privateKey, 'privkey.pem');
*/
const writePrivateKey = async (privateKey, filename) => {
await fs.mkdir(path.dirname(filename), {'recursive': true});
await fs.writeFile(filename, privateKeyToPem(privateKey));
};
/**
* Writes a certificate.
*
* @static
* @access protected
* @param {Object} certificate - The certificate.
* @param {String} filename - The filename.
* @example
* writeCertificate(makeCertificate(makeKeyPair()), 'cert.pem');
*/
const writeCertificate = async (certificate, filename) => {
await fs.mkdir(path.dirname(filename), {'recursive': true});
await fs.writeFile(filename, certificateToPem(certificate));
};
/**
* Generates a self-signed TLS certificate.
*
* @static
* @param {String} keyFilename - The filename of the private key.
* @param {String} certificateFilename - The filename of the certificate.
* @example
* // Generates a self-signed certificate.
* generateSelfsignedCertificate('tls/privkey.pem', 'tls/cert.pem');
*/
const generateSelfsignedCertificate = async (
keyFilename, certificateFilename,
) => {
const keyPair = makeKeyPair();
const certificate = makeCertificate(keyPair);
await writePrivateKey(keyPair.privateKey, keyFilename);
await writeCertificate(certificate, certificateFilename);
};
module.exports = generateSelfsignedCertificate;