keyfork-shard: begin work on (re)provisioning shardholder keys

This commit is contained in:
Ryan Heywood 2024-02-20 05:26:00 -05:00
parent b15d088905
commit 1b30b17691
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
5 changed files with 63 additions and 4 deletions

1
Cargo.lock generated
View File

@ -1829,6 +1829,7 @@ dependencies = [
"card-backend-pcsc", "card-backend-pcsc",
"hkdf", "hkdf",
"keyfork-derive-openpgp", "keyfork-derive-openpgp",
"keyfork-derive-util",
"keyfork-mnemonic-util", "keyfork-mnemonic-util",
"keyfork-prompt", "keyfork-prompt",
"keyfork-qrcode", "keyfork-qrcode",

View File

@ -78,9 +78,7 @@ fn validate(
subkey_format: &str, subkey_format: &str,
default_userid: &str, default_userid: &str,
) -> Result<(DerivationPath, Vec<KeyType>, UserID), Box<dyn std::error::Error>> { ) -> Result<(DerivationPath, Vec<KeyType>, UserID), Box<dyn std::error::Error>> {
let mut pgp_u32 = [0u8; 4]; let index = DerivationIndex::new(u32::from_be_bytes(*b"\x00pgp"), true)?;
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
let index = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
let path = DerivationPath::from_str(path)?; let path = DerivationPath::from_str(path)?;
assert_eq!(2, path.len(), "Expected path of m/{index}/account_id'"); assert_eq!(2, path.len(), "Expected path of m/{index}/account_id'");

View File

@ -36,3 +36,4 @@ card-backend-pcsc = { version = "0.5.0", optional = true }
openpgp-card-sequoia = { version = "0.2.0", optional = true, default-features = false } openpgp-card-sequoia = { version = "0.2.0", optional = true, default-features = false }
openpgp-card = { version = "0.4.0", optional = true } openpgp-card = { version = "0.4.0", optional = true }
sequoia-openpgp = { version = "1.17.0", optional = true, default-features = false } sequoia-openpgp = { version = "1.17.0", optional = true, default-features = false }
keyfork-derive-util = { version = "0.1.0", path = "../derive/keyfork-derive-util", default-features = false }

View File

@ -7,6 +7,7 @@ use aes_gcm::{
Aes256Gcm, KeyInit, Nonce, Aes256Gcm, KeyInit, Nonce,
}; };
use hkdf::Hkdf; use hkdf::Hkdf;
use keyfork_derive_util::{DerivationIndex, DerivationPath};
use keyfork_mnemonic_util::{English, Mnemonic}; use keyfork_mnemonic_util::{English, Mnemonic};
use keyfork_prompt::{ use keyfork_prompt::{
validators::{mnemonic::MnemonicSetValidator, Validator}, validators::{mnemonic::MnemonicSetValidator, Validator},
@ -60,6 +61,52 @@ pub trait Format {
/// A type representing the parsed, but encrypted, Shard data. /// A type representing the parsed, but encrypted, Shard data.
type EncryptedData; type EncryptedData;
/// Provision hardware with a deterministic key based on a shardholder's DerivationIndex.
///
/// The derivation path for provisioned shardholder keys is built using the following template:
/// `m / purpose ' / shard_index ' / shardholder_index '`.
///
/// Purpose is defined by the Format, and can be a four-byte sequence transformed into a u32
/// using `u32::from_be_bytes(*purpose)`. For OpenPGP, for legacy reasons, this purpose is
/// "\x00pgp". The purpose can be _any_ sequence of four bytes so long as the _first_ byte is
/// not higher than 0x80 (meaning, all ASCII / 7-bit characters are allowed).
///
/// The shard index is provided by Keyfork, and is equivalent to b"shrd".
///
/// The shardholder index is how Keyfork is able to recreate keys for specific shardholders -
/// the only necessary information is which shardholder is not accounted for. Shardholders are
/// encouraged to mark hardware with the shardholder number so shardholders can verify their
/// index.
fn provision_shardholder_key(
&self,
derivation_path: DerivationPath,
seed: &[u8],
) -> Result<(), Self::Error>;
/// Return a DerivationIndex for the Format.
///
/// The derivation path for provisioned shardholder keys is built using the following template:
/// `m / purpose ' / shard_index ' / shardholder_index '`.
///
/// Purpose is defined by the Format, and can be a four-byte sequence transformed into a u32
/// using `u32::from_be_bytes(*purpose)`. For OpenPGP, for legacy reasons, this purpose is
/// "\x00pgp". The purpose can be _any_ sequence of four bytes so long as the _first_ byte is
/// not higher than 0x80 (meaning, all ASCII / 7-bit characters are allowed).
fn purpose_derivation_index(&self) -> DerivationIndex;
/// Create a shardholder derivation path for the given format.
///
/// The derivation path for provisioned shardholder keys is built using the following template:
/// `m / purpose ' / shard_index ' / shardholder_index '`.
fn create_derivation_path(&self, shardholder_index: DerivationIndex) -> DerivationPath {
let purpose = self.purpose_derivation_index();
let shard_index = DerivationIndex::new(u32::from_be_bytes(*b"shrd"), true).unwrap();
DerivationPath::default()
.chain_push(purpose)
.chain_push(shard_index)
.chain_push(shardholder_index)
}
/// Derive a signer /// Derive a signer
fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey; fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey;

View File

@ -8,7 +8,7 @@ use std::{
}; };
use keyfork_derive_openpgp::{ use keyfork_derive_openpgp::{
derive_util::{DerivationPath, VariableLengthSeed}, derive_util::{DerivationIndex, DerivationPath, VariableLengthSeed},
XPrv, XPrv,
}; };
use openpgp::{ use openpgp::{
@ -216,6 +216,18 @@ impl Format for OpenPGP {
type SigningKey = Cert; type SigningKey = Cert;
type EncryptedData = EncryptedMessage; type EncryptedData = EncryptedMessage;
fn provision_shardholder_key(
&self,
derivation_path: DerivationPath,
seed: &[u8],
) -> Result<(), Self::Error> {
todo!()
}
fn purpose_derivation_index(&self) -> DerivationIndex {
DerivationIndex::new(u32::from_be_bytes(*b"\x00pgp"), true).unwrap()
}
/// Derive an OpenPGP Shard certificate from the given seed. /// Derive an OpenPGP Shard certificate from the given seed.
fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey { fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey {
let seed = VariableLengthSeed::new(seed); let seed = VariableLengthSeed::new(seed);