Skip to content

Commit

Permalink
feat: 🐛 Complete signing feature and start working on verifying.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jisu-Woniu committed Nov 24, 2023
1 parent fc38e7b commit 7bca000
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 59 deletions.
27 changes: 18 additions & 9 deletions src-tauri/crypto/src/from_file.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{io::Read, path::Path};
use std::{
io::{BufReader, Cursor, Read, Seek},
path::Path,
};

use async_trait::async_trait;
use pgp::{
Expand All @@ -8,29 +11,35 @@ use pgp::{
};
use tokio::{
fs::File,
io::{AsyncRead, BufReader},
io::{AsyncRead, AsyncSeek},
};
use tokio_util::io::SyncIoBridge;

use crate::Result;

#[async_trait]
pub(crate) trait FromFile: Sized {
fn try_from_reader(reader: impl Read + Send + Unpin) -> Result<Self>;
fn try_from_async_reader(async_reader: impl AsyncRead + Send + Unpin) -> Result<Self> {
fn try_from_reader(reader: impl Read + Seek + Send + Unpin) -> Result<Self>;
fn try_from_async_reader(
async_reader: impl AsyncRead + Send + Unpin + AsyncSeek,
) -> Result<Self> {
Self::try_from_reader(SyncIoBridge::new(async_reader))
}
async fn try_from_file(path: impl AsRef<Path> + Send) -> Result<Self> {
let file = File::open(path).await?;
Ok(Self::try_from_async_reader(BufReader::new(file))?)
let file = File::open(path).await?.into_std().await;
Ok(Self::try_from_reader(BufReader::new(file))?)
}

fn try_from_armored_bytes(bytes: impl AsRef<[u8]> + Send + Unpin) -> Result<Self> {
Self::try_from_reader(Cursor::new(bytes))
}
}

macro_rules! impl_from_file {
($type:ty) => {
impl FromFile for $type {
fn try_from_reader(reader: impl Read + Send + Unpin) -> Result<Self> {
Ok(Self::from_bytes(reader)?)
fn try_from_reader(reader: impl Read + Seek + Send + Unpin) -> Result<Self> {
Ok(Self::from_armor_single(reader)?.0)
}
}
};
Expand All @@ -40,7 +49,7 @@ impl_from_file!(SignedSecretKey);
impl_from_file!(SignedPublicKey);

impl FromFile for Signature {
fn try_from_reader(reader: impl Read + Send + Unpin) -> Result<Self> {
fn try_from_reader(reader: impl Read + Send + Unpin + Seek) -> Result<Self> {
let signature = PacketParser::new(reader)
.find_map(|packet| {
if let Ok(Packet::Signature(s)) = packet {
Expand Down
9 changes: 5 additions & 4 deletions src-tauri/crypto/src/keygen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ where

#[cfg(test)]
mod tests {
use pgp::{types::KeyTrait, Deserializable, SignedSecretKey};

use crate::{key_pair::KeyPair, Result};
use pgp::{types::KeyTrait, SignedSecretKey};

use crate::{from_file::FromFile, key_pair::KeyPair, Result};

#[test]
fn test() -> Result<()> {
Expand All @@ -88,9 +89,9 @@ mod tests {
fn extract_key_info() -> Result<()> {
let secret_key_str = KeyPair::generate("example", "[email protected]", String::new)?
.secret_key()
.to_armored_string(None)?;
.to_armored_bytes(None)?;

let secret_key = SignedSecretKey::from_string(&secret_key_str)?.0;
let secret_key = SignedSecretKey::try_from_armored_bytes(secret_key_str)?;
dbg!(&secret_key);
let key_id = secret_key.key_id();
dbg!(&key_id);
Expand Down
99 changes: 69 additions & 30 deletions src-tauri/crypto/src/signing.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
//! Utilities for signing files.
use std::path::{Path, PathBuf};
use std::{
io::{self, Read},
path::{Path, PathBuf},
};

use chrono::Utc;
use pgp::{
packet::{self, SignatureConfigBuilder, SignatureType, Subpacket, SubpacketData},
types::{PublicKeyTrait, SecretKeyTrait},
Signature, SignedSecretKey,
types::SecretKeyTrait,
Signature, SignedPublicKey, SignedSecretKey,
};
use tokio::fs::{read, File};
use tokio::fs::File;

use crate::{from_file::FromFile, Result};

/// Generate a signature of the given data.
fn sign(data: &[u8], secret_key: &impl SecretKeyTrait) -> Result<Signature> {
fn sign(
data: impl Read,
secret_key: &impl SecretKeyTrait,
passwd_fn: impl FnOnce() -> String + Clone,
) -> Result<Signature> {
let now = Utc::now();
let sig_conf = SignatureConfigBuilder::default()
.pub_alg(secret_key.algorithm())
Expand All @@ -26,12 +33,7 @@ fn sign(data: &[u8], secret_key: &impl SecretKeyTrait) -> Result<Signature> {
])
.unhashed_subpackets(vec![])
.build()?;
Ok(sig_conf.sign(secret_key, String::new, data)?)
}
#[allow(dead_code)]
/// Verify a signature of the given data.
fn verify(data: &[u8], public_key: &impl PublicKeyTrait, signature: &Signature) -> Result<()> {
Ok(signature.verify(public_key, data)?)
Ok(sig_conf.sign(secret_key, passwd_fn, data)?)
}

/// Sign the given file with the given secret key.
Expand All @@ -42,13 +44,19 @@ fn verify(data: &[u8], public_key: &impl PublicKeyTrait, signature: &Signature)
/// - The file or secret key cannot be read.
/// - Secret key is invalid.
/// - Failed to write to signature file.
pub async fn sign_file_with_key(
file_path: impl AsRef<Path>,
secret_key_path: impl AsRef<Path>,
) -> Result<PathBuf> {
let file_data = read(&file_path).await?;
let secret_key = SignedSecretKey::try_from_file(secret_key_path.as_ref()).await?;
let signature = sign(&file_data, &secret_key)?;
pub async fn sign_file_with_key<F, S, PF>(
file_path: F,
secret_key_path: S,
passwd_fn: PF,
) -> Result<PathBuf>
where
F: AsRef<Path>,
S: AsRef<Path> + Send,
PF: FnOnce() -> String + Clone,
{
let file = File::open(&file_path).await?;
let secret_key = SignedSecretKey::try_from_file(secret_key_path).await?;
let signature = sign(file.into_std().await, &secret_key, passwd_fn)?;
let mut signature_path = file_path.as_ref().to_owned();
signature_path.as_mut_os_string().push(".sig");
let mut signature_file = File::options()
Expand All @@ -62,14 +70,43 @@ pub async fn sign_file_with_key(
Ok(signature_path)
}

/// .
///
/// # Errors
///
/// This function will return an error if .
pub async fn verify_file_with_key<S, P>(signature_path: S, public_key_path: P) -> Result<bool>
where
S: AsRef<Path> + Send,
P: AsRef<Path> + Send,
{
let signature_path = signature_path.as_ref();
let signature = Signature::try_from_file(signature_path).await?;
let public_key = SignedPublicKey::try_from_file(public_key_path).await?;
if let Some(file_path) = signature_path
.extension()
.is_some_and(|ext| ext == "sig")
.then(|| signature_path.with_extension(""))
{
Ok(signature
.verify(&public_key, File::open(file_path).await?.into_std().await)
.is_ok())
} else {
Err(io::Error::new(
io::ErrorKind::NotFound,
"Original file not found.",
))?
}
}

#[cfg(test)]
mod tests {

use pgp::{Signature, SignedPublicKey};
use temp_dir::TempDir;
use tokio::fs::write;

use super::{sign, sign_file_with_key, verify};
use super::{sign, sign_file_with_key};
use crate::{
from_file::FromFile,
key_pair::KeyPair,
Expand All @@ -81,25 +118,27 @@ mod tests {
fn test_sign() -> Result<()> {
let key_pair = KeyPair::generate("example", "[email protected]", String::new)?;
let (secret_key, public_key) = (key_pair.secret_key(), key_pair.public_key());
let signature = sign(b"Hello", &secret_key)?;
verify(b"Hello", &public_key, &signature)?;
let signature = sign(&b"Hello"[..], &secret_key, String::new)?;
signature.verify(public_key, &b"Hello"[..])?;
Ok(())
}

#[test]
fn test_sign_error() -> Result<()> {
let key_pair = KeyPair::generate("example", "[email protected]", String::new)?;
let (secret_key, public_key) = (key_pair.secret_key(), key_pair.public_key());
let signature = sign(b"Hello", &secret_key)?;
let signature = sign(&b"Hello"[..], &secret_key, String::new)?;
eprintln!(
"{:?}",
verify(b"Help", &public_key, &signature).expect_err("Should not pass")
signature
.verify(&public_key, &b"Help"[..])
.expect_err("Should not pass")
);
Ok(())
}

#[tokio::test]
async fn test_sign_file() -> Result<()> {
async fn test_whole_process() -> Result<()> {
let tmp_dir = TempDir::new()?;
let KeyPairPaths {
secret_key_path,
Expand All @@ -118,16 +157,16 @@ mod tests {

let data_path = tmp_dir.path().join("data");
write(&data_path, data).await?;

let public_key = SignedPublicKey::try_from_file(&public_key_path).await?;

let sig_path = sign_file_with_key(&data_path, secret_key_path).await?;

dbg!(&data_path);
let sig_path = sign_file_with_key(&data_path, &secret_key_path, String::new).await?;
dbg!(&sig_path);
let signature = Signature::try_from_file(sig_path).await?;
let public_key = SignedPublicKey::try_from_file(&public_key_path).await?;
dbg!(&public_key);

dbg!(&signature);

let verified = verify(data, &public_key, &signature).is_ok();
let verified = signature.verify(&public_key, &data[..]).is_ok(); //verify(data, &public_key, &signature).is_ok();

dbg!(verified);

Expand Down
8 changes: 6 additions & 2 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@

// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command

use crate::{fs::detect_file_type, keygen::generate_key_pair, sign::sign_files};
use crate::{
fs::detect_file_type, keygen::generate_key_pair, sign::sign_files, verify::verify_signatures,
};

mod error;
mod fs;
mod keygen;
mod sign;
mod verify;

fn main() -> anyhow::Result<()> {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
generate_key_pair,
detect_file_type,
sign_files
sign_files,
verify_signatures
])
.run(tauri::generate_context!())?;
Ok(())
Expand Down
16 changes: 9 additions & 7 deletions src-tauri/src/sign.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
use std::path::{Path, PathBuf};

use digital_signature_crypto::signing::sign_file_with_key;
use futures::{future::join_all, TryFutureExt};
use futures::future::try_join_all;

use crate::error::Result;

#[tauri::command]
pub async fn sign_files(file_paths: Vec<&Path>, key_path: &Path) -> Result<Vec<PathBuf>> {
join_all(
pub async fn sign_files(
file_paths: Vec<&Path>,
key_path: &Path,
passwd: &str,
) -> Result<Vec<PathBuf>> {
Ok(try_join_all(
file_paths
.into_iter()
.map(|file_path| sign_file_with_key(file_path, key_path).err_into()),
.map(|file_path| sign_file_with_key(file_path, key_path, || String::from(passwd))),
)
.await
.into_iter()
.collect::<Result<_>>()
.await?)
}
16 changes: 16 additions & 0 deletions src-tauri/src/verify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::{convert::identity, path::Path};

use digital_signature_crypto::signing::verify_file_with_key;
use futures::future::try_join_all;

use crate::error::Result;
#[tauri::command]
pub async fn verify_signatures(sig_paths: Vec<&Path>, public_key_path: &Path) -> Result<bool> {
let result = try_join_all(
sig_paths
.into_iter()
.map(|sig_path| verify_file_with_key(sig_path, public_key_path)),
)
.await?;
Ok(result.into_iter().all(identity))
}
6 changes: 6 additions & 0 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ export const enum FileType {

export const detectFileType = (path: string) =>
invoke<FileType>("detect_file_type", { path });

export const signFiles = (
filePaths: string[],
keyPath: string,
passwd: string,
) => invoke<string[]>("sign_files", { filePaths, keyPath, passwd });
Loading

0 comments on commit 7bca000

Please sign in to comment.