/** * @RDE_HEADER@ * Generates an SQL file from a workstation JSON file (usually located under Lts/Tests/DatabaseInitialisation/%project%_asm_values_workstations.json) * $Id: tpm_exported_scripts_dialog.cpp 52234 2020-10-01 13:34:21Z wille_m $ * $HeadURL: svn://slssvn/TDS/branches/product/devel/trunk/Lts/Plugins/TakeHomePackageManager/tpm_exported_scripts_dialog.cpp $ * $Author: wille_m $ * * Created by Mike Wille, SLTE, 2020-10-10 */ // Parts stolen from https://github.com/wille-io/fast-ca/blob/master/main.cpp @ https://github.com/wille-io/fast-ca/blob/7cd703a2751d995da5b0f918198532d4955934f6/main.cpp #include #include #include #include #include #include #include #include // bug: botan 2.12.1 does a sizeof for (undefined) PK_Signer when using X509CA #include #include #include #include #include #include // grr.. not portable #include // blank pwd / echo on/off #include #include #include #include #include #include #include #include #include #include using namespace Botan; termios t; void echoRestore() { tcsetattr(STDIN_FILENO, TCSANOW, &t); } void echoOff() { termios _t; tcgetattr(STDIN_FILENO, &_t); t = _t; _t.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSAFLUSH, &_t); } int main(int argc, char **argv) { NGLCoreApplication app(argc, argv); app.createLogger("AsmWorkstationCertifier"); NGL::INGLLogger &logger(app.baseLogger()); STARTUP_LOG(logger); QStringList args(app.arguments()); if (args.count() < 5) { ERROR_LOG(logger) << "usage: " << " " << " " << " "; return -1; } QString inputFile(args.at(1)); QString outputFile(args.at(2)); DEBUG_LOG(logger) << "input file: " << inputFile << ", output file: " << outputFile; QFile input(inputFile); if (!input.open(QIODevice::ReadOnly | QIODevice::Text)) { ERROR_LOG(logger) << "cannot open input file: " << input.errorString(); return -1; } // botan stuff INFO_LOG(logger) << "Loading CA certificate ..."; X509_Certificate caCert = X509_Certificate(args.at(3).toStdString()); AutoSeeded_RNG rng; INFO_LOG(logger) << "Loading CA key ..."; Private_Key *caPrivKey = nullptr; std::string pwd; printf("> Enter the CA's private key's password: "); echoOff(); std::cin >> pwd; echoRestore(); printf("\n"); try { caPrivKey = PKCS8::load_key(args.at(4).toStdString(), rng, pwd); } catch(...) { ERROR_LOG(logger) << "CA keys couldn't be unlocked with the given password!"; return -1; } INFO_LOG(logger) << "Initializing CA ..."; X509_CA ca = X509_CA(caCert, *caPrivKey, "SHA-256", rng); // TODO: free caPrivKey QSaveFile output(outputFile); if (!output.open(QIODevice::WriteOnly | QIODevice::Truncate /* empty file */ | QIODevice::Text)) { ERROR_LOG(logger) << "cannot open / create output file: " << output.errorString(); return -1; } QTextStream os(&output); // INSERT INTO asm.asm_asset(id, name, asset_type_id, location_id, certificate) VALUES os << "-- Do not edit this file! File was generated with: " << args.join(QStringLiteral(" ")) << endl << endl << endl << "-- @RDE_HEADER@" << endl << "-- SQL file for asm.asm_asset with generated certificates per hostname" << endl << "-- $Id: $" << endl << "-- $HeadURL: $" << endl << "-- $Author: $" << endl << endl << "-- Created by Mike Wille, SLTE, 2020-10-10" << endl << endl << endl; os << "-- Workstations"; QJsonParseError err; QJsonDocument doc(QJsonDocument::fromJson(input.readAll(), &err)); if (err.error != QJsonParseError::NoError) { ERROR_LOG(logger) << "JSON input file " << inputFile << " contains an invalid json document: " << err.errorString() << " at offset " << err.offset; return -1; } QJsonObject root(doc.object()); if (root.isEmpty()) { ERROR_LOG(logger) << "JSON input file " << inputFile << " is missing the root object"; return -1; } QJsonArray grpArr(root[QStringLiteral("groups")].toArray()); if (grpArr.isEmpty()) { ERROR_LOG(logger) << "JSON input file " << inputFile << " is missing the groups array"; return -1; } for (QJsonValueRef grpRef : grpArr) { const QJsonObject grp(grpRef.toObject()); os << endl << endl << "-- " << grp[QStringLiteral("name")].toString() << endl; os << "INSERT INTO asm.asm_asset(id, name, asset_type_id, location_id, certificate) VALUES" << endl; QJsonArray wsArr(grp[QStringLiteral("workstations")].toArray()); if (wsArr.isEmpty()) { ERROR_LOG(logger) << "JSON input file " << inputFile << " is missing the workstations array in a group"; return -1; } bool first = true; for (const QJsonValue &wsRef : qAsConst(wsArr)) { const QJsonObject ws(wsRef.toObject()); int id = ws[QStringLiteral("id")].toInt(); QString host = ws[QStringLiteral("host")].toString(); int asset = ws[QStringLiteral("asset")].toInt(); int location = ws[QStringLiteral("location")].toInt(); if (first) first = false; else os << endl << ","; INFO_LOG(logger) << "Creating client keys for " << host << "..."; RSA_PrivateKey privKey = RSA_PrivateKey(rng, 4096); std::string fqdn(host.toStdString()); INFO_LOG(logger) << "Creating client certificate request ..."; X509_Cert_Options opts(""); opts.common_name = fqdn; opts.dns = fqdn; opts.add_constraints(Key_Constraints::DIGITAL_SIGNATURE); opts.add_ex_constraint("PKIX.ClientAuth"); PKCS10_Request request = X509::create_cert_req(opts, privKey, "SHA-256", rng); INFO_LOG(logger) << "Creating client certificate ..."; std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); X509_Certificate cert = ca.sign_request(request, rng, X509_Time(now), X509_Time(now + std::chrono::hours(20 * 365 * 24))); // creates a certificate that is valid from now, for incredible 20 years (that should really be enough for a software in an age of super fast code depreciation) std::string certData = cert.PEM_encode(); os << "( " << id << ", '" << host << "', " << asset << ", " << location << ", decode('" << QString::fromUtf8(QString::fromStdString(certData).toUtf8().toHex()) // wow << "', 'hex'))"; DEBUG_LOG(logger) << "Worksation '"<