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 {...@@ -130,6 +130,8 @@ pub const nacl = struct {
130 pub const SealedBox = salsa20.SealedBox;130 pub const SealedBox = salsa20.SealedBox;
131};131};
132132
133pub const utils = @import("crypto/utils.zig");
134
133const std = @import("std.zig");135const std = @import("std.zig");
134pub const randomBytes = std.os.getrandom;136pub const randomBytes = std.os.getrandom;
135137
lib/std/crypto/bcrypt.zig+2-1
...@@ -11,6 +11,7 @@ const math = std.math;...@@ -11,6 +11,7 @@ const math = std.math;
11const mem = std.mem;11const mem = std.mem;
12const debug = std.debug;12const debug = std.debug;
13const testing = std.testing;13const testing = std.testing;
14const utils = std.crypto.utils;
1415
15const salt_length: usize = 16;16const salt_length: usize = 16;
16const salt_str_length: usize = 22;17const salt_str_length: usize = 22;
...@@ -226,7 +227,7 @@ fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8)...@@ -226,7 +227,7 @@ fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8)
226 state.expand0(passwordZ);227 state.expand0(passwordZ);
227 state.expand0(salt[0..]);228 state.expand0(salt[0..]);
228 }229 }
229 mem.secureZero(u8, &password_buf);230 utils.secureZero(u8, &password_buf);
230231
231 var cdata = [6]u32{ 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; // "OrpheanBeholderScryDoubt"232 var cdata = [6]u32{ 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; // "OrpheanBeholderScryDoubt"
232 k = 0;233 k = 0;
lib/std/crypto/ghash.zig+2-1
...@@ -10,6 +10,7 @@ const std = @import("../std.zig");...@@ -10,6 +10,7 @@ const std = @import("../std.zig");
10const assert = std.debug.assert;10const assert = std.debug.assert;
11const math = std.math;11const math = std.math;
12const mem = std.mem;12const mem = std.mem;
13const utils = std.crypto.utils;
1314
14/// GHASH is a universal hash function that features multiplication15/// GHASH is a universal hash function that features multiplication
15/// by a fixed parameter within a Galois field.16/// by a fixed parameter within a Galois field.
...@@ -305,7 +306,7 @@ pub const Ghash = struct {...@@ -305,7 +306,7 @@ pub const Ghash = struct {
305 mem.writeIntBig(u64, out[0..8], st.y1);306 mem.writeIntBig(u64, out[0..8], st.y1);
306 mem.writeIntBig(u64, out[8..16], st.y0);307 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)]);
309 }310 }
310311
311 pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void {312 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 @@...@@ -4,6 +4,7 @@
4// The MIT license requires this copyright notice to be included in all copies4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.5// and substantial portions of the software.
6const std = @import("../std.zig");6const std = @import("../std.zig");
7const utils = std.crypto.utils;
7const mem = std.mem;8const mem = std.mem;
89
9pub const Poly1305 = struct {10pub const Poly1305 = struct {
...@@ -195,7 +196,7 @@ pub const Poly1305 = struct {...@@ -195,7 +196,7 @@ pub const Poly1305 = struct {
195 mem.writeIntLittle(u64, out[0..8], st.h[0]);196 mem.writeIntLittle(u64, out[0..8], st.h[0]);
196 mem.writeIntLittle(u64, out[8..16], st.h[1]);197 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)]);
199 }200 }
200201
201 pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void {202 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;...@@ -9,6 +9,7 @@ const crypto = std.crypto;
9const debug = std.debug;9const debug = std.debug;
10const math = std.math;10const math = std.math;
11const mem = std.mem;11const mem = std.mem;
12const utils = std.crypto.utils;
12const Vector = std.meta.Vector;13const Vector = std.meta.Vector;
1314
14const Poly1305 = crypto.onetimeauth.Poly1305;15const Poly1305 = crypto.onetimeauth.Poly1305;
...@@ -414,7 +415,7 @@ pub const XSalsa20Poly1305 = struct {...@@ -414,7 +415,7 @@ pub const XSalsa20Poly1305 = struct {
414 acc |= computedTag[i] ^ tag[i];415 acc |= computedTag[i] ^ tag[i];
415 }416 }
416 if (acc != 0) {417 if (acc != 0) {
417 mem.secureZero(u8, &computedTag);418 utils.secureZero(u8, &computedTag);
418 return error.AuthenticationFailed;419 return error.AuthenticationFailed;
419 }420 }
420 mem.copy(u8, m[0..mlen0], block0[32..][0..mlen0]);421 mem.copy(u8, m[0..mlen0], block0[32..][0..mlen0]);
...@@ -532,7 +533,7 @@ pub const SealedBox = struct {...@@ -532,7 +533,7 @@ pub const SealedBox = struct {
532 const nonce = createNonce(ekp.public_key, public_key);533 const nonce = createNonce(ekp.public_key, public_key);
533 mem.copy(u8, c[0..public_length], ekp.public_key[0..]);534 mem.copy(u8, c[0..public_length], ekp.public_key[0..]);
534 try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);535 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..]);
536 }537 }
537538
538 /// Decrypt a message using a key pair.539 /// 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" {...@@ -342,26 +342,6 @@ test "mem.zeroes" {
342 testing.expectEqual(@as(u8, 0), c.a);342 testing.expectEqual(@as(u8, 0), c.a);
343}343}
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
365/// Initializes all fields of the struct with their default value, or zero values if no default value is present.345/// Initializes all fields of the struct with their default value, or zero values if no default value is present.
366/// If the field is present in the provided initial values, it will have that value instead.346/// If the field is present in the provided initial values, it will have that value instead.
367/// Structs are initialized recursively.347/// Structs are initialized recursively.