81 lines
4.2 KiB
Markdown
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
|