This commit is contained in:
Oguz Bastemur 2019-04-11 10:19:52 -07:00
Родитель 0cb368693e
Коммит 967cb0a31e
4 изменённых файлов: 437 добавлений и 0 удалений

15
CHANGELOG Normal file
Просмотреть файл

@ -0,0 +1,15 @@
### 0.0.2
Implement
--new-config : creates a new 'certgen.config.json' file (overwrites the existing one)
--new-root-cert : creates a new root certificate that is based on 'certgen.config.json'
--new-leaf-cert <subject> : creates a new certificate and signs it with the root certificate's private key
--is2K : use 2048 bits. default 4096 bits
and test cases.
### 0.0.3
- Remove basicConstraints from the base certificate configuration. (cA)
- Remove other unrelated certificate extensions from the base certificate configuration

285
index.js Executable file
Просмотреть файл

@ -0,0 +1,285 @@
#!/usr/bin/env node
// ----------------------------------------------------------------------------
// Copyright (C) Microsoft
// Licensed under the MIT license.
// ----------------------------------------------------------------------------
const path = require('path');
const fs = require('fs');
const forge = require('node-forge');
const colors = require('colors');
var pki = forge.pki;
var user_current_path = process.cwd();
var default_config = {
'cert_defaults':
[{
name: 'countryName',
value: 'US'
}, {
shortName: 'ST',
value: 'Washington'
}, {
name: 'localityName',
value: 'Redmond'
}, {
name: 'organizationName',
value: 'CoolOrg'
}, {
shortName: 'OU',
value: 'TestCertificate-' + (Date.now() % 99)
}],
'cert_extensions':
[{
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
}, {
name: 'extKeyUsage',
clientAuth: true,
timeStamping: true
}, {
name: 'nsCertType',
client: true
}, {
name: 'subjectAltName',
altNames: [{
type: 6, // URI
value: 'http://example.org/webid#me'
}, {
type: 7, // IP
ip: '127.0.0.1'
}
]
}, {
name: 'subjectKeyIdentifier'
}],
validity : {
notBefore : 0,
notAfter : 0
}
};
default_config.validity.notBefore = new Date();
default_config.validity.notAfter = new Date();
default_config.validity.notAfter.setFullYear(default_config.validity.notBefore.getFullYear() + 2);
var rand_serial_number = (Date.now() % 99) + '';
rand_serial_number = rand_serial_number.length > 1 ? rand_serial_number : ('0' + rand_serial_number);
default_config.serialNumber = rand_serial_number;
var user_config;
var is2K = false;
function read_cofig() {
// check if user has a cert config file
if (fs.existsSync(path.join(user_current_path, 'certgen.config.json'))) {
try {
user_config = JSON.parse(fs.readFileSync('certgen.config.json'));
} catch (e) {
console.error('ERROR (while reading certgen.config.json)', e.message);
process.exit();
}
} else {
user_config = JSON.parse(JSON.stringify(default_config));
}
}
function dump_config(force) {
var exists = fs.existsSync(path.join(user_current_path, 'certgen.config.json'));
if (force || !exists) {
var config_path = path.join(user_current_path, 'certgen.config.json');
console.log('\nDumped default certificate configuration into', colors.bold(config_path));
console.log('Feel free to edit this file and execute `dps-certgen` tool with `--new-root-cert` argument to use it');
fs.writeFileSync(config_path, JSON.stringify(default_config, 0, 2));
}
}
function create_root() {
console.log('\nCreating a new certificate..');
console.log('\t... it may take some time.');
// generate a keypair and create an X.509v3 certificate
var keys = pki.rsa.generateKeyPair(is2K ? 2048 : 4096);
var cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = user_config['serialNumber'];
cert.validity.notBefore = new Date(user_config['validity'].notBefore);
cert.validity.notAfter = new Date(user_config['validity'].notAfter);
cert.setSubject(user_config['cert_defaults']);
cert.setIssuer(user_config['cert_defaults']);
cert.setExtensions(user_config['cert_extensions']);
cert.sign(keys.privateKey, forge.md.sha256.create());
// convert a Forge certificate to PEM
var pem = pki.certificateToPem(cert);
var private_key = pki.privateKeyToPem(keys.privateKey);
var public_key = pki.publicKeyToPem(keys.publicKey);
console.log('');
var cert_path = path.join(user_current_path, './root-cert.pem');
console.log('Writing certificate into ', cert_path);
fs.writeFileSync(cert_path, pem);
var public_key_path = path.join(user_current_path, './root-public-key.pem');
console.log('Writing public key into ', public_key_path);
fs.writeFileSync(public_key_path, public_key);
var private_key_path = path.join(user_current_path, './root-private-key.pem');
console.log('Writing private key into ', private_key_path);
fs.writeFileSync(private_key_path, private_key);
}
function create_signed(subject) {
console.log('\nCreating a leaf certificate with subject..');
console.log('\t... it may take some time.');
// generate a keypair and create an X.509v3 certificate
var keys = {}, root_cert;
var root_cert_path = path.join(user_current_path, 'root-cert.pem');
var pem_private_path = path.join(user_current_path, 'root-private-key.pem');
var pem_public_path = path.join(user_current_path, 'root-public-key.pem');
try {
root_cert = pki.certificateFromPem(fs.readFileSync(root_cert_path) + '');
keys.privateKey = pki.privateKeyFromPem(fs.readFileSync(pem_private_path) + '');
keys.publicKey = pki.publicKeyFromPem(fs.readFileSync(pem_public_path) + '');
} catch(e) {
console.error('Error:',
'Something went wrong while reading private key from pem file @',
pem_private_path, 'or @', pem_public_path);
console.error(e.message);
process.exit(1);
}
var leaf_keys = pki.rsa.generateKeyPair(is2K ? 2048 : 4096);
var cert = pki.createCertificate();
cert.publicKey = leaf_keys.publicKey;
var sign_config = JSON.parse(JSON.stringify(user_config));
sign_config.cert_defaults.push({
'name': 'commonName',
'value': subject
});
cert.serialNumber = sign_config.serialNumber;
cert.validity.notBefore = new Date(user_config['validity'].notBefore);
cert.validity.notAfter = new Date(user_config['validity'].notAfter);
cert.setSubject(sign_config['cert_defaults']);
cert.setExtensions(user_config['cert_extensions']);
cert.setIssuer(root_cert.subject.attributes);
cert.sign(keys.privateKey, forge.md.sha256.create());
if (!root_cert.verify(cert)) {
console.error('Error:', 'Something went wrong while issuing leaf certificate.');
process.exit(1);
}
// convert a Forge certificate to PEM
var pem = pki.certificateToPem(cert);
var private_key = pki.privateKeyToPem(leaf_keys.privateKey);
var public_key = pki.publicKeyToPem(leaf_keys.publicKey);
var cert_path = path.join(user_current_path, './subject-cert.pem');
console.log('Writing certificate into ', cert_path);
fs.writeFileSync(cert_path, pem);
var public_key_path = path.join(user_current_path, './subject-public-key.pem');
console.log('Writing public key into ', public_key_path);
fs.writeFileSync(public_key_path, public_key);
var private_key_path = path.join(user_current_path, './subject-private-key.pem');
console.log('Writing private key into ', private_key_path);
fs.writeFileSync(private_key_path, private_key);
}
function showUsage() {
console.log('Usage:');
console.log(colors.bold('dps-certgen'), '<args>');
console.log('\nargs:');
console.log('--new-config : creates a new certgen.config.json file (overwrites the existing one)');
console.log('--new-root-cert : creates a new root certificate that is based on certgen.config.json');
console.log('--new-leaf-cert <subject> : creates a new certificate and signs it with the root certificate\'s private key');
console.log('\n--is2K : use 2048 bits. default 4096 bits');
console.log('\ni.e. => dps-certgen --new-root-cert');
console.log('');
}
function main() {
var version = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'))).version;
console.log(`\nAzure IoT x509 Cerficate Helper Tool v${version}`);
if (process.argv.length < 3) {
showUsage();
process.exit(0);
}
var args = {
'--new-config': 0,
'--new-root': 0,
'--new-cert': 0,
'--new-root-cert': 0,
'--new-leaf': 0,
'--new-leaf-cert': 0,
'--is2K': 0,
'--is2k': 0
};
var found_arg = 0;
for (var i = 1; i < process.argv.length; i++) {
var arg = process.argv[i];
if (arg.startsWith('-')) {
if (args.hasOwnProperty(arg)) {
args[arg] = 1;
found_arg = 1;
if (arg == '--new-leaf-cert' || arg == '--new-leaf') {
if (i + 1 >= process.argv.length) {
console.error('Error:', arg + ' argument needs <subject>');
process.exit(1);
}
if (process.argv[i + 1] == '--is2K' || process.argv[i + 1] == '--is2k') {
console.error('Error:', 'Put subject right after \'' + arg + '\'.. ');
process.exit(1);
}
args['--new-leaf-cert'] = process.argv[i + 1]; i++;
}
}
}
}
if (found_arg == 0) {
showUsage();
process.exit(1);
}
var config_dumped = false;
if (args['--is2K'] || args['--is2k']) {
is2K = true;
}
if (args['--new-config']) {
dump_config(1 /* force */);
config_dumped = true;
}
if (args['--new-root-cert'] || args['--new-cert'] || args['--new-root']) {
if (!config_dumped) dump_config(0 /*dont force*/);
read_cofig();
create_root();
}
if (args['--new-leaf-cert'] || args['--new-leaf']) {
if (!config_dumped) dump_config(0 /*dont force*/);
read_cofig();
if (!fs.existsSync(path.join(user_current_path, 'root-private-key.pem'))) {
create_root();
}
create_signed(args['--new-leaf-cert']);
}
console.log('\ndone.');
}
main();

