keyfork/docs/src/dev-guide/provisioners.md

81 lines
4.2 KiB
Markdown

# Developing Provisioners
**Note:** This document makes heavy use of references to OpenPGP and assumes
the user is familiar with the concept of storing cryptographic keys on external
hardware.
A provisioner is a binary that deploys a derived key (formatted using any
particular mechanism) to an external source, such as a smart card. Provisioners
should hardcode at least one path index (such as `7366512`, for "PGP") specific
to the usage of the key to be provisioned (and such index should be recorded in
the keyfork-path-data crate), and accept at least one index to use as what
bip32 calls an "account". While some users may never practically make use of
multiple accounts, having the option to specify multiple accounts is important.
## Plumbing
Provisioners should be split into two crates: one that takes a path and
generates formatted keys from that path, and one that takes the formatted keys
and deploys them to the provisioning target. An example of this are the
`keyfork-derive-openpgp` and `keyfork-provision-openpgp-card` crates. One
creates an OpenPGP Transferable Secret Key (TSK) which may be usable by any
application, and the other takes the TSK and deploys it to a smart card.
By themselves, these plumbing crates are not meant to be intuitive - they are
meant to take a raw derivation path and as little as possible information, and
perform their operations. The derivation crate should request one key from
Keyforkd and, if further keys are required, derive from there. This is the case
for OpenPGP, where an OpenPGP certificate may have multiple subkeys. The
provisioning crate must enforce the given formatted key is of a correct format.
For example, OpenPGP TSKs can contain any number of subkeys, but a TSK that is
provisioned to a smart card _must_ have exactly one signing, encryption, and
authentication key. Whether or not the subkeys share this functionality between
them is irrelevant - what is important is that all functionality must be
available and all key deployment must be ambiguous. Additionally, provisioning
plumbing _must_ have some way to denote the target the formatted keys will be
provisioned to, avoiding ambiguities when (for example) multiple devices are
plugged into a host. A plumbing crate may provide a binary to list identifiers
for potential targets, if possible.
### Deriving Data
A quick example is provided to show how data can be requested from the Keyfork
server. The response contains the bytes representing the private key; the chain
code, depth, and algorithm are also included in the response, and can be used
for further derivation if required.
```rs
use keyfork_derive_util::{
request::{DerivationAlgorithm, DerivationRequest, DerivationResponse},
DerivationIndex, DerivationPath,
};
use keyforkd_client::Client;
let path = DerivationPath::from_str("m/44'/0'/0'")?;
let request = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &path);
let response: DerivationResponse = Client::discover_socket()?
.request(&request.into())?
.try_into()?;
let derived_key = response.data;
```
## Porcelain
Once the plumbing crates have been written, porcelain subcommands should be
written. If target discovery is necessary, Keyfork provides a subcommand
`keyfork provision [provisioner name] discover` using the `discover()` method
for a provisioner. For the case of OpenPGP cards, the command `keyfork
provision openpgp-card discover` could iterate through all cards available on
the system, and print both their [application identifier] and optionally
[cardholder name] if present.
The subcommand for `keyfork provision` should accept the previously mentioned
account index, but should be opinionated about the keys provisioned to a
device. The porcelain provisioner code should make a best-effort attempt to
derive unique keys for each use, such as OpenPGP capabilities or PIV slots.
Additionally, when provisioning to a key, the configuration for that
provisioner should be stored to the configuration file.
[application identifier]: https://docs.rs/openpgp-card-sequoia/latest/openpgp_card_sequoia/struct.Card.html#method.application_identifier
[cardholder name]: https://docs.rs/openpgp-card-sequoia/latest/openpgp_card_sequoia/struct.Card.html#method.cardholder_name