use std::io::Cursor;
use std::path::Path;
use pgp::composed::{
CleartextSignedMessage, DetachedSignature, MessageBuilder, SignedSecretKey, SignedSecretSubKey,
};
use pgp::crypto::hash::HashAlgorithm;
use pgp::packet::SignatureType;
use pgp::types::{KeyDetails, Password, PublicParams};
use rand::thread_rng;
use crate::error::{Error, Result};
use crate::internal::{
can_details_sign, is_key_expired, parse_secret_key, validate_signing_usage, SigningKeyUsage,
};
fn select_hash_for_params(params: &PublicParams) -> HashAlgorithm {
match params {
PublicParams::ECDSA(ecdsa) => {
use pgp::types::EcdsaPublicParams;
match ecdsa {
EcdsaPublicParams::P256 { .. } => HashAlgorithm::Sha256,
EcdsaPublicParams::P384 { .. } => HashAlgorithm::Sha384,
EcdsaPublicParams::P521 { .. } => HashAlgorithm::Sha512,
_ => HashAlgorithm::Sha256,
}
}
PublicParams::EdDSALegacy(_) | PublicParams::Ed25519(_) => HashAlgorithm::Sha256,
PublicParams::Ed448(_) => HashAlgorithm::Sha512,
PublicParams::RSA(_) => HashAlgorithm::Sha256,
_ => HashAlgorithm::Sha256,
}
}
fn find_signing_subkey(secret_key: &SignedSecretKey) -> Option<&SignedSecretSubKey> {
let mut best: Option<&SignedSecretSubKey> = None;
for subkey in &secret_key.secret_subkeys {
let has_sign_flag = subkey.signatures.iter().any(|sig| sig.key_flags().sign());
if !has_sign_flag {
continue;
}
let is_revoked = subkey
.signatures
.iter()
.any(|sig| sig.typ() == Some(SignatureType::SubkeyRevocation));
if is_revoked {
continue;
}
let most_recent_sig = subkey
.signatures
.iter()
.filter(|sig| sig.key_expiration_time().is_some())
.max_by_key(|sig| sig.created().map(|t| t.as_secs()).unwrap_or(0));
if let Some(sig) = most_recent_sig {
if let Some(validity) = sig.key_expiration_time() {
let creation_time: std::time::SystemTime = subkey.key.created_at().into();
if is_key_expired(creation_time, Some(validity.as_secs() as u64)) {
continue;
}
}
}
let dominated = match best {
Some(prev) => subkey.key.created_at() > prev.key.created_at(),
None => true,
};
if dominated {
best = Some(subkey);
}
}
best
}
pub fn sign_bytes(secret_cert: &[u8], data: &[u8], password: &str) -> Result<Vec<u8>> {
sign_bytes_internal(secret_cert, data, password, false, false)
}
pub fn sign_bytes_with_primary_key(
secret_cert: &[u8],
data: &[u8],
password: &str,
) -> Result<Vec<u8>> {
sign_bytes_internal(secret_cert, data, password, false, true)
}
pub fn sign_bytes_cleartext(secret_cert: &[u8], data: &[u8], password: &str) -> Result<Vec<u8>> {
sign_bytes_internal(secret_cert, data, password, true, false)
}
pub fn sign_bytes_cleartext_with_primary_key(
secret_cert: &[u8],
data: &[u8],
password: &str,
) -> Result<Vec<u8>> {
sign_bytes_internal(secret_cert, data, password, true, true)
}
pub fn sign_bytes_detached(secret_cert: &[u8], data: &[u8], password: &str) -> Result<String> {
sign_bytes_detached_impl(secret_cert, data, password, false)
}
pub fn sign_bytes_detached_with_primary_key(
secret_cert: &[u8],
data: &[u8],
password: &str,
) -> Result<String> {
sign_bytes_detached_impl(secret_cert, data, password, true)
}
fn sign_bytes_detached_impl(
secret_cert: &[u8],
data: &[u8],
password: &str,
use_primary: bool,
) -> Result<String> {
let secret_key = parse_secret_key(secret_cert)?;
validate_signing_usage(
secret_key.primary_key.created_at().into(),
&secret_key.details,
SigningKeyUsage::DataSignature,
)?;
let password: Password = password.into();
let mut rng = thread_rng();
let signature = if !use_primary {
if let Some(subkey) = find_signing_subkey(&secret_key) {
let hash_alg = select_hash_for_params(subkey.key.public_params());
DetachedSignature::sign_binary_data(
&mut rng,
&subkey.key,
&password,
hash_alg,
Cursor::new(data),
)
.map_err(|e| Error::Crypto(e.to_string()))?
} else if can_details_sign(&secret_key.details) {
let hash_alg = select_hash_for_params(secret_key.primary_key.public_params());
DetachedSignature::sign_binary_data(
&mut rng,
&secret_key.primary_key,
&password,
hash_alg,
Cursor::new(data),
)
.map_err(|e| Error::Crypto(e.to_string()))?
} else {
return Err(Error::NoSigningSubkey);
}
} else {
let hash_alg = select_hash_for_params(secret_key.primary_key.public_params());
DetachedSignature::sign_binary_data(
&mut rng,
&secret_key.primary_key,
&password,
hash_alg,
Cursor::new(data),
)
.map_err(|e| Error::Crypto(e.to_string()))?
};
signature
.to_armored_string(None.into())
.map_err(|e| Error::Crypto(e.to_string()))
}
pub fn sign_file(
secret_cert: &[u8],
input: impl AsRef<Path>,
output: impl AsRef<Path>,
password: &str,
) -> Result<()> {
let data = std::fs::read(input.as_ref())?;
let signed = sign_bytes(secret_cert, &data, password)?;
std::fs::write(output.as_ref(), signed)?;
Ok(())
}
pub fn sign_file_cleartext(
secret_cert: &[u8],
input: impl AsRef<Path>,
output: impl AsRef<Path>,
password: &str,
) -> Result<()> {
let data = std::fs::read(input.as_ref())?;
let signed = sign_bytes_cleartext(secret_cert, &data, password)?;
std::fs::write(output.as_ref(), signed)?;
Ok(())
}
pub fn sign_file_detached(
secret_cert: &[u8],
input: impl AsRef<Path>,
password: &str,
) -> Result<String> {
let data = std::fs::read(input.as_ref())?;
sign_bytes_detached(secret_cert, &data, password)
}
fn sign_bytes_internal(
secret_cert: &[u8],
data: &[u8],
password: &str,
cleartext: bool,
use_primary: bool,
) -> Result<Vec<u8>> {
let secret_key = parse_secret_key(secret_cert)?;
validate_signing_usage(
secret_key.primary_key.created_at().into(),
&secret_key.details,
SigningKeyUsage::DataSignature,
)?;
let password_obj: Password = password.into();
let mut rng = thread_rng();
let signing_subkey = if !use_primary {
find_signing_subkey(&secret_key)
} else {
None
};
let use_subkey = signing_subkey.is_some();
if !use_subkey && !use_primary && !can_details_sign(&secret_key.details) {
return Err(Error::NoSigningSubkey);
}
if cleartext {
let text = String::from_utf8_lossy(data);
let csf = if let Some(subkey) = signing_subkey {
CleartextSignedMessage::sign(&mut rng, &text, &subkey.key, &password_obj)
.map_err(|e| Error::Crypto(e.to_string()))?
} else {
CleartextSignedMessage::sign(&mut rng, &text, &secret_key.primary_key, &password_obj)
.map_err(|e| Error::Crypto(e.to_string()))?
};
csf.to_armored_string(None.into())
.map(|s| s.into_bytes())
.map_err(|e| Error::Crypto(e.to_string()))
} else {
let mut builder = MessageBuilder::from_bytes("", data.to_vec());
if let Some(subkey) = signing_subkey {
let hash_alg = select_hash_for_params(subkey.key.public_params());
builder.sign(&subkey.key, password_obj, hash_alg);
} else {
let hash_alg = select_hash_for_params(secret_key.primary_key.public_params());
builder.sign(&secret_key.primary_key, password_obj, hash_alg);
};
builder
.to_armored_string(&mut rng, None.into())
.map(|s| s.into_bytes())
.map_err(|e| Error::Crypto(e.to_string()))
}
}
#[cfg(test)]
mod tests {
}