Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/libstore/binary-cache-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,29 @@ namespace nix {
BinaryCacheStore::BinaryCacheStore(Config & config)
: config{config}
{
if (!config.secretKeyFile.get().empty())
signers.push_back(std::make_unique<LocalSigner>(SecretKey::parse(readFile(config.secretKeyFile.get()))));
auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore);
if (!config.secretKeyFile.get().empty()) {
Copy link
Copy Markdown
Author

@numinit numinit May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets us use URIs in secret-keys. :)

auto isUri = keystoreEnabled && !std::get<0>(splitColon(config.secretKeyFile.get().string())).empty();
signers.push_back(
std::make_unique<LocalSigner>(
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<LocalSigner>(SecretKey::parse(readFile(keyPath))));
auto isUri = keystoreEnabled && !std::get<0>(splitColon(keyPath)).empty();
signers.push_back(
std::make_unique<LocalSigner>(
SecretKey::parse(isUri ? keyPath : readFile(keyPath), isUri)
)
);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/libstore/include/nix/store/binary-cache-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
)"};

Setting<AbsolutePath> 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<std::string> 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<std::optional<AbsolutePath>> localNarCache{
this,
Expand Down
5 changes: 4 additions & 1 deletion src/libstore/keys.cc
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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());
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... 🤔

Possible this may not work with an EVP_PKEY bound to PKCS#11. Will need to test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, if it doesn't the catch should handle it.

} catch (SystemError & e) {
/* Ignore unreadable key files. That's normal in a
Expand Down
8 changes: 6 additions & 2 deletions src/libstore/store-api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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);
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/libutil-tests/local-keys.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -58,7 +58,7 @@ TEST(local_keys, rfc8032TestVector)
auto skBytes = seed + pubKeyBytes;
auto skString = "test:" + base64::encode(std::as_bytes(std::span<const char>{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");
Expand Down Expand Up @@ -157,7 +157,7 @@ TEST(local_keys, rfc6979EcdsaP384TestVector)
"99ef4aeb15f178cea1fe40db2603138f130e740a19624526203b6351d0a3a94fa329c145786e679e7b82c71a38628ac8");

auto skString = "rfc6979-test:" + base64::encode(std::as_bytes(std::span<const char>{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");
Expand Down Expand Up @@ -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<const char>{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));
Expand Down
10 changes: 9 additions & 1 deletion src/libutil/experimental-features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(Xp::CNSA);
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::Keystore);

constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails = {{
{
Expand Down Expand Up @@ -315,6 +315,14 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
)",
.trackingUrl = "",
},
{
.tag = Xp::Keystore,
.name = "keystore",
.description = R"(
Enable support for loading signing keys from OpenSSL store URIs.
)",
.trackingUrl = "",
},
}};

static_assert(
Expand Down
1 change: 1 addition & 0 deletions src/libutil/include/nix/util/experimental-features.hh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ enum struct ExperimentalFeature {
WasmDerivations,
Provenance,
CNSA,
Keystore,
};

extern std::set<std::string> stabilizedFeatures;
Expand Down
4 changes: 3 additions & 1 deletion src/libutil/include/nix/util/signature/local-keys.hh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ enum KeyType {
ECDSAP384,
};

std::tuple<std::string_view, std::string_view> splitColon(std::string_view s);

KeyType parseKeyType(std::string_view s);

const StringSet & getKeyTypes();
Expand Down Expand Up @@ -78,7 +80,7 @@ struct SecretKey : Key

virtual ~SecretKey() {};

static std::unique_ptr<SecretKey> parse(std::string_view s);
static std::unique_ptr<SecretKey> parse(std::string_view s, bool forceUri);

/**
* Return a detached signature of the given string.
Expand Down
Loading