initial implementation - canonical request
	
		
			
	
		
	
	
		
	
		
			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
							
								
									a01c01522c
								
							
						
					
					
						commit
						8d36300f27
					
				
					 1 changed files with 510 additions and 3 deletions
				
			
		|  | @ -52,7 +52,7 @@ pub const Config = struct { | |||
|     // In the CRT, this is only used if the body has been precalculated. We don't have | ||||
|     // this use case, and we'll ignore | ||||
|     //     .signed_body_value = c.aws_byte_cursor_from_c_str(""), | ||||
|     signed_body_header: enum { sha256, none } = .sha256, // https://github.com/awslabs/aws-c-auth/blob/ace1311f8ef6ea890b26dd376031bed2721648eb/include/aws/auth/signing_config.h#L131 | ||||
|     signed_body_header: SignatureType = .sha256, // https://github.com/awslabs/aws-c-auth/blob/ace1311f8ef6ea890b26dd376031bed2721648eb/include/aws/auth/signing_config.h#L131 | ||||
| 
 | ||||
|     // This is more complex in the CRT. We'll just take the creds. Someone | ||||
|     // else can use a provider and get them in advance | ||||
|  | @ -61,8 +61,11 @@ pub const Config = struct { | |||
|     // string, equal to the value specified here.  If this value is zero or if header signing is being used then | ||||
|     // this parameter has no effect. | ||||
|     expiration_in_seconds: u64 = 0, | ||||
| 
 | ||||
|     flags: ConfigFlags = .{}, | ||||
| }; | ||||
| 
 | ||||
| pub const SignatureType = enum { sha256, none }; | ||||
| pub const SigningError = error{ | ||||
|     NotImplemented, | ||||
| }; | ||||
|  | @ -75,6 +78,510 @@ pub fn signRequest(allocator: std.mem.Allocator, http_request: base.Request, con | |||
| } | ||||
| 
 | ||||
| fn validateConfig(config: Config) SigningError!void { | ||||
|     _ = config; | ||||
|     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) | ||||
|         return SigningError.NotImplemented; | ||||
| } | ||||
| 
 | ||||
