Я пытаюсь сделать авторизацию AWS версии 4, подписанную GET-запросом к S3, и получаю ошибку неверного запроса 400 Code:InvalidRequest Message:Missing required header
для этого запроса: x-amz-content-sha256
Если я добавляю заголовок к "Authorization: "
, я получаю ошибку Code:InvalidArgument Message:Unsupported Authorization Type <ArgumentName>Authorization</ArgumentName> <ArgumentValue>Authorization: AWS4-HMAC-SHA256 Credential=XXXXXXXXXXXXXXXXXXX/20200408/eu-west-3/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=vdchzint97uwyt3g%2fjehszrc8zpkbjsx4tfqacsqfow%3d</ArgumentValue>
Я использую Delphi XE5 с компонентом Indy's TIdHTTP
. Может кто-нибудь сказать мне, что я делаю не так? Я включил свой код ниже.
begin
bucket := 'mybucket.ata-test';
obj := 'test.xml';
region := 'eu-west-3';
service := 's3';
aws := 'amazonaws.com';
YYYYMMDD := FormatDateTime('yyyymmdd', now);
amzDate := FormatDateTime('yyyymmdd"T"hhnnss"Z"', TTimeZone.Local.ToUniversalTime(Now), TFormatSettings.Create('en-US'));
emptyHash := lowercase(SHA256HashAsHex(''));
host := Format('%s.%s.%s.%s', [bucket, service, region, aws]);
url := Format('%s://%s.%s.%s.%s/%s', ['https', bucket, service, region, aws, obj]);
// *** 1. Build the Canonical Request for Signature Version 4 ***
// HTTPRequestMethod
CanonicalRequest := URLEncodeValue('GET') +#10;
// CanonicalURI
CanonicalRequest := CanonicalRequest + '/' + URLEncodeValue(obj) +#10;
// CanonicalQueryString (empty just a newline)
CanonicalRequest := CanonicalRequest +#10;
// CanonicalHeaders
CanonicalRequest := CanonicalRequest + 'host:' + Trim(host) +#10
+ 'x-amz-content-sha256:' + emptyHash +#10
+ 'x-amz-date:' + Trim(amzDate) +#10;
// SignedHeaders
CanonicalRequest := CanonicalRequest + 'host;x-amz-content-sha256;x-amz-date' +#10;
// HexEncode(Hash(RequestPayload)) - (hash of an empty string)
CanonicalRequest := CanonicalRequest + emptyHash;
// *** 2. Create a String to Sign for Signature Version 4 ***
StringToSign := 'AWS4-HMAC-SHA256' +#10
+ amzDate +#10
+ UTF8String(YYYYMMDD) +'/'+ UTF8String(region) +'/'+ UTF8String(service) +UTF8String('/aws4_request') +#10
+ lowercase(SHA256HashAsHex(CanonicalRequest));
// *** 3. Calculate the Signature for AWS Signature Version 4 ***
DateKey := CalculateHMACSHA256(YYYYMMDD, 'AWS4' + SecretAccessKey);
DateRegionKey := CalculateHMACSHA256(region, DateKey);
DateRegionServiceKey := CalculateHMACSHA256(service, DateRegionKey);
SigningKey := CalculateHMACSHA256('aws4_request', DateRegionServiceKey);
Signature := lowercase(UrlEncodeValue(CalculateHMACSHA256(StringToSign, SigningKey)));
// *** 4. Create Authorisation Header and Add the Signature to the HTTP Request ***
AuthorisationHeader := 'AWS4-HMAC-SHA256 Credential='+AccessIdKey+'/'+YYYYMMDD+'/'+region+'/'+service+'/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature='+signature;
// (Gives <Code>InvalidRequest</Code> <Message>Missing required header for this request: x-amz-content-sha256</Message>)
// Have also tried
// AuthorisationHeader := 'Authorization: AWS4-HMAC-SHA256 Credential='+AccessIdKey+'/'+YYYYMMDD+'/'+region+'/'+service+'/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature='+signature;
// (Gives <Code>InvalidArgument</Code> <Message>Unsupported Authorization Type</Message>)
// *** 5. Add Header and Make Request ***
stm := TMemoryStream.Create;
try
try
Idhttp.Request.CustomHeaders.FoldLines := False;
Idhttp.Request.CustomHeaders.AddValue('Authorization', AuthorisationHeader);
Idhttp.Get(URL, stm);
except
on PE: EIdHTTPProtocolException do begin
s := PE.ErrorMessage;
Raise;
end;
on E: Exception do begin
s := E.Message;
Raise;
end;
end;
stm.Position := 0;
Memo1.Lines.LoadFromStream(stm);
finally
FreeAndNil(stm);
end;
end;
function SHA256HashAsHex(const value: string): String;
/// used for stringtosign
var
sha: TIdHashSHA256;
begin
LoadOpenSSLLibrary;
if not TIdHashSHA256.IsAvailable then
raise Exception.Create('SHA256 hashing is not available!');
sha := TIdHashSHA256.Create;
try
result := sha.HashStringAsHex(value, nil);
finally
sha.Free;
end;
end;
function CalculateHMACSHA256(const value, salt: String): String;
/// used for signingkey
var
hmac: TIdHMACSHA256;
hash: TIdBytes;
begin
LoadOpenSSLLibrary;
if not TIdHashSHA256.IsAvailable then
raise Exception.Create('SHA256 hashing is not available!');
hmac := TIdHMACSHA256.Create;
try
hmac.Key := IndyTextEncoding_UTF8.GetBytes(salt);
hash := hmac.HashValue(IndyTextEncoding_UTF8.GetBytes(value));
Result := EncodeBytes64(TArray<Byte>(hash));
finally
hmac.Free;
end;
end;