# cryptsetup-passphrase-agent

A non-interactive systemd password agent for encrypted disk passphrases written in Rust.

## Overview

This agent watches `/run/systemd/ask-password/` for password requests from systemd and automatically responds to `cryptsetup` requests (encrypted disk passphrases) with a configured passphrase. All other password requests are ignored.

This is useful for systems with encrypted disks that need to be unlocked automatically without user interaction, such as headless servers or embedded systems.

## Project Structure

```
src/
├── main.rs              # Entry point and main loop
├── parse.rs             # Parsing utilities for systemd config values
├── password_request.rs  # PasswordRequest parsing and PasswordRequestWatcher iterator
├── cryptsetup.rs        # CryptsetupPassphraseRequest for handling cryptsetup requests
├── passphrase.rs        # Passphrase derivation from device ID
└── rpifwcrypto.rs       # FFI bindings to librpifwcrypto

debian/
├── control              # Package metadata
├── rules                # Build rules
├── changelog            # Package changelog
├── copyright            # Licence information
├── gbp.conf             # git-buildpackage configuration
├── cargo-checksum.json  # Cargo checksum placeholder
├── salsa-ci.yml         # Salsa CI configuration
├── source/format        # Source package format
├── cryptsetup-passphrase-agent.service  # systemd service unit
├── cryptsetup-passphrase-agent.path     # systemd path unit
└── cryptsetup-passphrase-agent.install  # Installation paths
```

## Building for Debian

This project is packaged as a native Debian package and uses Debian's `librust-*` packages for dependencies rather than downloading crates from crates.io, ensuring an isolated and reproducible build environment.

### Build Instructions

Install build dependencies:

```bash
sudo mk-build-deps --install --remove
```

Build the package:

```bash
debuild -b -uc -us
```

The built `.deb` package will be created in the parent directory.

### Incremental Builds with Cargo

Cargo may be used directly in support of incremental builds by setting the following environment variables:

```bash
export CARGO_HOME=debian/cargo_home
export CARGO_REGISTRY=debian/cargo_registry
```

Then use cargo as normal:

```bash
cargo build --release
cargo test
```

The binary will be at `target/release/cryptsetup-passphrase-agent`.

## Usage

The agent must be run as root (or with appropriate privileges) to access `/run/systemd/ask-password/` and respond to password requests.

```bash
sudo ./target/release/cryptsetup-passphrase-agent
```

The agent will:
1. Watch `/run/systemd/ask-password/` for new password request files
2. Parse each `ask.*` file that appears
3. For `cryptsetup:` requests only, send the configured passphrase
4. Ignore all other password requests

## Passphrase Derivation

The passphrase for each encrypted device is derived using:

1. **Device Innate ID** - A unique identifier for the block device (e.g., the CID for eMMC/SD cards)
2. **HMAC-SHA256** - The ID is hashed using a firmware OTP key via `rpifwcrypto`

This ensures that:
- Each device gets a unique passphrase
- The passphrase cannot be derived without access to the firmware crypto service
- The raw key material never leaves the firmware

The device innate ID is retrieved using the `block-device-id` crate, which reads hardware identifiers from sysfs (e.g., MMC CID from `/sys/block/mmcblk0/device/cid` or NVMe serial numbers).

## systemd Integration

The package installs two systemd units:

### `cryptsetup-passphrase-agent.path`

A path unit that watches `/run/systemd/ask-password/` and triggers the service when the directory becomes non-empty. Configured to run before `cryptsetup.target`:

```ini
[Unit]
DefaultDependencies=no
Before=paths.target cryptsetup.target
Conflicts=emergency.service shutdown.target

[Path]
DirectoryNotEmpty=/run/systemd/ask-password
MakeDirectory=yes

[Install]
WantedBy=sysinit.target
```

### `cryptsetup-passphrase-agent.service`

The service unit that runs the agent:

```ini
[Service]
Type=simple
ExecStart=/usr/bin/cryptsetup-passphrase-agent
```

### Enabling

After installing the Debian package:

```bash
sudo systemctl enable cryptsetup-passphrase-agent.path
```

The path unit will automatically start the service when password requests appear.

## How It Works

