Refactor piv-p256-specific stanza unwrapping onto RecipientLine
This commit is contained in:
+36
-7
@@ -1,7 +1,7 @@
|
|||||||
use age_core::{
|
use age_core::{
|
||||||
format::{FileKey, Stanza},
|
format::{FileKey, Stanza, FILE_KEY_BYTES},
|
||||||
primitives::aead_encrypt,
|
primitives::{aead_decrypt, aead_encrypt, hkdf},
|
||||||
secrecy::ExposeSecret,
|
secrecy::{zeroize::Zeroize, ExposeSecret},
|
||||||
};
|
};
|
||||||
use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine};
|
use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine};
|
||||||
use p256::{
|
use p256::{
|
||||||
@@ -11,8 +11,9 @@ use p256::{
|
|||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
|
||||||
use crate::{p256::Recipient, STANZA_TAG};
|
use crate::{key::Connection, p256::Recipient};
|
||||||
|
|
||||||
|
const STANZA_TAG: &str = "piv-p256";
|
||||||
pub(crate) const STANZA_KEY_LABEL: &[u8] = b"piv-p256";
|
pub(crate) const STANZA_KEY_LABEL: &[u8] = b"piv-p256";
|
||||||
|
|
||||||
const TAG_BYTES: usize = 4;
|
const TAG_BYTES: usize = 4;
|
||||||
@@ -117,9 +118,7 @@ impl RecipientLine {
|
|||||||
|
|
||||||
let shared_secret = esk.diffie_hellman(pk.public_key());
|
let shared_secret = esk.diffie_hellman(pk.public_key());
|
||||||
|
|
||||||
let mut salt = vec![];
|
let salt = salt(&epk_bytes, pk);
|
||||||
salt.extend_from_slice(epk_bytes.as_bytes());
|
|
||||||
salt.extend_from_slice(pk.to_encoded().as_bytes());
|
|
||||||
|
|
||||||
let enc_key = {
|
let enc_key = {
|
||||||
let mut okm = [0; 32];
|
let mut okm = [0; 32];
|
||||||
@@ -142,4 +141,34 @@ impl RecipientLine {
|
|||||||
encrypted_file_key,
|
encrypted_file_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result<FileKey, ()> {
|
||||||
|
assert_eq!(self.tag, conn.recipient().tag());
|
||||||
|
|
||||||
|
// The YubiKey API for performing scalar multiplication takes the point in its
|
||||||
|
// uncompressed SEC-1 encoding.
|
||||||
|
let shared_secret = conn.p256_ecdh(self.epk_bytes.decompress().as_bytes())?;
|
||||||
|
|
||||||
|
let salt = salt(&self.epk_bytes, conn.recipient());
|
||||||
|
|
||||||
|
let enc_key = hkdf(&salt, STANZA_KEY_LABEL, shared_secret.as_ref());
|
||||||
|
|
||||||
|
// A failure to decrypt is fatal, because we assume that we won't
|
||||||
|
// encounter 32-bit collisions on the key tag embedded in the header.
|
||||||
|
aead_decrypt(&enc_key, FILE_KEY_BYTES, &self.encrypted_file_key)
|
||||||
|
.map_err(|_| ())
|
||||||
|
.map(|mut pt| {
|
||||||
|
FileKey::init_with_mut(|file_key| {
|
||||||
|
file_key.copy_from_slice(&pt);
|
||||||
|
pt.zeroize();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn salt(epk_bytes: &EphemeralKeyBytes, pk: &Recipient) -> Vec<u8> {
|
||||||
|
let mut salt = vec![];
|
||||||
|
salt.extend_from_slice(epk_bytes.as_bytes());
|
||||||
|
salt.extend_from_slice(pk.to_encoded().as_bytes());
|
||||||
|
salt
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-29
@@ -1,10 +1,6 @@
|
|||||||
//! Structs for handling YubiKeys.
|
//! Structs for handling YubiKeys.
|
||||||
|
|
||||||
use age_core::{
|
use age_core::secrecy::{ExposeSecret, SecretString};
|
||||||
format::{FileKey, FILE_KEY_BYTES},
|
|
||||||
primitives::{aead_decrypt, hkdf},
|
|
||||||
secrecy::{zeroize::Zeroize, ExposeSecret, SecretString},
|
|
||||||
};
|
|
||||||
use age_plugin::{identity, Callbacks};
|
use age_plugin::{identity, Callbacks};
|
||||||
use bech32::{ToBase32, Variant};
|
use bech32::{ToBase32, Variant};
|
||||||
use dialoguer::Password;
|
use dialoguer::Password;
|
||||||
@@ -25,7 +21,7 @@ use yubikey::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
fl,
|
fl,
|
||||||
format::{RecipientLine, STANZA_KEY_LABEL},
|
format::RecipientLine,
|
||||||
p256::{Recipient, TAG_BYTES},
|
p256::{Recipient, TAG_BYTES},
|
||||||
util::{otp_serial_prefix, Metadata},
|
util::{otp_serial_prefix, Metadata},
|
||||||
IDENTITY_PREFIX,
|
IDENTITY_PREFIX,
|
||||||
@@ -623,7 +619,6 @@ impl Stub {
|
|||||||
cert,
|
cert,
|
||||||
pk,
|
pk,
|
||||||
slot: self.slot,
|
slot: self.slot,
|
||||||
tag: self.tag,
|
|
||||||
identity_index: self.identity_index,
|
identity_index: self.identity_index,
|
||||||
cached_metadata: None,
|
cached_metadata: None,
|
||||||
last_touch: None,
|
last_touch: None,
|
||||||
@@ -636,7 +631,6 @@ pub(crate) struct Connection {
|
|||||||
cert: Certificate,
|
cert: Certificate,
|
||||||
pk: Recipient,
|
pk: Recipient,
|
||||||
slot: RetiredSlotId,
|
slot: RetiredSlotId,
|
||||||
tag: [u8; 4],
|
|
||||||
identity_index: usize,
|
identity_index: usize,
|
||||||
cached_metadata: Option<Metadata>,
|
cached_metadata: Option<Metadata>,
|
||||||
last_touch: Option<Instant>,
|
last_touch: Option<Instant>,
|
||||||
@@ -705,8 +699,10 @@ impl Connection {
|
|||||||
Ok(Ok(()))
|
Ok(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unwrap_file_key(&mut self, line: &RecipientLine) -> Result<FileKey, ()> {
|
pub(crate) fn p256_ecdh(&mut self, epk_bytes: &[u8]) -> Result<yubikey::Buffer, ()> {
|
||||||
assert_eq!(self.tag, line.tag);
|
// The YubiKey API for performing scalar multiplication takes the point in its
|
||||||
|
// uncompressed SEC-1 encoding.
|
||||||
|
assert_eq!(epk_bytes.len(), 65);
|
||||||
|
|
||||||
// Check if the touch policy requires a touch.
|
// Check if the touch policy requires a touch.
|
||||||
let needs_touch = match (
|
let needs_touch = match (
|
||||||
@@ -718,11 +714,9 @@ impl Connection {
|
|||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The YubiKey API for performing scalar multiplication takes the point in its
|
|
||||||
// uncompressed SEC-1 encoding.
|
|
||||||
let shared_secret = match decrypt_data(
|
let shared_secret = match decrypt_data(
|
||||||
&mut self.yubikey,
|
&mut self.yubikey,
|
||||||
line.epk_bytes.decompress().as_bytes(),
|
epk_bytes,
|
||||||
AlgorithmId::EccP256,
|
AlgorithmId::EccP256,
|
||||||
SlotId::Retired(self.slot),
|
SlotId::Retired(self.slot),
|
||||||
) {
|
) {
|
||||||
@@ -739,22 +733,7 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut salt = vec![];
|
Ok(shared_secret)
|
||||||
salt.extend_from_slice(line.epk_bytes.as_bytes());
|
|
||||||
salt.extend_from_slice(self.pk.to_encoded().as_bytes());
|
|
||||||
|
|
||||||
let enc_key = hkdf(&salt, STANZA_KEY_LABEL, shared_secret.as_ref());
|
|
||||||
|
|
||||||
// A failure to decrypt is fatal, because we assume that we won't
|
|
||||||
// encounter 32-bit collisions on the key tag embedded in the header.
|
|
||||||
aead_decrypt(&enc_key, FILE_KEY_BYTES, &line.encrypted_file_key)
|
|
||||||
.map_err(|_| ())
|
|
||||||
.map(|mut pt| {
|
|
||||||
FileKey::init_with_mut(|file_key| {
|
|
||||||
file_key.copy_from_slice(&pt);
|
|
||||||
pt.zeroize();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close this connection without resetting the YubiKey.
|
/// Close this connection without resetting the YubiKey.
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ const PLUGIN_NAME: &str = "yubikey";
|
|||||||
const BINARY_NAME: &str = "age-plugin-yubikey";
|
const BINARY_NAME: &str = "age-plugin-yubikey";
|
||||||
const RECIPIENT_PREFIX: &str = "age1yubikey";
|
const RECIPIENT_PREFIX: &str = "age1yubikey";
|
||||||
const IDENTITY_PREFIX: &str = "age-plugin-yubikey-";
|
const IDENTITY_PREFIX: &str = "age-plugin-yubikey-";
|
||||||
const STANZA_TAG: &str = "piv-p256";
|
|
||||||
|
|
||||||
const USABLE_SLOTS: [RetiredSlotId; 20] = [
|
const USABLE_SLOTS: [RetiredSlotId; 20] = [
|
||||||
RetiredSlotId::R1,
|
RetiredSlotId::R1,
|
||||||
|
|||||||
+1
-1
@@ -252,7 +252,7 @@ impl IdentityPluginV1 for IdentityPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (stanza_index, line) in stanzas.iter().enumerate() {
|
for (stanza_index, line) in stanzas.iter().enumerate() {
|
||||||
match conn.unwrap_file_key(line) {
|
match line.unwrap_file_key(&mut conn) {
|
||||||
Ok(file_key) => {
|
Ok(file_key) => {
|
||||||
// We've managed to decrypt this file!
|
// We've managed to decrypt this file!
|
||||||
file_keys.entry(file_index).or_insert(Ok(file_key));
|
file_keys.entry(file_index).or_insert(Ok(file_key));
|
||||||
|
|||||||
Reference in New Issue
Block a user