add x-amz-content-sha256 to all requests
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Emil Lerch 2022-05-25 14:24:56 -07:00
parent 0e46bb04b2
commit e6d2559b80
Signed by: lobo
GPG Key ID: A7B62D657EF764F8

View File

@ -5,24 +5,32 @@ const date = @import("date.zig");
const log = std.log.scoped(.aws_signing);
// TODO: Remove this?! This is an aws_signing, so we should know a thing
// or two about aws. So perhaps the right level of abstraction here
// is to have our service signing idiosyncracies dealt with in this
// code base. Pretty much all these flags are specific to use with S3
// except omit_session_token, which will likely apply to serveral services,
// just not sure which one yet. I'll leave this here, commented for now
// in case we need to revisit the decision
//
// see https://github.com/awslabs/aws-c-auth/blob/ace1311f8ef6ea890b26dd376031bed2721648eb/include/aws/auth/signing_config.h#L186-L207
pub const ConfigFlags = packed struct {
// We assume the uri will be encoded once in preparation for transmission. Certain services
// do not decode before checking signature, requiring us to actually double-encode the uri in the canonical
// request in order to pass a signature check.
use_double_uri_encode: bool = true,
// Controls whether or not the uri paths should be normalized when building the canonical request
should_normalize_uri_path: bool = true,
// Controls whether "X-Amz-Security-Token" is omitted from the canonical request.
// "X-Amz-Security-Token" is added during signing, as a header or
// query param, when credentials have a session token.
// If false (the default), this parameter is included in the canonical request.
// If true, this parameter is still added, but omitted from the canonical request.
omit_session_token: bool = true,
};
// pub const ConfigFlags = packed struct {
// // We assume the uri will be encoded once in preparation for transmission. Certain services
// // do not decode before checking signature, requiring us to actually double-encode the uri in the canonical
// // request in order to pass a signature check.
//
// use_double_uri_encode: bool = true,
//
// // Controls whether or not the uri paths should be normalized when building the canonical request
// should_normalize_uri_path: bool = true,
//
// // Controls whether "X-Amz-Security-Token" is omitted from the canonical request.
// // "X-Amz-Security-Token" is added during signing, as a header or
// // query param, when credentials have a session token.
// // If false (the default), this parameter is included in the canonical request.
// // If true, this parameter is still added, but omitted from the canonical request.
// omit_session_token: bool = true,
// };
pub const Config = struct {
// These two should be all you need to set most of the time
@ -66,7 +74,7 @@ pub const Config = struct {
// this parameter has no effect.
expiration_in_seconds: u64 = 0,
flags: ConfigFlags = .{},
// flags: ConfigFlags = .{},
};
pub const SignatureType = enum { sha256, none };
@ -155,27 +163,38 @@ pub fn signRequest(allocator: std.mem.Allocator, request: base.Request, config:
);
errdefer freeSignedRequest(allocator, &rc, config);
var additional_header_count: u2 = 2;
var additional_header_count: u3 = 3;
if (config.credentials.session_token != null)
additional_header_count += 1;
const newheaders = try allocator.alloc(base.Header, rc.headers.len + additional_header_count);
errdefer allocator.free(newheaders);
const oldheaders = rc.headers;
if (config.credentials.session_token) |t| {
newheaders[newheaders.len - 3] = base.Header{
newheaders[newheaders.len - 4] = base.Header{
.name = "X-Amz-Security-Token",
.value = try allocator.dupe(u8, t),
};
}
errdefer freeSignedRequest(allocator, &rc, config);
std.mem.copy(base.Header, newheaders, oldheaders);
newheaders[newheaders.len - 2] = base.Header{
newheaders[newheaders.len - 3] = base.Header{
.name = "X-Amz-Date",
.value = signing_iso8601,
};
// From the AWS nitro enclaves SDK, it appears that there is no reason
// to avoid *ALWAYS* adding the x-amz-content-sha256 header
// https://github.com/aws/aws-nitro-enclaves-sdk-c/blob/9ecb83d07fe953636e3c0b861d6dac0a15d00f82/source/rest.c#L464
const payload_hash = try hash(allocator, request.body, config.signed_body_header);
// This will be freed in freeSignedRequest
// defer allocator.free(payload_hash);
newheaders[newheaders.len - 2] = base.Header{
.name = "x-amz-content-sha256",
.value = payload_hash,
};
rc.headers = newheaders[0 .. newheaders.len - 1];
log.debug("Signing with access key: {s}", .{config.credentials.access_key});
const canonical_request = try createCanonicalRequest(allocator, rc, config);
const canonical_request = try createCanonicalRequest(allocator, rc, payload_hash, config);
defer {
allocator.free(canonical_request.arr);
allocator.free(canonical_request.hash);
@ -252,11 +271,12 @@ pub fn freeSignedRequest(allocator: std.mem.Allocator, request: *base.Request, c
return;
};
var remove_len: u2 = 0;
var remove_len: u3 = 0;
for (request.headers) |h| {
if (std.ascii.eqlIgnoreCase(h.name, "X-Amz-Date") or
std.ascii.eqlIgnoreCase(h.name, "Authorization") or
std.ascii.eqlIgnoreCase(h.name, "X-Amz-Security-Token"))
std.ascii.eqlIgnoreCase(h.name, "X-Amz-Security-Token") or
std.ascii.eqlIgnoreCase(h.name, "x-amz-content-sha256"))
{
allocator.free(h.value);
remove_len += 1;
@ -303,10 +323,7 @@ fn validateConfig(config: Config) SigningError!void {
if (config.signature_type != .headers or
config.signed_body_header != .sha256 or
config.expiration_in_seconds != 0 or
config.algorithm != .v4 or
!config.flags.omit_session_token or
!config.flags.should_normalize_uri_path or
!config.flags.use_double_uri_encode)
config.algorithm != .v4)
return SigningError.NotImplemented;
}
@ -321,7 +338,7 @@ const Hashed = struct {
headers: CanonicalHeaders,
};
fn createCanonicalRequest(allocator: std.mem.Allocator, request: base.Request, config: Config) !Hashed {
fn createCanonicalRequest(allocator: std.mem.Allocator, request: base.Request, payload_hash: []const u8, config: Config) !Hashed {
// CanonicalRequest =
// HTTPRequestMethod + '\n' +
// CanonicalURI + '\n' +
@ -340,15 +357,13 @@ fn createCanonicalRequest(allocator: std.mem.Allocator, request: base.Request, c
// TODO: This is all better as a writer - less allocations/copying
const canonical_method = canonicalRequestMethod(request.method);
const canonical_url = try canonicalUri(allocator, request.path, config.flags.use_double_uri_encode);
const canonical_url = try canonicalUri(allocator, request.path, true); // TODO: set false for s3
defer allocator.free(canonical_url);
log.debug("final uri: {s}", .{canonical_url});
const canonical_query = try canonicalQueryString(allocator, request.query);
defer allocator.free(canonical_query);
log.debug("canonical query: {s}", .{canonical_query});
const canonical_headers = try canonicalHeaders(allocator, request.headers, config.flags);
const payload_hash = try hash(allocator, request.body, config.signed_body_header);
defer allocator.free(payload_hash);
const canonical_headers = try canonicalHeaders(allocator, request.headers, config.service);
const canonical_request = try std.fmt.allocPrint(allocator, fmt, .{
canonical_method,
@ -572,7 +587,7 @@ const CanonicalHeaders = struct {
str: []const u8,
signed_headers: []const u8,
};
fn canonicalHeaders(allocator: std.mem.Allocator, headers: []base.Header, flags: ConfigFlags) !CanonicalHeaders {
fn canonicalHeaders(allocator: std.mem.Allocator, headers: []base.Header, service: []const u8) !CanonicalHeaders {
//
// Doc example. Original:
//
@ -613,9 +628,8 @@ fn canonicalHeaders(allocator: std.mem.Allocator, headers: []base.Header, flags:
// canonical (signed) request. For other services, you add this
// parameter at the end, after you calculate the signature. For
// details, see the API reference documentation for that service.
if (flags.omit_session_token and std.ascii.eqlIgnoreCase(h.name, "X-Amz-Security-Token")) {
if (!std.mem.eql(u8, service, "s3") and std.ascii.eqlIgnoreCase(h.name, "X-Amz-Security-Token")) {
skip = true;
break;
}
if (skip) continue;
@ -743,7 +757,7 @@ test "canonical headers" {
\\x-amz-date:20150830T123600Z
\\
;
const actual = try canonicalHeaders(allocator, headers.items, .{});
const actual = try canonicalHeaders(allocator, headers.items, "dummy");
defer allocator.free(actual.str);
defer allocator.free(actual.signed_headers);
try std.testing.expectEqualStrings(expected, actual.str);
@ -768,7 +782,7 @@ test "canonical request" {
const secret_key = try allocator.dupe(u8, "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY");
const credential = auth.Credentials.init(allocator, access_key, secret_key, null);
defer credential.deinit();
const request = try createCanonicalRequest(allocator, req, .{
const request = try createCanonicalRequest(allocator, req, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", .{
.region = "us-west-2", // us-east-1
.service = "sts", // service
.credentials = credential,
@ -846,12 +860,15 @@ test "can sign" {
var signed_req = try signRequest(allocator, req, config);
defer freeSignedRequest(allocator, &signed_req, config);
try std.testing.expectEqualStrings("X-Amz-Date", signed_req.headers[signed_req.headers.len - 2].name);
try std.testing.expectEqualStrings("20150830T123600Z", signed_req.headers[signed_req.headers.len - 2].value);
try std.testing.expectEqualStrings("X-Amz-Date", signed_req.headers[signed_req.headers.len - 3].name);
try std.testing.expectEqualStrings("20150830T123600Z", signed_req.headers[signed_req.headers.len - 3].value);
try std.testing.expectEqualStrings("x-amz-content-sha256", signed_req.headers[signed_req.headers.len - 2].name);
try std.testing.expectEqualStrings("9095672bbd1f56dfc5b65f3e153adc8731a4a654192329106275f4c7b24d0b6e", signed_req.headers[signed_req.headers.len - 2].value);
// c_aws_auth tests don't seem to have valid data. Live endpoint is
// accepting what we're doing
const expected_auth = "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date, Signature=2b9566917226a17022b710430a367d343cbff33af7ee50b0ff8f44d75a4a46d8";
const expected_auth = "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-content-sha256;x-amz-date, Signature=328d1b9eaadca9f5818ef05e8392801e091653bafec24fcab71e7344e7f51422";
try std.testing.expectEqualStrings("Authorization", signed_req.headers[signed_req.headers.len - 1].name);
try std.testing.expectEqualStrings(expected_auth, signed_req.headers[signed_req.headers.len - 1].value);