Compare commits

..

7 Commits

8 changed files with 47 additions and 32 deletions

View File

@ -234,7 +234,7 @@ impl Client {
} }
let depth = path.len() as u8; let depth = path.len() as u8;
Ok(ExtendedPrivateKey::new_from_parts( Ok(ExtendedPrivateKey::from_parts(
&d.data, &d.data,
depth, depth,
d.chain_code, d.chain_code,

View File

@ -59,8 +59,12 @@ pub enum Error {
#[allow(missing_docs)] #[allow(missing_docs)]
pub type Result<T, E = Error> = std::result::Result<T, E>; pub type Result<T, E = Error> = std::result::Result<T, E>;
/// Create an OpenPGP Cert with derived keys from the given derivation response, keys, and User /// Create an OpenPGP Cert with private key data, with derived keys from the given derivation
/// ID. /// response, keys, and User ID.
///
/// Certificates are created with a default expiration of one day, but may be configured to expire
/// later using the `KEYFORK_OPENPGP_EXPIRE` environment variable using values such as "15d" (15
/// days), "1m" (one month), or "2y" (two years).
/// ///
/// # Errors /// # Errors
/// The function may error for any condition mentioned in [`Error`]. /// The function may error for any condition mentioned in [`Error`].
@ -109,7 +113,7 @@ pub fn derive(xprv: XPrv, keys: &[KeyFlags], userid: &UserID) -> Result<Cert> {
let cert = cert.insert_packets(vec![Packet::from(userid.clone()), binding.into()])?; let cert = cert.insert_packets(vec![Packet::from(userid.clone()), binding.into()])?;
let policy = sequoia_openpgp::policy::StandardPolicy::new(); let policy = sequoia_openpgp::policy::StandardPolicy::new();
// Set certificate expiration to one day // Set certificate expiration to configured expiration or (default) one day
let mut keypair = primary_key.clone().into_keypair()?; let mut keypair = primary_key.clone().into_keypair()?;
let signatures = let signatures =
cert.set_expiration_time(&policy, None, &mut keypair, Some(expiration_date))?; cert.set_expiration_time(&policy, None, &mut keypair, Some(expiration_date))?;

View File

@ -124,9 +124,9 @@ mod serde_with {
K: PrivateKey + Clone, K: PrivateKey + Clone,
{ {
let variable_len_bytes = <&[u8]>::deserialize(deserializer)?; let variable_len_bytes = <&[u8]>::deserialize(deserializer)?;
let bytes: [u8; 32] = variable_len_bytes let bytes: [u8; 32] = variable_len_bytes.try_into().expect(bug!(
.try_into() "unable to parse serialized private key; no support for static len"
.expect(bug!("unable to parse serialized private key; no support for static len")); ));
Ok(K::from_bytes(&bytes)) Ok(K::from_bytes(&bytes))
} }
} }
@ -179,13 +179,20 @@ where
.into_bytes(); .into_bytes();
let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8); let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8);
Self::new_from_parts( assert!(
!private_key.iter().all(|byte| *byte == 0),
bug!("hmac function returned all-zero master key")
);
Self::from_parts(
private_key private_key
.try_into() .try_into()
.expect(bug!("KEY_SIZE / 8 did not give a 32 byte slice")), .expect(bug!("KEY_SIZE / 8 did not give a 32 byte slice")),
0, 0,
// Checked: chain_code is always the same length, hash is static size // Checked: chain_code is always the same length, hash is static size
chain_code.try_into().expect(bug!("Invalid chain code length")), chain_code
.try_into()
.expect(bug!("Invalid chain code length")),
) )
} }
@ -205,9 +212,9 @@ where
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code); /// let xprv = ExtendedPrivateKey::<PrivateKey>::from_parts(key, 4, *chain_code);
/// ``` /// ```
pub fn new_from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Self { pub fn from_parts(key: &[u8; 32], depth: u8, chain_code: [u8; 32]) -> Self {
Self { Self {
private_key: K::from_bytes(key), private_key: K::from_bytes(key),
depth, depth,
@ -229,7 +236,7 @@ where
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code); /// let xprv = ExtendedPrivateKey::<PrivateKey>::from_parts(key, 4, *chain_code);
/// assert_eq!(xprv.private_key(), &PrivateKey::from_bytes(key)); /// assert_eq!(xprv.private_key(), &PrivateKey::from_bytes(key));
/// ``` /// ```
pub fn private_key(&self) -> &K { pub fn private_key(&self) -> &K {
@ -262,7 +269,7 @@ where
/// # } /// # }
/// ``` /// ```
pub fn extended_public_key(&self) -> ExtendedPublicKey<K::PublicKey> { pub fn extended_public_key(&self) -> ExtendedPublicKey<K::PublicKey> {
ExtendedPublicKey::new_from_parts(self.public_key(), self.depth, self.chain_code) ExtendedPublicKey::from_parts(self.public_key(), self.depth, self.chain_code)
} }
/// Return a public key for the current [`PrivateKey`]. /// Return a public key for the current [`PrivateKey`].
@ -301,7 +308,7 @@ where
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code); /// let xprv = ExtendedPrivateKey::<PrivateKey>::from_parts(key, 4, *chain_code);
/// assert_eq!(xprv.depth(), 4); /// assert_eq!(xprv.depth(), 4);
/// ``` /// ```
pub fn depth(&self) -> u8 { pub fn depth(&self) -> u8 {
@ -321,7 +328,7 @@ where
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let xprv = ExtendedPrivateKey::<PrivateKey>::new_from_parts(key, 4, *chain_code); /// let xprv = ExtendedPrivateKey::<PrivateKey>::from_parts(key, 4, *chain_code);
/// assert_eq!(chain_code, &xprv.chain_code()); /// assert_eq!(chain_code, &xprv.chain_code());
/// ``` /// ```
pub fn chain_code(&self) -> [u8; 32] { pub fn chain_code(&self) -> [u8; 32] {

View File

@ -60,11 +60,11 @@ where
/// let chain_code: &[u8; 32] = // /// let chain_code: &[u8; 32] = //
/// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// let pubkey = PublicKey::from_bytes(key); /// let pubkey = PublicKey::from_bytes(key);
/// let xpub = ExtendedPublicKey::<PublicKey>::new_from_parts(pubkey, 0, *chain_code); /// let xpub = ExtendedPublicKey::<PublicKey>::from_parts(pubkey, 0, *chain_code);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn new_from_parts(public_key: K, depth: u8, chain_code: ChainCode) -> Self { pub fn from_parts(public_key: K, depth: u8, chain_code: ChainCode) -> Self {
Self { Self {
public_key, public_key,
depth, depth,
@ -86,7 +86,7 @@ where
/// # let chain_code: &[u8; 32] = b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # let chain_code: &[u8; 32] = b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// # let pubkey = PublicKey::from_bytes(key); /// # let pubkey = PublicKey::from_bytes(key);
/// let xpub = // /// let xpub = //
/// # ExtendedPublicKey::<PublicKey>::new_from_parts(pubkey, 0, *chain_code); /// # ExtendedPublicKey::<PublicKey>::from_parts(pubkey, 0, *chain_code);
/// let pubkey = xpub.public_key(); /// let pubkey = xpub.public_key();
/// # Ok(()) /// # Ok(())
/// # } /// # }
@ -121,7 +121,7 @@ where
/// # let chain_code: &[u8; 32] = b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; /// # let chain_code: &[u8; 32] = b"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
/// # let pubkey = PublicKey::from_bytes(key); /// # let pubkey = PublicKey::from_bytes(key);
/// let xpub = // /// let xpub = //
/// # ExtendedPublicKey::<PublicKey>::new_from_parts(pubkey, 0, *chain_code); /// # ExtendedPublicKey::<PublicKey>::from_parts(pubkey, 0, *chain_code);
/// let index = DerivationIndex::new(0, false)?; /// let index = DerivationIndex::new(0, false)?;
/// let child = xpub.derive_child(&index)?; /// let child = xpub.derive_child(&index)?;
/// # Ok(()) /// # Ok(())

View File

@ -300,7 +300,7 @@ mod secp256k1 {
fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> { fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> {
match value.algorithm { match value.algorithm {
DerivationAlgorithm::Secp256k1 => Ok(Self::new_from_parts( DerivationAlgorithm::Secp256k1 => Ok(Self::from_parts(
&value.data, &value.data,
value.depth, value.depth,
value.chain_code, value.chain_code,
@ -335,7 +335,7 @@ mod ed25519 {
fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> { fn try_from(value: &DerivationResponse) -> Result<Self, Self::Error> {
match value.algorithm { match value.algorithm {
DerivationAlgorithm::Ed25519 => Ok(Self::new_from_parts( DerivationAlgorithm::Ed25519 => Ok(Self::from_parts(
&value.data, &value.data,
value.depth, value.depth,
value.chain_code, value.chain_code,

View File

@ -248,10 +248,9 @@ pub trait Format {
// create our shared key // create our shared key
let our_key = EphemeralSecret::random(); let our_key = EphemeralSecret::random();
let our_pubkey_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?; let our_pubkey_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?;
let shared_secret = our_key let shared_secret = our_key.diffie_hellman(&PublicKey::from(their_pubkey));
.diffie_hellman(&PublicKey::from(their_pubkey)) assert!(shared_secret.was_contributory(), bug!("shared secret might be insecure"));
.to_bytes(); let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret);
let mut shared_key_data = [0u8; 256 / 8]; let mut shared_key_data = [0u8; 256 / 8];
hkdf.expand(b"key", &mut shared_key_data)?; hkdf.expand(b"key", &mut shared_key_data)?;
@ -515,8 +514,9 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
bug!("invalid payload data") bug!("invalid payload data")
); );
let shared_secret = our_key.diffie_hellman(&PublicKey::from(pubkey)).to_bytes(); let shared_secret = our_key.diffie_hellman(&PublicKey::from(pubkey));
let hkdf = Hkdf::<Sha256>::new(None, &shared_secret); assert!(shared_secret.was_contributory(), bug!("shared secret might be insecure"));
let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut shared_key_data = [0u8; 256 / 8]; let mut shared_key_data = [0u8; 256 / 8];
hkdf.expand(b"key", &mut shared_key_data)?; hkdf.expand(b"key", &mut shared_key_data)?;

View File

@ -20,8 +20,12 @@ pub enum DeriveSubcommands {
/// Derive an OpenPGP Transferable Secret Key (private key). The key is encoded using OpenPGP /// Derive an OpenPGP Transferable Secret Key (private key). The key is encoded using OpenPGP
/// ASCII Armor, a format usable by most programs using OpenPGP. /// ASCII Armor, a format usable by most programs using OpenPGP.
/// ///
/// The key is generated with a 24-hour expiration time. The operation to set the expiration /// Certificates are created with a default expiration of one day, but may be configured to
/// time to a higher value is left to the user to ensure the key is usable by the user. /// expire later using the `KEYFORK_OPENPGP_EXPIRE` environment variable using values such as
/// "15d" (15 days), "1m" (one month), or "2y" (two years).
///
/// It is recommended to use the default expiration of one day and to change the expiration
/// using an external utility, to ensure the Certify key is usable.
#[command(name = "openpgp")] #[command(name = "openpgp")]
OpenPGP { OpenPGP {
/// Default User ID for the certificate, using the OpenPGP User ID format. /// Default User ID for the certificate, using the OpenPGP User ID format.

View File

@ -38,7 +38,7 @@ fn derive_key(seed: [u8; 32], index: u8) -> Result<Cert> {
let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
let mut shrd_u32 = [0u8; 4]; let mut shrd_u32 = [0u8; 4];
shrd_u32[..].copy_from_slice(&"shrd".bytes().collect::<Vec<u8>>()); shrd_u32[..].copy_from_slice(&"shrd".bytes().collect::<Vec<u8>>());
let account = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?; let account = DerivationIndex::new(u32::from_be_bytes(shrd_u32), true)?;
let subkey = DerivationIndex::new(u32::from(index), true)?; let subkey = DerivationIndex::new(u32::from(index), true)?;
let path = DerivationPath::default() let path = DerivationPath::default()
.chain_push(chain) .chain_push(chain)
@ -132,8 +132,8 @@ fn generate_shard_secret(
for i in 0..keys_per_shard { for i in 0..keys_per_shard {
pm.prompt_message(Message::Text(format!( pm.prompt_message(Message::Text(format!(
"Please remove all keys and insert key #{} for user #{}", "Please remove all keys and insert key #{} for user #{}",
i + 1, (i as u16) + 1,
index + 1, (index as u16) + 1,
)))?; )))?;
let card_backend = loop { let card_backend = loop {
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? { if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {