authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-11-16 13:39:31-08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-11-16 13:39:31-08:00
logba967ae9a1eada3a08803470f557bafcfb69151e
tree34ea140e3b9ee6ceec225e20c2dc483feb75dc81
parent8f47e8feb60e60cc85674a9eef281197ed8a7c27
parent7f9e3e419c24ba51e80a4c41bcbefc820f7e0a88
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7002 from jedisct1/timingSafeEqlMinimal

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

7 files changed, 92 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+81
...@@ -0,0 +1,81 @@
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 acc = @reduce(.Or, a ^ b);
30 comptime const s = @typeInfo(C).Int.bits;
31 comptime const Cu = std.meta.Int(.unsigned, s);
32 comptime const Cext = std.meta.Int(.unsigned, s + 1);
33 return @bitCast(bool, @truncate(u1, (@as(Cext, @bitCast(Cu, acc)) -% 1) >> s));
34 },
35 else => {
36 @compileError("Only arrays and vectors can be compared");
37 },
38 }
39}
40
41/// Sets a slice to zeroes.
42/// Prevents the store from being optimized out.
43pub fn secureZero(comptime T: type, s: []T) void {
44 // NOTE: We do not use a volatile slice cast here since LLVM cannot
45 // see that it can be replaced by a memset.
46 const ptr = @ptrCast([*]volatile u8, s.ptr);
47 const length = s.len * @sizeOf(T);
48 @memset(ptr, 0, length);
49}
50
51test "crypto.utils.timingSafeEql" {
52 var a: [100]u8 = undefined;
53 var b: [100]u8 = undefined;
54 try std.crypto.randomBytes(a[0..]);
55 try std.crypto.randomBytes(b[0..]);
56 testing.expect(!timingSafeEql([100]u8, a, b));
57 mem.copy(u8, a[0..], b[0..]);
58 testing.expect(timingSafeEql([100]u8, a, b));
59}
60
61test "crypto.utils.timingSafeEql (vectors)" {
62 var a: [100]u8 = undefined;
63 var b: [100]u8 = undefined;
64 try std.crypto.randomBytes(a[0..]);
65 try std.crypto.randomBytes(b[0..]);
66 const v1: std.meta.Vector(100, u8) = a;
67 const v2: std.meta.Vector(100, u8) = b;
68 testing.expect(!timingSafeEql(std.meta.Vector(100, u8), v1, v2));
69 const v3: std.meta.Vector(100, u8) = a;
70 testing.expect(timingSafeEql(std.meta.Vector(100, u8), v1, v3));
71}
72
73test "crypto.utils.secureZero" {
74 var a = [_]u8{0xfe} ** 8;
75 var b = [_]u8{0xfe} ** 8;
76
77 mem.set(u8, a[0..], 0);
78 secureZero(u8, b[0..]);
79
80 testing.expectEqualSlices(u8, a[0..], b[0..]);
81}
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.