Files
qt5-legatus-legacy/Lts/Tools/AsmWorkstationCertifier/main.cpp
T
2026-08-13 18:33:10 +02:00

265 lines
7.3 KiB
C++
Executable File

/**
* @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 <botan/x509_ca.h>
#include <botan/x509cert.h>
#include <botan/x509self.h>
#include <botan/rsa.h>
#include <botan/pk_keys.h>
#include <botan/bigint.h>
#include <botan/auto_rng.h>
#include <botan/pubkey.h> // bug: botan 2.12.1 does a sizeof for (undefined) PK_Signer when using X509CA
#include <botan/pkcs8.h>
#include <stdio.h>
#include <string>
#include <memory>
#include <vector>
#include <unistd.h> // grr.. not portable
#include <termios.h> // blank pwd / echo on/off
#include <iostream>
#include <nglapplication/nglcoreapplication.h>
#include <NGLLogger/INGLLogger.h>
#include <QFile>
#include <QSaveFile>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonArray>
#include <QTextStream>
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: <input (project_asm_values_worksations.json)>"
<< " <output (project_asm_values_workstations.sql)>"
<< " <ca certificate (ca.crt)>"
<< " <ca private key (ca.ber)>";
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 '"<<ws[QStringLiteral("host")].toString()<<"' done!";
}
os << ";";
DEBUG_LOG(logger) << "Group '"<<grp[QStringLiteral("name")].toString()<<"' done!";
}
os << endl
<< endl
<< "ALTER SEQUENCE asm.asm_asset_id_seq RESTART WITH 200;";
os.flush();
if (!output.commit())
{
ERROR_LOG(logger) << "Unable to commit output file data! " << output.errorString();
return -1;
}
SHUTDOWN_LOG(logger);
return 0;
}