#! /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 '); 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 or EC'); println(' -keyLength - Key length'); println(' -validYear - Valid year(s)'); println(' -certId - Cert ID'); println(' -subject - Subject'); println(' -dnsName - 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();