> ## Documentation Index
> Fetch the complete documentation index at: https://docs.efference.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Access Control and Encryption

> Lock the USB control plane and encrypt recordings at rest

A factory device is unrestricted: Bluetooth LE control is password-gated, USB is
not, and recordings are written unencrypted. Both settings are configurable and
independent of each other. Locking the control plane determines who can command
the device; encrypting at rest determines who can read the recordings it has
already made.

## One password, both transports

The device uses a single control password (factory default `123456`). BLE always
requires it. USB does not enforce it until the device is locked, which keeps
bench work convenient while allowing a fielded device to be secured.

```sh theme={null}
ef-cli lock on                       # USB now gates exactly like BLE
ef-cli --password 123456 record list
ef-cli --password 123456 lock off
```

Changing the lock in either direction requires the current password, so an
unattended device cannot be unlocked without it. Rekeying is available from
either transport and applies to both:

```sh theme={null}
ef-cli set-password <new>            # over UNLOCKED USB: physical access is the credential
ef-cli set-password <old> <new>      # over BLE, or over locked USB
```

Once USB is locked, the form that omits the old password is unavailable and
[`factory-reset`](#recovering-a-lost-password) is the only remaining route.

<Info>
  `info`, `state`, `storage` and `factory-reset` respond regardless of lock state,
  so a locked device still reports its identity and can always be recovered.
  `ef-cli info` reports `usb access` and `encryption`, so a device that is refusing
  a command indicates why.
</Info>

## Authentication is per link and expires

Authenticate once and the remainder of that connection is authenticated. USB and
BLE authenticate independently of each other.

The grant is time-limited, because USB provides the device no means of detecting
that the client which obtained it has disconnected. It ends at whichever occurs
first: two minutes idle, ten minutes from the time it was granted (traffic does
not extend this), a new authentication attempt on the same link, or a cable
cycle.

`ef::Device` repeats the handshake and retries transparently, so a long-lived
session is unaffected. A client implementing the wire protocol directly must
handle `AUTH_REQUIRED` on any gated verb and re-authenticate in response.

`open()` succeeds even with an incorrect password, because `info`, `state` and
`storage` are required by an operator recovering a device whose password has been
lost. Verify that gated calls will succeed before issuing one:

```cpp theme={null}
dev.open(init);
if (!dev.is_authenticated())
    std::puts("password not accepted: only info, state and storage will answer");
```

## Opening a locked device for one session

Rather than supplying `--password` with every command, open the device for the
duration of the power session:

```sh theme={null}
ef-cli lock off --session      # open until re-locked, or power is lost
ef-cli lock on  --session      # close it again
```

This does not change the stored policy. The device continues to report itself as
locked, so a power loss closes it with no state to retain, and `ef-cli info`
reports `LOCKED, open for this session` rather than either state alone. The
command is refused on a device that is not locked, and a plain `lock on` from
either transport ends the session.

Authentication never applies this implicitly. A password authorizes one caller
for one call, whereas opening the device is a separate operator decision, since a
grant that extended from one client to every other client on the cable is the
behavior this replaced.

## Encrypting recordings at rest

Recordings can be AES-256-GCM encrypted under a per-device key. The device
generates the key itself and returns it once, at creation:

```sh theme={null}
ef-cli encryption create             # generates the key and PRINTS IT, this one time
ef-cli encryption on                 # applies to the NEXT recording
```

<Warning>
  **Save the key when `create` prints it.** The device retains a working copy, but
  no command other than `key show` prints it again, and a factory reset destroys
  it. Without a saved copy, recordings made under that key cannot be read.
</Warning>

```sh theme={null}
ef-cli key show                      # print it (stdout, so it can be piped)
ef-cli key show --out device1.key    # or write it to a 0600 file, never echoed
```

To use a key you already hold, supply it instead of having the device generate
one. The key is exactly 32 bytes, given as a file, on stdin, or as 64 hex
characters:

```sh theme={null}
ef-cli key set --in device1.key      # from a file
cat device1.key | ef-cli key set -   # from stdin
ef-cli key set <64-hex>              # inline
```

`key set` is refused while a key already exists, and while the device is
recording. The reply names the key by ID and never returns the bytes.

<Warning>
  **Replacing a key is three steps, not one.** `encryption delete` turns
  encryption off, and `key set` does not turn it back on:

  ```sh theme={null}
  ef-cli encryption delete --confirm <key_id>
  ef-cli key set --in newkey.key
  ef-cli encryption on                 # required, or recordings continue in the clear
  ```
</Warning>

`--out` refuses to overwrite an existing file rather than truncating it, because
the file it would replace may be the only copy of another device's key.

Enabling encryption without a key is refused, so a session cannot report that it
is encrypting while recording unencrypted. Existing recordings retain the state
they were written with; the setting applies only to subsequent recordings.

`ef-cli info` reports the key ID, which is the first four bytes of its SHA-256
and never the key itself. The same ID is written into each recording header,
which identifies the saved key required to open a given file. On a locked device
the ID requires the password like any other gated field, since that value in a
file header would otherwise allow a nearby party to associate a device with
recordings in their possession. Whether a key exists is always readable.

`record list` marks each recording `[encrypted]` or `[unencrypted]` by reading
the container identifier from the file, so it reports the state on disk rather
than the current setting.

## Reading an encrypted recording

Download the recording, then decrypt it on the host with `ef-decrypt`:

```sh theme={null}
ef-cli download clip1 clip1.enc
ef-decrypt clip1.enc device1.key clip1.mcap
```

`download` neither decrypts nor renames. Without a destination it writes
`<name>.mcap` regardless of the contents, so an encrypted recording is written to
a file that no MCAP reader will open unless a destination is specified, as above.

The key file is the one written by `key show --out`: 32 raw bytes, or 64 hex
characters with an optional trailing newline. `ef-decrypt` writes the plaintext
to the named output and its summary to stderr, allowing it to be used in a
pipeline.

| Exit | Meaning                                                                                                       |
| ---- | ------------------------------------------------------------------------------------------------------------- |
| `0`  | Clean: the end marker was present and every chunk verified.                                                   |
| `1`  | Truncated, or a chunk failed its tag. Everything before that point is valid and is retained.                  |
| `2`  | Unusable input: bad header, wrong key, an unimplemented algorithm, or I/O failure. No output file is written. |

A recording truncated by power loss returns exit `1` rather than indicating
corruption, and decrypts up to its last complete chunk. An incorrect key is
detected at the header rather than through repeated chunk failures, and the error
names the `key_id` the file requires.

`ef-decrypt` requires libcrypto (`apt install libssl-dev`) and is omitted by the
build when it is unavailable.

## Destroying the key

Two commands destroy the key, and both are irreversible for every recording
written under it, including copies already transferred elsewhere.

```sh theme={null}
ef-cli encryption delete             # shows the key and what will happen; destroys NOTHING
ef-cli encryption delete --confirm <key_id>
```

The first form allows the key to be saved if older recordings are still needed.
The device requires `key_id` to match the installed key, which prevents an SDK
caller from destroying a key it has not identified, and it is refused unless the
device is `IDLE`: a running session holds the key in memory and would otherwise
continue writing under a key the device had reported as destroyed.

Rotation is delete followed by create, as two deliberate steps. `create` is
refused while a key exists, because replacing one would render every recording
written under it permanently undecryptable.

## Recovering a lost password

Reset the device over USB, where physical possession serves as the credential:

```sh theme={null}
ef-cli factory-reset
```

This restores the password, lock state, encryption setting, WiFi credentials,
calibration, capture configuration and recordings to their defaults.

<Warning>
  `factory-reset` also **destroys the encryption key**, which renders every
  recording written under it permanently unreadable, wherever those files are now
  stored. Save the key with `key show --out <file>` first if those recordings are
  still required.
</Warning>

Unlike `encryption delete`, the reset does not return the key before destroying
it. It responds unauthenticated over USB, and returning a key to an
unauthenticated caller is the exposure that destroying it is intended to remove.
Over BLE it requires the password like any other verb, since an unauthenticated
remote reset would allow a nearby party to destroy the key for every recording.

The key is held in the device's writable storage, so a full reflash destroys it
as a reset does. A saved copy is the only form that survives either.

## Scripting these commands

`encryption delete --confirm` and `factory-reset` prompt for typed confirmation
on a terminal. Without a terminal (`ssh box 'ef-cli ...'`, cron, redirected
stdin) they refuse rather than prompt, so pass `--yes` to confirm in a script:

```sh theme={null}
ef-cli encryption delete --confirm <key_id> --yes
ef-cli factory-reset --yes
```

## From the library

```cpp theme={null}
ERROR_CODE set_usb_lock(bool locked, bool session_only = false);
ERROR_CODE set_encryption(bool enabled);
ERROR_CODE get_encryption_key(EncryptionKey& out);
ERROR_CODE create_encryption_key(EncryptionKey& out);
ERROR_CODE delete_encryption_key(const std::string& key_id, EncryptionKey& out);
ERROR_CODE factory_reset();
bool       is_authenticated() const;
```

Read the current state from `get_device_information()`: `usb_locked`,
`session_unlocked`, `encryption_enabled`, `encryption_key_present`,
`encryption_key_id` and `encryption_algorithm`. See the
[API reference](/api/device) for the full contracts.
