From 54151428e5236a0612fe36837ed5a6c1e28aec7a Mon Sep 17 00:00:00 2001
From: Andrew Kelley
{#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 3dc48ce146..186f287fdd 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 a72436668e..67cc13c8c0 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 2c28fb22d8..90717516c4 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 6cbb2e0867..a70ca54446 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 7691f14590..8e5f48c9d2 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 30d83c9a1b..f3c30ab5ce 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 ccac14511a..59d37db824 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 95698783bd..ba37a428ad 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 b89e4b1c59..bfb25446cd 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 2db733a8ac..73fd959b15 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 c13171227c..0c522d5148 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 23ea7bb8ec..00eade1c71 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 f2c21be8c5..1099e11347 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 3aa3d5b78f..254e19ae02 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 40b190f41a..d2ace6446f 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/utils.zig b/lib/std/crypto/timing_safe.zig similarity index 71% rename from lib/std/crypto/utils.zig rename to lib/std/crypto/timing_safe.zig index fd5544c45b..4adeb9cf69 100644 --- a/lib/std/crypto/utils.zig +++ b/lib/std/crypto/timing_safe.zig @@ -1,16 +1,15 @@ -const std = @import("../std.zig"); -const debug = std.debug; -const mem = std.mem; -const random = std.crypto.random; -const testing = std.testing; +//! 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 timingSafeEql(comptime T: type, a: T, b: T) bool { +pub fn eql(comptime T: type, a: T, b: T) bool { switch (@typeInfo(T)) { .Array => |info| { const C = info.child; @@ -45,8 +44,8 @@ pub fn timingSafeEql(comptime T: type, a: T, b: T) bool { /// 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); +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"), @@ -80,9 +79,9 @@ pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: E /// 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 { +pub fn add(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); + assert(len == b.len and len == result.len); var carry: u1 = 0; if (endian == .little) { var i: usize = 0; @@ -107,9 +106,9 @@ pub fn timingSafeAdd(comptime T: type, a: []const T, b: []const T, result: []T, /// 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 { +pub fn sub(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); + assert(len == b.len and len == result.len); var borrow: u1 = 0; if (endian == .little) { var i: usize = 0; @@ -132,50 +131,52 @@ pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T, 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 { +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 testing.expect(!timingSafeEql([100]u8, a, b)); + try expect(!eql([100]u8, a, b)); a = b; - try testing.expect(timingSafeEql([100]u8, a, b)); + try expect(eql([100]u8, a, b)); } -test "timingSafeEql (vectors)" { +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 testing.expect(!timingSafeEql(@Vector(100, u8), v1, v2)); + try expect(!eql(@Vector(100, u8), v1, v2)); const v3: @Vector(100, u8) = a; - try testing.expect(timingSafeEql(@Vector(100, u8), v1, v3)); + try expect(eql(@Vector(100, u8), v1, v3)); } -test timingSafeCompare { +test compare { + const expectEqual = std.testing.expectEqual; 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); + try expectEqual(compare(u8, &a, &b, .big), .eq); + try expectEqual(compare(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); + try expectEqual(compare(u8, &a, &b, .big), .lt); + try expectEqual(compare(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); + try expectEqual(compare(u8, &a, &b, .big), .gt); + try expectEqual(compare(u8, &a, &b, .little), .lt); } -test "timingSafe{Add,Sub}" { +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; @@ -186,21 +187,11 @@ test "timingSafe{Add,Sub}" { 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); + _ = 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); } } - -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..]); -} diff --git a/lib/std/crypto/tlcsprng.zig b/lib/std/crypto/tlcsprng.zig index ac359ce25a..672d6c2ecb 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.