| fn createCanonicalRequest(allocator: std.mem.Allocator, request: base.Request, config: Config) ![]const u8 { | ||||
|     // CanonicalRequest = | ||||
|     // HTTPRequestMethod + '\n' + | ||||
|     // CanonicalURI + '\n' + | ||||
|     // CanonicalQueryString + '\n' + | ||||
|     // CanonicalHeaders + '\n' + | ||||
|     // SignedHeaders + '\n' + | ||||
|     // HexEncode(Hash(RequestPayload)) | ||||
|     const fmt = | ||||
|         \\{s} | ||||
|         \\{s} | ||||
|         \\{s} | ||||
|         \\{s} | ||||
|         \\{s} | ||||
|         \\{s} | ||||
|     ; | ||||
| 
 | ||||
|     // 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); | ||||
|     defer allocator.free(canonical_url); | ||||
|     const canonical_query = try canonicalQueryString(allocator, request.path); | ||||
|     defer allocator.free(canonical_query); | ||||
|     const canonical_headers = try canonicalHeaders(allocator, request.headers); | ||||
|     defer allocator.free(canonical_headers.str); | ||||
|     defer allocator.free(canonical_headers.signed_headers); | ||||
|     const payload_hash = try hashPayload(allocator, request.body, config.signed_body_header); | ||||
|     defer allocator.free(payload_hash); | ||||
|     return try std.fmt.allocPrint(allocator, fmt, .{ | ||||
|         canonical_method, | ||||
|         canonical_url, | ||||
|         canonical_query, | ||||
|         canonical_headers.str, | ||||
|         canonical_headers.signed_headers, | ||||
|         payload_hash, | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| fn canonicalRequestMethod(method: []const u8) ![]const u8 { | ||||
|     return method; // We assume it's good | ||||
| } | ||||
| 
 | ||||
| fn canonicalUri(allocator: std.mem.Allocator, path: []const u8, double_encode: bool) ![]const u8 { | ||||
|     // Add the canonical URI parameter, followed by a newline character. The | ||||
|     // canonical URI is the URI-encoded version of the absolute path component | ||||
|     // of the URI, which is everything in the URI from the HTTP host to the | ||||
|     // question mark character ("?") that begins the query string parameters (if any). | ||||
|     // | ||||
|     // Normalize URI paths according to RFC 3986. Remove redundant and relative | ||||
|     // path components. Each path segment must be URI-encoded twice | ||||
|     // (except for Amazon S3 which only gets URI-encoded once). | ||||
|     // | ||||
|     // Note: In exception to this, you do not normalize URI paths for requests | ||||
|     // to Amazon S3. For example, if you have a bucket with an object | ||||
|     // named my-object//example//photo.user, use that path. Normalizing | ||||
|     // the path to my-object/example/photo.user will cause the request to | ||||
|     // fail. For more information, see Task 1: Create a Canonical Request in | ||||
|     // the Amazon Simple Storage Service API Reference. | ||||
|     // | ||||
|     // If the absolute path is empty, use a forward slash (/) | ||||
|     // | ||||
|     // For now, we will "Remove redundant and relative path components". This | ||||
|     // doesn't apply to S3 anyway, and we'll make it the callers's problem | ||||
|     if (!double_encode) | ||||
|         return error.S3NotImplemented; | ||||
|     if (path.len == 0 or path[0] == '?' or path[0] == '#') | ||||
|         return try allocator.dupe(u8, "/"); | ||||
|     const encoded_once = try encodeUri(allocator, path); | ||||
|     if (!double_encode) | ||||
|         return encoded_once[0 .. std.mem.lastIndexOf(u8, encoded_once, "?") orelse encoded_once.len]; | ||||
|     defer allocator.free(encoded_once); | ||||
|     const encoded_twice = try encodeUri(allocator, encoded_once); | ||||
|     return encoded_twice[0 .. std.mem.lastIndexOf(u8, encoded_twice, "?") orelse encoded_twice.len]; | ||||
| } | ||||
| 
 | ||||
| fn encodeParamPart(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { | ||||
|     const unreserved_marks = "-_.!~*'()"; | ||||
|     var encoded = try std.ArrayList(u8).initCapacity(allocator, path.len); | ||||
|     defer encoded.deinit(); | ||||
|     for (path) |c| { | ||||
|         var should_encode = true; | ||||
|         for (unreserved_marks) |r| | ||||
|             if (r == c) { | ||||
|                 should_encode = false; | ||||
|                 break; | ||||
|             }; | ||||
|         if (should_encode and std.ascii.isAlNum(c)) | ||||
|             should_encode = false; | ||||
| 
 | ||||
|         if (!should_encode) { | ||||
|             try encoded.append(c); | ||||
|             continue; | ||||
|         } | ||||
|         // Whatever remains, encode it | ||||
|         try encoded.append('%'); | ||||
|         const hex = try std.fmt.allocPrint(allocator, "{s}", .{std.fmt.fmtSliceHexUpper(&[_]u8{c})}); | ||||
|         defer allocator.free(hex); | ||||
|         try encoded.appendSlice(hex); | ||||
|     } | ||||
|     return encoded.toOwnedSlice(); | ||||
| } | ||||
| fn encodeUri(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { | ||||
|     const reserved_characters = ";,/?:@&=+$#"; | ||||
|     const unreserved_marks = "-_.!~*'()"; | ||||
|     var encoded = try std.ArrayList(u8).initCapacity(allocator, path.len); | ||||
|     defer encoded.deinit(); | ||||
|     for (path) |c| { | ||||
|         var should_encode = true; | ||||
|         for (reserved_characters) |r| | ||||
|             if (r == c) { | ||||
|                 should_encode = false; | ||||
|                 break; | ||||
|             }; | ||||
|         if (should_encode) { | ||||
|             for (unreserved_marks) |r| | ||||
|                 if (r == c) { | ||||
|                     should_encode = false; | ||||
|                     break; | ||||
|                 }; | ||||
|         } | ||||
|         if (should_encode and std.ascii.isAlNum(c)) | ||||
|             should_encode = false; | ||||
| 
 | ||||
|         if (!should_encode) { | ||||
|             try encoded.append(c); | ||||
|             continue; | ||||
|         } | ||||
|         // Whatever remains, encode it | ||||
|         try encoded.append('%'); | ||||
|         const hex = try std.fmt.allocPrint(allocator, "{s}", .{std.fmt.fmtSliceHexUpper(&[_]u8{c})}); | ||||
|         defer allocator.free(hex); | ||||
|         try encoded.appendSlice(hex); | ||||
|     } | ||||
|     return encoded.toOwnedSlice(); | ||||
| } | ||||
| 
 | ||||
| fn canonicalQueryString(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { | ||||
|     //     To construct the canonical query string, complete the following steps: | ||||
|     // | ||||
|     //     Sort the parameter names by character code point in ascending order. | ||||
|     //     Parameters with duplicate names should be sorted by value. For example, | ||||
|     //     a parameter name that begins with the uppercase letter F precedes a | ||||
|     //     parameter name that begins with a lowercase letter b. | ||||
|     // | ||||
|     //     URI-encode each parameter name and value according to the following rules: | ||||
|     // | ||||
|     //         Do not URI-encode any of the unreserved characters that RFC 3986 | ||||
|     //         defines: A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ). | ||||
|     // | ||||
|     //         Percent-encode all other characters with %XY, where X and Y are | ||||
|     //         hexadecimal characters (0-9 and uppercase A-F). For example, the | ||||
|     //         space character must be encoded as %20 (not using '+', as some | ||||
|     //         encoding schemes do) and extended UTF-8 characters must be in the | ||||
|     //         form %XY%ZA%BC. | ||||
|     // | ||||
|     //         Double-encode any equals ( = ) characters in parameter values. | ||||
|     // | ||||
|     //     Build the canonical query string by starting with the first parameter | ||||
|     //     name in the sorted list. | ||||
|     // | ||||
|     //     For each parameter, append the URI-encoded parameter name, followed by | ||||
|     //     the equals sign character (=), followed by the URI-encoded parameter | ||||
|     //     value. Use an empty string for parameters that have no value. | ||||
|     // | ||||
|     //     Append the ampersand character (&) after each parameter value, except | ||||
|     //     for the last value in the list. | ||||
|     // | ||||
|     // One option for the query API is to put all request parameters in the query | ||||
|     // string. For example, you can do this for Amazon S3 to create a presigned | ||||
|     // URL. In that case, the canonical query string must include not only | ||||
|     // parameters for the request, but also the parameters used as part of the | ||||
|     // signing process—the hashing algorithm, credential scope, date, and signed | ||||
|     // headers parameters. | ||||
|     // | ||||
|     // The following example shows a query string that includes authentication | ||||
|     // information. The example is formatted with line breaks for readability, but | ||||
|     // the canonical query string must be one continuous line of text in your code. | ||||
|     const first_question = std.mem.indexOf(u8, path, "?"); | ||||
|     if (first_question == null) | ||||
|         return try allocator.dupe(u8, ""); | ||||
| 
 | ||||
|     // We have a query string | ||||
|     const query = path[first_question.? + 1 ..]; | ||||
| 
 | ||||
|     // Split this by component | ||||
|     var portions = std.mem.split(u8, query, "&"); | ||||
|     var sort_me = std.ArrayList([]const u8).init(allocator); | ||||
|     defer sort_me.deinit(); | ||||
|     while (portions.next()) |item| | ||||
|         try sort_me.append(item); | ||||
|     std.sort.sort([]const u8, sort_me.items, {}, lessThanBinary); | ||||
| 
 | ||||
|     var normalized = try std.ArrayList(u8).initCapacity(allocator, path.len); | ||||
|     defer normalized.deinit(); | ||||
|     var first = true; | ||||
|     for (sort_me.items) |i| { | ||||
|         if (!first) try normalized.append('&'); | ||||
|         first = false; | ||||
|         var first_equals = std.mem.indexOf(u8, i, "="); | ||||
|         if (first_equals == null) { | ||||
|             // Rare. This is "foo=" | ||||
|             const normed_item = try encodeUri(allocator, i); | ||||
|             defer allocator.free(normed_item); | ||||
|             try normalized.appendSlice(i); // This should be encoded | ||||
|             try normalized.append('='); | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         // normal key=value stuff | ||||
|         const key = try encodeParamPart(allocator, i[0..first_equals.?]); | ||||
|         defer allocator.free(key); | ||||
| 
 | ||||
|         const value = try encodeParamPart(allocator, i[first_equals.? + 1 ..]); | ||||
|         defer allocator.free(value); | ||||
|         // Double-encode any = in the value. But not anything else? | ||||
|         const weird_equals_in_value_thing = try replace(allocator, value, "%3D", "%253D"); | ||||
|         defer allocator.free(weird_equals_in_value_thing); | ||||
|         try normalized.appendSlice(key); | ||||
|         try normalized.append('='); | ||||
|         try normalized.appendSlice(weird_equals_in_value_thing); | ||||
|     } | ||||
| 
 | ||||
|     return normalized.toOwnedSlice(); | ||||
| } | ||||
| 
 | ||||
| fn replace(allocator: std.mem.Allocator, haystack: []const u8, needle: []const u8, replacement_value: []const u8) ![]const u8 { | ||||
|     var buffer = try allocator.alloc(u8, std.mem.replacementSize(u8, haystack, needle, replacement_value)); | ||||
|     _ = std.mem.replace(u8, haystack, needle, replacement_value, buffer); | ||||
|     return buffer; | ||||
| } | ||||
| 
 | ||||
| fn lessThanBinary(context: void, lhs: []const u8, rhs: []const u8) bool { | ||||
|     _ = context; | ||||
|     return std.mem.lessThan(u8, lhs, rhs); | ||||
| } | ||||
| const CanonicalHeaders = struct { | ||||
|     str: []const u8, | ||||
|     signed_headers: []const u8, | ||||
| }; | ||||
| fn canonicalHeaders(allocator: std.mem.Allocator, headers: []base.Header) !CanonicalHeaders { | ||||
|     // | ||||
|     // Doc example. Original: | ||||
|     // | ||||
|     // Host:iam.amazonaws.com\n | ||||
|     // Content-Type:application/x-www-form-urlencoded; charset=utf-8\n | ||||
|     // My-header1:    a   b   c  \n | ||||
|     // X-Amz-Date:20150830T123600Z\n | ||||
|     // My-Header2:    "a   b   c"  \n | ||||
|     // | ||||
|     // Canonical form: | ||||
|     // content-type:application/x-www-form-urlencoded; charset=utf-8\n | ||||
|     // host:iam.amazonaws.com\n | ||||
|     // my-header1:a b c\n | ||||
|     // my-header2:"a b c"\n | ||||
|     // x-amz-date:20150830T123600Z\n | ||||
|     var dest = try allocator.alloc(base.Header, headers.len); | ||||
|     defer { | ||||
|         for (dest) |h| { | ||||
|             allocator.free(h.name); | ||||
|             allocator.free(h.value); | ||||
|         } | ||||
|         allocator.free(dest); | ||||
|     } | ||||
|     var total_len: usize = 0; | ||||
|     var total_name_len: usize = 0; | ||||
|     for (headers) |h, i| { | ||||
|         total_len += (h.name.len + h.value.len + 2); | ||||
|         total_name_len += (h.name.len + 1); | ||||
|         const value = try canonicalHeaderValue(allocator, h.value); | ||||
|         defer allocator.free(value); | ||||
|         dest[i] = .{ | ||||
|             .name = try std.ascii.allocLowerString(allocator, h.name), | ||||
|             .value = try std.fmt.allocPrint(allocator, "{s}", .{value}), | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     std.sort.sort(base.Header, dest, {}, lessThan); | ||||
| 
 | ||||
|     var dest_str = try std.ArrayList(u8).initCapacity(allocator, total_len); | ||||
|     defer dest_str.deinit(); | ||||
|     var signed_headers = try std.ArrayList(u8).initCapacity(allocator, total_name_len); | ||||
|     defer signed_headers.deinit(); | ||||
|     var first = true; | ||||
|     for (dest) |h| { | ||||
|         dest_str.appendSliceAssumeCapacity(h.name); | ||||
|         dest_str.appendAssumeCapacity(':'); | ||||
|         dest_str.appendSliceAssumeCapacity(h.value); | ||||
|         dest_str.appendAssumeCapacity('\n'); | ||||
| 
 | ||||
|         if (!first) signed_headers.appendAssumeCapacity(';'); | ||||
|         first = false; | ||||
|         signed_headers.appendSliceAssumeCapacity(h.name); | ||||
|     } | ||||
|     return CanonicalHeaders{ | ||||
|         .str = dest_str.toOwnedSlice(), | ||||
|         .signed_headers = signed_headers.toOwnedSlice(), | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| fn canonicalHeaderValue(allocator: std.mem.Allocator, value: []const u8) ![]const u8 { | ||||
|     var started = false; | ||||
|     var in_quote = false; | ||||
|     var start: usize = 0; | ||||
|     const rc = try allocator.alloc(u8, value.len); | ||||
|     var rc_inx: usize = 0; | ||||
|     for (value) |c, i| { | ||||
|         if (!started and !std.ascii.isSpace(c)) { | ||||
|             started = true; | ||||
|             start = i; | ||||
|         } | ||||
|         if (started) { | ||||
|             if (!in_quote and i > 0 and std.ascii.isSpace(c) and std.ascii.isSpace(value[i - 1])) | ||||
|                 continue; | ||||
|             // if (c == '"') in_quote = !in_quote; | ||||
|             rc[rc_inx] = c; | ||||
|             rc_inx += 1; | ||||
|         } | ||||
|     } | ||||
|     // Trim end | ||||
|     while (std.ascii.isSpace(rc[rc_inx - 1])) | ||||
|         rc_inx -= 1; | ||||
|     return rc[0..rc_inx]; | ||||
| } | ||||
| fn lessThan(context: void, lhs: base.Header, rhs: base.Header) bool { | ||||
|     _ = context; | ||||
|     return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name); | ||||
| } | ||||
| 
 | ||||
| fn hashPayload(allocator: std.mem.Allocator, payload: []const u8, sig_type: SignatureType) ![]const u8 { | ||||
|     if (sig_type != .sha256) | ||||
|         return error.NotImplemented; | ||||
|     const to_hash = blk: { | ||||
|         if (payload.len > 0) { | ||||
|             break :blk payload; | ||||
|         } | ||||
|         break :blk ""; | ||||
|     }; | ||||
|     var out: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; | ||||
|     std.crypto.hash.sha2.Sha256.hash(to_hash, &out, .{}); | ||||
|     return try std.fmt.allocPrint(allocator, "{s}", .{std.fmt.fmtSliceHexLower(&out)}); | ||||
| } | ||||
| // SignedHeaders + '\n' + | ||||
| // HexEncode(Hash(RequestPayload)) | ||||
| test "canonical method" { | ||||
|     const actual = try canonicalRequestMethod("GET"); | ||||
|     try std.testing.expectEqualStrings("GET", actual); | ||||
| } | ||||
| 
 | ||||
| test "canonical uri" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     const path = "/documents and settings/?foo=bar"; | ||||
|     const expected = "/documents%2520and%2520settings/"; | ||||
|     const actual = try canonicalUri(allocator, path, true); | ||||
|     defer allocator.free(actual); | ||||
|     try std.testing.expectEqualStrings(expected, actual); | ||||
| 
 | ||||
|     const slash = try canonicalUri(allocator, "", true); | ||||
|     defer allocator.free(slash); | ||||
|     try std.testing.expectEqualStrings("/", slash); | ||||
| } | ||||
| test "canonical query" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     const path = "blahblahblah?foo=bar&zed=dead&qux&equals=x=y&Action=ListUsers&Version=2010-05-08"; | ||||
| 
 | ||||
|     // { | ||||
|     //     // TODO: Remove block | ||||
|     //     std.testing.log_level = .debug; | ||||
|     //     _ = try std.io.getStdErr().write("\n"); | ||||
|     // } | ||||
|     const expected = "Action=ListUsers&Version=2010-05-08&equals=x%253Dy&foo=bar&qux=&zed=dead"; | ||||
|     const actual = try canonicalQueryString(allocator, path); | ||||
|     defer allocator.free(actual); | ||||
|     try std.testing.expectEqualStrings(expected, actual); | ||||
| } | ||||
| test "canonical headers" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     var headers = try std.ArrayList(base.Header).initCapacity(allocator, 5); | ||||
|     defer headers.deinit(); | ||||
|     try headers.append(.{ .name = "Host", .value = "iam.amazonaws.com" }); | ||||
|     try headers.append(.{ .name = "Content-Type", .value = "application/x-www-form-urlencoded; charset=utf-8" }); | ||||
|     try headers.append(.{ .name = "My-header1", .value = "  a  b  c  " }); | ||||
|     try headers.append(.{ .name = "X-Amz-Date", .value = "20150830T123600Z" }); | ||||
|     try headers.append(.{ .name = "My-header2", .value = "  \"a  b  c\"  " }); | ||||
|     const expected = | ||||
|         \\content-type:application/x-www-form-urlencoded; charset=utf-8 | ||||
|         \\host:iam.amazonaws.com | ||||
|         \\my-header1:a b c | ||||
|         \\my-header2:"a b c" | ||||
|         \\x-amz-date:20150830T123600Z | ||||
|         \\ | ||||
|     ; | ||||
|     // { | ||||
|     //     // TODO: Remove block | ||||
|     //     std.testing.log_level = .debug; | ||||
|     //     _ = try std.io.getStdErr().write("\n"); | ||||
|     // } | ||||
|     const actual = try canonicalHeaders(allocator, headers.items); | ||||
|     defer allocator.free(actual.str); | ||||
|     defer allocator.free(actual.signed_headers); | ||||
|     try std.testing.expectEqualStrings(expected, actual.str); | ||||
|     try std.testing.expectEqualStrings("content-type;host;my-header1;my-header2;x-amz-date", actual.signed_headers); | ||||
| } | ||||
| 
 | ||||
| test "canonical request" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     var headers = try std.ArrayList(base.Header).initCapacity(allocator, 5); | ||||
|     defer headers.deinit(); | ||||
|     try headers.append(.{ .name = "Content-Type", .value = "application/x-www-form-urlencoded" }); | ||||
|     try headers.append(.{ .name = "Content-Length", .value = "43" }); | ||||
|     try headers.append(.{ .name = "User-Agent", .value = "zig-aws 1.0, Powered by the AWS Common Runtime." }); | ||||
|     try headers.append(.{ .name = "Host", .value = "sts.us-west-2.amazonaws.com" }); | ||||
|     try headers.append(.{ .name = "Accept", .value = "application/json" }); | ||||
|     const req = base.Request{ | ||||
|         .path = "/", | ||||
|         .query = "", | ||||
|         .body = "Action=GetCallerIdentity&Version=2011-06-15", | ||||
|         .method = "POST", | ||||
|         .content_type = "application/json", | ||||
|         .headers = headers.items, | ||||
|     }; | ||||
|     { | ||||
|         // TODO: Remove block | ||||
|         std.testing.log_level = .debug; | ||||
|         _ = try std.io.getStdErr().write("\n"); | ||||
|     } | ||||
|     const request = try createCanonicalRequest(allocator, req, .{ | ||||
|         .region = "us-west-2", // us-east-1 | ||||
|         .service = "sts", // service | ||||
|         .credentials = .{ | ||||
|             .access_key = "AKIDEXAMPLE", | ||||
|             .secret_key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", | ||||
|             .session_token = null, | ||||
|         }, | ||||
|         .signing_time = 1642187728, // Should be 2015-08-30T12:36:00Z. Does zig stdlib have date parsing? | ||||
|     }); | ||||
|     defer allocator.free(request); | ||||
|     std.log.debug("canonical request:\n{s}", .{request}); | ||||
|     try std.testing.expect(request.len > 0); // TODO: improvify this | ||||
| 
 | ||||
| } | ||||
| test "can sign" { | ||||
|     // [debug] (aws): call: prefix sts, sigv4 sts, version 2011-06-15, action GetCallerIdentity | ||||
|     // [debug] (aws): proto: AwsProtocol.query | ||||
|     // [debug] (awshttp): host: sts.us-west-2.amazonaws.com, scheme: https, port: 443 | ||||
|     // [debug] (awshttp): Calling endpoint https://sts.us-west-2.amazonaws.com | ||||
|     // [debug] (awshttp): Path: / | ||||
|     // [debug] (awshttp): Query: | ||||
|     // [debug] (awshttp): Method: POST | ||||
|     // [debug] (awshttp): body length: 43 | ||||
|     // [debug] (awshttp): Body | ||||
|     // ==== | ||||
|     // Action=GetCallerIdentity&Version=2011-06-15 | ||||
|     // ==== | ||||
|     // [debug] (awshttp): All Request Headers: | ||||
|     // [debug] (awshttp):      Accept: application/json | ||||
|     // [debug] (awshttp):      Host: sts.us-west-2.amazonaws.com | ||||
|     // [debug] (awshttp):      User-Agent: zig-aws 1.0, Powered by the AWS Common Runtime. | ||||
|     // [debug] (awshttp):      Content-Type: application/x-www-form-urlencoded | ||||
|     // [debug] (awshttp):      Content-Length: 43 | ||||
| 
 | ||||
|     const allocator = std.testing.allocator; | ||||
|     var headers = try std.ArrayList(base.Header).initCapacity(allocator, 5); | ||||
|     defer headers.deinit(); | ||||
|     try headers.append(.{ .name = "Content-Type", .value = "application/x-www-form-urlencoded" }); | ||||
|     try headers.append(.{ .name = "Content-Length", .value = "43" }); | ||||
|     try headers.append(.{ .name = "User-Agent", .value = "zig-aws 1.0, Powered by the AWS Common Runtime." }); | ||||
|     try headers.append(.{ .name = "Host", .value = "sts.us-west-2.amazonaws.com" }); | ||||
|     try headers.append(.{ .name = "Accept", .value = "application/json" }); | ||||
|     const req = base.Request{ | ||||
|         .path = "/", | ||||
|         .query = "", | ||||
|         .body = "Action=GetCallerIdentity&Version=2011-06-15", | ||||
|         .method = "POST", | ||||
|         .content_type = "application/json", | ||||
|         .headers = headers.items, | ||||
|     }; | ||||
|     { | ||||
|         // TODO: Remove block | ||||
|         std.testing.log_level = .debug; | ||||
|         _ = try std.io.getStdErr().write("\n"); | ||||
|     } | ||||
| 
 | ||||
|     // we could look at sigv4 signing tests at: | ||||
|     // https://github.com/awslabs/aws-c-auth/blob/ace1311f8ef6ea890b26dd376031bed2721648eb/tests/sigv4_signing_tests.c#L1478 | ||||
|     // | ||||
|     // for valid signatures. TODO: Get literally anything working first | ||||
|     try signRequest(allocator, req, .{ | ||||
|         .region = "us-west-2", // us-east-1 | ||||
|         .service = "sts", // service | ||||
|         .credentials = .{ | ||||
|             .access_key = "AKIDEXAMPLE", | ||||
|             .secret_key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", | ||||
|             .session_token = null, | ||||
|         }, | ||||
|         .signing_time = 1642187728, // Should be 2015-08-30T12:36:00Z. Does zig stdlib have date parsing? | ||||
|     }); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue