Revised signing procedure.

This commit is contained in:
Robin Davies
2024-09-19 11:17:15 -04:00
parent f52e50b29a
commit f60ed1fe56
+187
View File
@@ -0,0 +1,187 @@
#include "CommandLineParser.hpp"
#include <filesystem>
#include "ss.hpp"
#include "config.hpp"
#include <stdexcept>
#include <iostream>
#include <memory>
#include <array>
#include "unistd.h"
#include "UpdaterSecurity.hpp"
#include "Updater.hpp"
#include <fstream>
using namespace std;
namespace fs = std::filesystem;
std::string getVersionString()
{
std::string displayVersion = PROJECT_DISPLAY_VERSION;
auto npos = displayVersion.find_last_of('-');
if (npos == std::string::npos)
{
throw std::runtime_error(SS("PROJECT_DISPLAY_VERSION does not contain a '-' character." << displayVersion));
}
std::string releaseKind = displayVersion.substr(npos + 1);
return SS(PROJECT_VER << "-" << releaseKind);
}
std::string psystem(const std::string &command)
{
std::string cmdRedirected = command + " 2>&1";
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmdRedirected.c_str(), "r"), pclose);
if (!pipe)
{
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
{
result += buffer.data();
}
return result;
}
using namespace pipedal;
void SignPackage()
{
auto versionString = getVersionString();
cout << "Processing release " << versionString << endl;
// build/pipedal_1.2.49_arm64.deb
fs::path packagePath = SS("build/pipedal_" << PROJECT_VER << "_arm64.deb");
packagePath = fs::absolute(packagePath);
if (!fs::exists(packagePath))
{
throw std::runtime_error(SS("File does not exist: " << packagePath));
}
// sign the package.
// gpg --armor --output "$filename".asc -b "$filename"
cout << "--------------------------------------------------------------" << endl;
cout << "Signing the package" << endl;
cout << "--------------------------------------------------------------" << endl;
std::string signCmd =
SS("/usr/bin/gpg --yes --default-key " << UPDATE_GPG_FINGERPRINT2
<< " --armor --output " << packagePath << ".asc"
<< " --detach-sign " << packagePath.c_str());
int result = system(signCmd.c_str());
if (result != EXIT_SUCCESS)
{
throw std::runtime_error("Failed to sign package.");
}
cout << "--------------------------------------------------------------" << endl;
cout << "Building a test keyring." << endl;
cout << "--------------------------------------------------------------" << endl;
fs::path keyringPath = fs::absolute("build/gpg");
fs::path keyPath = fs::absolute("config/updatekey2.asc");
fs::remove_all(keyringPath);
fs::create_directories(keyringPath);
std::ostringstream importCommand;
importCommand << "/usr/bin/gpg --homedir " << keyringPath.c_str() << " --import " << keyPath.c_str();
int rc = system(importCommand.str().c_str());
if (rc != EXIT_SUCCESS)
{
throw std::runtime_error("Failed to create test keyring.");
}
cout << "--------------------------------------------------------------" << endl;
cout << "Verifying signature" << endl;
cout << "--------------------------------------------------------------" << endl;
// verify the signature.
// warnings are unavoidable as there's no way to create a root certificate without
// an elaborate manual procedure.
cout << "Warnings are expected, since there's no convenient way to install a " << endl;
cout << "trusted certificate authority in the Updater keychain." << endl;
std::string verifyCommand = SS("/usr/bin/gpg --verify --no-default-keyring "
<< "--homedir " << keyringPath.c_str()
<< " --armor " << packagePath << ".asc " << packagePath.c_str());
auto output = psystem(verifyCommand.c_str());
cout << output << endl;
auto npos = output.find("Good signature from \"" UPDATE_GPG_ADDRESS2 "\"");
if (npos == std::string::npos)
{
throw std::runtime_error("Signature validation failed.");
}
npos == output.find("using RSA key " UPDATE_GPG_FINGERPRINT2);
if (npos == std::string::npos)
{
throw std::runtime_error("Signature validation failed.");
}
cout << "Signature check succeeded." << endl;
cout << "--------------------------------------------------------------" << endl;
cout << "Validating signing procedure" << endl;
cout << "--------------------------------------------------------------" << endl;
// Actually a test to make sure that signature checks fail on a modified binary.
// Calling the test "Testing signature failure" is a more disconcerting than it needs to be.
// Just one last sanity check before letting this procedure loose in the world.
// create a bad test file.
fs::path badPackage = "build/gpgtest/bad_package.deb";
fs::create_directories("build/gpgtest");
fs::remove(badPackage);
fs::copy_file(packagePath, badPackage);
{
std::ofstream f;
f.open(badPackage, ios_base::out | ios_base::ate);
if (!f.is_open())
{
throw std::runtime_error(SS("Can't open " << badPackage));
}
f << "Bad stuff.";
}
// verify the signature.
{
std::string verifyCommand = SS("/usr/bin/gpg --verify --no-default-keyring "
<< "--homedir " << keyringPath.c_str()
<< " --armor " << packagePath << ".asc " << badPackage.c_str());
auto output = psystem(verifyCommand.c_str());
auto npos = output.find("BAD signature from ");
if (npos == std::string::npos)
{
throw std::runtime_error("Signature validation succeeded when it should not have.");
}
}
cout << "Test succeeded." << endl;
}
void GenerateUpdateJson()
{
cout << "--------------------------------------------------------------" << endl;
cout << "Validating signing procedure" << endl;
cout << "--------------------------------------------------------------" << endl;
// Actually a test to make sure that signature checks fail on a modified binary.
// Calling the test "Testing signature failure" is a more disconcerting than it needs to be.
// Just one last sanity check in the
std::filesystem::path workingPath = fs::absolute("build/updater");
Updater::ptr updater = Updater::Create(workingPath); // do NOT start it. We'll do the github request on the current thread.
std::string packageName = SS("build/pipedal_" << PROJECT_VER << "_arm64.deb");
// pipedal_1.2.3_arm64.deb
UpdateStatus updateStatus = updater->GetReleaseGeneratorStatus();
updateStatus.AddRelease(packageName);
}
int main(int argc, char **argv)
{
CommandLineParser commandLine;
try
{
SignPackage();
}
catch (const std::exception &e)
{
cout << "ERROR: " << e.what() << endl;
return EXIT_FAILURE;
}
}