diff --git a/doc/langref.html.in b/doc/langref.html.in index 9d38e9b8565752e07bf59c728c02597e1013dd3c..9a80fbd194e9059f504300db7a78964deb1e598e 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5053,7 +5053,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val It may have any alignment, and it may have any element type.
{#syntax#}elem{#endsyntax#} is coerced to the element type of {#syntax#}dest{#endsyntax#}.
For securely zeroing out sensitive contents from memory, you should use - {#syntax#}std.crypto.utils.secureZero{#endsyntax#}
+ {#syntax#}std.crypto.secureZero{#endsyntax#} {#header_close#} {#header_open|@min#} diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 3dc48ce146e4ed854ecd3ec786d0034d382758ce..186f287fddcb7027523ff653fcbc10b1782101b0 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -2,6 +2,8 @@ const root = @import("root"); +pub const timing_safe = @import("crypto/timing_safe.zig"); + /// Authenticated Encryption with Associated Data pub const aead = struct { pub const aegis = struct { @@ -180,8 +182,6 @@ pub const nacl = struct { pub const SealedBox = salsa20.SealedBox; }; -pub const utils = @import("crypto/utils.zig"); - /// Finite-field arithmetic. pub const ff = @import("crypto/ff.zig"); @@ -301,7 +301,8 @@ test { _ = nacl.SecretBox; _ = nacl.SealedBox; - _ = utils; + _ = secureZero; + _ = timing_safe; _ = ff; _ = random; _ = errors; @@ -353,3 +354,36 @@ test "issue #4532: no index out of bounds" { try std.testing.expectEqual(out1, out2); } } + +/// Sets a slice to zeroes. +/// Prevents the store from being optimized out. +pub inline fn secureZero(comptime T: type, s: []volatile T) void { + @memset(s, 0); +} + +test secureZero { + var a = [_]u8{0xfe} ** 8; + var b = [_]u8{0xfe} ** 8; + + @memset(&a, 0); + secureZero(u8, &b); + + try std.testing.expectEqualSlices(u8, &a, &b); +} + +/// Deprecated in favor of `std.crypto`. To be removed after Zig 0.14.0 is released. +/// +/// As a reminder, never use "utils" in a namespace (in any programming language). +/// https://ziglang.org/documentation/0.13.0/#Avoid-Redundancy-in-Names +pub const utils = struct { + /// Deprecated in favor of `std.crypto.secureZero`. + pub const secureZero = std.crypto.secureZero; + /// Deprecated in favor of `std.crypto.timing_safe.eql`. + pub const timingSafeEql = timing_safe.eql; + /// Deprecated in favor of `std.crypto.timing_safe.compare`. + pub const timingSafeCompare = timing_safe.compare; + /// Deprecated in favor of `std.crypto.timing_safe.add`. + pub const timingSafeAdd = timing_safe.add; + /// Deprecated in favor of `std.crypto.timing_safe.sub`. + pub const timingSafeSub = timing_safe.sub; +}; diff --git a/lib/std/crypto/aegis.zig b/lib/std/crypto/aegis.zig index a72436668e80d15d8264f06f2dd8cd5547e2f50a..67cc13c8c09fad163c75a68760766b2348ec02bb 100644 --- a/lib/std/crypto/aegis.zig +++ b/lib/std/crypto/aegis.zig @@ -208,9 +208,9 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type { blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32])); } var computed_tag = state.mac(tag_bits, ad.len, m.len); - const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); + const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { - crypto.utils.secureZero(u8, &computed_tag); + crypto.secureZero(u8, &computed_tag); @memset(m, undefined); return error.AuthenticationFailed; } @@ -390,9 +390,9 @@ fn Aegis256Generic(comptime tag_bits: u9) type { blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst)); } var computed_tag = state.mac(tag_bits, ad.len, m.len); - const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); + const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { - crypto.utils.secureZero(u8, &computed_tag); + crypto.secureZero(u8, &computed_tag); @memset(m, undefined); return error.AuthenticationFailed; } diff --git a/lib/std/crypto/aes_gcm.zig b/lib/std/crypto/aes_gcm.zig index 2c28fb22d88388bb76bab1255e8a48bd55b97152..90717516c4a90baf07c1320dafba8f03c5dfe4cf 100644 --- a/lib/std/crypto/aes_gcm.zig +++ b/lib/std/crypto/aes_gcm.zig @@ -95,9 +95,9 @@ fn AesGcm(comptime Aes: anytype) type { computed_tag[i] ^= x; } - const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); + const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { - crypto.utils.secureZero(u8, &computed_tag); + crypto.secureZero(u8, &computed_tag); @memset(m, undefined); return error.AuthenticationFailed; } diff --git a/lib/std/crypto/aes_ocb.zig b/lib/std/crypto/aes_ocb.zig index 6cbb2e086720a4aad61f3bb1708ad63273a20054..a70ca5444681d23e4f13af0d97508df5a2a295e8 100644 --- a/lib/std/crypto/aes_ocb.zig +++ b/lib/std/crypto/aes_ocb.zig @@ -234,9 +234,9 @@ fn AesOcb(comptime Aes: anytype) type { var e = xorBlocks(xorBlocks(sum, offset), lx.dol); aes_enc_ctx.encrypt(&e, &e); var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad)); - const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); + const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { - crypto.utils.secureZero(u8, &computed_tag); + crypto.secureZero(u8, &computed_tag); @memset(m, undefined); return error.AuthenticationFailed; } diff --git a/lib/std/crypto/ascon.zig b/lib/std/crypto/ascon.zig index 7691f14590b4a4ab11564e637af3b413e645136a..8e5f48c9d28dc20a9064773314653dae10c65c70 100644 --- a/lib/std/crypto/ascon.zig +++ b/lib/std/crypto/ascon.zig @@ -152,7 +152,7 @@ pub fn State(comptime endian: std.builtin.Endian) type { /// Clear the entire state, disabling compiler optimizations. pub fn secureZero(self: *Self) void { - std.crypto.utils.secureZero(u64, &self.st); + std.crypto.secureZero(u64, &self.st); } /// Apply a reduced-round permutation to the state. diff --git a/lib/std/crypto/bcrypt.zig b/lib/std/crypto/bcrypt.zig index 30d83c9a1b4ff65663dd2d8d0c3390b9049d9e59..f3c30ab5ce589706689319900ce66781d6f9b062 100644 --- a/lib/std/crypto/bcrypt.zig +++ b/lib/std/crypto/bcrypt.zig @@ -9,7 +9,6 @@ const pwhash = crypto.pwhash; const testing = std.testing; const HmacSha512 = crypto.auth.hmac.sha2.HmacSha512; const Sha512 = crypto.hash.sha2.Sha512; -const utils = crypto.utils; const phc_format = @import("phc_encoding.zig"); @@ -446,7 +445,7 @@ pub fn bcrypt( state.expand0(passwordZ); state.expand0(salt[0..]); } - utils.secureZero(u8, &password_buf); + crypto.secureZero(u8, &password_buf); var cdata = [6]u32{ 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; // "OrpheanBeholderScryDoubt" k = 0; @@ -556,8 +555,8 @@ const pbkdf_prf = struct { } // zap - crypto.utils.secureZero(u32, &cdata); - crypto.utils.secureZero(u32, &state.subkeys); + crypto.secureZero(u32, &cdata); + crypto.secureZero(u32, &state.subkeys); return out; } diff --git a/lib/std/crypto/chacha20.zig b/lib/std/crypto/chacha20.zig index ccac14511a77a913322080d96b3cffb805808a22..59d37db824971d4a37eb77f5b72d6e62eef32de6 100644 --- a/lib/std/crypto/chacha20.zig +++ b/lib/std/crypto/chacha20.zig @@ -714,9 +714,9 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type { var computed_tag: [16]u8 = undefined; mac.final(computed_tag[0..]); - const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); + const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { - crypto.utils.secureZero(u8, &computed_tag); + crypto.secureZero(u8, &computed_tag); @memset(m, undefined); return error.AuthenticationFailed; } diff --git a/lib/std/crypto/ff.zig b/lib/std/crypto/ff.zig index 95698783bdda84e9e7513a1fb1d27662ad50ea71..ba37a428adf4eb998c68f5fc716bb082f9f5626e 100644 --- a/lib/std/crypto/ff.zig +++ b/lib/std/crypto/ff.zig @@ -225,12 +225,12 @@ pub fn Uint(comptime max_bits: comptime_int) type { /// Returns `true` if both integers are equal. pub fn eql(x: Self, y: Self) bool { - return crypto.utils.timingSafeEql([max_limbs_count]Limb, x.limbs_buffer, y.limbs_buffer); + return crypto.timing_safe.eql([max_limbs_count]Limb, x.limbs_buffer, y.limbs_buffer); } /// Compares two integers. pub fn compare(x: Self, y: Self) math.Order { - return crypto.utils.timingSafeCompare( + return crypto.timing_safe.compare( Limb, x.limbsConst(), y.limbsConst(), diff --git a/lib/std/crypto/ghash_polyval.zig b/lib/std/crypto/ghash_polyval.zig index b89e4b1c591c0e3e6238d01b1ea62374c49e45ea..bfb25446cde9990eba421f9d1f5c17666e6d8472 100644 --- a/lib/std/crypto/ghash_polyval.zig +++ b/lib/std/crypto/ghash_polyval.zig @@ -3,7 +3,6 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const math = std.math; const mem = std.mem; -const utils = std.crypto.utils; const Precomp = u128; @@ -403,7 +402,7 @@ fn Hash(comptime endian: std.builtin.Endian, comptime shift_key: bool) type { st.pad(); mem.writeInt(u128, out[0..16], st.acc, endian); - utils.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Self)]); + std.crypto.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Self)]); } /// Compute the GHASH of a message. diff --git a/lib/std/crypto/isap.zig b/lib/std/crypto/isap.zig index 2db733a8aca33fc1b61116353d45d8ac900fbe10..73fd959b152f6927a1435e47af725d599f1cddca 100644 --- a/lib/std/crypto/isap.zig +++ b/lib/std/crypto/isap.zig @@ -158,9 +158,9 @@ pub const IsapA128A = struct { /// Contents of `m` are undefined if an error is returned. pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, key: [key_length]u8) AuthenticationError!void { var computed_tag = mac(c, ad, npub, key); - const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); + const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { - crypto.utils.secureZero(u8, &computed_tag); + crypto.secureZero(u8, &computed_tag); @memset(m, undefined); return error.AuthenticationFailed; } diff --git a/lib/std/crypto/keccak_p.zig b/lib/std/crypto/keccak_p.zig index c13171227ccf81287a540bd54df2d5696d7366ac..0c522d5148c1b18fbd32207c491daad349a6d850 100644 --- a/lib/std/crypto/keccak_p.zig +++ b/lib/std/crypto/keccak_p.zig @@ -132,7 +132,7 @@ pub fn KeccakF(comptime f: u11) type { /// Clear the entire state, disabling compiler optimizations. pub fn secureZero(self: *Self) void { - std.crypto.utils.secureZero(T, &self.st); + std.crypto.secureZero(T, &self.st); } inline fn round(self: *Self, rc: T) void { diff --git a/lib/std/crypto/ml_kem.zig b/lib/std/crypto/ml_kem.zig index 23ea7bb8ec3ec72e2103bf142d55951e6190247a..00eade1c718323849677a40b100909b01b3f19a0 100644 --- a/lib/std/crypto/ml_kem.zig +++ b/lib/std/crypto/ml_kem.zig @@ -1508,7 +1508,7 @@ fn Mat(comptime K: u8) type { // Returns `true` if a ≠ b. fn ctneq(comptime len: usize, a: [len]u8, b: [len]u8) u1 { - return 1 - @intFromBool(crypto.utils.timingSafeEql([len]u8, a, b)); + return 1 - @intFromBool(crypto.timing_safe.eql([len]u8, a, b)); } // Copy src into dst given b = 1. diff --git a/lib/std/crypto/pcurves/common.zig b/lib/std/crypto/pcurves/common.zig index f2c21be8c5b7515a011c82bdcdb4647ed78c9b3f..1099e11347adc8d02accde86be5e18dc4b39e82c 100644 --- a/lib/std/crypto/pcurves/common.zig +++ b/lib/std/crypto/pcurves/common.zig @@ -57,7 +57,7 @@ pub fn Field(comptime params: FieldParams) type { mem.writeInt(std.meta.Int(.unsigned, encoded_length * 8), &fos, field_order, .little); break :fos fos; }; - if (crypto.utils.timingSafeCompare(u8, &s, &field_order_s, .little) != .lt) { + if (crypto.timing_safe.compare(u8, &s, &field_order_s, .little) != .lt) { return error.NonCanonical; } } diff --git a/lib/std/crypto/poly1305.zig b/lib/std/crypto/poly1305.zig index 3aa3d5b78f348269ee4a694c18c75be1a141a5c2..254e19ae02ca7fd379f426aed6eae09103127b65 100644 --- a/lib/std/crypto/poly1305.zig +++ b/lib/std/crypto/poly1305.zig @@ -1,5 +1,4 @@ const std = @import("../std.zig"); -const utils = std.crypto.utils; const mem = std.mem; const mulWide = std.math.mulWide; @@ -185,7 +184,7 @@ pub const Poly1305 = struct { mem.writeInt(u64, out[0..8], st.h[0], .little); mem.writeInt(u64, out[8..16], st.h[1], .little); - utils.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Poly1305)]); + std.crypto.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Poly1305)]); } pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void { diff --git a/lib/std/crypto/salsa20.zig b/lib/std/crypto/salsa20.zig index 40b190f41a8eaac755797f6bcd4305fba2a08494..d2ace6446f794fb0924b4e26e9913ea52a704468 100644 --- a/lib/std/crypto/salsa20.zig +++ b/lib/std/crypto/salsa20.zig @@ -4,7 +4,6 @@ const crypto = std.crypto; const debug = std.debug; const math = std.math; const mem = std.mem; -const utils = std.crypto.utils; const Poly1305 = crypto.onetimeauth.Poly1305; const Blake2b = crypto.hash.blake2.Blake2b; @@ -419,9 +418,9 @@ pub const XSalsa20Poly1305 = struct { var computed_tag: [tag_length]u8 = undefined; mac.final(&computed_tag); - const verify = utils.timingSafeEql([tag_length]u8, computed_tag, tag); + const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); if (!verify) { - utils.secureZero(u8, &computed_tag); + crypto.secureZero(u8, &computed_tag); @memset(m, undefined); return error.AuthenticationFailed; } @@ -540,7 +539,7 @@ pub const SealedBox = struct { const nonce = createNonce(ekp.public_key, public_key); c[0..public_length].* = ekp.public_key; try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key); - utils.secureZero(u8, ekp.secret_key[0..]); + crypto.secureZero(u8, ekp.secret_key[0..]); } /// Decrypt a message using a key pair. diff --git a/lib/std/crypto/timing_safe.zig b/lib/std/crypto/timing_safe.zig new file mode 100644 index 0000000000000000000000000000000000000000..4adeb9cf69de691be46fbf9085a121b067f703c1 --- /dev/null +++ b/lib/std/crypto/timing_safe.zig @@ -0,0 +1,197 @@ +//! Please see this accepted proposal for the long-term plans regarding +//! constant-time operations in Zig: https://github.com/ziglang/zig/issues/1776 + +const std = @import("../std.zig"); +const assert = std.debug.assert; +const Endian = std.builtin.Endian; +const Order = std.math.Order; + +/// Compares two arrays in constant time (for a given length) and returns whether they are equal. +/// This function was designed to compare short cryptographic secrets (MACs, signatures). +/// For all other applications, use mem.eql() instead. +pub fn eql(comptime T: type, a: T, b: T) bool { + switch (@typeInfo(T)) { + .Array => |info| { + const C = info.child; + if (@typeInfo(C) != .Int) { + @compileError("Elements to be compared must be integers"); + } + var acc = @as(C, 0); + for (a, 0..) |x, i| { + acc |= x ^ b[i]; + } + const s = @typeInfo(C).Int.bits; + const Cu = std.meta.Int(.unsigned, s); + const Cext = std.meta.Int(.unsigned, s + 1); + return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s)))); + }, + .Vector => |info| { + const C = info.child; + if (@typeInfo(C) != .Int) { + @compileError("Elements to be compared must be integers"); + } + const acc = @reduce(.Or, a ^ b); + const s = @typeInfo(C).Int.bits; + const Cu = std.meta.Int(.unsigned, s); + const Cext = std.meta.Int(.unsigned, s + 1); + return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s)))); + }, + else => { + @compileError("Only arrays and vectors can be compared"); + }, + } +} + +/// Compare two integers serialized as arrays of the same size, in constant time. +/// Returns .lt if ab and .eq if a=b +pub fn compare(comptime T: type, a: []const T, b: []const T, endian: Endian) Order { + assert(a.len == b.len); + const bits = switch (@typeInfo(T)) { + .Int => |cinfo| if (cinfo.signedness != .unsigned) @compileError("Elements to be compared must be unsigned") else cinfo.bits, + else => @compileError("Elements to be compared must be integers"), + }; + const Cext = std.meta.Int(.unsigned, bits + 1); + var gt: T = 0; + var eq: T = 1; + if (endian == .little) { + var i = a.len; + while (i != 0) { + i -= 1; + const x1 = a[i]; + const x2 = b[i]; + gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq; + eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits)); + } + } else { + for (a, 0..) |x1, i| { + const x2 = b[i]; + gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq; + eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits)); + } + } + if (gt != 0) { + return Order.gt; + } else if (eq != 0) { + return Order.eq; + } + return Order.lt; +} + +/// Add two integers serialized as arrays of the same size, in constant time. +/// The result is stored into `result`, and `true` is returned if an overflow occurred. +pub fn add(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { + const len = a.len; + assert(len == b.len and len == result.len); + var carry: u1 = 0; + if (endian == .little) { + var i: usize = 0; + while (i < len) : (i += 1) { + const ov1 = @addWithOverflow(a[i], b[i]); + const ov2 = @addWithOverflow(ov1[0], carry); + result[i] = ov2[0]; + carry = ov1[1] | ov2[1]; + } + } else { + var i: usize = len; + while (i != 0) { + i -= 1; + const ov1 = @addWithOverflow(a[i], b[i]); + const ov2 = @addWithOverflow(ov1[0], carry); + result[i] = ov2[0]; + carry = ov1[1] | ov2[1]; + } + } + return @as(bool, @bitCast(carry)); +} + +/// Subtract two integers serialized as arrays of the same size, in constant time. +/// The result is stored into `result`, and `true` is returned if an underflow occurred. +pub fn sub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { + const len = a.len; + assert(len == b.len and len == result.len); + var borrow: u1 = 0; + if (endian == .little) { + var i: usize = 0; + while (i < len) : (i += 1) { + const ov1 = @subWithOverflow(a[i], b[i]); + const ov2 = @subWithOverflow(ov1[0], borrow); + result[i] = ov2[0]; + borrow = ov1[1] | ov2[1]; + } + } else { + var i: usize = len; + while (i != 0) { + i -= 1; + const ov1 = @subWithOverflow(a[i], b[i]); + const ov2 = @subWithOverflow(ov1[0], borrow); + result[i] = ov2[0]; + borrow = ov1[1] | ov2[1]; + } + } + return @as(bool, @bitCast(borrow)); +} + +test eql { + const random = std.crypto.random; + const expect = std.testing.expect; + var a: [100]u8 = undefined; + var b: [100]u8 = undefined; + random.bytes(a[0..]); + random.bytes(b[0..]); + try expect(!eql([100]u8, a, b)); + a = b; + try expect(eql([100]u8, a, b)); +} + +test "eql (vectors)" { + if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest; + + const random = std.crypto.random; + const expect = std.testing.expect; + var a: [100]u8 = undefined; + var b: [100]u8 = undefined; + random.bytes(a[0..]); + random.bytes(b[0..]); + const v1: @Vector(100, u8) = a; + const v2: @Vector(100, u8) = b; + try expect(!eql(@Vector(100, u8), v1, v2)); + const v3: @Vector(100, u8) = a; + try expect(eql(@Vector(100, u8), v1, v3)); +} + +test compare { + const expectEqual = std.testing.expectEqual; + var a = [_]u8{10} ** 32; + var b = [_]u8{10} ** 32; + try expectEqual(compare(u8, &a, &b, .big), .eq); + try expectEqual(compare(u8, &a, &b, .little), .eq); + a[31] = 1; + try expectEqual(compare(u8, &a, &b, .big), .lt); + try expectEqual(compare(u8, &a, &b, .little), .lt); + a[0] = 20; + try expectEqual(compare(u8, &a, &b, .big), .gt); + try expectEqual(compare(u8, &a, &b, .little), .lt); +} + +test "add and sub" { + const expectEqual = std.testing.expectEqual; + const expectEqualSlices = std.testing.expectEqualSlices; + const random = std.crypto.random; + const len = 32; + var a: [len]u8 = undefined; + var b: [len]u8 = undefined; + var c: [len]u8 = undefined; + const zero = [_]u8{0} ** len; + var iterations: usize = 100; + while (iterations != 0) : (iterations -= 1) { + random.bytes(&a); + random.bytes(&b); + const endian = if (iterations % 2 == 0) Endian.big else Endian.little; + _ = sub(u8, &a, &b, &c, endian); // a-b + _ = add(u8, &c, &b, &c, endian); // (a-b)+b + try expectEqualSlices(u8, &c, &a); + const borrow = sub(u8, &c, &a, &c, endian); // ((a-b)+b)-a + try expectEqualSlices(u8, &c, &zero); + try expectEqual(borrow, false); + } +} diff --git a/lib/std/crypto/tlcsprng.zig b/lib/std/crypto/tlcsprng.zig index ac359ce25a38cf41389f713b59be59afed873f56..672d6c2ecb9f05a98d84855595adf476888a7664 100644 --- a/lib/std/crypto/tlcsprng.zig +++ b/lib/std/crypto/tlcsprng.zig @@ -137,7 +137,7 @@ fn childAtForkHandler() callconv(.C) void { // The atfork handler is global, this function may be called after // fork()-ing threads that never initialized the CSPRNG context. if (wipe_mem.len == 0) return; - std.crypto.utils.secureZero(u8, wipe_mem); + std.crypto.secureZero(u8, wipe_mem); } fn fillWithCsprng(buffer: []u8) void { @@ -159,7 +159,7 @@ fn initAndFill(buffer: []u8) void { const ctx = @as(*Context, @ptrCast(wipe_mem.ptr)); ctx.rng = Rng.init(seed); - std.crypto.utils.secureZero(u8, &seed); + std.crypto.secureZero(u8, &seed); // This is at the end so that accidental recursive dependencies result // in stack overflows instead of invalid random data. diff --git a/lib/std/crypto/utils.zig b/lib/std/crypto/utils.zig deleted file mode 100644 index fd5544c45b94f3359b004914aa31618128c7ab24..0000000000000000000000000000000000000000 --- a/lib/std/crypto/utils.zig +++ /dev/null @@ -1,206 +0,0 @@ -const std = @import("../std.zig"); -const debug = std.debug; -const mem = std.mem; -const random = std.crypto.random; -const testing = std.testing; - -const Endian = std.builtin.Endian; -const Order = std.math.Order; - -/// Compares two arrays in constant time (for a given length) and returns whether they are equal. -/// This function was designed to compare short cryptographic secrets (MACs, signatures). -/// For all other applications, use mem.eql() instead. -pub fn timingSafeEql(comptime T: type, a: T, b: T) bool { - switch (@typeInfo(T)) { - .Array => |info| { - const C = info.child; - if (@typeInfo(C) != .Int) { - @compileError("Elements to be compared must be integers"); - } - var acc = @as(C, 0); - for (a, 0..) |x, i| { - acc |= x ^ b[i]; - } - const s = @typeInfo(C).Int.bits; - const Cu = std.meta.Int(.unsigned, s); - const Cext = std.meta.Int(.unsigned, s + 1); - return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s)))); - }, - .Vector => |info| { - const C = info.child; - if (@typeInfo(C) != .Int) { - @compileError("Elements to be compared must be integers"); - } - const acc = @reduce(.Or, a ^ b); - const s = @typeInfo(C).Int.bits; - const Cu = std.meta.Int(.unsigned, s); - const Cext = std.meta.Int(.unsigned, s + 1); - return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s)))); - }, - else => { - @compileError("Only arrays and vectors can be compared"); - }, - } -} - -/// Compare two integers serialized as arrays of the same size, in constant time. -/// Returns .lt if ab and .eq if a=b -pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: Endian) Order { - debug.assert(a.len == b.len); - const bits = switch (@typeInfo(T)) { - .Int => |cinfo| if (cinfo.signedness != .unsigned) @compileError("Elements to be compared must be unsigned") else cinfo.bits, - else => @compileError("Elements to be compared must be integers"), - }; - const Cext = std.meta.Int(.unsigned, bits + 1); - var gt: T = 0; - var eq: T = 1; - if (endian == .little) { - var i = a.len; - while (i != 0) { - i -= 1; - const x1 = a[i]; - const x2 = b[i]; - gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq; - eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits)); - } - } else { - for (a, 0..) |x1, i| { - const x2 = b[i]; - gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq; - eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits)); - } - } - if (gt != 0) { - return Order.gt; - } else if (eq != 0) { - return Order.eq; - } - return Order.lt; -} - -/// Add two integers serialized as arrays of the same size, in constant time. -/// The result is stored into `result`, and `true` is returned if an overflow occurred. -pub fn timingSafeAdd(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { - const len = a.len; - debug.assert(len == b.len and len == result.len); - var carry: u1 = 0; - if (endian == .little) { - var i: usize = 0; - while (i < len) : (i += 1) { - const ov1 = @addWithOverflow(a[i], b[i]); - const ov2 = @addWithOverflow(ov1[0], carry); - result[i] = ov2[0]; - carry = ov1[1] | ov2[1]; - } - } else { - var i: usize = len; - while (i != 0) { - i -= 1; - const ov1 = @addWithOverflow(a[i], b[i]); - const ov2 = @addWithOverflow(ov1[0], carry); - result[i] = ov2[0]; - carry = ov1[1] | ov2[1]; - } - } - return @as(bool, @bitCast(carry)); -} - -/// Subtract two integers serialized as arrays of the same size, in constant time. -/// The result is stored into `result`, and `true` is returned if an underflow occurred. -pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { - const len = a.len; - debug.assert(len == b.len and len == result.len); - var borrow: u1 = 0; - if (endian == .little) { - var i: usize = 0; - while (i < len) : (i += 1) { - const ov1 = @subWithOverflow(a[i], b[i]); - const ov2 = @subWithOverflow(ov1[0], borrow); - result[i] = ov2[0]; - borrow = ov1[1] | ov2[1]; - } - } else { - var i: usize = len; - while (i != 0) { - i -= 1; - const ov1 = @subWithOverflow(a[i], b[i]); - const ov2 = @subWithOverflow(ov1[0], borrow); - result[i] = ov2[0]; - borrow = ov1[1] | ov2[1]; - } - } - return @as(bool, @bitCast(borrow)); -} - -/// Sets a slice to zeroes. -/// Prevents the store from being optimized out. -pub inline fn secureZero(comptime T: type, s: []T) void { - @memset(@as([]volatile T, s), 0); -} - -test timingSafeEql { - var a: [100]u8 = undefined; - var b: [100]u8 = undefined; - random.bytes(a[0..]); - random.bytes(b[0..]); - try testing.expect(!timingSafeEql([100]u8, a, b)); - a = b; - try testing.expect(timingSafeEql([100]u8, a, b)); -} - -test "timingSafeEql (vectors)" { - if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest; - - var a: [100]u8 = undefined; - var b: [100]u8 = undefined; - random.bytes(a[0..]); - random.bytes(b[0..]); - const v1: @Vector(100, u8) = a; - const v2: @Vector(100, u8) = b; - try testing.expect(!timingSafeEql(@Vector(100, u8), v1, v2)); - const v3: @Vector(100, u8) = a; - try testing.expect(timingSafeEql(@Vector(100, u8), v1, v3)); -} - -test timingSafeCompare { - var a = [_]u8{10} ** 32; - var b = [_]u8{10} ** 32; - try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .eq); - try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .eq); - a[31] = 1; - try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .lt); - try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .lt); - a[0] = 20; - try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .gt); - try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .lt); -} - -test "timingSafe{Add,Sub}" { - const len = 32; - var a: [len]u8 = undefined; - var b: [len]u8 = undefined; - var c: [len]u8 = undefined; - const zero = [_]u8{0} ** len; - var iterations: usize = 100; - while (iterations != 0) : (iterations -= 1) { - random.bytes(&a); - random.bytes(&b); - const endian = if (iterations % 2 == 0) Endian.big else Endian.little; - _ = timingSafeSub(u8, &a, &b, &c, endian); // a-b - _ = timingSafeAdd(u8, &c, &b, &c, endian); // (a-b)+b - try testing.expectEqualSlices(u8, &c, &a); - const borrow = timingSafeSub(u8, &c, &a, &c, endian); // ((a-b)+b)-a - try testing.expectEqualSlices(u8, &c, &zero); - try testing.expectEqual(borrow, false); - } -} - -test secureZero { - var a = [_]u8{0xfe} ** 8; - var b = [_]u8{0xfe} ** 8; - - @memset(a[0..], 0); - secureZero(u8, b[0..]); - - try testing.expectEqualSlices(u8, a[0..], b[0..]); -}