yb-core 0.5.1

Secure blob storage on a YubiKey — core library
Documentation
<!--
SPDX-FileCopyrightText: 2025 - 2026 Frederic Ruget <fred@atlant.is> (GitHub: @douzebis)

SPDX-License-Identifier: MIT
-->

[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/douzebis/yb)
[![Crates.io](https://img.shields.io/crates/v/yb.svg)](https://crates.io/crates/yb)

# yb — Secure Blob Storage in Your YubiKey

**yb** is a command-line tool for securely storing, retrieving, and managing
binary blobs directly within a [YubiKey](https://www.yubico.com/products/)
using its PIV application. Blobs are stored under human-friendly names,
optionally encrypted with hardware-backed hybrid cryptography.

> **Installing with `cargo install`?**  It builds yb from source.  On Linux,
> install `pkg-config` and the PC/SC library (pcsclite) first; on NixOS,
> build in a `nix-shell` that provides them.  See
> [Installation]#installation.  On macOS, nothing extra is needed.

**GitHub**: https://github.com/douzebis/yb

---

## Features

- **Store** binary blobs under human-friendly names (files or stdin)
- **Encrypt** data using hybrid ECDH + HKDF-SHA256 + AES-256-GCM, with the
  private key never leaving the YubiKey
- **Sign** every stored blob with a P-256 ECDSA signature for tamper detection
- **Integrity checking** in both `list` and `fsck` — CORRUPTED blobs are
  flagged automatically, no PIN required
- **List**, **fetch**, and **remove** blobs by exact name or glob pattern
- **Multiple YubiKeys** supported via `--serial`
- **Management key kept on the YubiKey**, unlocked by the PIN: writes need
  only the PIN
- **Shell completions** for bash, zsh, and fish (dynamic blob-name completion)
- No runtime dependencies beyond PC/SC — a single binary

---

## Installation

### NixOS / Nix

yb is packaged in nixpkgs (which may lag behind crates.io):

```nix
# configuration.nix
environment.systemPackages = [ pkgs.yb ];
services.pcscd.enable = true;
```

Or try it without installing: `nix-shell -p yb`.

Build and install from the repo:

```shell
nix-build
result/bin/yb --help
```

Or enter a development shell with `yb` on `PATH` and shell completions
activated automatically:

```shell
nix-shell
```

### cargo install (Debian, Arch, Fedora, NixOS, macOS, …)

`cargo install` builds yb from source.  On Linux, it needs `pkg-config` and
the PC/SC development library; install them first:

```shell
# Debian / Ubuntu
sudo apt install pkgconf libpcsclite-dev

# Arch
sudo pacman -S pkgconf pcsclite

# Fedora
sudo dnf install pkgconf pcsc-lite-devel

# macOS — PC/SC is built into the OS; no extra library needed
```

Then:

```shell
cargo install --locked yb
```

On NixOS, libraries are not installed globally: build in a shell that
provides them, in one step:

```shell
nix-shell -p cargo rustc pkg-config pcsclite --run 'cargo install --locked yb'
```

`--locked` is recommended: it uses the dependency versions yb was tested
with at release time.  Plain `cargo install yb` (as shown on crates.io)
works too; it picks the newest compatible versions, which may need a more
recent Rust toolchain than yours.

Runtime requirement: a PC/SC daemon must be running (`pcscd` on Linux; on
NixOS, `services.pcscd.enable = true;`).  No other external tools are
needed.

**Troubleshooting:**

- *The build fails with `Could not find a PCSC library` … `The pkg-config
  command could not be found`.*  `pkg-config` or the PC/SC library is
  missing: install them as above (on NixOS, use the `nix-shell` line).
- *`yb --version` answers `No such option: --version`, or `yb ls` reports
  `Store has bad object size`.*  An older yb (the 0.1.x Python version)
  comes first on your `PATH`.  Check with `type -a yb`: `cargo install`
  puts yb in `~/.cargo/bin`.  Remove the old one, or put `~/.cargo/bin`
  first on your `PATH`.

> **Man pages:** `cargo install` does not install man pages.  To generate
> them locally, run:
>
> ```shell
> cargo install --locked --bin yb-gen-man yb
> yb-gen-man /usr/local/share/man/man1
> ```
>
> Or, if you have the source tree available:
>
> ```shell
> cargo run --manifest-path rust/Cargo.toml --bin yb-gen-man -- /usr/local/share/man/man1
> ```

### Build from source

```shell
git clone https://github.com/douzebis/yb
cd yb/rust
cargo build --release
# Binary is at target/release/yb
```

---

## Shell Completions

yb supports dynamic shell completions — blob names and serial numbers are
completed live against the connected YubiKey.

### bash

```shell
source <(YB_COMPLETE=bash yb | sed 's/yb//2;s/yb//2')
```

Add to `~/.bashrc` for persistence.

### zsh

```shell
YB_COMPLETE=zsh yb > ~/.zfunc/_yb
# Ensure ~/.zfunc is in your fpath, then:
autoload -Uz compinit && compinit
```

### fish

```shell
YB_COMPLETE=fish yb > ~/.config/fish/completions/yb.fish
```

When installed via `nix-build`, completion scripts are installed automatically
into the system completion directories.  When using `nix-shell`, bash
completions are activated for the current session automatically.

---

## Quick Start

```shell
# One-time setup, guided: changes a factory PIN/PUK, generates a P-256 key,
# and keeps the management key on the YubiKey, unlocked by the PIN
yb format

# Stash a secret
echo "s3cr3t" | yb store -n api-token

# Retrieve it
yb fetch -p api-token

# See what's there — signatures verified automatically, no PIN needed
yb ls -l

# Something feels off? Check the store
yb fsck --nvm
```

For full option details: `yb <command> --help` or `man yb`.

---

## PIN Handling

yb resolves the PIN in this order:

1. `--pin-stdin` — reads one line from stdin (for scripting and pipelines)
2. `YB_PIN` environment variable
3. Interactive TTY prompt — deferred until a PIN is actually needed

Commands that never need a PIN (`list`, `fsck` without `--check-key`) never
prompt.
Both perform signature verification using only the public key from the
store's X.509 certificate.

> **Security note:** `YB_PIN` and `--pin-stdin` are convenient for
> scripting but carry OS-level exposure risks.  `YB_PIN` is visible to
> child processes and, on some systems, to other users via `/proc`.
> `--pin-stdin` is the safer non-interactive option — pipe the PIN on
> stdin rather than setting an environment variable when possible.
> The default interactive TTY prompt (`rpassword`) has no such exposure.

---

## Cryptographic Model

yb uses a **hybrid encryption scheme**:

1. A fresh ephemeral P-256 key pair is generated for each `store` operation.
2. ECDH between the ephemeral key and the YubiKey's resident P-256 key
   produces a shared secret — the YubiKey performs this operation on-card
   via GENERAL AUTHENTICATE; the private key never leaves the device.
3. The shared secret is fed into **HKDF-SHA256** to derive an AES-256 key.
4. The blob is encrypted with **AES-256-GCM** (authenticated encryption).
5. The ephemeral public key, nonce, and ciphertext are stored in the YubiKey
   PIV objects.

Decryption (`fetch`) reverses steps 2–4, again using the YubiKey for ECDH.

Legacy blobs produced by the Python predecessor (AES-256-CBC) are still
readable.

---

## Storage Model

yb stores blobs in custom PIV data objects (retired key certificate slots
`0x5F_0000`–`0x5F_0013`).  Large blobs are split across multiple objects
using a linked-chunk format.  Default store configuration:

| Parameter | Default | Notes |
|---|---|---|
| Object count | 32 | Tunable at format time (`--object-count`) |
| Max object size | 3,063 bytes | Each PIV object is written at the size its content requires |
| Gross capacity | up to ~61 KB | Shared with other PIV data; YubiKey 5 NVM pool is 51,200 bytes |
| ECDH key slot | `0x82` | Tunable at format time (`--key-slot`) |

---

## Multiple YubiKeys

```shell
# List connected readers
yb list-readers

# Use a specific YubiKey by serial number
yb --serial 12345678 list
yb --serial 12345678 store secret.txt
```

When multiple YubiKeys are connected and `--serial` is omitted, yb prints
the available serials and exits.

---

## Security: Default Credential Detection

yb checks for factory-default credentials (PIN `123456`, PUK `12345678`,
management key `010203...`).  It refuses to store secrets behind a factory
PIN or PUK, and warns elsewhere; `yb fsck` reports them.  On a new
YubiKey, the guided `yb format` changes them.  To change them without
touching the store:

```shell
ykman piv access change-pin
ykman piv access change-puk
```

For the management key, the convenient approach is to **keep it on the
YubiKey**: it is replaced with a random value stored on the YubiKey itself,
which only your PIN unlocks.  You then never need to keep or type the
management key: yb reads it when a write needs it (no `YB_MANAGEMENT_KEY`).
This is a convenience, not extra security.  ykman calls this "PIN-protected"
mode.

The guided `yb format` always sets it up.  From a script, at format time
(`--protect` keeps a key that is already stored on the YubiKey):

```shell
yb format --generate --protect
```

Or, on a YubiKey already formatted, without touching the store (this also
replaces a key that is already kept on the YubiKey):

```shell
yb rotate-management-key
```

To bypass the default-credential check (testing only): `--allow-defaults` or
`YB_SKIP_DEFAULT_CHECK=1`.

Requires YubiKey firmware 5.3 or later for credential detection.

---

## Format Specification

The yblob binary format is fully documented in
[`docs/YBLOB_FORMAT.md`](docs/YBLOB_FORMAT.md).  It is designed to be
implementation-independent: any tool that can read and write YubiKey PIV data
objects can interoperate with a yblob store without using `yb` itself.

---

## File Format Recognition

The yblob binary format is registered in the
[`file` magic database](https://github.com/file/file) (merged June 2025,
[bug #666](https://bugs.astron.com/view.php?id=666)).  On any system with an
up-to-date `file` installation, raw PIV object dumps are identified
automatically:

```shell
$ file yubikey_object_dump.bin
yubikey_object_dump.bin: yblob object store image data
```

The magic number is `0xF2ED5F0B` (little-endian u32 at offset 0), present in
every PIV object written by yb.

---

## License

MIT License. See `LICENSE` for full text.