Skip to content

Commit 196421f

Browse files
committed
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 } ```
1 parent 6b78b5d commit 196421f

11 files changed

Lines changed: 207 additions & 54 deletions

File tree

src/libstore/binary-cache-store.cc

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,29 @@ namespace nix {
2727
BinaryCacheStore::BinaryCacheStore(Config & config)
2828
: config{config}
2929
{
30-
if (!config.secretKeyFile.get().empty())
31-
signers.push_back(std::make_unique<LocalSigner>(SecretKey::parse(readFile(config.secretKeyFile.get()))));
30+
auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore);
31+
if (!config.secretKeyFile.get().empty()) {
32+
auto isUri = keystoreEnabled && !std::get<0>(splitColon(config.secretKeyFile.get().string())).empty();
33+
signers.push_back(
34+
std::make_unique<LocalSigner>(
35+
SecretKey::parse(
36+
isUri ? config.secretKeyFile.get().string() : readFile(config.secretKeyFile.get()),
37+
isUri
38+
)
39+
)
40+
);
41+
}
3242

3343
if (config.secretKeyFiles != "") {
3444
std::stringstream ss(config.secretKeyFiles);
3545
std::string keyPath;
3646
while (std::getline(ss, keyPath, ',')) {
37-
signers.push_back(std::make_unique<LocalSigner>(SecretKey::parse(readFile(keyPath))));
47+
auto isUri = keystoreEnabled && !std::get<0>(splitColon(keyPath)).empty();
48+
signers.push_back(
49+
std::make_unique<LocalSigner>(
50+
SecretKey::parse(isUri ? keyPath : readFile(keyPath), isUri)
51+
)
52+
);
3853
}
3954
}
4055

src/libstore/include/nix/store/binary-cache-store.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
4040
)"};
4141

4242
Setting<AbsolutePath> secretKeyFile{
43-
this, "", "secret-key", "Path to the secret key used to sign the binary cache."};
43+
this, "", "secret-key", "Path or URI to the secret key used to sign the binary cache."};
4444

4545
Setting<std::string> secretKeyFiles{
46-
this, "", "secret-keys", "List of comma-separated paths to the secret keys used to sign the binary cache."};
46+
this, "", "secret-keys", "List of comma-separated paths or URIs to the secret keys used to sign the binary cache."};
4747

4848
Setting<std::optional<AbsolutePath>> localNarCache{
4949
this,

src/libstore/keys.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "nix/util/file-system.hh"
22
#include "nix/store/globals.hh"
3+
#include "nix/util/signature/local-keys.hh"
34
#include "nix/store/keys.hh"
45

56
namespace nix {
@@ -17,9 +18,11 @@ PublicKeys getDefaultPublicKeys()
1718
}
1819

1920
// FIXME: keep secret keys in memory (see Store::signRealisation()).
21+
auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore);
2022
for (const auto & secretKeyFile : settings.secretKeyFiles.get()) {
2123
try {
22-
auto secretKey = SecretKey::parse(readFile(secretKeyFile));
24+
auto isUri = keystoreEnabled && !std::get<0>(splitColon(secretKeyFile)).empty();
25+
auto secretKey = SecretKey::parse(isUri ? secretKeyFile : readFile(secretKeyFile), isUri);
2326
publicKeys.emplace(secretKey->name, secretKey->toPublicKey());
2427
} catch (SystemError & e) {
2528
/* Ignore unreadable key files. That's normal in a

src/libstore/store-api.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,10 +1266,12 @@ void Store::signPathInfo(ValidPathInfo & info)
12661266
{
12671267
// FIXME: keep secret keys in memory.
12681268

1269+
auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore);
12691270
auto secretKeyFiles = settings.secretKeyFiles;
12701271

12711272
for (auto & secretKeyFile : secretKeyFiles.get()) {
1272-
LocalSigner signer(SecretKey::parse(readFile(secretKeyFile)));
1273+
auto isUri = keystoreEnabled && !std::get<0>(splitColon(secretKeyFile)).empty();
1274+
LocalSigner signer(SecretKey::parse(isUri ? secretKeyFile : readFile(secretKeyFile), isUri));
12731275
info.sign(*this, signer);
12741276
}
12751277
}
@@ -1278,10 +1280,12 @@ void Store::signRealisation(Realisation & realisation)
12781280
{
12791281
// FIXME: keep secret keys in memory.
12801282

1283+
auto keystoreEnabled = experimentalFeatureSettings.isEnabled(Xp::Keystore);
12811284
auto secretKeyFiles = settings.secretKeyFiles;
12821285

12831286
for (auto & secretKeyFile : secretKeyFiles.get()) {
1284-
LocalSigner signer(SecretKey::parse(readFile(secretKeyFile)));
1287+
auto isUri = keystoreEnabled && !std::get<0>(splitColon(secretKeyFile)).empty();
1288+
LocalSigner signer(SecretKey::parse(isUri ? secretKeyFile : readFile(secretKeyFile), isUri));
12851289
realisation.sign(realisation.id, signer);
12861290
}
12871291
}

src/libutil-tests/local-keys.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ TEST(local_keys, signAndVerify)
2424
ASSERT_EQ(sig.keyName, "test-key-1");
2525
ASSERT_TRUE(pk->verifyDetached("hello world", sig));
2626

27-
auto sk2 = SecretKey::parse(sk->to_string());
27+
auto sk2 = SecretKey::parse(sk->to_string(), false);
2828
ASSERT_EQ(sk2->name, sk->name);
2929
ASSERT_EQ(sk2->key, sk->key);
3030

@@ -58,7 +58,7 @@ TEST(local_keys, rfc8032TestVector)
5858
auto skBytes = seed + pubKeyBytes;
5959
auto skString = "test:" + base64::encode(std::as_bytes(std::span<const char>{skBytes.data(), skBytes.size()}));
6060

61-
auto sk = SecretKey::parse(skString);
61+
auto sk = SecretKey::parse(skString, false);
6262
auto sig = sk->signDetached(message);
6363

6464
ASSERT_EQ(sig.keyName, "test");
@@ -157,7 +157,7 @@ TEST(local_keys, rfc6979EcdsaP384TestVector)
157157
"99ef4aeb15f178cea1fe40db2603138f130e740a19624526203b6351d0a3a94fa329c145786e679e7b82c71a38628ac8");
158158

159159
auto skString = "rfc6979-test:" + base64::encode(std::as_bytes(std::span<const char>{skDer.data(), skDer.size()}));
160-
auto sk = SecretKey::parse(skString);
160+
auto sk = SecretKey::parse(skString, false);
161161

162162
auto sig = sk->signDetached("sample");
163163
ASSERT_EQ(sig.keyName, "rfc6979-test");
@@ -198,7 +198,7 @@ runMlDsaAcvpTest(std::string_view variant, std::string_view derPrefixHex, size_t
198198
auto der = base16::decode(derPrefixHex) + sk;
199199
auto skString =
200200
std::string(variant) + ":" + base64::encode(std::as_bytes(std::span<const char>{der.data(), der.size()}));
201-
auto parsed = SecretKey::parse(skString);
201+
auto parsed = SecretKey::parse(skString, false);
202202

203203
auto sig = parsed->signDetached(message);
204204
ASSERT_EQ(sig.keyName, std::string(variant));

src/libutil/experimental-features.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ struct ExperimentalFeatureDetails
2525
* feature, we either have no issue at all if few features are not added
2626
* at the end of the list, or a proper merge conflict if they are.
2727
*/
28-
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::CNSA);
28+
constexpr size_t numXpFeatures = 1 + static_cast<size_t>(Xp::Keystore);
2929

3030
constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails = {{
3131
{
@@ -315,6 +315,14 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
315315
)",
316316
.trackingUrl = "",
317317
},
318+
{
319+
.tag = Xp::Keystore,
320+
.name = "keystore",
321+
.description = R"(
322+
Enable support for loading signing keys from OpenSSL store URIs.
323+
)",
324+
.trackingUrl = "",
325+
},
318326
}};
319327

320328
static_assert(

src/libutil/include/nix/util/experimental-features.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ enum struct ExperimentalFeature {
4242
WasmDerivations,
4343
Provenance,
4444
CNSA,
45+
Keystore,
4546
};
4647

4748
extern std::set<std::string> stabilizedFeatures;

src/libutil/include/nix/util/signature/local-keys.hh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ enum KeyType {
4949
ECDSAP384,
5050
};
5151

52+
std::tuple<std::string_view, std::string_view> splitColon(std::string_view s);
53+
5254
KeyType parseKeyType(std::string_view s);
5355

5456
const StringSet & getKeyTypes();
@@ -78,7 +80,7 @@ struct SecretKey : Key
7880

7981
virtual ~SecretKey() {};
8082

81-
static std::unique_ptr<SecretKey> parse(std::string_view s);
83+
static std::unique_ptr<SecretKey> parse(std::string_view s, bool forceUri);
8284

8385
/**
8486
* Return a detached signature of the given string.

0 commit comments

Comments
 (0)