From c4b0fa64b33a9147f0f3f9af93cd1a4f024e80b2 Mon Sep 17 00:00:00 2001 From: Frederic Ruget Date: Wed, 1 Apr 2026 14:03:16 +0000 Subject: [PATCH] yb: 0.1.0 -> 0.4.2, rewrite in Rust The Python implementation has been replaced by a Rust rewrite. This updates the package definition accordingly: - Switch from buildPythonApplication to rustPlatform.buildRustPackage - Cargo workspace lives in rust/ subdirectory (cargoRoot = rust) - System dependency is now pcsclite only (no more opensc, openssl, yubico-piv-tool, yubikey-manager) - Install shell completions (bash, zsh, fish) via installShellFiles - Install man pages via yb-gen-man helper binary - Add passthru.tests.integration: NixOS VM test using vsmartcard-vpcd and piv-authenticator to exercise the full PIV stack without hardware - Add nix-update-script for automated version updates --- nixos/tests/all-tests.nix | 1 + nixos/tests/yb.nix | 54 +++++++++ pkgs/by-name/yb/yb/package.nix | 198 ++++++++++++++++++++++----------- 3 files changed, 185 insertions(+), 68 deletions(-) create mode 100644 nixos/tests/yb.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index b276d07786bc..1fc6c9f29f39 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1811,6 +1811,7 @@ in xterm = runTest ./xterm.nix; xxh = runTest ./xxh.nix; yarr = runTest ./yarr.nix; + yb = pkgs.callPackage ./yb.nix { inherit (pkgs.yb.passthru) ybPivHarnessTests testFixtures; }; ydotool = import ./ydotool.nix { inherit (pkgs) lib; inherit runTest; diff --git a/nixos/tests/yb.nix b/nixos/tests/yb.nix new file mode 100644 index 000000000000..696ee8170ab8 --- /dev/null +++ b/nixos/tests/yb.nix @@ -0,0 +1,54 @@ +{ + lib, + pkgs, + ybPivHarnessTests, + testFixtures, +}: + +pkgs.testers.nixosTest { + name = "yb-integration-tests"; + meta.maintainers = with lib.maintainers; [ douzebis ]; + + nodes.machine = + { pkgs, ... }: + { + services.pcscd = { + enable = true; + plugins = [ + pkgs.ccid + pkgs.vsmartcard-vpcd + ]; + }; + + environment.systemPackages = [ + pkgs.yb + ybPivHarnessTests + ]; + }; + + testScript = '' + machine.start() + machine.wait_for_unit("pcscd.socket") + + # Tier-2: virtual smart card PIV tests (each test gets a fresh + # RAM-backed card via vsmartcard-vpcd). Serialised to avoid + # concurrent vpcd connections. + out = machine.succeed("RUST_TEST_THREADS=1 hardware_piv_tests 2>&1") + print(out) + if "test result: ok" not in out: + raise Exception("hardware_piv_tests failed:\n" + out) + + # Tier-2: CLI subprocess tests against the Nix-built yb binary. + # YB_FIXTURE_DIR points to fixtures in the nix store (the build-sandbox + # path baked into CARGO_MANIFEST_DIR is gone at VM runtime). + out = machine.succeed( + "RUST_TEST_THREADS=1" + + " YB_BIN=${pkgs.yb}/bin/yb" + + " YB_FIXTURE_DIR=${testFixtures}" + + " yb_cli_tests 2>&1" + ) + print(out) + if "test result: ok" not in out: + raise Exception("yb_cli_tests failed:\n" + out) + ''; +} diff --git a/pkgs/by-name/yb/yb/package.nix b/pkgs/by-name/yb/yb/package.nix index b96acb2db702..198e7cc97dc9 100644 --- a/pkgs/by-name/yb/yb/package.nix +++ b/pkgs/by-name/yb/yb/package.nix @@ -1,97 +1,159 @@ { lib, - python3Packages, + rustPlatform, fetchFromGitHub, - opensc, - openssl, - yubico-piv-tool, - yubikey-manager, + installShellFiles, + versionCheckHook, + pcsclite, + pkg-config, + runCommand, + llvmPackages, + nix-update-script, + nixosTests, + stdenv, }: -python3Packages.buildPythonApplication (finalAttrs: { +rustPlatform.buildRustPackage (finalAttrs: { pname = "yb"; - version = "0.1.0"; - pyproject = true; + version = "0.4.2"; src = fetchFromGitHub { owner = "douzebis"; repo = "yb"; - rev = "v${finalAttrs.version}"; - hash = "sha256-Eq3qFDzi/G4qQ3UUZWyl42zMYAO2C0ipV2yXxt2EAUw="; + tag = "v${finalAttrs.version}"; + hash = "sha256-gX9s1R/75ipaPJFPTBMR2riIxMmw1KfuURx2Up6ovOM="; }; - build-system = with python3Packages; [ - setuptools - wheel - ]; + cargoRoot = "rust"; - dependencies = with python3Packages; [ - click - cryptography - prompt-toolkit - pyscard - pyyaml - yubikey-manager - ]; + cargoHash = "sha256-J91BH0eXuTtGZCSWWLYGM5KHtBjczD1Qu5PxIXAnFRI="; - nativeCheckInputs = with python3Packages; [ - pytestCheckHook - ]; - - buildInputs = [ - opensc - openssl - yubico-piv-tool - yubikey-manager - ]; - - makeWrapperArgs = [ - "--prefix" - "PATH" - ":" - "${lib.makeBinPath [ - opensc - openssl - yubico-piv-tool - yubikey-manager - ]}" - "--set" - "LD_LIBRARY_PATH" - "${yubico-piv-tool}/lib" - ]; - - pythonImportsCheck = [ + # Build and test only the yb crate (not the yb-piv-harness test harness, + # which requires a virtual smart card and runs in the NixOS VM test below). + buildAndTestSubdir = "rust/yb"; + cargoBuildFlags = [ + "-p" "yb" + "--features" + "self-test" + ]; + cargoTestFlags = [ + "-p" + "yb" + "--features" + "self-test" ]; - # Run subset of tests that don't require YubiKey hardware - doCheck = true; - enabledTestPaths = [ - "tests" + nativeBuildInputs = [ + pkg-config + installShellFiles ]; + buildInputs = lib.optionals stdenv.isLinux [ pcsclite ]; + + postInstall = lib.optionalString (stdenv.buildPlatform.canExecute stdenv.hostPlatform) '' + # Bash completion: patch two clap_complete quirks: + # 1. Add `compopt -o filenames` so path arguments complete correctly. + # 2. Replace $2 (bash-tokenized word) with the actual typed text up to + # the cursor, so arguments with spaces (e.g. --reader "Yubico YubiKey + # OTP+FIDO+CCID 00 00") complete correctly. + YB_COMPLETE=bash $out/bin/yb > _yb.bash + substituteInPlace _yb.bash \ + --replace-fail ' ) )' ' ) ) + compopt -o filenames 2>/dev/null' \ + --replace-fail 'words[COMP_CWORD]="$2"' 'local _cur="''${COMP_LINE:0:''${COMP_POINT}}" + _cur="''${_cur##* }" + words[COMP_CWORD]="''${_cur}"' + installShellCompletion --cmd yb \ + --bash _yb.bash \ + --zsh <(YB_COMPLETE=zsh $out/bin/yb) \ + --fish <(YB_COMPLETE=fish $out/bin/yb) + + # Man pages (generated by the yb-gen-man helper binary, also in this crate). + $out/bin/yb-gen-man $out/share/man/man1 + ''; + + nativeInstallCheckInputs = [ + versionCheckHook + ]; + doInstallCheck = true; + + passthru = + let + # Fixture files needed by yb_cli_tests at VM runtime. rustPlatform vendors + # the entire workspace but strips non-Rust files, so the compile-time + # CARGO_MANIFEST_DIR path for fixtures is gone at VM time. We ship them as + # a separate store path and inject YB_FIXTURE_DIR in the testScript. + testFixtures = runCommand "yb-test-fixtures" { } '' + mkdir -p $out + cp ${finalAttrs.src}/rust/yb-core/tests/fixtures/with_key.yaml $out/with_key.yaml + cp ${finalAttrs.src}/rust/yb-core/tests/fixtures/default.yaml $out/default.yaml + ''; + + # Compile the harness test binaries (--no-run: compiled but not executed + # here; the testScript in tests.nix invokes them directly). + ybPivHarnessTests = rustPlatform.buildRustPackage { + pname = "yb-piv-harness-tests"; + inherit (finalAttrs) + version + src + cargoRoot + cargoHash + ; + + buildAndTestSubdir = "rust"; + + nativeBuildInputs = [ + pkg-config + llvmPackages.libclang + ]; + buildInputs = lib.optionals stdenv.isLinux [ pcsclite ]; + + LIBCLANG_PATH = "${llvmPackages.libclang.lib}/lib"; + BINDGEN_EXTRA_CLANG_ARGS = "-I${llvmPackages.libclang.lib}/lib/clang/${lib.versions.major llvmPackages.release_version}/include"; + + # Skip the normal build — we only want the test binaries. + buildPhase = '' + (cd rust && cargo test --no-run \ + -p yb-piv-harness --features integration-tests \ + --offline --release) + ''; + + doCheck = false; + + installPhase = '' + mkdir -p $out/bin + (cd rust && for name in hardware_piv_tests yb_cli_tests; do + bin=$(find target/release/deps -maxdepth 1 -name "$name-*" ! -name "*.d" -type f) + if [ -z "$bin" ] || [ ! -f "$bin" ]; then + echo "ERROR: could not find $name binary" >&2 + exit 1 + fi + cp "$bin" $out/bin/$name + done) + ''; + }; + in + { + inherit ybPivHarnessTests testFixtures; + tests.integration = nixosTests.yb; + updateScript = nix-update-script { }; + }; + meta = { - description = "CLI tool for securely storing and retrieving binary blobs using YubiKey"; + description = "Secure blob storage on a YubiKey"; longDescription = '' - yb is a command-line tool that provides secure blob storage using a YubiKey device. - It leverages the YubiKey's PIV (Personal Identity Verification) application to store - encrypted or unencrypted binary data in custom PIV data objects. The tool uses hybrid - encryption (ECDH + AES-256-CBC) to protect sensitive data with hardware-backed - cryptographic keys. - - Features: - - Hardware-backed encryption using YubiKey PIV - - ~36 KB storage capacity (expandable to ~48 KB) - - PIN-protected management key mode - - Multi-device support with interactive selection - - Shell auto-completion for blob names - - Glob pattern filtering + Command-line tool for storing encrypted binary blobs on a YubiKey using + the PIV application. Uses hybrid encryption (ECDH + AES-256-GCM) with + hardware-backed keys, supports PIN-protected management key setup + (--protect), glob-pattern blob listing, and shell completions for bash, + zsh, and fish. ''; homepage = "https://github.com/douzebis/yb"; changelog = "https://github.com/douzebis/yb/releases/tag/v${finalAttrs.version}"; license = lib.licenses.mit; maintainers = with lib.maintainers; [ douzebis ]; mainProgram = "yb"; - platforms = lib.platforms.linux; + platforms = lib.platforms.unix; }; })