Skip to content

experimental-features: add 'keystore'#466

Draft
numinit wants to merge 1 commit into
DeterminateSystems:mainfrom
numinit:keystore
Draft

experimental-features: add 'keystore'#466
numinit wants to merge 1 commit into
DeterminateSystems:mainfrom
numinit:keystore

Conversation

@numinit
Copy link
Copy Markdown

@numinit numinit commented May 21, 2026

Motivation

I want to use PKCS#11 keystores with Nix to get keys out of the filesystem and into hardware keystores. May add more signature schemes too (e.g. an OpenSSL Ed25519 implementation).

Context

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).

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 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.uri \
  --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
}

cc @mschwaig

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f06a713e-17fd-4b5a-81ea-0a3d481a7f56

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@numinit
Copy link
Copy Markdown
Author

numinit commented May 21, 2026

Still needs tests, putting up for draft review.

@numinit numinit force-pushed the keystore branch 4 times, most recently from 9c1b587 to c35c4d8 Compare May 21, 2026 06:55
Comment thread src/libstore/binary-cache-store.cc Outdated
Comment thread src/libstore/store-api.cc Outdated
@numinit numinit force-pushed the keystore branch 2 times, most recently from b81e6cf to ed5d521 Compare May 21, 2026 07:01
Comment thread src/libutil/signature/local-keys.cc Outdated
Comment thread src/libutil/signature/local-keys.cc Outdated
@numinit numinit force-pushed the keystore branch 3 times, most recently from e5a8943 to c42a7cf Compare May 25, 2026 05:02
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. :)

Comment thread src/libstore/keys.cc
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.

std::optional<Error> err;
char errBuf[512];

OSSL_STORE_CTX *ctx = OSSL_STORE_open(
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.

I decided not to use the Auto objects for these since this fn is the only using them and all the lifetimes are easily manageable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

While that may be true now, can we guarantee that stays true forever? (Probably not...) It would be nice to use the Auto objects here regardless, IMVHO.

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.

Should be easy enough, honestly

Copy link
Copy Markdown
Author

@numinit numinit May 26, 2026

Choose a reason for hiding this comment

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

I believe that will handle the free for me if I assign by value, too

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
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants