Files
js-scripts/scripts/tinyca.js
2025-04-05 16:57:26 +08:00

479 lines
19 KiB
JavaScript

#! /usr/bin/env runjs
requireJS('component-json.js');
requireJS('component-colorprint.js');
requireJS('component-gpg.js');
requireJAR('bcpkix-jdk15on-154.jar');
requireJAR('bcprov-ext-jdk15on-154.jar');
requireJAR('bcprov-jdk15on-154.jar');
requireJAR('crypto-1.0.jar');
var StringReader = java.io.StringReader;
var BufferedReader = java.io.BufferedReader;
var TimeUnit = java.util.concurrent.TimeUnit;
var Bytes = Packages.me.hatter.tools.commons.bytes.Bytes;
var PEMUtil = Packages.me.hatter.tools.commons.security.pem.PEMUtil;
var UnixArgsUtil = Packages.me.hatter.tools.commons.args.UnixArgsUtil;
var X509CertUtil = Packages.me.hatter.tools.commons.security.cert.X509CertUtil;
var PrivateKeyParseTool = Packages.me.hatter.tools.commons.security.rsa.PrivateKeyParseTool;
var KeyPairGenerateTool = Packages.me.hatter.tools.commons.security.key.KeyPairGenerateTool;
var CertificateAuthority = Packages.me.hatter.tools.crypto.ca.CertificateAuthority;
var printHelp = () => {
println('tinyca.js - Tiny CA.')
println();
println('tinyca.js <type> <command>');
println('type - _, global, ca, intermediate, server, client');
println('command, cases below:');
println(' type = _, global - init');
println(' type = ca - create, list, delete');
println(' type = intermediate - create, list, delete');
println(' type = server - create, list, delete');
println(' type = client - create, list, delete');
println('params:');
println(' -type <rsa,ec> - RSA or EC');
println(' -keyLength <len> - Key length');
println(' -validYear <year> - Valid year(s)');
println(' -certId <certId> - Cert ID');
println(' -subject <subject> - Subject');
println(' -dnsName <name,name> - DNS name(s)');
};
var dvalue = (key) => {
var ks = key.split(/\./g);
var val = $GLOBAL();
for (var i = 0; i < ks.length; i++) {
var val2 = val[ks[i]];
if (val2 == null) { return null; }
val = val2;
}
return val;
};
var getTypeKeyLengthKeyPair = (ty) => {
var type = UnixArgsUtil.ARGS.kvalue('type', dvalue('GLOBAL_CA_CONFIG.defaults.' + ty + '.type') || dvalue('GLOBAL_CA_CONFIG.defaults._.type'));
var keyLength = UnixArgsUtil.ARGS.kvalue('keyLength', 0);
var kpt = KeyPairGenerateTool.instance();
if (type.toUpperCase() == 'RSA') {
kpt.rsa();
if (keyLength == 0) {
kpt.keyLength(dvalue('GLOBAL_CA_CONFIG.defaults.' + ty + '.keyLength.RSA') || dvalue('GLOBAL_CA_CONFIG.defaults._.keyLength.RSA'));
}
} else if (type.toUpperCase() == 'EC') {
kpt.ec();
if (keyLength == 0) {
kpt.keyLength(dvalue('GLOBAL_CA_CONFIG.defaults.' + ty + '.keyLength.EC') || dvalue('GLOBAL_CA_CONFIG.defaults._.keyLength.EC'));
}
} else {
println(colorPrint.fail.render('[FAIL]') + 'Unknown type: ' + type);
}
if (keyLength < 0) {
println(colorPrint.fail.render('[FAIL]') + 'Key length invalid: ' + keyLength);
} else if (keyLength > 0) {
kpt.keyLength(keyLength);
}
return [type, keyLength, kpt.generate()];
};
var displayLimitChars = (str) => {
if (str.length < 80) {
return str;
}
return str.substring(0, 87) + '...';
};
var listCerts = (dir) => {
$ARRAY(dir.listFiles()).forEach((d) => {
var fileCert = $$.file(d, 'cert.pem');
if (!fileCert.exists()) {
println(colorPrint.warning.render('[WARN]') + 'No cert found in: ' + d.getName());
return;
}
var certs = X509CertUtil.parseX509CertificateList($$.rFile(fileCert).bytes());
if ((certs == null) || (certs.size() == 0)) {
println(colorPrint.warning.render('[WARN]') + 'No cert found in: ' + fileCert);
return;
} else if (certs.size() > 1) {
println(colorPrint.warning.render('[WARN]') + 'More than one certs found in: ' + fileCert);
return;
}
var cert = certs.get(0);
var isValid = (cert.getNotBefore().getTime() <= $$.date().millis()) && (cert.getNotAfter().getTime() >= $$.date().millis());
println(colorPrint.bold.render('CERT ID: ')
+ colorPrint.underline.render(d.getName())
+ ' '
+ (isValid? colorPrint.okgreen.render('[VALID]'): colorPrint.okgreen.render('[EXPIRED]'))
+ (isValid? (' left ' + colorPrint.underline.render(parseInt((cert.getNotAfter().getTime() - $$.date().millis()) / TimeUnit.DAYS.toMillis(1))) + ' day(s)'): '')
);
println(' Subject : ' + cert.getSubjectDN());
println(' Issuer : ' + cert.getIssuerDN());
if ((cert.getSubjectAlternativeNames() != null) && (cert.getSubjectAlternativeNames().size() > 0)) {
println(' Alt Subject : ' + cert.getSubjectAlternativeNames());
}
println(' NotBefore : ' + cert.getNotBefore());
println(' NotAfter : ' + cert.getNotAfter());
if (UnixArgsUtil.ARGS.flags().containsAny('v', 'verbose')) {
var pkLines = $STR(cert.getPublicKey()).split("\n");
println(' PublicKey : ' + pkLines[0]);
for (var i = 1; i < pkLines.length; i++) {
println(' : ' + displayLimitChars(pkLines[i]));
}
println(' Issuer : ' + cert.getIssuerDN());
println(' Sign Alg : ' + cert.getSigAlgName());
// println(' Sign Alg OID : ' + cert.getSigAlgOID());
println(' Signature : ' + displayLimitChars(Bytes.from(cert.getSignature()).asHex()));
}
});
};
var writeCert = (kp, cert, dir) => {
var serialNo = Bytes.from(cert.getSerialNumber().toByteArray()).asHex().toUpperCase();
if (serialNo.startsWith('00')) { serialNo = serialNo.substring(2); }
var dirSerialNo = $$.file(dir, serialNo);
dirSerialNo.mkdirs();
var filePrivKey = $$.file(dirSerialNo, 'privkey.pem');
var filePrivKeyEncypted = $$.file(dirSerialNo, 'privkey.pem.asc');
var filePubKey = $$.file(dirSerialNo, 'pubkey.pem');
var fileCert = $$.file(dirSerialNo, 'cert.pem');
if (filePrivKey.exists() || filePrivKeyEncypted.exists() || filePubKey.exists() || fileCert.exists()) {
println(colorPrint.fail.render('[FAIL]') + 'Serial number already exsists: ' + serialNo);
return;
}
$$.rFile(filePrivKey).write(PEMUtil.printPEM('PRIVATE KEY', kp.getPrivate().getEncoded()));
$$.rFile(filePubKey).write(PEMUtil.printPEM('PUBLIC KEY', kp.getPublic().getEncoded()));
$$.rFile(fileCert).write(PEMUtil.printPEM('CERTIFICATE', cert.getEncoded()));
gpgEncryptArmor(dvalue('GLOBAL_CA_CONFIG.gpgKeyId') || '6FAFC0E0170985AA71545483C794B1646A886CD6', filePrivKey.getPath(), filePrivKeyEncypted.getPath());
if (!filePrivKeyEncypted.exists()) {
println(colorPrint.fail.render('[FAIL]') + 'Encrypt private key failed: ' + filePrivKey);
return;
}
filePrivKey.delete();
if (UnixArgsUtil.ARGS.flags().containsAny('v', 'verbose')) {
println('[INFO] Public key:');
println(kp.getPublic());
println('[INFO] Certificate:');
println(cert);
}
println(colorPrint.okgreen.render('[SUCCESS]') + 'Certificate created: ' + serialNo + ' ---> ' + dir);
};
var defaultCA_JSON = {
"name": "My Private CA",
"gpgKeyId": "6FAFC0E0170985AA71545483C794B1646A886CD6",
"dirs": {
"CA": "ca",
"INTERMEDATE": "intermediate",
"SERVER": "server",
"CLIENT": "client"
},
"defaults": {
"_": {
"type": "EC",
"keyLength": {
"RSA": 2048,
"EC": 256
},
"validYear": 5
},
"ca": {
"type": "EC",
"keyLength": {
"RSA": 4096,
"EC": 384
},
"validYear": 100
},
"intermediate": {
"validYear": 10
}
}
};
var main = () => {
UnixArgsUtil.parseGlobalArgs($ARGS);
var args = UnixArgsUtil.ARGS.args();
if (args == null || args.length != 2) {
printHelp();
return;
}
var type = $STR(args[0]);
var command = $STR(args[1]);
if (((type == '_') || (type == 'global')) && (command == 'init')) {
if ($$.file('ca.json').exists()) {
// println(colorPrint.okblue.render('[OK]') + 'File: ca.json already exists.');
} else {
$$.rFile('ca.json').write(prettyJSON(defaultCA_JSON));
println(colorPrint.okgreen.render('[SUCCESS]') + 'File: ca.json created.');
}
}
if (!$$.file('ca.json').exists()) {
println('No file ca.json found!');
println('');
println('Please create ca.json first:');
println(prettyJSON(defaultCA_JSON));
return;
}
GLOBAL_CA_CONFIG = JSON.parse($$.rFile('ca.json').string());
println('::::::Tiny CA programme: ' + (dvalue('GLOBAL_CA_CONFIG.name') || 'Unnamed CA') + ':');
if ((type == '_') || (type == 'global')) {
if (command == 'init') {
var tobeCreatedDirs = [dvalue('GLOBAL_CA_CONFIG.dirs.CA') || 'ca',
dvalue('GLOBAL_CA_CONFIG.dirs.INTERMEDATE') || 'intermediate',
dvalue('GLOBAL_CA_CONFIG.dirs.SERVER') || 'server',
dvalue('GLOBAL_CA_CONFIG.dirs.CLIENT') || 'client'];
tobeCreatedDirs.forEach((e) => {
var f = $$.file(e);
if (f.exists()) {
if (f.isDirectory()) {
println(colorPrint.okblue.render('[OK]') + 'Directory: ' + e + ' already exists.');
} else {
println(colorPrint.fail.render('[FAIL]') + 'File?: ' + e + ' exists, but not directory!');
}
} else {
f.mkdirs();
println(colorPrint.okgreen.render('[SUCCESS]') + 'Directory: ' + e + ' created.');
}
});
println(colorPrint.okgreen.render('[DONE]') + 'Global init finished.');
} else if (command == 'list') {
println('[CA]' + ' list:');
listCerts($$.file(dvalue('GLOBAL_CA_CONFIG.dirs.CA') || 'ca'));
println();
println('[INTERMEDIATE]' + ' list:');
listCerts($$.file(dvalue('GLOBAL_CA_CONFIG.dirs.INTERMEDIATE') || 'intermediate'));
println();
println('[SERVER]' + ' list:');
listCerts($$.file(dvalue('GLOBAL_CA_CONFIG.dirs.SERVER') || 'server'));
println();
println('[CLIENT]' + ' list:');
listCerts($$.file(dvalue('GLOBAL_CA_CONFIG.dirs.CLIENT') || 'client'));
}
} else if (type == 'ca') {
if (command == 'create') {
var typeKeyLengthKP = getTypeKeyLengthKeyPair('ca');
var type = typeKeyLengthKP[0];
var keyLength = typeKeyLengthKP[1];
var kp = typeKeyLengthKP[2];
var subject = UnixArgsUtil.ARGS.kvalue('subject', '').replaceAll('__', ' ');
if (subject == '') {
println(colorPrint.fail.render('[FAIL]') + 'Subject cannot be null.');
return;
}
var validYear = UnixArgsUtil.ARGS.kvalue('validYear', dvalue('GLOBAL_CA_CONFIG.defaults.ca.validYear') || dvalue('GLOBAL_CA_CONFIG.defaults._.validYear') || 100);
var cert = CertificateAuthority
.instance()
.validYears(validYear)
.subject(subject)
.certPubKey(kp.getPublic())
.signPrivKey(kp.getPrivate())
.createCA();
writeCert(kp, cert, dvalue('GLOBAL_CA_CONFIG.dirs.CA') || 'ca');
} else if (command == 'list') {
listCerts($$.file(dvalue('GLOBAL_CA_CONFIG.dirs.CA') || 'ca'));
} else if (command == 'delete') {
println(colorPrint.fail.render('[FAIL]') + 'Not implemented: ' + command);
} else {
println(colorPrint.fail.render('[FAIL]') + 'Unknow command: ' + command);
printHelp();
}
} else if (type == 'intermediate') {
if (command == 'create') {
var typeKeyLengthKP = getTypeKeyLengthKeyPair('intermediate');
var type = typeKeyLengthKP[0];
var keyLength = typeKeyLengthKP[1];
var kp = typeKeyLengthKP[2];
var subject = UnixArgsUtil.ARGS.kvalue('subject', '').replaceAll('__', ' ');
if (subject == '') {
println(colorPrint.fail.render('[FAIL]') + 'Subject cannot be null.');
return;
}
var validYear = UnixArgsUtil.ARGS.kvalue('validYear', dvalue('GLOBAL_CA_CONFIG.defaults.intermediate.validYear') || dvalue('GLOBAL_CA_CONFIG.defaults._.validYear') || 20);
var certId = UnixArgsUtil.ARGS.kvalue('certId');
if (certId == null) {
println(colorPrint.fail.render('[FAIL]') + 'Cert ID cannot be null.');
return;
}
var dirCA = $$.file(dvalue('GLOBAL_CA_CONFIG.dirs.CA') || 'ca');
var fileCACert = $$.file(dirCA, certId, "cert.pem");
var fileCAPrivKeyEncrypted = $$.file(dirCA, certId, "privkey.pem.asc");
if (!(fileCACert.exists()) || !fileCAPrivKeyEncrypted.exists()) {
println(colorPrint.fail.render('[FAIL]') + 'Cert ID not exists: ' + certId);
return;
}
var signCert = X509CertUtil.parseX509CertificateList($$.rFile(fileCACert).bytes()).get(0);
var privKeyVal;
try {
privKeyVal = gpgDecrypt(fileCAPrivKeyEncrypted.getPath());
} catch(ex) {
println(colorPrint.fail.render('[FAIL]') + 'Decrypt private key failed: ' + fileCAPrivKeyEncrypted);
return;
}
var signPrivKey = new PrivateKeyParseTool(new BufferedReader(new StringReader(privKeyVal))).read();
var cert = CertificateAuthority
.instance()
.validYears(validYear)
.subject(subject)
.certPubKey(kp.getPublic())
.signCert(signCert)
.signPrivKey(signPrivKey)
.createIntermediateCert();
writeCert(kp, cert, dvalue('GLOBAL_CA_CONFIG.dirs.INTERMEDATE') || 'intermediate');
} else if (command == 'list') {
listCerts($$.file(dvalue('GLOBAL_CA_CONFIG.dirs.INTERMEDATE') || 'intermediate'));
} else if (command == 'delete') {
println(colorPrint.fail.render('[FAIL]') + 'Not implemented: ' + command);
} else {
println(colorPrint.fail.render('[FAIL]') + 'Unknow command: ' + command);
printHelp();
}
} else if (type == 'server') {
if (command == 'create') {
var typeKeyLengthKP = getTypeKeyLengthKeyPair('server');
var type = typeKeyLengthKP[0];
var keyLength = typeKeyLengthKP[1];
var kp = typeKeyLengthKP[2];
var subject = UnixArgsUtil.ARGS.kvalue('subject', '').replaceAll('__', ' ');
if (subject == '') {
println(colorPrint.fail.render('[FAIL]') + 'Subject cannot be null.');
return;
}
var dnsName = UnixArgsUtil.ARGS.kvalue('dnsName', '');
if (dnsName == '') {
println(colorPrint.fail.render('[FAIL]') + 'DNSName cannot be null.');
return;
}
var validYear = UnixArgsUtil.ARGS.kvalue('validYear', dvalue('GLOBAL_CA_CONFIG.defaults.client.validYear') || dvalue('GLOBAL_CA_CONFIG.defaults._.validYear') || 5);
var certId = UnixArgsUtil.ARGS.kvalue('certId');
if (certId == null) {
println(colorPrint.fail.render('[FAIL]') + 'Cert ID cannot be null.');
return;
}
var dirCA = $$.file(dvalue('GLOBAL_CA_CONFIG.dirs.CA') || 'ca');
var dirIntermediate = $$.file(dvalue('GLOBAL_CA_CONFIG.dirs.INTERMEDATE') || 'intermediate');
var fileCACert;
var fileCAPrivKeyEncrypted;
if ($$.file(dirIntermediate, certId, "cert.pem").exists()) {
fileCACert = $$.file(dirIntermediate, certId, "cert.pem");
fileCAPrivKeyEncrypted = $$.file(dirIntermediate, certId, "privkey.pem.asc");
} else {
fileCACert = $$.file(dirCA, certId, "cert.pem");
fileCAPrivKeyEncrypted = $$.file(dirCA, certId, "privkey.pem.asc");
}
if (!(fileCACert.exists()) || !fileCAPrivKeyEncrypted.exists()) {
println(colorPrint.fail.render('[FAIL]') + 'Cert ID not exists: ' + certId);
return;
}
var signCert = X509CertUtil.parseX509CertificateList($$.rFile(fileCACert).string()).get(0);
var privKeyVal;
try {
privKeyVal = gpgDecrypt(fileCAPrivKeyEncrypted.getPath());
} catch(ex) {
println(colorPrint.fail.render('[FAIL]') + 'Decrypt private key failed: ' + fileCAPrivKeyEncrypted);
return;
}
var signPrivKey = new PrivateKeyParseTool(new BufferedReader(new StringReader(privKeyVal))).read();
var cert = CertificateAuthority
.instance()
.validYears(validYear)
.subject(subject)
.certPubKey(kp.getPublic())
.signCert(signCert)
.signPrivKey(signPrivKey)
.createServerCert($$.asList(dnsName.split(/,/g)));
writeCert(kp, cert, dvalue('GLOBAL_CA_CONFIG.dirs.SERVER') || 'server');
} else if (command == 'list') {
listCerts($$.file(dvalue('GLOBAL_CA_CONFIG.dirs.SERVER') || 'server'));
} else if (command == 'delete') {
println(colorPrint.fail.render('[FAIL]') + 'Not implemented: ' + command);
} else {
println(colorPrint.fail.render('[FAIL]') + 'Unknow command: ' + command);
printHelp();
}
} else if (type == 'client') {
if (command == 'create') {
var typeKeyLengthKP = getTypeKeyLengthKeyPair('client');
var type = typeKeyLengthKP[0];
var keyLength = typeKeyLengthKP[1];
var kp = typeKeyLengthKP[2];
var subject = UnixArgsUtil.ARGS.kvalue('subject', '').replaceAll('__', ' ');
if (subject == '') {
println(colorPrint.fail.render('[FAIL]') + 'Subject cannot be null.');
return;
}
var validYear = UnixArgsUtil.ARGS.kvalue('validYear', dvalue('GLOBAL_CA_CONFIG.defaults.client.validYear') || dvalue('GLOBAL_CA_CONFIG.defaults._.validYear') || 5);
var certId = UnixArgsUtil.ARGS.kvalue('certId');
if (certId == null) {
println(colorPrint.fail.render('[FAIL]') + 'Cert ID cannot be null.');
return;
}
var dirCA = $$.file(dvalue('GLOBAL_CA_CONFIG.dirs.CA') || 'ca');
var dirIntermediate = $$.file(dvalue('GLOBAL_CA_CONFIG.dirs.INTERMEDATE') || 'intermediate');
var fileCACert;
var fileCAPrivKeyEncrypted;
if ($$.file(dirIntermediate, certId, "cert.pem").exists()) {
fileCACert = $$.file(dirIntermediate, certId, "cert.pem");
fileCAPrivKeyEncrypted = $$.file(dirIntermediate, certId, "privkey.pem.asc");
} else {
fileCACert = $$.file(dirCA, certId, "cert.pem");
fileCAPrivKeyEncrypted = $$.file(dirCA, certId, "privkey.pem.asc");
}
if (!(fileCACert.exists()) || !fileCAPrivKeyEncrypted.exists()) {
println(colorPrint.fail.render('[FAIL]') + 'Cert ID not exists: ' + certId);
return;
}
var signCert = X509CertUtil.parseX509CertificateList($$.rFile(fileCACert).bytes()).get(0);
var privKeyVal;
try {
privKeyVal = gpgDecrypt(fileCAPrivKeyEncrypted.getPath());
} catch(ex) {
println(colorPrint.fail.render('[FAIL]') + 'Decrypt private key failed: ' + fileCAPrivKeyEncrypted);
return;
}
var signPrivKey = new PrivateKeyParseTool(new BufferedReader(new StringReader(privKeyVal))).read();
var cert = CertificateAuthority
.instance()
.validYears(validYear)
.subject(subject)
.certPubKey(kp.getPublic())
.signCert(signCert)
.signPrivKey(signPrivKey)
.createClientCert();
writeCert(kp, cert, dvalue('GLOBAL_CA_CONFIG.dirs.CLIENT') || 'client');
} else if (command == 'list') {
listCerts($$.file(dvalue('GLOBAL_CA_CONFIG.dirs.CLIENT') || 'client'));
} else if (command == 'delete') {
println(colorPrint.fail.render('[FAIL]') + 'Not implemented: ' + command);
} else {
println(colorPrint.fail.render('[FAIL]') + 'Unknow command: ' + command);
printHelp();
}
}
};
main();