The agent implements the [systemd Password Agents protocol](https://systemd.io/PASSWORD_AGENTS/):

1. Uses `inotify` to watch for `IN_CLOSE_WRITE` and `IN_MOVED_TO` events in `/run/systemd/ask-password/`
2. Parses `.ini` format ask files using `rust-ini`
3. Validates requests:
   - Checks if the requesting process is still alive
   - Checks if the request has expired (`NotAfter` field)
   - Only handles `cryptsetup:` requests (based on `Id` field)
4. Sends the passphrase prefixed with `+` to the Unix datagram socket specified in `Socket=`

## Testing

### Unit Tests

With the environment variables set (see above):

```bash
cargo test
```

### End-to-End Testing

To test the full passphrase derivation and disk unlock flow, you need a Raspberry Pi with an OTP key provisioned and a spare block device.

#### 1. Prerequisites

Ensure an OTP key is provisioned:

```bash
rpi-fw-crypto get-key-status 1
```

#### 2. Prepare a Test Block Device

Format the block device and create a single partition. In this example, we use `mmcblk0` (the SD card) whilst booting from NVMe:

```bash
# Save the device's CID for passphrase derivation
cp /sys/block/mmcblk0/device/cid ./tmp_cid
```

#### 3. Derive the Passphrase

Derive the disk encryption passphrase using the firmware HMAC:

```bash
rpi-fw-crypto hmac --in ./tmp_cid --key-id 1 --outform hex
```

This prints the passphrase to stdout. Use this when setting up LUKS.

#### 4. Set Up LUKS Encryption

Format the partition with LUKS using the derived passphrase:

```bash
sudo cryptsetup luksFormat /dev/mmcblk0p1
```

Enter the passphrase from step 3 when prompted.

#### 5. Test the Password Agent

The password agent only responds to requests from systemd services, not interactive terminal sessions. In production, `/etc/crypttab` entries are processed by `systemd-cryptsetup-generator` to create unlock services. For testing, create a service manually:

Create `/etc/systemd/system/systemd-cryptsetup@test.service`:

```ini
[Unit]
Description=Cryptography Setup for %I
Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)

DefaultDependencies=no
After=cryptsetup-pre.target systemd-udevd-kernel.socket
Before=blockdev@dev-mapper-%i.target
Wants=blockdev@dev-mapper-%i.target
IgnoreOnIsolate=true
Conflicts=umount.target
Before=umount.target
Before=cryptsetup.target
BindsTo=dev-mmcblk0p1
After=dev-mmcblk0p1

[Service]
Type=oneshot
RemainAfterExit=yes
TimeoutSec=infinity
KeyringMode=shared
OOMScoreAdjust=500
ExecStart=/usr/bin/systemd-cryptsetup attach 'test' '/dev/mmcblk0p1' 'none' 'luks,discard'
ExecStop=/usr/bin/systemd-cryptsetup detach 'test'
```

Reload systemd to pick up the new service, then start it:

```bash
sudo systemctl daemon-reload
sudo systemctl start systemd-cryptsetup@test.service
```

#### 6. Verify

Check the agent responded successfully:

```bash
systemctl status cryptsetup-passphrase-agent.service
```

The encrypted device should now be available at `/dev/mapper/test`.

## Security Considerations

- Passphrases are derived at runtime using hardware-bound cryptography:
  - The device's innate ID (e.g., MMC CID) is read from sysfs
  - HMAC-SHA256 is computed using a firmware OTP key that never leaves the hardware
- The agent runs with root privileges to access block device information and password sockets
- The passphrase is sent over a Unix socket with restricted permissions
- The systemd service unit includes security hardening options

## Dependencies

All Rust dependencies are satisfied by Debian's `librust-*` packages as specified in `debian/control`. This ensures the build is completely isolated from external package registries and uses only packages that have been reviewed and packaged by Debian maintainers.

### Rust Crates
- `block-device-id` - Retrieve innate unique identifiers from block devices
- `nix` - Safe Rust bindings to Unix APIs
- `rust-ini` - INI file parser

### System Libraries
- `librpifwcrypto` - Raspberry Pi firmware cryptography service

## Licence

MIT
