I am trying to implement something like the Authenticating Requests: Using the Authorization Header (AWS Signature Version 4) or Signing HTTP Messages draft.
Basically what I want to do is to add several headers to my HTTP request, where one of them is a digest of the request's body. Then I can construct a string that sums that digest header a nonce and some other headers and cryptographically sign that sum.
Having a digest for JSON might be problematic, since the orders of keys in an object might not be guaranteed, and the white space can be added and removed. So I wish to sign the body, and not the JSON I want to add to that body.
This is however difficult when using Reqwest, not only that body is a private to RequestBuilder
, and it has a write only interface for it, the Body object also does not expose it's content.
So I ended up adding a trait to the RequestBuilder
:
pub trait JsonDigest {
fn json_digest<T: Serialize + ?Sized>(
self,
json: &T,
algorithm: &'static digest::Algorithm,
) -> Self;
}
impl JsonDigest for RequestBuilder {
/// Sets the body to the JSON serialization of the passed value,
/// and also sets the `Content-Type: application/json` header, and
/// add a digest header encoded as hex.
///
/// # Errors
///
/// Serialization can fail if `T`'s implementation of `Serialize`
/// decides to fail, or if `T` contains a map with non-string
/// keys.
fn json_digest<T: Serialize + ?Sized>(
self,
json: &T,
algorithm: &'static digest::Algorithm,
) -> Self {
match serde_json::to_vec(json) {
Ok(json_bytes) => self
.header(
HeaderName::from_static("digest"),
hex::encode(
digest::digest(algorithm, json_bytes.as_ref()).as_ref(),
)
.as_bytes(),
)
.header(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
)
.body(json_bytes),
// HACK: Since json serialization failed, the following
// should also fail in the same manner, and when it
// does it will change self.request to be an
// Err(...). Note that since that is a private
// member we can't access it directly.
Err(_) => self.json(json),
}
}
}
On the response side I also encountered unforeseen difficulties, since Responce.json()
, Responce.text()
, and Responce.copy_to()
are all consume the requests content. So I can't easily both verify the digest and parse the JSON.
So either I misunderstand something fundamental about the Reqwest crate, or I am using the wrong crate for my task. In both cases I would appreciate any advice.