You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 lines
8.9 KiB
C++

#include "unpack.hh"
#include <system_error>
#include <archive.h>
#include <archive_entry.h>
#include <cryptopp/oaep.h>
#include <cryptopp/osrng.h>
#include <cryptopp/xed25519.h>
#ifdef WIN32
// FIXME: name collision possible
static char *mkdtemp(char *template)
{
if (_mktemp(template) == nullptr)
return nullptr;
if (_mkdir(template))
return nullptr;
return template;
}
#endif
namespace pEp {
namespace SignedPackage {
static bool path_empty(std::string path)
{
if (!std::filesystem::exists(path))
return true;
if (!std::filesystem::is_directory(path))
return false;
return std::filesystem::is_empty(path);
}
static void ensure_target_path(std::string path)
{
if (std::filesystem::exists(path)) {
if (!std::filesystem::is_directory(path))
std::filesystem::remove(path);
}
std::filesystem::create_directories(path);
}
static std::filesystem::path mktempdir()
{
std::filesystem::path path = std::filesystem::temp_directory_path() / "spXXXXXXXXXXXX";
std::unique_ptr< char[] > buffer { ::strdup(path.c_str()) };
if (!buffer.get())
throw std::bad_alloc();
char *p = mkdtemp(buffer.get());
if (!p) {
throw std::filesystem::filesystem_error(::strerror(errno),
std::error_code(errno, std::system_category()));
}
return buffer.get();
}
static void throw_archive_read_error(struct archive *a)
{
if (::archive_error_string(a)) {
std::string error_string = ::archive_error_string(a);
int e = ::archive_errno(a);
::archive_read_free(a);
throw std::filesystem::filesystem_error(error_string,
std::error_code(e, std::system_category()));
}
else if (errno) {
::archive_read_free(a);
throw std::filesystem::filesystem_error(::strerror(errno),
std::error_code(errno, std::system_category()));
}
}
static void Load(const std::string& filename, CryptoPP::BufferedTransformation& bt)
{
CryptoPP::FileSource file(filename.c_str(), true);
file.TransferTo(bt);
bt.MessageEnd();
}
void LoadPrivateKey(const std::string& filename, CryptoPP::PrivateKey& key)
{
CryptoPP::ByteQueue queue;
Load(filename, queue);
key.Load(queue);
}
void LoadPublicKey(const std::string& filename, CryptoPP::PublicKey& key)
{
CryptoPP::ByteQueue queue;
Load(filename, queue);
key.Load(queue);
}
void extract_archive(
std::string pkg_path,
std::string target_path
)
{
std::string cwd = std::filesystem::current_path();
struct archive *a = ::archive_read_new();
::archive_read_support_filter_all(a);
::archive_read_support_format_all(a);
int r = ::archive_read_open_filename(a, pkg_path.c_str(), 32768);
if (r != ARCHIVE_OK && r != ARCHIVE_WARN) {
std::filesystem::current_path(cwd);
throw_archive_read_error(a);
}
std::filesystem::current_path(target_path);
struct ::archive_entry *entry;
while (::archive_read_next_header(a, &entry) == ARCHIVE_OK) {
std::string pathname{::archive_entry_pathname(entry)};
do {
r = archive_read_extract(a, entry,
ARCHIVE_EXTRACT_ACL |
ARCHIVE_EXTRACT_FFLAGS |
ARCHIVE_EXTRACT_MAC_METADATA |
ARCHIVE_EXTRACT_NO_OVERWRITE |
ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS |
ARCHIVE_EXTRACT_SECURE_NODOTDOT |
ARCHIVE_EXTRACT_SECURE_SYMLINKS |
ARCHIVE_EXTRACT_TIME |
ARCHIVE_EXTRACT_XATTR
);
} while (r == ARCHIVE_RETRY);
if (r != ARCHIVE_OK && r != ARCHIVE_WARN) {
std::filesystem::current_path(cwd);
throw_archive_read_error(a);
}
}
::archive_read_free(a);
std::filesystem::current_path(cwd);
}
std::string decrypt_distribution_key(
std::filesystem::path key_path,
CryptoPP::PrivateKey& provisioning_key
)
{
CryptoPP::RSAES< CryptoPP::OAEP< CryptoPP::SHA256 > >::Decryptor
decryptor(provisioning_key);
CryptoPP::AutoSeededRandomPool rng;
std::string distribution_key;
CryptoPP::FileSource keySource(((std::string) key_path).c_str(), true,
new CryptoPP::PK_DecryptorFilter(rng, decryptor,
new CryptoPP::StringSink(distribution_key)));
return distribution_key;
}
void verify_distribution_signature(
std::filesystem::path archive,
std::filesystem::path sigfile,
CryptoPP::PublicKey& deployment_key
)
{
CryptoPP::ed25519::Verifier verifier(
dynamic_cast< CryptoPP::X509PublicKey& >(deployment_key));
const size_t size = 64; // ed25519 signature size
char signature[size];
{
std::ifstream _sigfile(sigfile, std::ios_base::in |
std::ios_base::binary);
_sigfile.read(signature, size);
}
std::ifstream _archive(archive);
bool valid = verifier.VerifyStream(_archive,
(unsigned char*) signature, size);
if (!valid)
throw std::runtime_error("signature does not match");
}
std::filesystem::path decrypt_distribution_archive(
std::filesystem::path archive,
std::filesystem::path key_path,
CryptoPP::PrivateKey& provisioning_key
)
{
std::string distribution_key = decrypt_distribution_key(key_path,
provisioning_key);
std::ifstream ifs(archive, std::ios_base::binary | std::ios_base::in);
char iv[12]; // 96 bit is GCM standard size
ifs.read(iv, sizeof(iv));
CryptoPP::GCM< CryptoPP::AES >::Decryption dec;
dec.SetKeyWithIV((const unsigned char*) distribution_key.c_str(),
distribution_key.size(), (const unsigned char*) iv, sizeof(iv));
std::filesystem::path decrypted_archive = ((std::string) archive) + "D";
CryptoPP::FileSource archiveSource(ifs, true,
new CryptoPP::AuthenticatedDecryptionFilter(dec,
new CryptoPP::FileSink(decrypted_archive.c_str())));
return decrypted_archive;
}
std::filesystem::path extract_deployment_archive(
CryptoPP::PublicKey& deployment_key,
CryptoPP::PrivateKey& provisioning_key,
std::filesystem::path pkg_path
)
{
std::filesystem::path target_path = mktempdir();
extract_archive(pkg_path, target_path);
verify_distribution_signature(target_path / "DIST.A",
target_path / "DIST.SIG", deployment_key);
return target_path;
}
void install_if_location_empty(
CryptoPP::PublicKey& deployment_key,
CryptoPP::PrivateKey& provisioning_key,
std::filesystem::path pkg_path,
std::string location,
std::filesystem::path target_path
)
{
if (path_empty(target_path)) {
std::filesystem::path tmp_path = extract_deployment_archive(deployment_key,
provisioning_key, pkg_path);
std::filesystem::path decrypted_archive =
decrypt_distribution_archive(tmp_path / "DIST.A",
tmp_path / "DIST.KEY", provisioning_key);
extract_archive(decrypted_archive, tmp_path);
ensure_target_path(target_path);
for (auto const& direntry : std::filesystem::directory_iterator{tmp_path / location}) {
std::filesystem::path source = direntry.path();
std::filesystem::rename(direntry.path(), target_path / source.filename());
}
std::filesystem::remove_all(tmp_path);
}
}
void provision_user(
pEp::UpdateClient::product p,
pEp::UpdateClient::PublicKey update_key,
CryptoPP::PublicKey& deployment_key,
CryptoPP::PrivateKey& provisioning_key,
pEp::notifyRead_t notifyRead,
locations loc
)
{
std::string target_path = loc["PER_USER_DIRECTORY"];
// do not provision systems, which formerly were used
if (!path_empty(target_path))
return;
std::string pkg_path = pEp::UpdateClient::update(p, update_key, notifyRead);
install_if_location_empty(deployment_key, provisioning_key, pkg_path, "PER_USER_DIRECTORY", target_path);
std::filesystem::remove(pkg_path);
}
}
}