diff --git a/doc/manual/src/installation/prerequisites-source.md b/doc/manual/src/installation/prerequisites-source.md index 88a411b7a..0ec2ec8c5 100644 --- a/doc/manual/src/installation/prerequisites-source.md +++ b/doc/manual/src/installation/prerequisites-source.md @@ -54,11 +54,6 @@ The most current alternative to this section is to read `package.nix` and see wh obtained from the its repository . - - The `libsodium` library for verifying cryptographic signatures - of contents fetched from binary caches. - It can be obtained from the official web site - . - - Recent versions of Bison and Flex to build the parser. (This is because Nix needs GLR support in Bison and reentrancy support in Flex.) For Bison, you need version 2.6, which can be obtained from diff --git a/lix/libstore/binary-cache-store.cc b/lix/libstore/binary-cache-store.cc index cc86f1f19..88ef9fee6 100644 --- a/lix/libstore/binary-cache-store.cc +++ b/lix/libstore/binary-cache-store.cc @@ -30,7 +30,7 @@ namespace nix { BinaryCacheStore::BinaryCacheStore(const BinaryCacheStoreConfig & config) { if (config.secretKeyFile != "") - secretKey = std::unique_ptr(new SecretKey(readFile(config.secretKeyFile))); + secretKey = std::make_unique(SecretKey::parse(readFile(config.secretKeyFile))); StringSink sink; sink << narVersionMagic1; diff --git a/lix/libstore/crypto.cc b/lix/libstore/crypto.cc index accec4db0..81038a784 100644 --- a/lix/libstore/crypto.cc +++ b/lix/libstore/crypto.cc @@ -4,98 +4,216 @@ #include "lix/libstore/globals.hh" #include "lix/libutil/strings.hh" -#include +#include namespace nix { -static std::pair split(std::string_view s) +constexpr size_t ED25519_KEY_BYTES = 32; +constexpr size_t ED25519_SIGNATURE_BYTES = 64; +constexpr size_t MAX_ERROR_MESSAGE_LENGTH = 256; + +using EvpPkeyCtxPtr = std::unique_ptr; +using EvpSignaturePtr = std::unique_ptr; + +static std::pair split(std::string_view s) { size_t colon = s.find(':'); if (colon == std::string::npos || colon == 0) return {"", ""}; - return {s.substr(0, colon), s.substr(colon + 1)}; + return {s.substr(0, colon), base64Decode(s.substr(colon + 1))}; } -Key::Key(std::string_view s) +std::string openssl_error() { - auto ss = split(s); - - name = ss.first; - key = ss.second; - - if (name == "" || key == "") - throw Error("secret key is corrupt"); - - key = base64Decode(key); + auto error = ERR_get_error(); + char buf[MAX_ERROR_MESSAGE_LENGTH]; + ERR_error_string_n(error, buf, MAX_ERROR_MESSAGE_LENGTH); + return buf; } -std::string Key::to_string() const -{ - return name + ":" + base64Encode(key); -} +SecretKey::SecretKey(std::string name, EvpPkeyPtr pkey) + : name(std::move(name)), pkey(std::move(pkey)) +{} -SecretKey::SecretKey(std::string_view s) - : Key(s) -{ - if (key.size() != crypto_sign_SECRETKEYBYTES) - throw Error("secret key is not valid"); -} +SecretKey::~SecretKey() = default; std::string SecretKey::signDetached(std::string_view data) const { - unsigned char sig[crypto_sign_BYTES]; - unsigned long long sigLen; - crypto_sign_detached( + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey.get(), nullptr); + if (!ctx) + throw Error("signing failed: %s", openssl_error()); + EvpPkeyCtxPtr pctx(ctx); + EVP_SIGNATURE *alg = EVP_SIGNATURE_fetch(nullptr, "ED25519", nullptr); + if (!alg) + throw Error("signing failed: %s", openssl_error()); + EvpSignaturePtr palg(alg); + if (EVP_PKEY_sign_message_init(pctx.get(), palg.get(), nullptr) != 1) + throw Error("signing failed: %s", openssl_error()); + + unsigned char sig[ED25519_SIGNATURE_BYTES]; + size_t sig_bytes = ED25519_SIGNATURE_BYTES; + if (EVP_PKEY_sign( + pctx.get(), sig, - &sigLen, - // the following is not a string function so no null termination issues are possible here. + &sig_bytes, // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) charptr_cast(data.data()), - data.size(), - charptr_cast(key.data()) - ); - return name + ":" + base64Encode(std::string(reinterpret_cast(sig), sigLen)); + data.size() + ) != 1) + throw Error("signing failed: %s", openssl_error()); + assert(sig_bytes == ED25519_SIGNATURE_BYTES); + + return fmt("%s:%s", name, base64Encode({charptr_cast(sig), ED25519_SIGNATURE_BYTES})); } PublicKey SecretKey::toPublicKey() const { - unsigned char pk[crypto_sign_PUBLICKEYBYTES]; - crypto_sign_ed25519_sk_to_pk(pk, charptr_cast(key.data())); - return PublicKey(name, std::string(reinterpret_cast(pk), crypto_sign_PUBLICKEYBYTES)); + unsigned char raw[ED25519_KEY_BYTES]; + size_t key_bytes = ED25519_KEY_BYTES; + assert(EVP_PKEY_get_raw_public_key(pkey.get(), raw, &key_bytes) == 1); + assert(key_bytes == ED25519_KEY_BYTES); + return PublicKey::fromRaw(name, {charptr_cast(raw), ED25519_KEY_BYTES}); +} + +std::string SecretKey::to_string() const +{ + // For compatibility reasons, the public key is included, even though it is redundant. + unsigned char keys[2 * ED25519_KEY_BYTES]; + size_t key_bytes = ED25519_KEY_BYTES; + assert(EVP_PKEY_get_raw_private_key(pkey.get(), keys, &key_bytes) == 1); + assert(key_bytes == ED25519_KEY_BYTES); + assert(EVP_PKEY_get_raw_public_key(pkey.get(), keys + ED25519_KEY_BYTES, &key_bytes) == 1); + assert(key_bytes == ED25519_KEY_BYTES); + return fmt("%s:%s", name, base64Encode({charptr_cast(keys), 2 * ED25519_KEY_BYTES})); } SecretKey SecretKey::generate(std::string_view name) { - unsigned char pk[crypto_sign_PUBLICKEYBYTES]; - unsigned char sk[crypto_sign_SECRETKEYBYTES]; - if (crypto_sign_keypair(pk, sk) != 0) - throw Error("key generation failed"); + EVP_PKEY *key = EVP_PKEY_Q_keygen(nullptr, nullptr, "ED25519"); + if (!key) + throw Error("key generation failed: %s", openssl_error()); + EvpPkeyPtr pkey(key); - return SecretKey(name, std::string(reinterpret_cast(sk), crypto_sign_SECRETKEYBYTES)); + return SecretKey(std::string(name), std::move(pkey)); } -PublicKey::PublicKey(std::string_view s) - : Key(s) +SecretKey SecretKey::parse(std::string_view s) { - if (key.size() != crypto_sign_PUBLICKEYBYTES) + auto [name, raw_key] = split(s); + + // For compatibility reasons, the public key is included, even though it is redundant. + if (raw_key.size() != 2 * ED25519_KEY_BYTES) + throw Error("secret key is not valid"); + + EVP_PKEY *key = EVP_PKEY_new_raw_private_key( + EVP_PKEY_ED25519, + nullptr, + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) + charptr_cast(raw_key.data()), + ED25519_KEY_BYTES + ); + if (!key) + throw Error("secret key is not valid: %s", openssl_error()); + EvpPkeyPtr pkey(key); + + // Verify that the redundant copy of the public key is correct. + unsigned char pk[ED25519_KEY_BYTES]; + size_t key_bytes = ED25519_KEY_BYTES; + assert(EVP_PKEY_get_raw_public_key(pkey.get(), pk, &key_bytes) == 1); + assert(key_bytes == ED25519_KEY_BYTES); + if (memcmp(pk, raw_key.data() + ED25519_KEY_BYTES, ED25519_KEY_BYTES) != 0) + throw Error("secret key is not valid"); + + return SecretKey(std::string(name), std::move(pkey)); +} + +PublicKey::PublicKey(std::string name, EvpPkeyPtr pkey) + : name(std::move(name)), pkey(std::move(pkey)) +{} + +PublicKey::~PublicKey() = default; + +bool PublicKey::verifyDetached(std::string_view data, std::string_view sig) const +{ + if (sig.size() != ED25519_SIGNATURE_BYTES) + throw Error("signature is not valid"); + + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey.get(), nullptr); + if (!ctx) + throw Error("signature verification failed: %s", openssl_error()); + EvpPkeyCtxPtr pctx(ctx); + EVP_SIGNATURE *alg = EVP_SIGNATURE_fetch(nullptr, "ED25519", nullptr); + if (!alg) + throw Error("signature verification failed: %s", openssl_error()); + EvpSignaturePtr palg(alg); + if (EVP_PKEY_verify_message_init(pctx.get(), palg.get(), nullptr) != 1) + throw Error("signature verification failed: %s", openssl_error()); + + int result = EVP_PKEY_verify( + pctx.get(), + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) + charptr_cast(sig.data()), + ED25519_SIGNATURE_BYTES, + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) + charptr_cast(data.data()), + data.size() + ); + switch (result) { + case 1: + // success + return true; + case 0: + // signature did not verify + return false; + default: + // negative return value indicates more serious error + throw Error("signature verification failed: %s", openssl_error()); + } +} + +std::string PublicKey::to_string() const +{ + unsigned char pk[ED25519_KEY_BYTES]; + size_t key_bytes = ED25519_KEY_BYTES; + assert(EVP_PKEY_get_raw_public_key(pkey.get(), pk, &key_bytes) == 1); + assert(key_bytes == ED25519_KEY_BYTES); + return fmt("%s:%s", name, base64Encode({charptr_cast(pk), ED25519_KEY_BYTES})); +} + +PublicKey PublicKey::fromRaw(std::string_view name, std::string_view raw) +{ + if (raw.size() != ED25519_KEY_BYTES) throw Error("public key is not valid"); + + EVP_PKEY *key = EVP_PKEY_new_raw_public_key( + EVP_PKEY_ED25519, + nullptr, + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) + charptr_cast(raw.data()), + ED25519_KEY_BYTES + ); + if (!key) + throw Error("public key is not valid: %s", openssl_error()); + EvpPkeyPtr pkey(key); + + return PublicKey(std::string(name), std::move(pkey)); +} + +PublicKey PublicKey::parse(std::string_view s) +{ + auto [name, raw] = split(s); + return PublicKey::fromRaw(name, raw); } bool verifyDetached(const std::string & data, const std::string & sig, const PublicKeys & publicKeys) { - auto ss = split(sig); + auto [name, sig2] = split(sig); - auto key = publicKeys.find(std::string(ss.first)); + auto key = publicKeys.find(std::string(name)); if (key == publicKeys.end()) return false; - auto sig2 = base64Decode(ss.second); - if (sig2.size() != crypto_sign_BYTES) - throw Error("signature is not valid"); - - return crypto_sign_verify_detached(charptr_cast(sig2.data()), - charptr_cast(data.data()), data.size(), - charptr_cast(key->second.key.data())) == 0; + return key->second.verifyDetached(data, sig2); } PublicKeys getDefaultPublicKeys() @@ -105,13 +223,14 @@ PublicKeys getDefaultPublicKeys() // FIXME: filter duplicates for (auto s : settings.trustedPublicKeys.get()) { - PublicKey key(s); - publicKeys.emplace(key.name, key); + auto key = PublicKey::parse(s); + auto name = key.name; + publicKeys.emplace(name, std::move(key)); } for (auto secretKeyFile : settings.secretKeyFiles.get()) { try { - SecretKey secretKey(readFile(secretKeyFile)); + auto secretKey = SecretKey::parse(readFile(secretKeyFile)); publicKeys.emplace(secretKey.name, secretKey.toPublicKey()); } catch (SysError & e) { /* Ignore unreadable key files. That's normal in a diff --git a/lix/libstore/crypto.hh b/lix/libstore/crypto.hh index 22588cf97..c7703365f 100644 --- a/lix/libstore/crypto.hh +++ b/lix/libstore/crypto.hh @@ -3,33 +3,25 @@ #include +#include #include +#include + namespace nix { -struct Key -{ - std::string name; - std::string key; - - /** - * Construct Key from a string in the format - * ‘:’. - */ - Key(std::string_view s); - - std::string to_string() const; - -protected: - Key(std::string_view name, std::string && key) - : name(name), key(std::move(key)) { } -}; +using EvpPkeyPtr = std::unique_ptr; struct PublicKey; -struct SecretKey : Key +struct SecretKey { - SecretKey(std::string_view s); + std::string name; + EvpPkeyPtr pkey; + + SecretKey(std::string name, EvpPkeyPtr pkey); + SecretKey(SecretKey &&) = default; + ~SecretKey(); /** * Return a detached signature of the given string. @@ -38,21 +30,39 @@ struct SecretKey : Key PublicKey toPublicKey() const; + std::string to_string() const; + static SecretKey generate(std::string_view name); -private: - SecretKey(std::string_view name, std::string && key) - : Key(name, std::move(key)) { } + /** + * Parse a secret key in the format `:`. + * For backwards compatibility, the key must be the concatenation of the secret and public key. + */ + static SecretKey parse(std::string_view s); }; -struct PublicKey : Key +struct PublicKey { - PublicKey(std::string_view data); + std::string name; + EvpPkeyPtr pkey; -private: - PublicKey(std::string_view name, std::string && key) - : Key(name, std::move(key)) { } - friend struct SecretKey; + PublicKey(std::string name, EvpPkeyPtr pkey); + PublicKey(PublicKey &&) = default; + ~PublicKey(); + + /** + * Check whether a detached signature is valid. + */ + bool verifyDetached(std::string_view data, std::string_view sig) const; + + std::string to_string() const; + + static PublicKey fromRaw(std::string_view name, std::string_view raw); + + /** + * Parse a public key in the format `:`. + */ + static PublicKey parse(std::string_view data); }; typedef std::map PublicKeys; diff --git a/lix/libstore/globals.cc b/lix/libstore/globals.cc index 75f68e670..d23147b09 100644 --- a/lix/libstore/globals.cc +++ b/lix/libstore/globals.cc @@ -16,8 +16,6 @@ #include #include -#include - #ifdef __GLIBC__ #include #include @@ -479,9 +477,6 @@ void assertLibStoreInitialized() { void initLibStore() { - if (sodium_init() == -1) - throw Error("could not initialise libsodium"); - loadConfFile(); preloadNSS(); diff --git a/lix/libstore/lix-store.pc.in b/lix/libstore/lix-store.pc.in index 179c90c7f..a039dba3b 100644 --- a/lix/libstore/lix-store.pc.in +++ b/lix/libstore/lix-store.pc.in @@ -6,5 +6,5 @@ Name: Lix libstore Description: Lix Package Manager (libstore) Version: @PACKAGE_VERSION@ Requires: lix-base lix-util -Requires.private: @AWS_SDK_IF_FOUND@ capnp-rpc libcurl libarchive libseccomp libsodium sqlite3 +Requires.private: @AWS_SDK_IF_FOUND@ capnp-rpc libcrypto libcurl libarchive libseccomp sqlite3 Libs: -L${libdir} -llixstore diff --git a/lix/libstore/local-store.cc b/lix/libstore/local-store.cc index 8978d3ae8..dcb6754d6 100644 --- a/lix/libstore/local-store.cc +++ b/lix/libstore/local-store.cc @@ -1685,7 +1685,7 @@ void LocalStore::signPathInfo(ValidPathInfo & info) auto secretKeyFiles = settings.secretKeyFiles; for (auto & secretKeyFile : secretKeyFiles.get()) { - SecretKey secretKey(readFile(secretKeyFile)); + auto secretKey = SecretKey::parse(readFile(secretKeyFile)); info.sign(*this, secretKey); } } diff --git a/lix/libstore/meson.build b/lix/libstore/meson.build index f428eb057..7828934f0 100644 --- a/lix/libstore/meson.build +++ b/lix/libstore/meson.build @@ -394,8 +394,8 @@ dependencies = [ kj, libarchive, nlohmann_json, + openssl, seccomp, - sodium, sqlite, ] diff --git a/lix/nix/sigs.cc b/lix/nix/sigs.cc index cbe661e19..9dd4b55f9 100644 --- a/lix/nix/sigs.cc +++ b/lix/nix/sigs.cc @@ -116,7 +116,7 @@ struct CmdSign : StorePathsCommand if (secretKeyFile.empty()) throw UsageError("you must specify a secret key file using '-k'"); - SecretKey secretKey(readFile(secretKeyFile)); + auto secretKey = SecretKey::parse(readFile(secretKeyFile)); size_t added = 0; @@ -198,7 +198,7 @@ struct CmdKeyConvertSecretToPublic : Command void run() override { - SecretKey secretKey(drainFD(STDIN_FILENO)); + auto secretKey = SecretKey::parse(drainFD(STDIN_FILENO)); writeFull(STDOUT_FILENO, secretKey.toPublicKey().to_string()); } }; diff --git a/meson.build b/meson.build index 242d8f762..985b245a4 100644 --- a/meson.build +++ b/meson.build @@ -339,8 +339,6 @@ configdata += { sqlite = dependency('sqlite3', 'sqlite', version : '>=3.6.19', required : true, include_type : 'system') -sodium = dependency('libsodium', 'sodium', required : true, include_type : 'system') - curl = dependency('libcurl', 'curl', required : true, include_type : 'system') editline = dependency('libeditline', 'editline', version : '>=1.14', required : true, include_type : 'system') diff --git a/package.nix b/package.nix index e5ecd6ff8..029b926eb 100644 --- a/package.nix +++ b/package.nix @@ -29,7 +29,6 @@ libarchive, libcpuid, libseccomp, - libsodium, libsystemtap, linuxPackages, lix-clang-tidy ? null, @@ -339,7 +338,6 @@ stdenv.mkDerivation (finalAttrs: { libarchive boost lowdown - libsodium toml11-lix pegtl capnproto diff --git a/perl/default.nix b/perl/default.nix index 025b59a5a..26a6f8f1d 100644 --- a/perl/default.nix +++ b/perl/default.nix @@ -10,7 +10,6 @@ bzip2, xz, boost, - libsodium, darwin, meson, ninja, @@ -36,21 +35,18 @@ perl.pkgs.toPerlModule ( ninja ]; - buildInputs = - [ - nix - curl - bzip2 - xz - perl - boost - perlPackages.DBI - perlPackages.DBDSQLite - # for kj-async - capnproto - ] - ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium - ++ lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.Security; + buildInputs = [ + nix + curl + bzip2 + xz + perl + boost + perlPackages.DBI + perlPackages.DBDSQLite + # for kj-async + capnproto + ] ++ lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.Security; # Nixpkgs' Meson hook likes to set this to "plain". mesonBuildType = "debugoptimized"; diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs index 094612897..605f21ccf 100644 --- a/perl/lib/Nix/Store.xs +++ b/perl/lib/Nix/Store.xs @@ -20,9 +20,6 @@ #include "lix/libutil/async.hh" #include "lix/libutil/json.hh" -#include - - using namespace nix; @@ -254,7 +251,7 @@ SV * convertHash(char * algo, char * s, int toBase32) SV * signString(char * secretKey_, char * msg) PPCODE: try { - auto sig = SecretKey(secretKey_).signDetached(msg); + auto sig = SecretKey::parse(secretKey_).signDetached(msg); XPUSHs(sv_2mortal(newSVpv(sig.c_str(), sig.size()))); } catch (Error & e) { croak("%s", e.what()); @@ -265,16 +262,12 @@ int checkSignature(SV * publicKey_, SV * sig_, char * msg) CODE: try { STRLEN publicKeyLen; - unsigned char * publicKey = (unsigned char *) SvPV(publicKey_, publicKeyLen); - if (publicKeyLen != crypto_sign_PUBLICKEYBYTES) - throw Error("public key is not valid"); + char * publicKey = SvPV(publicKey_, publicKeyLen); + auto key = PublicKey::fromRaw("", {publicKey, publicKeyLen}); STRLEN sigLen; - unsigned char * sig = (unsigned char *) SvPV(sig_, sigLen); - if (sigLen != crypto_sign_BYTES) - throw Error("signature is not valid"); - - RETVAL = crypto_sign_verify_detached(sig, (unsigned char *) msg, strlen(msg), publicKey) == 0; + char * sig = SvPV(sig_, sigLen); + RETVAL = key.verifyDetached(msg, {sig, sigLen}); } catch (Error & e) { croak("%s", e.what()); } diff --git a/perl/lib/Nix/meson.build b/perl/lib/Nix/meson.build index 0a6b0020e..0460d1d02 100644 --- a/perl/lib/Nix/meson.build +++ b/perl/lib/Nix/meson.build @@ -23,7 +23,6 @@ perl_libstore = shared_module( dependencies : [ libstore, libutil, - sodium, perl_include, kj, ], diff --git a/perl/meson.build b/perl/meson.build index d19b3cc7b..729b95339 100644 --- a/perl/meson.build +++ b/perl/meson.build @@ -60,8 +60,6 @@ perl_include = declare_dependency( include_directories : include_directories(perl_incdir, is_system : true), ) -sodium = dependency('libsodium', 'sodium', required : true) - if cxx.get_linker_id() in ['ld.bfd', 'ld.gold'] add_project_link_arguments('-Wl,--no-copy-dt-needed-entries', language : 'cpp') endif diff --git a/tests/functional/repl_characterization/meson.build b/tests/functional/repl_characterization/meson.build index 79de9a5f5..4eea9a60c 100644 --- a/tests/functional/repl_characterization/meson.build +++ b/tests/functional/repl_characterization/meson.build @@ -10,7 +10,6 @@ repl_characterization_tester = executable( libasanoptions, liblixutil, liblixutil_test_support, - sodium, editline, boost, lowdown, diff --git a/tests/functional/test-libstoreconsumer/meson.build b/tests/functional/test-libstoreconsumer/meson.build index 5cc04ba90..c874026bf 100644 --- a/tests/functional/test-libstoreconsumer/meson.build +++ b/tests/functional/test-libstoreconsumer/meson.build @@ -5,7 +5,6 @@ libstoreconsumer_tester = executable( libasanoptions, liblixutil, liblixstore, - sodium, editline, boost, lowdown,