From 4b0284a778996503bed66c6b0b31ac81667db3b3 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Fri, 23 Feb 2024 17:19:32 -0800 Subject: [PATCH] add credential generation --- build.zig | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ src/Account.zig | 6 +++- src/main.zig | 4 +-- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/build.zig b/build.zig index 4aaa63b..6fe1463 100644 --- a/build.zig +++ b/build.zig @@ -89,4 +89,91 @@ pub fn build(b: *std.Build) !void { cs.addIncludePath(.{ .path = "c" }); cs.linkLibrary(sqlite_dep.artifact("sqlite")); } + + var creds_step = b.step("generate_credentials", "Generate credentials for access_keys.csv"); + creds_step.makeFn = generateCredentials; +} + +fn generateCredentials(s: *std.build.Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void { + // Format: + // Access Key,Account Id,Existing encoded encryption key, New encoded encryption + _ = prog_node; + const encryption = @import("src/encryption.zig"); + var key: [encryption.encoded_key_length]u8 = undefined; + encryption.randomEncodedKey(&key); + + const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp())))); + var prng = std.rand.DefaultPrng.init(seed); + var rand = prng.random(); + const account_number = rand.intRangeAtMost(u64, 100000000000, 999999999999); + const access_key_suffix: u128 = blk: { // workaround for u64 max on rand.intRangeAtMost + const min = 0xECFF3BCC40CA2000000000; + // const max = 0x2153E468B91C6E0000000000; + // const diff = max - min; // 0x2066e52cecdba40000000000 (is 12 bytes/96 bits) + // So we can use a full 64 bit range and just add to the min + break :blk @as(u128, rand.int(u64)) + min; + }; + const access_key_suffix_encoded = encode( + u128, + s.owner.allocator, + access_key_suffix, + ) catch return error.MakeFailed; + var secret_key: [30]u8 = undefined; + rand.bytes(&secret_key); // The rest don't need to be cryptographically secure...does this? + var encoded_secret: [40]u8 = undefined; + _ = std.base64.standard.Encoder.encode(&encoded_secret, secret_key[0..]); + + std.debug.print( + "access_key: EL{s}, secret_key: {s}, account_number: {d}, db_encryption_key: {s}", + .{ + access_key_suffix_encoded, + encoded_secret, + account_number, + key, + }, + ); + // Documentation describes account id as a 12 digit number: + // https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html + // Random u64 + // Max: 0x3b9ac9ff (0d999999999) + // Min: 0x05f5e100 (0d100000000) + // + // Access key and secret key are probably more loose. Here is one: + // + // "AccessKey": { + // "AccessKeyId": "AKIAYAM4POHXNMQUDBNG", + // "SecretAccessKey": "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", + // } + // Access key appears 20 characters A-Z, 0-9. Starts with AK or AS, so + // 18 characters of random, and it looks like base36 + // https://ziglang.org/documentation/0.11.0/std/src/std/base64.zig.html + // https://en.wikipedia.org/wiki/Base36 + // For 18 characters, the lower end would be: + // NN100000000000000000 (hex: ECFF3BCC40CA2000000000) + // Upper: + // NNZZZZZZZZZZZZZZZZZZ (hex: 2153E468B91C6E0000000000) + // Which can be stored in u24 + // Secret key here is 40 characters and roughly looks like base64 encoded + // random binary data, which it probably is. 40 characters of base64 is 32 bytes of data +} + +/// encodes an unsigned integer into base36 +pub fn encode(comptime T: type, allocator: std.mem.Allocator, data: T) ![]const u8 { + const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const ti = @typeInfo(T); + if (ti != .Int or ti.Int.signedness != .unsigned) + @compileError("encode only works with unsigned integers"); + const bits = ti.Int.bits; + // We cannot have more than 6 bits (2^6 = 64) represented per byte in our final output + var al = try std.ArrayList(u8).initCapacity(allocator, bits / 6); + defer al.deinit(); + + var remaining = data; + while (remaining > 0) : (remaining /= 36) { + al.appendAssumeCapacity(alphabet[@as(usize, @intCast(remaining % 36))]); + } + // This is not exact, but 6 bits + var rc = try al.toOwnedSlice(); + std.mem.reverse(u8, rc); + return rc; } diff --git a/src/Account.zig b/src/Account.zig index 68afeeb..6301e6c 100644 --- a/src/Account.zig +++ b/src/Account.zig @@ -4,6 +4,7 @@ const sqlite = @import("sqlite"); // TODO: If we use this across all services, A const test_account_key = "09aGW6z6QofVsPlWP9FGqVnshxHWAWrKZwLkwkgWs7w="; +const log = std.log.scoped(.Account); const Self = @This(); allocator: std.mem.Allocator, @@ -13,7 +14,10 @@ pub fn accountForId(allocator: std.mem.Allocator, account_id: []const u8) !Self // TODO: Allow environment variables to house encoded keys. If not in the // environment, check with LocalDB table to get it. We're // building LocalDB, though, so we need that working first... - if (!std.mem.eql(u8, account_id, "1234")) return error.NotImplemented; + if (!std.mem.eql(u8, account_id, "1234")) { + log.err("Got account id '{s}', but only '1234' is valid right now", .{account_id}); + return error.NotImplemented; + } var key = try allocator.alloc(u8, encryption.key_length); errdefer allocator.free(key); try encryption.decodeKey(key[0..encryption.key_length], test_account_key.*); diff --git a/src/main.zig b/src/main.zig index 011a89e..dc082e2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -127,8 +127,8 @@ fn getCreds(access: []const u8) ?signing.Credentials { fn accountForAccessKey(allocator: std.mem.Allocator, access_key: []const u8) ![]const u8 { _ = allocator; - _ = access_key; - return "1234, Get your woman, on the floor"; + log.debug("Finding account for access key: '{s}'", .{access_key}); + return "1234"; } /// Function assumes an authenticated request, so signing.verify must be called /// and returned true before calling this function. If authentication header