use std::{
fmt::Debug,
fs::{self, OpenOptions},
io::{BufReader, Write},
path::{Path, PathBuf},
str,
time::{SystemTime, UNIX_EPOCH},
};
use base64::{engine::general_purpose::STANDARD, Engine};
use serde::{Deserialize, Serialize};
use crate::{
util::{self, PathExt},
Error,
};
#[derive(Clone, Debug)]
pub struct KeyPair {
pub pk: String,
pub sk: String,
}
#[tracing::instrument(level = "trace")]
pub fn generate_key(password: Option<String>) -> crate::Result<KeyPair> {
let minisign::KeyPair { pk, sk } = minisign::KeyPair::generate_encrypted_keypair(password)?;
let pk_box_str = pk.to_box()?.to_string();
let sk_box_str = sk.to_box(None)?.to_string();
let encoded_pk = base64::engine::general_purpose::STANDARD.encode(pk_box_str);
let encoded_sk = base64::engine::general_purpose::STANDARD.encode(sk_box_str);
Ok(KeyPair {
pk: encoded_pk,
sk: encoded_sk,
})
}
fn decode_base64(base64_key: &str) -> crate::Result<String> {
let decoded_str = &base64::engine::general_purpose::STANDARD.decode(base64_key)?[..];
Ok(String::from(str::from_utf8(decoded_str)?))
}
#[tracing::instrument(level = "trace")]
pub fn decode_private_key(
private_key: &str,
password: Option<&str>,
) -> crate::Result<minisign::SecretKey> {
let decoded_secret = decode_base64(private_key)?;
let sk_box = minisign::SecretKeyBox::from_string(&decoded_secret)?;
let sk = sk_box.into_secret_key(password.map(Into::into))?;
Ok(sk)
}
#[tracing::instrument(level = "trace")]
pub fn save_keypair<P: AsRef<Path> + Debug>(
keypair: &KeyPair,
path: P,
force: bool,
) -> crate::Result<(PathBuf, PathBuf)> {
let path = path.as_ref();
let pubkey_path = format!("{}.pub", path.display());
let pk_path = Path::new(&pubkey_path);
if path.exists() {
if !force {
return Err(Error::SigningKeyExists(path.to_path_buf()));
} else {
fs::remove_file(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;
}
}
if pk_path.exists() {
fs::remove_file(pk_path).map_err(|e| Error::IoWithPath(pk_path.to_path_buf(), e))?;
}
let mut sk_writer = util::create_file(path)?;
write!(sk_writer, "{}", keypair.sk)?;
sk_writer.flush()?;
let mut pk_writer = util::create_file(pk_path)?;
write!(pk_writer, "{}", keypair.pk)?;
pk_writer.flush()?;
Ok((
dunce::canonicalize(path).map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?,
dunce::canonicalize(pk_path).map_err(|e| Error::IoWithPath(pk_path.to_path_buf(), e))?,
))
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[non_exhaustive]
pub struct SigningConfig {
pub private_key: String,
pub password: Option<String>,
}
impl SigningConfig {
pub fn new() -> Self {
Self::default()
}
pub fn private_key<S: Into<String>>(mut self, private_key: S) -> Self {
self.private_key = private_key.into();
self
}
pub fn password<S: Into<String>>(mut self, password: S) -> Self {
self.password.replace(password.into());
self
}
}
#[tracing::instrument(level = "trace")]
pub fn sign_file<P: AsRef<Path> + Debug>(
config: &SigningConfig,
path: P,
) -> crate::Result<PathBuf> {
let secret_key = decode_private_key(&config.private_key, config.password.as_deref())?;
sign_file_with_secret_key(&secret_key, path)
}
#[tracing::instrument(level = "trace")]
pub fn sign_file_with_secret_key<P: AsRef<Path> + Debug>(
secret_key: &minisign::SecretKey,
path: P,
) -> crate::Result<PathBuf> {
let path = path.as_ref();
let signature_path = path.with_additional_extension("sig");
let signature_path = dunce::simplified(&signature_path);
let mut signature_box_writer = util::create_file(signature_path)?;
let start = SystemTime::now();
let since_epoch = start.duration_since(UNIX_EPOCH)?.as_secs();
let trusted_comment = format!(
"timestamp:{}\tfile:{}",
since_epoch,
path.file_name()
.ok_or_else(|| crate::Error::FailedToExtractFilename(path.to_path_buf()))?
.to_string_lossy()
);
let file = OpenOptions::new()
.read(true)
.open(path)
.map_err(|e| Error::IoWithPath(path.to_path_buf(), e))?;
let file_reader = BufReader::new(file);
let signature_box = minisign::sign(
None,
secret_key,
file_reader,
Some(trusted_comment.as_str()),
Some("signature from cargo-packager secret key"),
)?;
let encoded_signature = STANDARD.encode(signature_box.to_string());
signature_box_writer.write_all(encoded_signature.as_bytes())?;
signature_box_writer.flush()?;
dunce::canonicalize(signature_path).map_err(|e| crate::Error::IoWithPath(path.to_path_buf(), e))
}