Key and http signing abstractions for noteposter, signed_post request function

pull/3/head
Avery 5 days ago
parent f153a8b9c7
commit 52241b91bb
Signed by untrusted user: Avery
GPG Key ID: 4E53F4CB69B2CC8D

@ -3,7 +3,7 @@ mod users;
mod webfinger;
use std::{
fmt::{self, Debug},
fmt,
iter::once,
panic,
task::{Context, Poll},

@ -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,
}

@ -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<Private>,
pub(crate) pubkey: PKey<Public>,
pub(crate) key_id: String,
}
impl Key {
pub fn from_files(
privkey_file: PathBuf,
pubkey_file: PathBuf,
key_id: String,
) -> Result<Self, io::Error> {
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())
}
}

@ -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<u8>) {
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"),
)
}

@ -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")
);
}

Loading…
Cancel
Save