mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-06-17 08:04:13 +00:00
127 lines
3.9 KiB
Zig
127 lines
3.9 KiB
Zig
/// Session token management.
|
|
///
|
|
/// After successful challenge-response auth, the server issues an HMAC-based
|
|
/// session token. Clients include this token in subsequent requests.
|
|
///
|
|
/// Token format: base64(identity_hash || expiry_timestamp || hmac_tag)
|
|
/// The HMAC binds the identity and expiry to the server secret.
|
|
const std = @import("std");
|
|
const HmacSha256 = std.crypto.auth.hmac.sha2.HmacSha256;
|
|
|
|
pub const SESSION_EXPIRY_NS: i128 = 3600 * std.time.ns_per_s; // 1 hour
|
|
|
|
pub const SessionToken = struct {
|
|
identity: [64]u8, // hex-encoded identity hash (padded)
|
|
identity_len: u8,
|
|
expiry_ns: i128,
|
|
tag: [HmacSha256.mac_length]u8,
|
|
};
|
|
|
|
pub const SessionError = error{
|
|
SessionExpired,
|
|
InvalidSession,
|
|
};
|
|
|
|
/// Issue a session token for the given identity.
|
|
pub fn issueToken(identity: []const u8, server_secret: [32]u8) SessionToken {
|
|
const now = std.time.nanoTimestamp();
|
|
const expiry = now + SESSION_EXPIRY_NS;
|
|
|
|
var id_buf: [64]u8 = .{0} ** 64;
|
|
const copy_len = @min(identity.len, 64);
|
|
@memcpy(id_buf[0..copy_len], identity[0..copy_len]);
|
|
|
|
var mac = HmacSha256.init(&server_secret);
|
|
mac.update(&id_buf);
|
|
var expiry_bytes: [16]u8 = undefined;
|
|
std.mem.writeInt(i128, &expiry_bytes, expiry, .little);
|
|
mac.update(&expiry_bytes);
|
|
|
|
var tag: [HmacSha256.mac_length]u8 = undefined;
|
|
mac.final(&tag);
|
|
|
|
return SessionToken{
|
|
.identity = id_buf,
|
|
.identity_len = @intCast(copy_len),
|
|
.expiry_ns = expiry,
|
|
.tag = tag,
|
|
};
|
|
}
|
|
|
|
/// Verify a session token.
|
|
pub fn verifyToken(token: SessionToken, server_secret: [32]u8) SessionError![]const u8 {
|
|
// Check expiry
|
|
const now = std.time.nanoTimestamp();
|
|
if (now > token.expiry_ns) {
|
|
return SessionError.SessionExpired;
|
|
}
|
|
|
|
// Recompute HMAC
|
|
var mac = HmacSha256.init(&server_secret);
|
|
mac.update(&token.identity);
|
|
var expiry_bytes: [16]u8 = undefined;
|
|
std.mem.writeInt(i128, &expiry_bytes, token.expiry_ns, .little);
|
|
mac.update(&expiry_bytes);
|
|
|
|
var expected: [HmacSha256.mac_length]u8 = undefined;
|
|
mac.final(&expected);
|
|
|
|
// Constant-time comparison to prevent timing attacks
|
|
if (!timingSafeEqual(&expected, &token.tag)) {
|
|
return SessionError.InvalidSession;
|
|
}
|
|
|
|
return token.identity[0..token.identity_len];
|
|
}
|
|
|
|
/// Constant-time byte comparison to prevent timing side-channel attacks.
|
|
fn timingSafeEqual(a: []const u8, b: []const u8) bool {
|
|
if (a.len != b.len) return false;
|
|
var diff: u8 = 0;
|
|
for (a, b) |x, y| {
|
|
diff |= x ^ y;
|
|
}
|
|
return diff == 0;
|
|
}
|
|
|
|
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
|
|
test "session: issue and verify" {
|
|
var secret: [32]u8 = undefined;
|
|
std.crypto.random.bytes(&secret);
|
|
|
|
const token = issueToken("abcdef1234", secret);
|
|
const identity = try verifyToken(token, secret);
|
|
try std.testing.expectEqualSlices(u8, "abcdef1234", identity);
|
|
}
|
|
|
|
test "session: wrong secret fails" {
|
|
var secret1: [32]u8 = undefined;
|
|
var secret2: [32]u8 = undefined;
|
|
std.crypto.random.bytes(&secret1);
|
|
std.crypto.random.bytes(&secret2);
|
|
|
|
const token = issueToken("alice", secret1);
|
|
try std.testing.expectError(SessionError.InvalidSession, verifyToken(token, secret2));
|
|
}
|
|
|
|
test "session: tampered identity fails" {
|
|
var secret: [32]u8 = undefined;
|
|
std.crypto.random.bytes(&secret);
|
|
|
|
var token = issueToken("alice", secret);
|
|
token.identity[0] = 'X'; // tamper
|
|
|
|
try std.testing.expectError(SessionError.InvalidSession, verifyToken(token, secret));
|
|
}
|
|
|
|
test "session: tampered expiry fails" {
|
|
var secret: [32]u8 = undefined;
|
|
std.crypto.random.bytes(&secret);
|
|
|
|
var token = issueToken("alice", secret);
|
|
token.expiry_ns += 1; // tamper
|
|
|
|
try std.testing.expectError(SessionError.InvalidSession, verifyToken(token, secret));
|
|
}
|