265 lines
7.3 KiB
C++
Executable File
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;
|
|
}
|