parent
f153a8b9c7
commit
52241b91bb
@ -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"),
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in new issue