authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-08-23 01:36:37+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-11-07 20:18:43+01:00
logbd07154242664e9d25ba60333e1a497562aa7c27
treee636fb1dce620013b041a0226509ae0057d4939f
parent03ae77b8b02eef4da09a6b0de12d6fb0192b81d4

Add mem.timingSafeEql() for constant-time array comparison

This is a trivial implementation that just does a or[xor] loop. However, this pattern is used by virtually all crypto libraries and in practice, even without assembly barriers, LLVM never turns it into code with conditional jumps, even if one of the parameters is constant. This has been verified to still be the case with LLVM 11.0.0.

7 files changed, 97 insertions(+), 25 deletions(-)

lib/std/crypto.zig+2
......@@ -130,6 +130,8 @@ pub const nacl = struct {
130130 pub const SealedBox = salsa20.SealedBox;
131131};
132132
133pub const utils = @import("crypto/utils.zig");
134
133135const std = @import("std.zig");
134136pub const randomBytes = std.os.getrandom;
135137
lib/std/crypto/bcrypt.zig+2-1
......@@ -11,6 +11,7 @@ const math = std.math;
1111const mem = std.mem;
1212const debug = std.debug;
1313const testing = std.testing;
14const utils = std.crypto.utils;
1415
1516const salt_length: usize = 16;
1617const salt_str_length: usize = 22;
......@@ -226,7 +227,7 @@ fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8)
226227 state.expand0(passwordZ);
227228 state.expand0(salt[0..]);
228229 }
229 mem.secureZero(u8, &password_buf);
230 utils.secureZero(u8, &password_buf);
230231
231232 var cdata = [6]u32{ 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; // "OrpheanBeholderScryDoubt"
232233 k = 0;
lib/std/crypto/ghash.zig+2-1
......@@ -10,6 +10,7 @@ const std = @import("../std.zig");
1010const assert = std.debug.assert;
1111const math = std.math;
1212const mem = std.mem;
13const utils = std.crypto.utils;
1314
1415/// GHASH is a universal hash function that features multiplication
1516/// by a fixed parameter within a Galois field.
......@@ -305,7 +306,7 @@ pub const Ghash = struct {
305306 mem.writeIntBig(u64, out[0..8], st.y1);
306307 mem.writeIntBig(u64, out[8..16], st.y0);
307308
308 mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Ghash)]);
309 utils.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Ghash)]);
309310 }
310311
311312 pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void {
lib/std/crypto/poly1305.zig+2-1
......@@ -4,6 +4,7 @@
44// The MIT license requires this copyright notice to be included in all copies
55// and substantial portions of the software.
66const std = @import("../std.zig");
7const utils = std.crypto.utils;
78const mem = std.mem;
89
910pub const Poly1305 = struct {
......@@ -195,7 +196,7 @@ pub const Poly1305 = struct {
195196 mem.writeIntLittle(u64, out[0..8], st.h[0]);
196197 mem.writeIntLittle(u64, out[8..16], st.h[1]);
197198
198 std.mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Poly1305)]);
199 utils.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Poly1305)]);
199200 }
200201
201202 pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void {
lib/std/crypto/salsa20.zig+3-2
......@@ -9,6 +9,7 @@ const crypto = std.crypto;
99const debug = std.debug;
1010const math = std.math;
1111const mem = std.mem;
12const utils = std.crypto.utils;
1213const Vector = std.meta.Vector;
1314
1415const Poly1305 = crypto.onetimeauth.Poly1305;
......@@ -414,7 +415,7 @@ pub const XSalsa20Poly1305 = struct {
414415 acc |= computedTag[i] ^ tag[i];
415416 }
416417 if (acc != 0) {
417 mem.secureZero(u8, &computedTag);
418 utils.secureZero(u8, &computedTag);
418419 return error.AuthenticationFailed;
419420 }
420421 mem.copy(u8, m[0..mlen0], block0[32..][0..mlen0]);
......@@ -532,7 +533,7 @@ pub const SealedBox = struct {
532533 const nonce = createNonce(ekp.public_key, public_key);
533534 mem.copy(u8, c[0..public_length], ekp.public_key[0..]);
534535 try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);
535 mem.secureZero(u8, ekp.secret_key[0..]);
536 utils.secureZero(u8, ekp.secret_key[0..]);
536537 }
537538
538539 /// Decrypt a message using a key pair.
lib/std/crypto/utils.zig created+86
......@@ -0,0 +1,86 @@
1const std = @import("../std.zig");
2const mem = std.mem;
3const testing = std.testing;
4
5/// Compares two arrays in constant time (for a given length) and returns whether they are equal.
6/// This function was designed to compare short cryptographic secrets (MACs, signatures).
7/// For all other applications, use mem.eql() instead.
8pub fn timingSafeEql(comptime T: type, a: T, b: T) bool {
9 switch (@typeInfo(T)) {
10 .Array => |info| {
11 const C = info.child;
12 if (@typeInfo(C) != .Int) {
13 @compileError("Elements to be compared must be integers");
14 }
15 var acc = @as(C, 0);
16 for (a) |x, i| {
17 acc |= x ^ b[i];
18 }
19 comptime const s = @typeInfo(C).Int.bits;
20 comptime const Cu = std.meta.Int(.unsigned, s);
21 comptime const Cext = std.meta.Int(.unsigned, s + 1);
22 return @bitCast(bool, @truncate(u1, (@as(Cext, @bitCast(Cu, acc)) -% 1) >> s));
23 },
24 .Vector => |info| {
25 const C = info.child;
26 if (@typeInfo(C) != .Int) {
27 @compileError("Elements to be compared must be integers");
28 }
29 const z = a ^ b;
30 var acc = @as(C, 0);
31 var i: usize = 0;
32 while (i < info.len) : (i += 1) {
33 acc |= z[i];
34 }
35 comptime const s = @typeInfo(C).Int.bits;
36 comptime const Cu = std.meta.Int(.unsigned, s);
37 comptime const Cext = std.meta.Int(.unsigned, s + 1);
38 return @bitCast(bool, @truncate(u1, (@as(Cext, @bitCast(Cu, acc)) -% 1) >> s));
39 },
40 else => {
41 @compileError("Only arrays and vectors can be compared");
42 },
43 }
44}
45
46/// Sets a slice to zeroes.
47/// Prevents the store from being optimized out.
48pub fn secureZero(comptime T: type, s: []T) void {
49 // NOTE: We do not use a volatile slice cast here since LLVM cannot
50 // see that it can be replaced by a memset.
51 const ptr = @ptrCast([*]volatile u8, s.ptr);
52 const length = s.len * @sizeOf(T);
53 @memset(ptr, 0, length);
54}
55
56test "crypto.utils.timingSafeEql" {
57 var a: [100]u8 = undefined;
58 var b: [100]u8 = undefined;
59 try std.crypto.randomBytes(a[0..]);
60 try std.crypto.randomBytes(b[0..]);
61 testing.expect(!timingSafeEql([100]u8, a, b));
62 mem.copy(u8, a[0..], b[0..]);
63 testing.expect(timingSafeEql([100]u8, a, b));
64}
65
66test "crypto.utils.timingSafeEql (vectors)" {
67 var a: [100]u8 = undefined;
68 var b: [100]u8 = undefined;
69 try std.crypto.randomBytes(a[0..]);
70 try std.crypto.randomBytes(b[0..]);
71 const v1: std.meta.Vector(100, u8) = a;
72 const v2: std.meta.Vector(100, u8) = b;
73 testing.expect(!timingSafeEql(std.meta.Vector(100, u8), v1, v2));
74 const v3: std.meta.Vector(100, u8) = a;
75 testing.expect(timingSafeEql(std.meta.Vector(100, u8), v1, v3));
76}
77
78test "crypto.utils.secureZero" {
79 var a = [_]u8{0xfe} ** 8;
80 var b = [_]u8{0xfe} ** 8;
81
82 mem.set(u8, a[0..], 0);
83 secureZero(u8, b[0..]);
84
85 testing.expectEqualSlices(u8, a[0..], b[0..]);
86}
lib/std/mem.zig-20
......@@ -342,26 +342,6 @@ test "mem.zeroes" {
342342 testing.expectEqual(@as(u8, 0), c.a);
343343}
344344
345/// Sets a slice to zeroes.
346/// Prevents the store from being optimized out.
347pub fn secureZero(comptime T: type, s: []T) void {
348 // NOTE: We do not use a volatile slice cast here since LLVM cannot
349 // see that it can be replaced by a memset.
350 const ptr = @ptrCast([*]volatile u8, s.ptr);
351 const length = s.len * @sizeOf(T);
352 @memset(ptr, 0, length);
353}
354
355test "mem.secureZero" {
356 var a = [_]u8{0xfe} ** 8;
357 var b = [_]u8{0xfe} ** 8;
358
359 set(u8, a[0..], 0);
360 secureZero(u8, b[0..]);
361
362 testing.expectEqualSlices(u8, a[0..], b[0..]);
363}
364
365345/// Initializes all fields of the struct with their default value, or zero values if no default value is present.
366346/// If the field is present in the provided initial values, it will have that value instead.
367347/// Structs are initialized recursively.