add x-amz-content-sha256 to all requests
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		
							parent
							
								
									0e46bb04b2
								
							
						
					
					
						commit
						e6d2559b80
					
				
					 1 changed files with 58 additions and 41 deletions
				
			
		|  | @ -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); | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue