use std::fs;
use std::path::Path;
use std::sync::Mutex;
use p256::SecretKey;
use p256::ecdsa::{Signature, SigningKey, signature::Signer};
use sha2::{Digest, Sha256};
#[derive(Debug, thiserror::Error)]
pub enum IdentityError {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("bad key material: {0}")]
Key(String),
}
pub use crate::core::tpm_device_present as tpm_available;
pub struct LocalIdentity {
signing_key: SigningKey,
public_key: [u8; 65],
key_id: String,
sign_lock: Mutex<()>,
}
impl LocalIdentity {
pub fn open_or_create(key_path: &Path) -> Result<Self, IdentityError> {
let signing = if key_path.exists() {
let bytes = fs::read(key_path)?;
let arr: [u8; 32] = bytes
.as_slice()
.try_into()
.map_err(|_| IdentityError::Key("unexpected key file length".into()))?;
let secret = SecretKey::from_bytes(&arr.into())
.map_err(|e| IdentityError::Key(e.to_string()))?;
SigningKey::from(secret)
} else {
let secret = SecretKey::random(&mut rand::thread_rng());
let signing = SigningKey::from(secret.clone());
fs::write(key_path, secret.to_bytes())?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
fs::set_permissions(key_path, fs::Permissions::from_mode(0o600))?;
}
signing
};
let verifying = signing.verifying_key();
let encoded = verifying.to_encoded_point(false);
let mut public_key = [0u8; 65];
public_key.copy_from_slice(encoded.as_bytes());
let key_id = hex::encode(Sha256::digest(public_key));
Ok(Self {
signing_key: signing,
public_key,
key_id,
sign_lock: Mutex::new(()),
})
}
pub fn key_id(&self) -> &str {
&self.key_id
}
#[must_use]
pub const fn public_key(&self) -> [u8; 65] {
self.public_key
}
pub fn backed_by_tpm(&self) -> bool {
tpm_available()
}
pub fn sign(&self, data: &[u8]) -> Vec<u8> {
let _g = self
.sign_lock
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let sig: Signature = self.signing_key.sign(data);
sig.to_der().as_bytes().to_vec()
}
}