#amazon-s3 #s3 #aws #signature #web

s3v4

Library for signing requests and URLs using AWS' S3 version 4 protocol

2 releases

0.3.5 Jun 17, 2023
0.3.4 Jun 5, 2022

#190 in HTTP client

45 downloads per month

BSD-3-Clause

24KB
324 lines

s3v4: A crate for signing S3 requests and pre-signing URLs

reference

This crate provides a signature function that can be used to sign a request to an S3 endpoint and a pre_signed_url function for generating a presigned URL.

Both functions return an Error generated by the error_chain crate which can be converted to a String or accessed through the description method or display_chain and backtrace methods in case a full backtrace is needed.

Examples showing how to upload and download data with signed headers or pre-signed URLs are provided in the ./examples folder.

Examples

Signing a request

   let signature: s3v4::Signature = s3v4::signature(
       url,
       method,
       &access,
       &secret,
       &region,
       &"s3",
       "UNSIGNED-PAYLOAD", //payload hash, or "UNSIGNED-PAYLOAD"
   ).map_err(|err| format!("Signature error: {}", err.display_chain()))?;

Using the signature data to make a request

Hyper

   let req = Request::builder()
       .method(Method::PUT)
       .header("x-amz-content-sha256", "UNSIGNED-PAYLOAD")
       .header("x-amz-date", &signature.date_time)
       .header("authorization", &signature.auth_header)

Ureq

   let agent = AgentBuilder::new().build();
   let response = agent
       .put(&uri)
       .set("x-amz-content-sha256", "UNSIGNED-PAYLOAD")
       .set("x-amz-date", &signature.date_time)
       .set("authorization", &signature.auth_header)

Generting a pre-signed URL

    let pre_signed_url = s3v4::pre_signed_url(
        &access,
        &secret,
        expiration,
        &url,
        &method,
        &payload_hash,
        &region,
        &date_time,
        &service,
    )
    .map_err(|err| format!("{:?}", err))?;

The following code can be used as is to generate a presigned URL.

use url;
fn main() -> Result<(), String> {
    let url =
        url::Url::parse(&std::env::args().nth(1).expect("missing url")).expect("malformed URL");
    let access = std::env::args().nth(2).expect("missing access");
    let secret = std::env::args().nth(3).expect("missing secret");
    let method = std::env::args().nth(4).expect("missing method");
    let expiration = std::env::args()
        .nth(5)
        .expect("missing expiration (seconds)")
        .parse::<u64>()
        .expect("wrong expiration format");
    let region = std::env::args().nth(6).expect("missing region");
    let service = std::env::args().nth(7).expect("missing service");
    let date_time: chrono::DateTime<chrono::Utc> = match std::env::args().nth(8) {
        Some(d) => chrono::DateTime::parse_from_rfc3339(&d)
            .expect("Invalid date format (should be \"YYYY-MM-DDTHH:MM:SSZ)\"")
            .into(),
        None => chrono::Utc::now(),
    };
    let payload_hash = "UNSIGNED-PAYLOAD";
    let pre_signed_url = s3v4::pre_signed_url(
        &access,
        &secret,
        expiration,
        &url,
        &method,
        &payload_hash,
        &region,
        &date_time,
        &service,
    )
    .map_err(|err| format!("{:?}", err))?;
    println!("{}", pre_signed_url);
    Ok(())
}

Run with

cargo run --example presign -- <endpoint URL> <access> <secret> <method> \
   <expiration in seconds> <region> ["YYYY-MM-DDTHH:MM:SSZ" (timestamp)]

To send the request just use curl with

  • -I for HEAD requests
  • --file-upload for PUT requests
  • nothing for GET requests

Dependencies

~5.5–7MB
~161K SLoC