Commit 84cdb62159
Changed files (1)
lib
std
crypto
lib/std/crypto/timing_safe.zig
@@ -131,6 +131,54 @@ pub fn sub(comptime T: type, a: []const T, b: []const T, result: []T, endian: En
return @as(bool, @bitCast(borrow));
}
+fn markSecret(ptr: anytype, comptime action: enum { classify, declassify }) void {
+ const t = @typeInfo(@TypeOf(ptr));
+ if (t != .pointer) @compileError("Pointer expected - Found: " ++ @typeName(@TypeOf(ptr)));
+ const p = t.pointer;
+ if (p.is_allowzero) @compileError("A nullable pointer is always assumed to leak information via side channels");
+ const child = @typeInfo(p.child);
+
+ switch (child) {
+ .void, .null, .comptime_int, .comptime_float => return,
+ .pointer => {
+ if (child.pointer.size == .Slice) {
+ @compileError("Found pointer to pointer. If the intent was to pass a slice, maybe remove the leading & in the function call");
+ }
+ @compileError("A pointer value is always assumed leak information via side channels");
+ },
+ else => {
+ const mem8: *const [@sizeOf(@TypeOf(ptr.*))]u8 = @constCast(@ptrCast(ptr));
+ if (action == .classify) {
+ std.valgrind.memcheck.makeMemUndefined(mem8);
+ } else {
+ std.valgrind.memcheck.makeMemDefined(mem8);
+ }
+ },
+ }
+}
+
+/// Mark a value as sensitive or secret, helping to detect potential side-channel vulnerabilities.
+///
+/// When Valgrind is enabled, this function allows for the detection of conditional jumps or lookups
+/// that depend on secrets or secret-derived data. Violations are reported by Valgrind as operations
+/// relying on uninitialized values.
+///
+/// If Valgrind is disabled, it has no effect.
+///
+/// Use this function to verify that cryptographic operations perform constant-time arithmetic on sensitive data,
+/// ensuring the confidentiality of secrets and preventing information leakage through side channels.
+pub fn classify(ptr: anytype) void {
+ markSecret(ptr, .classify);
+}
+
+/// Mark a value as non-sensitive or public, indicating it's safe from side-channel attacks.
+///
+/// Signals that a value has been securely processed and is no longer confidential, allowing for
+/// relaxed handling without fear of information leakage through conditional jumps or lookups.
+pub fn declassify(ptr: anytype) void {
+ markSecret(ptr, .declassify);
+}
+
test eql {
const random = std.crypto.random;
const expect = std.testing.expect;
@@ -195,3 +243,39 @@ test "add and sub" {
try expectEqual(borrow, false);
}
}
+
+test classify {
+ const random = std.crypto.random;
+ const expect = std.testing.expect;
+
+ var secret: [32]u8 = undefined;
+ random.bytes(&secret);
+
+ // Input of the hash function is marked as secret
+ classify(&secret);
+
+ var out: [32]u8 = undefined;
+ std.crypto.hash.sha3.TurboShake128(null).hash(&secret, &out, .{});
+
+ // Output of the hash function is derived from secret data, so
+ // it will automatically be considered secret as well. But it can be
+ // declassified; the input itself will still be considered secret.
+ declassify(&out);
+
+ // Comparing public data in non-constant time is acceptable.
+ try expect(!std.mem.eql(u8, &out, &[_]u8{0} ** out.len));
+
+ // Comparing secret data must be done in constant time. The result
+ // is going to be considered as secret as well.
+ var res = std.crypto.utils.timingSafeEql([32]u8, out, secret);
+
+ // If we want to make a conditional jump based on a secret,
+ // it has to be declassified.
+ declassify(&res);
+ try expect(!res);
+
+ // Once a secret has been declassified, a comparison in
+ // non-constant time is fine.
+ declassify(&secret);
+ try expect(!std.mem.eql(u8, &out, &secret));
+}