18
package.json Normal file
Просмотреть файл

@ -0,0 +1,18 @@
{
"name": "dps-certgen",
"version": "0.0.3",
"description": "x509 Certificate Helper Tool",
"main": "index.js",
"scripts": {
"test": "eslint ./**/*.js && cd test && ./test.sh"
},
"author": "Oguz Bastemur",
"license": "MIT",
"dependencies": {
"colors": "^1.3.3",
"node-forge": "^0.8.2"
},
"bin": {
"dps-certgen": "./index.js"
}
}

119
test/test.sh Executable file
Просмотреть файл

@ -0,0 +1,119 @@
#!/bin/bash
# ----------------------------------------------------------------------------
# Copyright (C) Microsoft
# Licensed under the MIT license.
# ----------------------------------------------------------------------------
rm -f *.pem
rm -f *.json
CERT_FILE='certgen.config.json'
# check if --new-config will create a config file
node ../index.js --new-config > /dev/null
if [ ! -f $CERT_FILE ]; then
echo '$CERT_FILE is not on the path.. [1]'
exit 1
fi
rm $CERT_FILE
# check if new-root-cert will create a config file (when there is no any)
node ../index.js --new-root-cert > /dev/null
if [ ! -f $CERT_FILE ]; then
echo '$CERT_FILE is not on the path.. [2]'
exit 1
fi
# update config file
node -e "fs=require('fs');f=JSON.parse(fs.readFileSync('./$CERT_FILE'));f.XXX=1;fs.writeFileSync('./$CERT_FILE',JSON.stringify(f,0,2))"
if [ $? != 0 ]; then
exit 1;
fi
# see if new-root-cert will overwrite the config file
node ../index.js --new-root-cert > /dev/null
if [ ! -f $CERT_FILE ]; then
echo '$CERT_FILE is not on the path.. [3]'
exit 1
fi
node -e "fs=require('fs');f=JSON.parse(fs.readFileSync('./$CERT_FILE'));if(f.XXX != 1) {console.error('new-root-cert overwrote the config file');process.exit(1);}"
if [ $? != 0 ]; then
exit 1;
fi
# create and verify a leaf cert
node ../index.js --new-leaf-cert 12345 > /dev/null
if [ ! -f $CERT_FILE ]; then
echo '$CERT_FILE is not on the path.. [4]'
exit 1
fi
# see if new-leaf-cert has overwrote the config file
node -e "fs=require('fs');f=JSON.parse(fs.readFileSync('./$CERT_FILE'));if(f.XXX != 1) {console.error('new-root-cert overwrote the config file');process.exit(1);}"
if [ $? != 0 ]; then
exit 1;
fi
# try all with 2K
rm -f *.pem
rm -f *.json
# check if --new-config will create a config file
node ../index.js --new-config --is2K > /dev/null
if [ ! -f $CERT_FILE ]; then
echo '$CERT_FILE is not on the path.. [5]'
exit 1
fi
rm $CERT_FILE
# check if new-root-cert will create a config file (when there is no any)
node ../index.js --new-root-cert --is2K > /dev/null
if [ ! -f $CERT_FILE ]; then
echo '$CERT_FILE is not on the path.. [6]'
exit 1
fi
# update config file
node -e "fs=require('fs');f=JSON.parse(fs.readFileSync('./$CERT_FILE'));f.XXX=1;fs.writeFileSync('./$CERT_FILE',JSON.stringify(f,0,2))"
if [ $? != 0 ]; then
exit 1;
fi
# see if new-root-cert will overwrite the config file
node ../index.js --new-root-cert --is2K > /dev/null
if [ ! -f $CERT_FILE ]; then
echo '$CERT_FILE is not on the path.. [7]'
exit 1
fi
node -e "fs=require('fs');f=JSON.parse(fs.readFileSync('./$CERT_FILE'));if(f.XXX != 1) {console.error('new-root-cert overwrote the config file');process.exit(1);}"
if [ $? != 0 ]; then
exit 1;
fi
# create and verify a leaf cert
node ../index.js --new-leaf-cert 12345 --is2K > /dev/null
if [ ! -f $CERT_FILE ]; then
echo '$CERT_FILE is not on the path.. [8]'
exit 1
fi
# see if new-leaf-cert has overwrote the config file
node -e "fs=require('fs');f=JSON.parse(fs.readFileSync('./$CERT_FILE'));if(f.XXX != 1) {console.error('new-root-cert overwrote the config file');process.exit(1);}"
if [ $? != 0 ]; then
exit 1;
fi
# put 2K in between the subject and --new-leaf-cert
node ../index.js --new-leaf-cert --is2K 12345 > /dev/null 2>&1
if [ $? == 0 ]; then
echo "Test: put 2K in between the subject and --new-leaf-cert .... was supposed to fail! (expectedly)"
exit 1;
fi
rm -f *.pem
rm -f *.json
echo "PASS!"