diff --git a/aws-s3/authorization.ml b/aws-s3/authorization.ml index d6845d7..6bc8ce7 100644 --- a/aws-s3/authorization.ml +++ b/aws-s3/authorization.ml @@ -103,6 +103,55 @@ let make_auth_header ~credentials ~scope ~signed_headers ~signature = signed_headers signature +let make_presigned_url ?(scheme=`Https) ~credentials ~date ~region ~path ~bucket ~verb ~duration () = + let service = "s3" in + let ((y, m, d), ((h, mi, s), _)) = Ptime.to_date_time date in + let verb = match verb with + | `Get -> "GET" + | `Put -> "PUT" in + let scheme = match scheme with + | `Http -> "http" + | `Https -> "https" in + let date = sprintf "%02d%02d%02d" y m d in + let time = sprintf "%02d%02d%02d" h mi s in + let host = sprintf "%s.s3.amazonaws.com" bucket in + let duration = string_of_int duration in + let region = Region.to_string region in + let headers = Headers.singleton "Host" host in + let query = [ + ("X-Amz-Algorithm", "AWS4-HMAC-SHA256"); + ("X-Amz-Credential", sprintf "%s/%s/%s/s3/aws4_request" credentials.Credentials.access_key date region); + ("X-Amz-Date", sprintf "%sT%sZ" date time); + ("X-Amz-Expires", duration); + ("X-Amz-SignedHeaders", "host"); + ] in + let scope = make_scope ~date ~region ~service in + let signing_key = make_signing_key ~date ~region ~service ~credentials () in + let signature, _signed_headers = + make_signature ~date ~time ~verb ~path ~headers ~query ~signing_key ~scope ~payload_sha:"UNSIGNED-PAYLOAD" + in + let query = + ("X-Amz-Signature", signature) :: query + |> List.map ~f:(fun (k, v) -> (k, [v])) + in + Uri.make ~scheme ~host ~path ~query () + +let%test "presigned_url" = + let credentials = Credentials.make + ~access_key:"AKIAIOSFODNN7EXAMPLE" + ~secret_key:"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + () + in + let date = match Ptime.of_date (2013, 5, 24) with Some x -> x | _ -> Ptime.epoch in + let region = Region.of_string "us-east-1" in + let path = "/test.txt" in + let bucket = "examplebucket" in + let verb = `Get in + let duration = 86400 in + let expected = "https://examplebucket.s3.amazonaws.com/test.txt?X-Amz-Signature=aeeed9bbccd4d02ee5c0109b86d86835f995330da4c265957d157751f604d404&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host" in + let actual = make_presigned_url ~credentials ~date ~region ~path ~bucket ~verb ~duration () |> Uri.to_string in + expected = actual + let%test "signing key" = let credentials = Credentials.make ~access_key:"AKIAIOSFODNN7EXAMPLE" diff --git a/aws-s3/authorization.mli b/aws-s3/authorization.mli index 9e24e2c..a3df2fe 100644 --- a/aws-s3/authorization.mli +++ b/aws-s3/authorization.mli @@ -48,3 +48,16 @@ val chunk_signature: previous_signature:string -> sha:Digestif.SHA256.t -> Digestif.SHA256.t (**/**) + + (** This makes a presigned url that can be used to upload or download a file from s3 without any credentials other than those embedded in the url. [verb] should be either the string GET for download or PUT for upload.*) +val make_presigned_url : + ?scheme:[`Http | `Https] -> + credentials:Credentials.t -> + date:Ptime.t -> + region:Region.t -> + path:string -> + bucket:string -> + verb:[`Get | `Put] -> + duration:int -> + unit -> + Uri.t diff --git a/aws-s3/aws_s3.ml b/aws-s3/aws_s3.ml index 4ddb052..e209250 100644 --- a/aws-s3/aws_s3.ml +++ b/aws-s3/aws_s3.ml @@ -2,3 +2,4 @@ module S3 = S3 module Types = Types module Credentials = Credentials module Region = Region +module Authorization = Authorization