diff --git a/Cargo.lock b/Cargo.lock index 2bc027e..6be1609 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,6 +1829,7 @@ dependencies = [ "card-backend-pcsc", "hkdf", "keyfork-derive-openpgp", + "keyfork-derive-util", "keyfork-mnemonic-util", "keyfork-prompt", "keyfork-qrcode", diff --git a/crates/derive/keyfork-derive-openpgp/src/main.rs b/crates/derive/keyfork-derive-openpgp/src/main.rs index 8431b17..60e9d71 100644 --- a/crates/derive/keyfork-derive-openpgp/src/main.rs +++ b/crates/derive/keyfork-derive-openpgp/src/main.rs @@ -78,9 +78,7 @@ fn validate( subkey_format: &str, default_userid: &str, ) -> Result<(DerivationPath, Vec, UserID), Box> { - let mut pgp_u32 = [0u8; 4]; - pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::>()); - let index = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; + let index = DerivationIndex::new(u32::from_be_bytes(*b"\x00pgp"), true)?; let path = DerivationPath::from_str(path)?; assert_eq!(2, path.len(), "Expected path of m/{index}/account_id'"); diff --git a/crates/keyfork-shard/Cargo.toml b/crates/keyfork-shard/Cargo.toml index b9c2cc7..24e5451 100644 --- a/crates/keyfork-shard/Cargo.toml +++ b/crates/keyfork-shard/Cargo.toml @@ -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 = { version = "0.4.0", optional = true } 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 } diff --git a/crates/keyfork-shard/src/lib.rs b/crates/keyfork-shard/src/lib.rs index d930bef..95ece31 100644 --- a/crates/keyfork-shard/src/lib.rs +++ b/crates/keyfork-shard/src/lib.rs @@ -7,6 +7,7 @@ use aes_gcm::{ Aes256Gcm, KeyInit, Nonce, }; use hkdf::Hkdf; +use keyfork_derive_util::{DerivationIndex, DerivationPath}; use keyfork_mnemonic_util::{English, Mnemonic}; use keyfork_prompt::{ validators::{mnemonic::MnemonicSetValidator, Validator}, @@ -60,6 +61,52 @@ pub trait Format { /// A type representing the parsed, but encrypted, Shard data. 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 fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey; diff --git a/crates/keyfork-shard/src/openpgp.rs b/crates/keyfork-shard/src/openpgp.rs index f689959..15e2115 100644 --- a/crates/keyfork-shard/src/openpgp.rs +++ b/crates/keyfork-shard/src/openpgp.rs @@ -8,7 +8,7 @@ use std::{ }; use keyfork_derive_openpgp::{ - derive_util::{DerivationPath, VariableLengthSeed}, + derive_util::{DerivationIndex, DerivationPath, VariableLengthSeed}, XPrv, }; use openpgp::{ @@ -216,6 +216,18 @@ impl Format for OpenPGP { type SigningKey = Cert; 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. fn derive_signing_key(&self, seed: &[u8]) -> Self::SigningKey { let seed = VariableLengthSeed::new(seed);