From a280161ea7409ac076a472b9a120fec21f5e9f07 Mon Sep 17 00:00:00 2001 From: Morgan Jones Date: Wed, 20 May 2026 22:54:49 -0700 Subject: [PATCH] experimental-features: add 'keystore' Support OpenSSL keystores. Formatting is identical to our normal private key format (keyname:private-key-here) but OpenSSL will parse it as a URI (e.g. keyname:scheme:private-key-here). Add --key-uri in addition to --key-file that automatically enables the 'keystore' feature and performs the signature, without the need to put the private key in a file. This allows using PEM-formatted private keys if desired (e.g. mykey:file:/etc/nix/mykey.pem), in addition to PKCS#11 (e.g. mykey:pkcs11:id=%01;object=mykey;token=nixpkcs;type=private?foo). Tested using [nixpkcs](https://github.com/numinit/nixpkcs) by injecting an OpenSSL config into Nix that adds support for the PKCS#11 scheme with pkcs11-provider. Signing: ``` $ nix-shell -p openssl pkcs11-provider yubico-piv-tool $ openssl ecparam -genkey -name secp384r1 -noout -out p384.pem $ echo "p384:file:$(realpath p384.pem)" > p384.uri $ ./src/nix/nix store sign \ /nix/store/icq1cx1x7fjxim84sfanrv1j3vgb1qwp-pkcs11-provider-1.1 \ --key-file ./p384.key \ --extra-experimental-features 'cnsa keystore' $ nixpkcs-uri ca pkcs11:id=%02;token=YubiKey%20PIV%20%236108039;type=private?\ module-path=%2Fnix%2Fstore%2Fxcmf5v8y8vn5g5krsr2cyxp7hjmjgijc-yubico-piv-tool-2.7.2%2Flib%2Flibykcs11.so&\ pin-source=file%3A%2Fetc%2Fnixpkcs%2Fyubikeys%2F6108039%2Fuser.pin $ # generated with nixpkcs: $ export OPENSSL_CONF='/nix/store/gq3izqn2wflfr5cxan2nqz0nrww415h3-openssl-with-pkcs11.openssl.cnf' $ ./src/nix/nix store sign \ /nix/store/icq1cx1x7fjxim84sfanrv1j3vgb1qwp-pkcs11-provider-1.1 \ --key-uri yubikey-6108039:$(nixpkcs-uri ca) \ --extra-experimental-features cnsa ``` Verifying: ``` $ nix path-info --json --json-format 2 \ /nix/store/icq1cx1x7fjxim84sfanrv1j3vgb1qwp-pkcs11-provider-1.1 { "info": { "icq1cx1x7fjxim84sfanrv1j3vgb1qwp-pkcs11-provider-1.1": { "ca": null, "deriver": "1lparccpa6kjh2sc7n4hkd3vkr4n1c1h-pkcs11-provider-1.1.drv", "narHash": "sha256-iS7ETDBufxea39YxmAWeJ67NHcSuPAvONWe462pQpAk=", "narSize": 613744, "references": [ "1xj3zlgsv40gbhc0fxm0fphxsd4b7l7k-p11-kit-0.25.9", "daamdpmaz2vjvna55ccrc30qw3qb8h6d-glibc-2.40-66", "llswcygvgv9x2sa3z6j7i0g5iqqmn5gn-openssl-3.6.0" ], "registrationTime": 1779338946, "signatures": [ "cache.nixos.org-1:mULTk4OTkR3WVcGF1ClS3kJdQcRMlgbjy7GhH0inFKe9qi4Fw9kVDb/3SaYpbXTgQzfpQJypI91Jx9lq5JhwBg==", "p384:MGUCMQDXldyCdoiVKOp/Mqf1cDjZ1lmmNgmnedh6eJFeHFtMgck0EjsfFXnWe/TMH+Rc1boCMDhvOj9n8yUkkketqM1thIE6fqiFp5lUYZ3KEZ2l8B2q4Sm1V/3ASeVYzBJ7y5hLeQ==", "yubikey-6108039:MGUCMQCzcVYwFttNbQxcxflbIsmEcAEPCI2fiNZEissy0razpmZDMT0MdjuIsN8HYyFe7f8CMFVxVfVn0kqXE3C01RWIVLy5BslkFX3xYTI6w56ooSWo4jRZCbdVXoKWNO5YVJcvYg==" ], "storeDir": "/nix/store", "ultimate": false, "version": 2 } }, "storeDir": "/nix/store", "version": 2 } ``` --- src/libstore/binary-cache-store.cc | 21 ++- .../include/nix/store/binary-cache-store.hh | 4 +- src/libstore/keys.cc | 5 +- src/libstore/store-api.cc | 8 +- src/libutil-tests/local-keys.cc | 8 +- src/libutil/experimental-features.cc | 10 +- .../include/nix/util/experimental-features.hh | 1 + .../include/nix/util/signature/local-keys.hh | 4 +- src/libutil/signature/local-keys.cc | 161 ++++++++++++++---- src/nix/sigs.cc | 37 +++- src/perl/lib/Nix/Store.xs | 2 +- 11 files changed, 207 insertions(+), 54 deletions(-) diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc index c00d56c62c3d..8a47d1f50033 100644 --- a/src/libstore/binary-cache-store.cc +++ b/src/libstore/binary-cache-store.cc @@ -27,14 +27,29 @@ namespace nix { BinaryCacheStore::BinaryCacheStore(Config & config) : config{config} { - if (!config.secretKeyFile.get().empty()) - signers.push_back(std::make_unique(SecretKey::parse(readFile(config.secretKeyFile.get())))); + auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore); + if (!config.secretKeyFile.get().empty()) { + auto isUri = keystoreEnabled && !std::get<0>(splitColon(config.secretKeyFile.get().string())).empty(); + signers.push_back( + std::make_unique( + SecretKey::parse( + isUri ? config.secretKeyFile.get().string() : readFile(config.secretKeyFile.get()), + isUri + ) + ) + ); + } if (config.secretKeyFiles != "") { std::stringstream ss(config.secretKeyFiles); std::string keyPath; while (std::getline(ss, keyPath, ',')) { - signers.push_back(std::make_unique(SecretKey::parse(readFile(keyPath)))); + auto isUri = keystoreEnabled && !std::get<0>(splitColon(keyPath)).empty(); + signers.push_back( + std::make_unique( + SecretKey::parse(isUri ? keyPath : readFile(keyPath), isUri) + ) + ); } } diff --git a/src/libstore/include/nix/store/binary-cache-store.hh b/src/libstore/include/nix/store/binary-cache-store.hh index fbd347e7684f..188f13a2e407 100644 --- a/src/libstore/include/nix/store/binary-cache-store.hh +++ b/src/libstore/include/nix/store/binary-cache-store.hh @@ -40,10 +40,10 @@ struct BinaryCacheStoreConfig : virtual StoreConfig )"}; Setting secretKeyFile{ - this, "", "secret-key", "Path to the secret key used to sign the binary cache."}; + this, "", "secret-key", "Path or URI to the secret key used to sign the binary cache."}; Setting secretKeyFiles{ - this, "", "secret-keys", "List of comma-separated paths to the secret keys used to sign the binary cache."}; + this, "", "secret-keys", "List of comma-separated paths or URIs to the secret keys used to sign the binary cache."}; Setting> localNarCache{ this, diff --git a/src/libstore/keys.cc b/src/libstore/keys.cc index 604b6e36ff40..41a3f9523420 100644 --- a/src/libstore/keys.cc +++ b/src/libstore/keys.cc @@ -1,5 +1,6 @@ #include "nix/util/file-system.hh" #include "nix/store/globals.hh" +#include "nix/util/signature/local-keys.hh" #include "nix/store/keys.hh" namespace nix { @@ -17,9 +18,11 @@ PublicKeys getDefaultPublicKeys() } // FIXME: keep secret keys in memory (see Store::signRealisation()). + auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore); for (const auto & secretKeyFile : settings.secretKeyFiles.get()) { try { - auto secretKey = SecretKey::parse(readFile(secretKeyFile)); + auto isUri = keystoreEnabled && !std::get<0>(splitColon(secretKeyFile)).empty(); + auto secretKey = SecretKey::parse(isUri ? secretKeyFile : readFile(secretKeyFile), isUri); publicKeys.emplace(secretKey->name, secretKey->toPublicKey()); } catch (SystemError & e) { /* Ignore unreadable key files. That's normal in a diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 1145c2574e56..3897a680d95e 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -1266,10 +1266,12 @@ void Store::signPathInfo(ValidPathInfo & info) { // FIXME: keep secret keys in memory. + auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore); auto secretKeyFiles = settings.secretKeyFiles; for (auto & secretKeyFile : secretKeyFiles.get()) { - LocalSigner signer(SecretKey::parse(readFile(secretKeyFile))); + auto isUri = keystoreEnabled && !std::get<0>(splitColon(secretKeyFile)).empty(); + LocalSigner signer(SecretKey::parse(isUri ? secretKeyFile : readFile(secretKeyFile), isUri)); info.sign(*this, signer); } } @@ -1278,10 +1280,12 @@ void Store::signRealisation(Realisation & realisation) { // FIXME: keep secret keys in memory. + auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore); auto secretKeyFiles = settings.secretKeyFiles; for (auto & secretKeyFile : secretKeyFiles.get()) { - LocalSigner signer(SecretKey::parse(readFile(secretKeyFile))); + auto isUri = keystoreEnabled && !std::get<0>(splitColon(secretKeyFile)).empty(); + LocalSigner signer(SecretKey::parse(isUri ? secretKeyFile : readFile(secretKeyFile), isUri)); realisation.sign(realisation.id, signer); } } diff --git a/src/libutil-tests/local-keys.cc b/src/libutil-tests/local-keys.cc index fc83ca8da825..e41c89a4aae2 100644 --- a/src/libutil-tests/local-keys.cc +++ b/src/libutil-tests/local-keys.cc @@ -24,7 +24,7 @@ TEST(local_keys, signAndVerify) ASSERT_EQ(sig.keyName, "test-key-1"); ASSERT_TRUE(pk->verifyDetached("hello world", sig)); - auto sk2 = SecretKey::parse(sk->to_string()); + auto sk2 = SecretKey::parse(sk->to_string(), false); ASSERT_EQ(sk2->name, sk->name); ASSERT_EQ(sk2->key, sk->key); @@ -58,7 +58,7 @@ TEST(local_keys, rfc8032TestVector) auto skBytes = seed + pubKeyBytes; auto skString = "test:" + base64::encode(std::as_bytes(std::span{skBytes.data(), skBytes.size()})); - auto sk = SecretKey::parse(skString); + auto sk = SecretKey::parse(skString, false); auto sig = sk->signDetached(message); ASSERT_EQ(sig.keyName, "test"); @@ -157,7 +157,7 @@ TEST(local_keys, rfc6979EcdsaP384TestVector) "99ef4aeb15f178cea1fe40db2603138f130e740a19624526203b6351d0a3a94fa329c145786e679e7b82c71a38628ac8"); auto skString = "rfc6979-test:" + base64::encode(std::as_bytes(std::span{skDer.data(), skDer.size()})); - auto sk = SecretKey::parse(skString); + auto sk = SecretKey::parse(skString, false); auto sig = sk->signDetached("sample"); ASSERT_EQ(sig.keyName, "rfc6979-test"); @@ -198,7 +198,7 @@ runMlDsaAcvpTest(std::string_view variant, std::string_view derPrefixHex, size_t auto der = base16::decode(derPrefixHex) + sk; auto skString = std::string(variant) + ":" + base64::encode(std::as_bytes(std::span{der.data(), der.size()})); - auto parsed = SecretKey::parse(skString); + auto parsed = SecretKey::parse(skString, false); auto sig = parsed->signDetached(message); ASSERT_EQ(sig.keyName, std::string(variant)); diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index bcd4cf54624a..5fa62f699826 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -25,7 +25,7 @@ struct ExperimentalFeatureDetails * feature, we either have no issue at all if few features are not added * at the end of the list, or a proper merge conflict if they are. */ -constexpr size_t numXpFeatures = 1 + static_cast(Xp::CNSA); +constexpr size_t numXpFeatures = 1 + static_cast(Xp::Keystore); constexpr std::array xpFeatureDetails = {{ { @@ -315,6 +315,14 @@ constexpr std::array xpFeatureDetails )", .trackingUrl = "", }, + { + .tag = Xp::Keystore, + .name = "keystore", + .description = R"( + Enable support for loading signing keys from OpenSSL store URIs. + )", + .trackingUrl = "", + }, }}; static_assert( diff --git a/src/libutil/include/nix/util/experimental-features.hh b/src/libutil/include/nix/util/experimental-features.hh index f2bdecee5e54..67ec27876e78 100644 --- a/src/libutil/include/nix/util/experimental-features.hh +++ b/src/libutil/include/nix/util/experimental-features.hh @@ -42,6 +42,7 @@ enum struct ExperimentalFeature { WasmDerivations, Provenance, CNSA, + Keystore, }; extern std::set stabilizedFeatures; diff --git a/src/libutil/include/nix/util/signature/local-keys.hh b/src/libutil/include/nix/util/signature/local-keys.hh index 6d7620335174..0fe5358cded7 100644 --- a/src/libutil/include/nix/util/signature/local-keys.hh +++ b/src/libutil/include/nix/util/signature/local-keys.hh @@ -49,6 +49,8 @@ enum KeyType { ECDSAP384, }; +std::tuple splitColon(std::string_view s); + KeyType parseKeyType(std::string_view s); const StringSet & getKeyTypes(); @@ -78,7 +80,7 @@ struct SecretKey : Key virtual ~SecretKey() {}; - static std::unique_ptr parse(std::string_view s); + static std::unique_ptr parse(std::string_view s, bool forceUri); /** * Return a detached signature of the given string. diff --git a/src/libutil/signature/local-keys.cc b/src/libutil/signature/local-keys.cc index 49c8407dda09..430ad95f6472 100644 --- a/src/libutil/signature/local-keys.cc +++ b/src/libutil/signature/local-keys.cc @@ -1,12 +1,17 @@ +#include #include #include #include #include +#include #include #include +#include +#include #include "nix/util/base-n.hh" #include "nix/util/signature/local-keys.hh" +#include "nix/util/configuration.hh" #include "nix/util/json-utils.hh" #include "nix/util/util.hh" #include "nix/util/deleter.hh" @@ -20,32 +25,44 @@ using AutoEVP_PKEY_CTX = std::unique_ptr>; using AutoBIO = std::unique_ptr>; -std::string_view keyNamePart(std::string_view s) -{ - auto colon = s.find(':'); - return colon == std::string_view::npos ? std::string_view{} : s.substr(0, colon); -} +/** + * Some data with a label that can also be a URI to something. + * Used for keys and signatures. + */ +struct LabeledData { + std::string label; + std::string data; + bool isUri; +}; /** - * Parse a colon-separated string where the second part is Base64-encoded. + * Parse a colon-separated string where the second part is Base64-encoded or a URI. * * @param s The string to parse in the format `:`. * @param typeName Name of the type being parsed (for error messages). + * @param allowUri true if we should allow URIs * @return A pair of (name, decoded-data). */ -std::pair parseColonBase64(std::string_view s, std::string_view typeName) +LabeledData parseColonBase64OrUri(std::string_view s, std::string_view typeName, bool allowUri) { - size_t colon = s.find(':'); - if (colon == std::string::npos || colon == 0) + auto [name, rest] = splitColon(s); + if (name.empty() || rest.empty()) throw FormatError("%s is corrupt", typeName); - auto name = std::string(s.substr(0, colon)); - auto data = base64::decode(s.substr(colon + 1)); + bool isUri = false; + std::string data; + if (!allowUri || std::get<0>(splitColon(rest)).empty()) + data = base64::decode(rest); + else { + // Maybe a URI. If it's from a file, there may be a newline at the end, so trim it. + isUri = true; + data = boost::trim_right_copy(rest); + } - if (name.empty() || data.empty()) + if (data.empty()) throw FormatError("%s is corrupt", typeName); - return {std::move(name), std::move(data)}; + return {.label = std::string(name), .data = std::move(data), .isUri = isUri}; } /** @@ -120,13 +137,73 @@ std::optional detectOpenSSLKeyType(EVP_PKEY * pkey) } /** - * Parse a DER-encoded PKCS#8 `PrivateKeyInfo`. + * Parses an OpenSSL store URI, trying to find a single matching PKEY object. + * @param uri the URI + * @returns the PKEY */ -AutoEVP_PKEY parsePrivateKey(std::string_view der) +static AutoEVP_PKEY parseOsslStoreUri(std::string_view uri) { + std::optional err; + + // Per https://docs.openssl.org/3.5/man3/ERR_error_string/#description: + // buf must be at least 256 bytes long, so just make it twice that. + char errBuf[512] = {0}; + + OSSL_STORE_CTX *ctx = OSSL_STORE_open( + uri.data(), + UI_get_default_method(), + nullptr, + nullptr, + nullptr + ); + if (ctx == nullptr) { + auto errCode = ERR_get_error(); + ERR_error_string_n(errCode, errBuf, sizeof(errBuf)); + err = Error("error opening OpenSSL store URI '%s': %s (%x)", uri, errBuf, errCode); + } + + // Find the first matching private key object. If there are more than one, error. + EVP_PKEY *found = nullptr; + while (!err.has_value() && !OSSL_STORE_eof(ctx)) { + OSSL_STORE_INFO *info = OSSL_STORE_load(ctx); + if (info != nullptr) { + if (OSSL_STORE_INFO_get_type(info) == OSSL_STORE_INFO_PKEY) { + // Use get1 over get0 because we want to take a reference to it. + // get0 will only last as long as 'info'. + if (found == nullptr) + found = OSSL_STORE_INFO_get1_PKEY(info); + else { + // Clean it up to avoid a leak. + EVP_PKEY_free(found); + found = nullptr; + err = Error("multiple matches for OpenSSL store URI '%s'", uri); + } + } + OSSL_STORE_INFO_free(info); + } + } + + if (ctx != nullptr) + OSSL_STORE_close(ctx); + + if (err.has_value()) + throw err.value(); + + return AutoEVP_PKEY(found); +} + +/** + * Parse a DER-encoded PKCS#8 `PrivateKeyInfo` or a URI. + * @param key the DER to parse, or a URI + * @param isUri true if it is a URI + */ +AutoEVP_PKEY parsePrivateKey(std::string_view key, bool isUri) { - auto p = (const unsigned char *) der.data(); - AutoEVP_PKEY pkey(d2i_AutoPrivateKey(nullptr, &p, der.size())); - return pkey; + if (isUri) + return parseOsslStoreUri(key); + else { + auto p = (const unsigned char *) key.data(); + return AutoEVP_PKEY(d2i_AutoPrivateKey(nullptr, &p, key.size())); + } } /** @@ -157,10 +234,10 @@ AutoEVP_PKEY parsePublicKey(std::string_view der, KeyType type) Signature Signature::parse(std::string_view s) { - auto [keyName, sig] = parseColonBase64(s, "signature"); + auto sig = parseColonBase64OrUri(s, "signature", false); return Signature{ - .keyName = std::move(keyName), - .sig = std::move(sig), + .keyName = std::move(sig.label), + .sig = std::move(sig.data), }; } @@ -209,6 +286,20 @@ const StringSet & getKeyTypes() return validKeyTypes; } +/** + * Splits a string_view at ':', and returns the parts before and after. + * If there is no ':', will return the prefix as empty and the suffix as the whole string. + * @param s The string to parse. + * @return A pair of (name, data). + */ +std::tuple splitColon(std::string_view s) +{ + auto colon = s.find(':'); + auto prefix = colon == std::string_view::npos ? std::string_view{} : s.substr(0, colon); + auto suffix = colon == std::string_view::npos ? s : s.substr(colon + 1); + return {prefix, suffix}; +} + KeyType parseKeyType(std::string_view s) { auto i = keyTypeMap.find(s); @@ -477,23 +568,25 @@ struct OpenSSLSecretKey : SecretKey } }; -std::unique_ptr SecretKey::parse(std::string_view s) +std::unique_ptr SecretKey::parse(std::string_view s, bool forceUri = false) { try { - auto [name, key] = parseColonBase64(s, "key"); + auto key = parseColonBase64OrUri(s, "key", experimentalFeatureSettings.isEnabled(Xp::Keystore)); + if (forceUri && !key.isUri) + throw Error("secret key was not a URI"); - if (key.size() == crypto_sign_SECRETKEYBYTES) - return std::make_unique(name, std::move(key)); - else if (auto pkey = parsePrivateKey(key); experimentalFeatureSettings.isEnabled(Xp::CNSA) && pkey) { + if (!key.isUri && key.data.size() == crypto_sign_SECRETKEYBYTES) + return std::make_unique(key.label, std::move(key.data)); + else if (auto pkey = parsePrivateKey(key.data, key.isUri); experimentalFeatureSettings.isEnabled(Xp::CNSA) && pkey) { auto type = detectOpenSSLKeyType(pkey.get()); if (!type) throw Error("secret key has unsupported type '%s'", EVP_PKEY_get0_type_name(pkey.get())); - return std::make_unique(*type, name, std::move(key), std::move(pkey)); + return std::make_unique(*type, key.label, std::move(key.data), std::move(pkey)); } else throw Error("secret key is not valid"); } catch (Error & e) { - e.addTrace({}, "while decoding key '%s'", keyNamePart(s)); + e.addTrace({}, "while decoding key '%s'", std::get<0>(splitColon(s))); throw; } } @@ -519,19 +612,19 @@ std::unique_ptr SecretKey::generate(std::string_view name, KeyType ty std::unique_ptr PublicKey::parse(std::string_view s) { try { - auto [name, key] = parseColonBase64(s, "key"); + auto key = parseColonBase64OrUri(s, "key", false); - if (key.size() == crypto_sign_PUBLICKEYBYTES) - return std::make_unique(name, std::move(key)); - else if (auto pkey = parsePublicKey(key); experimentalFeatureSettings.isEnabled(Xp::CNSA) && pkey) { + if (key.data.size() == crypto_sign_PUBLICKEYBYTES) + return std::make_unique(key.label, std::move(key.data)); + else if (auto pkey = parsePublicKey(key.data); experimentalFeatureSettings.isEnabled(Xp::CNSA) && pkey) { auto type = detectOpenSSLKeyType(pkey.get()); if (!type) throw Error("public key has unsupported type '%s'", EVP_PKEY_get0_type_name(pkey.get())); - return std::make_unique(*type, name, std::move(key), std::move(pkey)); + return std::make_unique(*type, key.label, std::move(key.data), std::move(pkey)); } else throw Error("public key is not valid"); } catch (Error & e) { - e.addTrace({}, "while decoding key '%s'", keyNamePart(s)); + e.addTrace({}, "while decoding key '%s'", std::get<0>(splitColon(s))); throw; } } diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc index 600c7b42903e..e6d5c9d9a09d 100644 --- a/src/nix/sigs.cc +++ b/src/nix/sigs.cc @@ -1,3 +1,6 @@ +#include "nix/store/globals.hh" +#include "nix/util/config-global.hh" +#include "nix/util/configuration.hh" #include "nix/util/signals.hh" #include "nix/cmd/command.hh" #include "nix/main/shared.hh" @@ -100,17 +103,28 @@ static auto rCmdCopySigs = registerCommand2({"store", "copy-sigs"}) struct CmdSign : StorePathsCommand { std::filesystem::path secretKeyFile; + std::string secretKeyUri; CmdSign() { addFlag({ .longName = "key-file", .shortName = 'k', - .description = "File containing the secret signing key.", + .description = "File containing the secret signing key, or a URI to one.", .labels = {"file"}, .handler = {&secretKeyFile}, .completer = completePath, - .required = true, + .required = false, + }); + addFlag({ + .longName = "key-uri", + .shortName = 'u', + .description = "Name-prefixed URI pointing to an OpenSSL keystore-compatible " + "secret signing key (e.g. keyname:file:/etc/nix/my.key). " + "Enables feature 'keystore' automatically and overrides 'key-file'.", + .labels = {"uri"}, + .handler = {&secretKeyUri}, + .required = false, }); } @@ -121,7 +135,20 @@ struct CmdSign : StorePathsCommand void run(ref store, StorePaths && storePaths) override { - LocalSigner signer(SecretKey::parse(readFile(secretKeyFile))); + std::string secretKey; + if (secretKeyUri.empty()) { + if (secretKeyFile.empty()) { + throw UsageError("you must specify either a key file or URI"); + } else { + secretKey = readFile(secretKeyFile); + } + } else { + // Passing key-uri implies 'keystore'. + experimentalFeatureSettings.set("extra-experimental-features", "keystore"); + secretKey = secretKeyUri; + } + + LocalSigner signer(SecretKey::parse(secretKey, !secretKeyUri.empty())); size_t added{0}; @@ -204,7 +231,7 @@ struct CmdKeyConvertSecretToPublic : Command void run() override { logger->stop(); - writeFull(getStandardOutput(), SecretKey::parse(drainFD(STDIN_FILENO))->toPublicKey()->to_string()); + writeFull(getStandardOutput(), SecretKey::parse(drainFD(STDIN_FILENO), false)->toPublicKey()->to_string()); } }; @@ -225,7 +252,7 @@ struct CmdKeyConvertSecretToPem : Command void run() override { logger->stop(); - writeFull(getStandardOutput(), SecretKey::parse(drainFD(STDIN_FILENO))->toPEM()); + writeFull(getStandardOutput(), SecretKey::parse(drainFD(STDIN_FILENO), false)->toPEM()); } }; diff --git a/src/perl/lib/Nix/Store.xs b/src/perl/lib/Nix/Store.xs index d7493ceeff70..2e974916af4e 100644 --- a/src/perl/lib/Nix/Store.xs +++ b/src/perl/lib/Nix/Store.xs @@ -301,7 +301,7 @@ SV * convertHash(char * algo, char * s, int toBase32) SV * signString(char * secretKey_, char * msg) PPCODE: try { - auto sig = SecretKey::parse(secretKey_)->signDetached(msg).to_string(); + auto sig = SecretKey::parse(secretKey_, false)->signDetached(msg).to_string(); XPUSHs(sv_2mortal(newSVpv(sig.c_str(), sig.size()))); } catch (Error & e) { croak("%s", e.what());