123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197(* Auth based on aws papers
https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
*)openStdLabelsletsprintf=Printf.sprintfletdebug=falseletlogfmt=matchdebugwith|true->Printf.kfprintf(fun_->())stderr("%s: "^^fmt^^"\n%!")__MODULE__|false->Printf.ikfprintf(fun_->())stderrfmtlethash_sha256s=Digestif.SHA256.digest_stringslethmac_sha256~keyv=Digestif.SHA256.hmac_string~keyvletto_rawsha256=Digestif.SHA256.to_raw_stringsha256letto_hexstr=Digestif.SHA256.to_hexstrletmake_signing_key=letcache=Hashtbl.create0infun?(bypass_cache=false)~date~region~credentials~service()->matchHashtbl.find_optcache(credentials.Credentials.access_key,region)with|Some(d,signing_key)whend=date&¬bypass_cache->signing_key|Some_|None->letdate_key=hmac_sha256~key:("AWS4"^credentials.Credentials.secret_key)dateinletdate_region_key=hmac_sha256~key:(to_rawdate_key)regioninletdate_region_service_key=hmac_sha256~key:(to_rawdate_region_key)serviceinletsigning_key=hmac_sha256~key:(to_rawdate_region_service_key)"aws4_request"inHashtbl.replacecache(credentials.Credentials.access_key,region)(date,signing_key);signing_keyletmake_scope~date~region~service=sprintf"%s/%s/%s/aws4_request"dateregionserviceletstring_to_sign~date~time~verb~path~query~headers~payload_sha~scope=letquery=List.sort~cmp:(funab->String.compare(fsta)(fstb))queryinassert(Headers.cardinalheaders>0);(* Count sizes of headers *)let(key_size,value_size)=Headers.fold(funkeydata(h,v)->(h+String.lengthkey,v+String.lengthdata))headers(0,0)inletheader_count=Headers.cardinalheadersinletcanonical_headers=Buffer.create(key_size+value_size+(2(*:\n*)*header_count))inletsigned_headers=Buffer.create(key_size+(Headers.cardinalheaders-1))inletfirst=reftrueinHeaders.iter(funkeydata->letlower_header=String.lowercase_asciikeyinif(not!first)thenBuffer.add_stringsigned_headers";";Buffer.add_stringsigned_headerslower_header;Buffer.add_stringcanonical_headerslower_header;Buffer.add_stringcanonical_headers":";Buffer.add_stringcanonical_headersdata;Buffer.add_stringcanonical_headers"\n";first:=false;)headers;(* Strip the trailing from signed_headers *)letsigned_headers=Buffer.contentssigned_headersinletcanonical_query=query|>List.map~f:(fun(k,v)->sprintf"%s=%s"(Uri.pct_encode~component:`Userinfok)(Uri.pct_encode~component:`Userinfov))|>String.concat~sep:"&"inletcanonical_request=sprintf"%s\n%s\n%s\n%s\n%s\n%s"verb(Util.encode_stringpath)canonical_query(Buffer.contentscanonical_headers)signed_headerspayload_shainlog"Canonical request:\n%s\n"canonical_request;(* This could be cached. Its more or less static *)letstring_to_sign=sprintf"AWS4-HMAC-SHA256\n%sT%sZ\n%s\n%s"datetimescope(hash_sha256canonical_request|>to_hex)inlog"String to sign:\n%s\n"string_to_sign;log"Signed headers:\n%s\n"signed_headers;(string_to_sign,signed_headers)letmake_signature~date~time~verb~path~headers~query~scope~(signing_key:Digestif.SHA256.t)~payload_sha=let(string_to_sign,signed_headers)=string_to_sign~date~time~verb~path~query~headers~payload_sha~scopein(hmac_sha256~key:(to_rawsigning_key)string_to_sign|>to_hex,signed_headers)letmake_auth_header~credentials~scope~signed_headers~signature=sprintf"AWS4-HMAC-SHA256 Credential=%s/%s,SignedHeaders=%s,Signature=%s"credentials.Credentials.access_keyscopesigned_headerssignaturelet%test"signing key"=letcredentials=Credentials.make~access_key:"AKIAIOSFODNN7EXAMPLE"~secret_key:"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"()inletdate="20120215"inletregion="us-east-1"inletservice="iam"inletsigning_key=make_signing_key~bypass_cache:true~date~region~service~credentials()|>to_hexinletexpected="f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d"insigning_key=expectedlet%test"auth header"=letcredentials=Credentials.make~access_key:"AKIAIOSFODNN7EXAMPLE"~secret_key:"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"()inletdate="20130524"inletregion="us-east-1"inletpath="/test.txt"inletservice="s3"inletheaders=[("Host","examplebucket.s3.amazonaws.com");("Range","bytes=0-9");("x-amz-content-sha256","e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");("x-amz-date","20130524T000000Z")]|>List.fold_left~f:(funacc(key,value)->Headers.add~key~valueacc)~init:Headers.emptyinletverb="GET"inletquery=[]inletpayload_sha=hash_sha256""|>to_hexinletscope=make_scope~date~region~serviceinletsigning_key=make_signing_key~date~region~service~credentials()inletsignature,signed_headers=make_signature~date~time:"000000"~verb~path~headers~query~signing_key~scope~payload_shainletauth=make_auth_header~credentials~signature~scope~signed_headersinletexpected="AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,SignedHeaders=host;range;x-amz-content-sha256;x-amz-date,Signature=f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41"inauth=expectedletempty_sha_hex=hash_sha256""|>to_hexletchunk_signature~(signing_key:Digestif.SHA256.t)~date~time~scope~previous_signature~sha=let_initial="STREAMING-AWS4-HMAC-SHA256-PAYLOAD"inletstring_to_sign=sprintf"AWS4-HMAC-SHA256-PAYLOAD\n%sT%sZ\n%s\n%s\n%s\n%s"datetimescopeprevious_signatureempty_sha_hex(to_hexsha)inhmac_sha256~key:(to_rawsigning_key)string_to_signlet%test"chunk_signature"=letcredentials=Credentials.make~access_key:"AKIAIOSFODNN7EXAMPLE"~secret_key:"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"()inletprevious_signature="4f232c4386841ef735655705268965c44a0e4690baa4adea153f7db9fa80a0a9"inletdate="20130524"inlettime="000000"inletscope="20130524/us-east-1/s3/aws4_request"inletsigning_key=make_signing_key~bypass_cache:true~date~region:"us-east-1"~service:"s3"~credentials()inletsha=String.make65536'a'|>hash_sha256inletsignature=chunk_signature~signing_key~date~time~scope~previous_signature~shainletexpect="ad80c730a21e5b8d04586a2213dd63b9a0e99e0e2307b0ade35a65485a288648"insignature|>to_hex=expect