Huakun Shen 99b940b03b
File Transfer (Local Network) (#34)
* feat: implement a file streamer for file share

Only server with hardcoded file path

* bump valibot version

* feat: add server-info gRPC module to serve server info

* feat: add ssl cert and public key to peers state

When peer is online, KK always have know its cert and pub key for future secure communication

* feat: add grpc ts package

* Enable "/refresh-worker-extension" rest API, grpc version isn't ready  yet

* update pnpm lock

* ci: fix CI by moving protobuf install order

* ci: fix

* upgrade api due to valibot incompatibility

* fix: use fs instead of bun shell to be compatible with windows

* skip grpc pkg build on windows

* feat: local network file transfer prototype working

* fix: grpc build.ts

* download next to 14

* ci: add ci env try to fix next

* fix: hideRefreshBtn and a few other btns' hide API in iframe ext page

* feat: disable NODE_TLS_REJECT_UNAUTHORIZED for extension HMR refresh

* fix: manifest json schema with objectWithRest to allow any other fields in package.json

* chore: update valibot and related dependencies to version 1.0.0-beta.9 in pnpm-lock.yaml

* ci: add protobuf compiler installation to manifest-schema-upload workflow

* refactor: move grpc code from jarvis to a separate grpc crate

for easier testing

* feat(file-transfer): POC multi file + directory file transfer

* feat(file-transfer): replace file transfer recursive download in ts with rust

* feat(file-transfer): implement on_progress event for file transfer

* feat(file-transfer): report progress every 1MB instead of 100 iterations

* feat(file-transfer): add progress bar

* feat(file-transfer): UI

* feat(file-transfer): add file transfer bucket info preview

Show total size and number of files

* feat(file-transfer): improve UX

Show bucket info during confirm; improve progress bar UI, prevent inconsistent width

* feat(grpc): skip build in Cloudflare Pages due to missing protoc

* refactor: with cargo fix, unused imports removed

* ci: debug cloudflare pages env var

* fix(grpc): update environment variable access for Cloudflare Pages build check

* fix(grpc): add error handling for protoc command in build script

* chore: update kkrpc version to 0.0.13, remove kkrpc submodule, and enhance grpc build script logging

- Updated kkrpc dependency version from 0.0.12 to 0.0.13 in package.json.
- Removed the kkrpc submodule from the project.
- Enhanced logging in the grpc build script to include additional Cloudflare Pages environment variables for better debugging.

* fix(api): typescript error, remove base.json from tsconfig

* chore: update pnpm lock

* fix(api): update TypeScript configuration to extend base.json and clean up unused options

* refactor(api): update TypeScript configuration to extend path-alias.json and enhance compiler options

* fix(api): restore KunkunManifestPermission in PermissionUnion and update valibot import in schema tests

* fix: missing trait error

* fix: js require replaced with import

* test: fix a unit test with a more robust method

---------

Co-authored-by: Huakun Shen <huaukun.shen@huakunshen.com>
2024-12-11 08:14:40 -05:00

138 lines
4.9 KiB
Rust

use openssl::{
hash::MessageDigest,
pkey::{PKey, Private, Public},
rsa::Rsa,
sign::{Signer, Verifier},
};
use crate::types::Signature;
pub struct RsaCrypto {}
impl RsaCrypto {
pub fn generate_rsa() -> anyhow::Result<Rsa<Private>> {
Rsa::generate(2048).map_err(anyhow::Error::from)
}
pub fn private_key_to_public_key(private_key: &Rsa<Private>) -> Rsa<Public> {
let public_key_pem = private_key
.public_key_to_pem()
.expect("Failed to convert private key to public key");
RsaCrypto::public_key_from_pem(&public_key_pem)
.expect("Failed to convert pem to public key")
}
pub fn generate_rsa_key_pair_pem() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
let rsa = Rsa::generate(2048)?;
let private_pem = rsa.private_key_to_pem()?;
let public_pem = rsa.public_key_to_pem()?;
Ok((private_pem, public_pem))
}
pub fn private_key_from_pem(pem: &[u8]) -> anyhow::Result<Rsa<Private>> {
Rsa::private_key_from_pem(pem).map_err(anyhow::Error::from)
}
pub fn public_key_from_pem(pem: &[u8]) -> anyhow::Result<Rsa<Public>> {
Rsa::public_key_from_pem(pem).map_err(anyhow::Error::from)
}
pub fn public_key_to_string(public_key: &Rsa<Public>) -> String {
String::from_utf8(
public_key
.public_key_to_pem()
.expect("Failed to convert public key to pem"),
)
.expect("Failed to convert public key pem to string")
}
pub fn encrypt_message(public_key: &Rsa<Public>, message: &[u8]) -> anyhow::Result<Vec<u8>> {
let mut encrypted = vec![0; public_key.size() as usize];
public_key.public_encrypt(message, &mut encrypted, openssl::rsa::Padding::PKCS1)?;
Ok(encrypted)
}
pub fn decrypt_message(
private_key: &Rsa<Private>,
encrypted: &[u8],
) -> anyhow::Result<Vec<u8>> {
let mut decrypted = vec![0; private_key.size() as usize];
private_key.private_decrypt(encrypted, &mut decrypted, openssl::rsa::Padding::PKCS1)?;
Ok(decrypted)
}
pub fn sign(private_key: &Rsa<Private>, message: &[u8]) -> anyhow::Result<Vec<u8>> {
let pkey = PKey::from_rsa(private_key.clone())?;
let mut signer = Signer::new(MessageDigest::sha256(), &pkey)?;
signer.update(message)?;
let signature = signer.sign_to_vec()?;
Ok(signature)
}
pub fn verify(
public_key: &Rsa<Public>,
message: &[u8],
signature: &[u8],
) -> anyhow::Result<bool> {
let pkey = PKey::from_rsa(public_key.clone())?;
let mut verifier = Verifier::new(MessageDigest::sha256(), &pkey)?;
verifier.update(message)?;
Ok(verifier.verify(&signature)?)
}
}
impl Signature for RsaCrypto {
fn sign_with_pem(private_pem: &[u8], message: &[u8]) -> anyhow::Result<Vec<u8>> {
let private_key = RsaCrypto::private_key_from_pem(private_pem)?;
RsaCrypto::sign(&private_key, message)
}
fn verify_with_pem(
public_pem: &[u8],
message: &[u8],
signature: &[u8],
) -> anyhow::Result<bool> {
let public_key = RsaCrypto::public_key_from_pem(public_pem)?;
RsaCrypto::verify(&public_key, message, signature)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt_decrypt() {
let rsa = RsaCrypto::generate_rsa().unwrap();
let public_key = rsa.public_key_to_pem().unwrap();
let private_key = rsa.private_key_to_pem().unwrap();
let public_rsa = RsaCrypto::public_key_from_pem(&public_key).unwrap();
let private_rsa = RsaCrypto::private_key_from_pem(&private_key).unwrap();
let encrypted = RsaCrypto::encrypt_message(&public_rsa, b"hello world").unwrap();
let decrypted = RsaCrypto::decrypt_message(&private_rsa, &encrypted).unwrap();
assert_eq!(b"hello world", &decrypted[..11]);
}
#[test]
fn test_rsa_sign_verify() {
let (private_pem, public_pem) = RsaCrypto::generate_rsa_key_pair_pem().unwrap();
let message = b"hello world";
let private_key = RsaCrypto::private_key_from_pem(&private_pem).unwrap();
let public_key = RsaCrypto::public_key_from_pem(&public_pem).unwrap();
let signature = RsaCrypto::sign(&private_key, message).unwrap();
let verified = RsaCrypto::verify(&public_key, message, &signature).unwrap();
assert!(verified);
assert!(!RsaCrypto::verify(&public_key, b"hello world2", &signature).unwrap());
}
#[test]
fn test_rsa_sign_verify_with_pem() {
let (private_pem, public_pem) = RsaCrypto::generate_rsa_key_pair_pem().unwrap();
let message = b"hello world";
let signature = RsaCrypto::sign_with_pem(&private_pem, message).unwrap();
let verified = RsaCrypto::verify_with_pem(&public_pem, message, &signature).unwrap();
assert!(verified);
}
}