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
This commit is contained in:
Frederic Ruget
2026-04-01 14:03:16 +00:00
parent b24161b087
commit c4b0fa64b3
3 changed files with 185 additions and 68 deletions

View File

@@ -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;

54
nixos/tests/yb.nix Normal file
View File

@@ -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)
'';
}

View File

@@ -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;
};
})