diff --git a/HitLock/src/main.rs b/HitLock/src/main.rs index 7ec9f01..828e01d 100644 --- a/HitLock/src/main.rs +++ b/HitLock/src/main.rs @@ -3,7 +3,7 @@ mod users; mod webfinger; use std::{ - fmt::{self, Debug}, + fmt, iter::once, panic, task::{Context, Poll}, diff --git a/NotePoster/src/http.rs b/NotePoster/src/http.rs new file mode 100644 index 0000000..c759458 --- /dev/null +++ b/NotePoster/src/http.rs @@ -0,0 +1,43 @@ +use std::{fmt::Display, path::Path}; + +pub use ureq::http::{HeaderMap, StatusCode}; + +#[derive(Clone, Copy)] +pub enum Method { + GET, + HEAD, + OPTIONS, + TRACE, + PUT, + DELETE, + POST, + PATCH, + CONNECT, +} + +impl Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Method::GET => write!(f, "get"), + Method::HEAD => write!(f, "head"), + Method::OPTIONS => write!(f, "options"), + Method::TRACE => write!(f, "trace"), + Method::PUT => write!(f, "put"), + Method::DELETE => write!(f, "delete"), + Method::POST => write!(f, "post"), + Method::PATCH => write!(f, "patch"), + Method::CONNECT => write!(f, "connect"), + } + } +} + +pub struct Target<'a> { + pub host: &'a str, + pub path: &'a Path, +} + +pub struct HttpSignature { + pub signature: String, + pub digest: String, + pub date: String, +} diff --git a/NotePoster/src/key.rs b/NotePoster/src/key.rs new file mode 100644 index 0000000..08b73cf --- /dev/null +++ b/NotePoster/src/key.rs @@ -0,0 +1,44 @@ +use std::{fs, io, path::PathBuf}; + +use base64::prelude::*; +use openssl::{ + hash::MessageDigest, + pkey::{PKey, Private, Public}, + sign::Signer, +}; + +pub struct Key { + pub(crate) privkey: PKey, + pub(crate) pubkey: PKey, + pub(crate) key_id: String, +} + +impl Key { + pub fn from_files( + privkey_file: PathBuf, + pubkey_file: PathBuf, + key_id: String, + ) -> Result { + let privkey_file = fs::read(privkey_file)?; + let pubkey_file = fs::read(pubkey_file)?; + + Ok(Self::from_bytes(&privkey_file, &pubkey_file, key_id)) + } + + pub fn from_bytes(privkey: &[u8], pubkey: &[u8], key_id: String) -> Self { + let privkey = PKey::private_key_from_pem(privkey).expect("Failed decoding private key pem"); + let pubkey = PKey::public_key_from_pem(pubkey).expect("Failed decoding public key pem"); + + Self { + privkey, + pubkey, + key_id, + } + } + + pub fn sign(&self, bytes: &[u8]) -> String { + let mut signer = Signer::new(MessageDigest::sha256(), &self.privkey).unwrap(); + signer.update(bytes).unwrap(); + BASE64_STANDARD.encode(signer.sign_to_vec().unwrap()) + } +} diff --git a/NotePoster/src/lib.rs b/NotePoster/src/lib.rs new file mode 100644 index 0000000..ded3165 --- /dev/null +++ b/NotePoster/src/lib.rs @@ -0,0 +1,90 @@ +use std::time::SystemTime; + +use base64::prelude::*; +use http::{HeaderMap, HttpSignature, Method, StatusCode, Target}; +use httpdate::fmt_http_date; +use key::Key; +use log::debug; +use sha2::Digest; + +pub mod http; +pub mod key; + +fn sha256sum(bytes: &[u8]) -> String { + let mut hasher = sha2::Sha256::new(); + + hasher.update(bytes); + + return BASE64_STANDARD.encode(hasher.finalize()); +} + +pub fn sign_request( + key: Key, + Target { host, path }: Target, + method: Method, + body: &[u8], +) -> HttpSignature { + let date = fmt_http_date(SystemTime::now()); + + let digest = format!("SHA-256={}", sha256sum(body)); + debug!("digest: {}", digest); + + let mut headers = String::new(); + headers.push_str(format!("(request-target): {} {}\n", method, path.display()).as_str()); + headers.push_str(format!("host: {}\n", host).as_str()); + headers.push_str(format!("date: {}\n", date).as_str()); + headers.push_str(format!("digest: {}", digest).as_str()); + + debug!("signed_string: {:?}", headers); + + let signature = key.sign(headers.as_bytes()); + + let header = format!( + "keyId=\"{}\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest\",signature=\"{}\"", + key.key_id, signature + ); + + debug!("header: {}", header); + + HttpSignature { + signature: header, + digest, + date, + } +} + +pub fn signed_post( + key: Key, + target @ Target { host, path }: Target, + mut extra_headers: HeaderMap, + body: &[u8], +) -> (StatusCode, Vec) { + let HttpSignature { + signature, + digest, + date, + } = sign_request(key, target, Method::POST, body); + + let url = format!("https://{}{}", host, path.display()); + + let mut req = ureq::post(url); + { + let headers = req.headers_mut().unwrap(); + headers.extend(extra_headers.drain()); + } + let response = req + .header("Signature", signature) + .header("host", host) + .header("date", date) + .header("digest", digest) + .send(body) + .expect("Failed to send request"); + + ( + response.status(), + response + .into_body() + .read_to_vec() + .expect("Failed to read the response as bytes"), + ) +} diff --git a/NotePoster/src/main.rs b/NotePoster/src/main.rs index e249e46..bb679bd 100644 --- a/NotePoster/src/main.rs +++ b/NotePoster/src/main.rs @@ -1,86 +1,37 @@ -use base64::prelude::*; -use httpdate::fmt_http_date; -use openssl::hash::MessageDigest; -use openssl::pkey::PKey; -use openssl::sign::Signer; -use serde::{Deserialize, Serialize}; -use sha2::Digest; -use std::time::SystemTime; - -#[derive(Serialize, Deserialize)] -struct CreateActivity { - street: String, - city: String, -} - -fn sha256sum(bytes: &[u8]) -> String { - let mut hasher = sha2::Sha256::new(); - - hasher.update(bytes); - - return BASE64_STANDARD.encode(hasher.finalize()); -} +use log::info; +use noteposter::{http::Target, key::Key, signed_post}; +use std::path::Path; +use ureq::http::{HeaderMap, HeaderValue, header::CONTENT_TYPE}; fn main() { let host = "estradiol.city"; - let path = "/users/ity/inbox"; - - let url = format!("https://{}{}", host, path); + let path = Path::new("/users/ity/inbox"); let key_id = "https://testing4host.com/users/test71/#main-key"; - let json = include_bytes!("note.json"); - - let digest = format!("SHA-256={}", sha256sum(json)); - println!("digest: {}", digest); - - let date = fmt_http_date(SystemTime::now()); - - let mut headers = String::new(); - headers.push_str(format!("(request-target): post {}\n", path).as_str()); - headers.push_str(format!("host: {}\n", host).as_str()); - headers.push_str(format!("date: {}\n", date).as_str()); - headers.push_str(format!("digest: {}", digest).as_str()); - - println!("signed_string: {:?}", headers); - - let privkey_file = include_str!("private.pem"); - let pubkey_file = include_str!("public.pem"); - - let privkey = - PKey::private_key_from_pem(privkey_file.as_bytes()).expect("Failed decoding private.pem"); - let _pubkey = - PKey::public_key_from_pem(pubkey_file.as_bytes()).expect("Failed decoding public.pem"); - - let mut signer = Signer::new(MessageDigest::sha256(), &privkey).unwrap(); - signer.update(headers.as_bytes()).unwrap(); - let signature = BASE64_STANDARD.encode(signer.sign_to_vec().unwrap()); - - let header = format!( - "keyId=\"{}\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest\",signature=\"{}\"", - key_id, signature + let key = Key::from_files( + "private.pem".into(), + "public.pem".into(), + key_id.to_string(), + ) + .expect("Failed to read key files"); + + let (_status, res) = signed_post( + key, + Target { host, path }, + { + let mut headers = HeaderMap::new(); + headers.insert( + CONTENT_TYPE, + HeaderValue::from_static("application/activity+json"), + ); + headers + }, + include_bytes!("note.json"), ); - println!("header: {}", header); - - let response = ureq::post(url) - .header("Content-Type", "application/activity+json") - .header("Signature", header) - .header("host", host) - .header("date", date) - .header("digest", digest) - .send(json); - - match response { - Err(v) => println!("{:#?}", v), - Ok(v) => { - println!("Status: {}", v.status()); - println!( - "Response: {}", - v.into_body() - .read_to_string() - .expect("Invalid response: decode error") - ); - } - } + info!( + "Response: {}", + String::from_utf8(res).expect("Invalid response: decode error") + ); }