| author | |
| committer | |
| log | ba967ae9a1eada3a08803470f557bafcfb69151e |
| tree | 34ea140e3b9ee6ceec225e20c2dc483feb75dc81 |
| parent | 8f47e8feb60e60cc85674a9eef281197ed8a7c27 |
| parent | 7f9e3e419c24ba51e80a4c41bcbefc820f7e0a88 |
| signature |
Add mem.timingSafeEql() for constant-time array comparison7 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 | }; |
| 132 | 132 | ||
| 133 | pub const utils = @import("crypto/utils.zig"); | ||
| 134 | |||
| 133 | const std = @import("std.zig"); | 135 | const std = @import("std.zig"); |
| 134 | pub const randomBytes = std.os.getrandom; | 136 | pub const randomBytes = std.os.getrandom; |
| 135 | 137 |
lib/std/crypto/bcrypt.zig+2-1| ... | @@ -11,6 +11,7 @@ const math = std.math; | ... | @@ -11,6 +11,7 @@ const math = std.math; |
| 11 | const mem = std.mem; | 11 | const mem = std.mem; |
| 12 | const debug = std.debug; | 12 | const debug = std.debug; |
| 13 | const testing = std.testing; | 13 | const testing = std.testing; |
| 14 | const utils = std.crypto.utils; | ||
| 14 | 15 | ||
| 15 | const salt_length: usize = 16; | 16 | const salt_length: usize = 16; |
| 16 | const salt_str_length: usize = 22; | 17 | const 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); |
| 230 | 231 | ||
| 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"); |
| 10 | const assert = std.debug.assert; | 10 | const assert = std.debug.assert; |
| 11 | const math = std.math; | 11 | const math = std.math; |
| 12 | const mem = std.mem; | 12 | const mem = std.mem; |
| 13 | const utils = std.crypto.utils; | ||
| 13 | 14 | ||
| 14 | /// GHASH is a universal hash function that features multiplication | 15 | /// 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); |
| 307 | 308 | ||
| 308 | mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Ghash)]); | 309 | utils.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Ghash)]); |
| 309 | } | 310 | } |
| 310 | 311 | ||
| 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 copies | 4 | // 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. |
| 6 | const std = @import("../std.zig"); | 6 | const std = @import("../std.zig"); |
| 7 | const utils = std.crypto.utils; | ||
| 7 | const mem = std.mem; | 8 | const mem = std.mem; |
| 8 | 9 | ||
| 9 | pub const Poly1305 = struct { | 10 | pub 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]); |
| 197 | 198 | ||
| 198 | std.mem.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Poly1305)]); | 199 | utils.secureZero(u8, @ptrCast([*]u8, st)[0..@sizeOf(Poly1305)]); |
| 199 | } | 200 | } |
| 200 | 201 | ||
| 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; |
| 9 | const debug = std.debug; | 9 | const debug = std.debug; |
| 10 | const math = std.math; | 10 | const math = std.math; |
| 11 | const mem = std.mem; | 11 | const mem = std.mem; |
| 12 | const utils = std.crypto.utils; | ||
| 12 | const Vector = std.meta.Vector; | 13 | const Vector = std.meta.Vector; |
| 13 | 14 | ||
| 14 | const Poly1305 = crypto.onetimeauth.Poly1305; | 15 | const 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 | } |
| 537 | 538 | ||
| 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 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const mem = std.mem; | ||
| 3 | const 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. | ||
| 8 | pub 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. | ||
| 43 | pub 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 | |||
| 51 | test "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 | |||
| 61 | test "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 | |||
| 73 | test "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 | } |
| 344 | 344 | ||
| 345 | /// Sets a slice to zeroes. | ||
| 346 | /// Prevents the store from being optimized out. | ||
| 347 | pub 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 | |||
| 355 | test "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. |