add credential generation

This commit is contained in:
Emil Lerch 2024-02-23 17:19:32 -08:00
parent d4911b3bc1
commit 4b0284a778
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
3 changed files with 94 additions and 3 deletions

View File

@ -89,4 +89,91 @@ pub fn build(b: *std.Build) !void {
cs.addIncludePath(.{ .path = "c" }); cs.addIncludePath(.{ .path = "c" });
cs.linkLibrary(sqlite_dep.artifact("sqlite")); 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;
} }

View File

@ -4,6 +4,7 @@ const sqlite = @import("sqlite"); // TODO: If we use this across all services, A
const test_account_key = "09aGW6z6QofVsPlWP9FGqVnshxHWAWrKZwLkwkgWs7w="; const test_account_key = "09aGW6z6QofVsPlWP9FGqVnshxHWAWrKZwLkwkgWs7w=";
const log = std.log.scoped(.Account);
const Self = @This(); const Self = @This();
allocator: std.mem.Allocator, 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 // TODO: Allow environment variables to house encoded keys. If not in the
// environment, check with LocalDB table to get it. We're // environment, check with LocalDB table to get it. We're
// building LocalDB, though, so we need that working first... // 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); var key = try allocator.alloc(u8, encryption.key_length);
errdefer allocator.free(key); errdefer allocator.free(key);
try encryption.decodeKey(key[0..encryption.key_length], test_account_key.*); try encryption.decodeKey(key[0..encryption.key_length], test_account_key.*);

View File

@ -127,8 +127,8 @@ fn getCreds(access: []const u8) ?signing.Credentials {
fn accountForAccessKey(allocator: std.mem.Allocator, access_key: []const u8) ![]const u8 { fn accountForAccessKey(allocator: std.mem.Allocator, access_key: []const u8) ![]const u8 {
_ = allocator; _ = allocator;
_ = access_key; log.debug("Finding account for access key: '{s}'", .{access_key});
return "1234, Get your woman, on the floor"; return "1234";
} }
/// Function assumes an authenticated request, so signing.verify must be called /// Function assumes an authenticated request, so signing.verify must be called
/// and returned true before calling this function. If authentication header /// and returned true before calling this function. If authentication header