| author | |
| committer | |
| log | 54151428e5236a0612fe36837ed5a6c1e28aec7a |
| tree | 89e3fda0a275c3e6aa94f9f03a7050fcba30bd72 |
| parent | ae5bf2faabfe949e2cb94a230e79614c7eafd84a |
std.crypto has quite a few instances of breaking naming conventions.
This is the beginning of an effort to address that.
Deprecates `std.crypto.utils`.19 files changed, 263 insertions(+), 242 deletions(-)
doc/langref.html.in+1-1| ... | ... | @@ -5053,7 +5053,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val |
| 5053 | 5053 | It may have any alignment, and it may have any element type.</p> |
| 5054 | 5054 | <p>{#syntax#}elem{#endsyntax#} is coerced to the element type of {#syntax#}dest{#endsyntax#}.</p> |
| 5055 | 5055 | <p>For securely zeroing out sensitive contents from memory, you should use |
| 5056 | {#syntax#}std.crypto.utils.secureZero{#endsyntax#}</p> | |
| 5056 | {#syntax#}std.crypto.secureZero{#endsyntax#}</p> | |
| 5057 | 5057 | {#header_close#} |
| 5058 | 5058 | |
| 5059 | 5059 | {#header_open|@min#} |
lib/std/crypto.zig+37-3| ... | ... | @@ -2,6 +2,8 @@ |
| 2 | 2 | |
| 3 | 3 | const root = @import("root"); |
| 4 | 4 | |
| 5 | pub const timing_safe = @import("crypto/timing_safe.zig"); | |
| 6 | ||
| 5 | 7 | /// Authenticated Encryption with Associated Data |
| 6 | 8 | pub const aead = struct { |
| 7 | 9 | pub const aegis = struct { |
| ... | ... | @@ -180,8 +182,6 @@ pub const nacl = struct { |
| 180 | 182 | pub const SealedBox = salsa20.SealedBox; |
| 181 | 183 | }; |
| 182 | 184 | |
| 183 | pub const utils = @import("crypto/utils.zig"); | |
| 184 | ||
| 185 | 185 | /// Finite-field arithmetic. |
| 186 | 186 | pub const ff = @import("crypto/ff.zig"); |
| 187 | 187 | |
| ... | ... | @@ -301,7 +301,8 @@ test { |
| 301 | 301 | _ = nacl.SecretBox; |
| 302 | 302 | _ = nacl.SealedBox; |
| 303 | 303 | |
| 304 | _ = utils; | |
| 304 | _ = secureZero; | |
| 305 | _ = timing_safe; | |
| 305 | 306 | _ = ff; |
| 306 | 307 | _ = random; |
| 307 | 308 | _ = errors; |
| ... | ... | @@ -353,3 +354,36 @@ test "issue #4532: no index out of bounds" { |
| 353 | 354 | try std.testing.expectEqual(out1, out2); |
| 354 | 355 | } |
| 355 | 356 | } |
| 357 | ||
| 358 | /// Sets a slice to zeroes. | |
| 359 | /// Prevents the store from being optimized out. | |
| 360 | pub inline fn secureZero(comptime T: type, s: []volatile T) void { | |
| 361 | @memset(s, 0); | |
| 362 | } | |
| 363 | ||
| 364 | test secureZero { | |
| 365 | var a = [_]u8{0xfe} ** 8; | |
| 366 | var b = [_]u8{0xfe} ** 8; | |
| 367 | ||
| 368 | @memset(&a, 0); | |
| 369 | secureZero(u8, &b); | |
| 370 | ||
| 371 | try std.testing.expectEqualSlices(u8, &a, &b); | |
| 372 | } | |
| 373 | ||
| 374 | /// Deprecated in favor of `std.crypto`. To be removed after Zig 0.14.0 is released. | |
| 375 | /// | |
| 376 | /// As a reminder, never use "utils" in a namespace (in any programming language). | |
| 377 | /// https://ziglang.org/documentation/0.13.0/#Avoid-Redundancy-in-Names | |
| 378 | pub const utils = struct { | |
| 379 | /// Deprecated in favor of `std.crypto.secureZero`. | |
| 380 | pub const secureZero = std.crypto.secureZero; | |
| 381 | /// Deprecated in favor of `std.crypto.timing_safe.eql`. | |
| 382 | pub const timingSafeEql = timing_safe.eql; | |
| 383 | /// Deprecated in favor of `std.crypto.timing_safe.compare`. | |
| 384 | pub const timingSafeCompare = timing_safe.compare; | |
| 385 | /// Deprecated in favor of `std.crypto.timing_safe.add`. | |
| 386 | pub const timingSafeAdd = timing_safe.add; | |
| 387 | /// Deprecated in favor of `std.crypto.timing_safe.sub`. | |
| 388 | pub const timingSafeSub = timing_safe.sub; | |
| 389 | }; |
lib/std/crypto/aegis.zig+4-4| ... | ... | @@ -208,9 +208,9 @@ fn Aegis128LGeneric(comptime tag_bits: u9) type { |
| 208 | 208 | blocks[4] = blocks[4].xorBlocks(AesBlock.fromBytes(dst[16..32])); |
| 209 | 209 | } |
| 210 | 210 | var computed_tag = state.mac(tag_bits, ad.len, m.len); |
| 211 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 211 | const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); | |
| 212 | 212 | if (!verify) { |
| 213 | crypto.utils.secureZero(u8, &computed_tag); | |
| 213 | crypto.secureZero(u8, &computed_tag); | |
| 214 | 214 | @memset(m, undefined); |
| 215 | 215 | return error.AuthenticationFailed; |
| 216 | 216 | } |
| ... | ... | @@ -390,9 +390,9 @@ fn Aegis256Generic(comptime tag_bits: u9) type { |
| 390 | 390 | blocks[0] = blocks[0].xorBlocks(AesBlock.fromBytes(&dst)); |
| 391 | 391 | } |
| 392 | 392 | var computed_tag = state.mac(tag_bits, ad.len, m.len); |
| 393 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 393 | const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); | |
| 394 | 394 | if (!verify) { |
| 395 | crypto.utils.secureZero(u8, &computed_tag); | |
| 395 | crypto.secureZero(u8, &computed_tag); | |
| 396 | 396 | @memset(m, undefined); |
| 397 | 397 | return error.AuthenticationFailed; |
| 398 | 398 | } |
lib/std/crypto/aes_gcm.zig+2-2| ... | ... | @@ -95,9 +95,9 @@ fn AesGcm(comptime Aes: anytype) type { |
| 95 | 95 | computed_tag[i] ^= x; |
| 96 | 96 | } |
| 97 | 97 | |
| 98 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 98 | const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); | |
| 99 | 99 | if (!verify) { |
| 100 | crypto.utils.secureZero(u8, &computed_tag); | |
| 100 | crypto.secureZero(u8, &computed_tag); | |
| 101 | 101 | @memset(m, undefined); |
| 102 | 102 | return error.AuthenticationFailed; |
| 103 | 103 | } |
lib/std/crypto/aes_ocb.zig+2-2| ... | ... | @@ -234,9 +234,9 @@ fn AesOcb(comptime Aes: anytype) type { |
| 234 | 234 | var e = xorBlocks(xorBlocks(sum, offset), lx.dol); |
| 235 | 235 | aes_enc_ctx.encrypt(&e, &e); |
| 236 | 236 | var computed_tag = xorBlocks(e, hash(aes_enc_ctx, &lx, ad)); |
| 237 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 237 | const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); | |
| 238 | 238 | if (!verify) { |
| 239 | crypto.utils.secureZero(u8, &computed_tag); | |
| 239 | crypto.secureZero(u8, &computed_tag); | |
| 240 | 240 | @memset(m, undefined); |
| 241 | 241 | return error.AuthenticationFailed; |
| 242 | 242 | } |
lib/std/crypto/ascon.zig+1-1| ... | ... | @@ -152,7 +152,7 @@ pub fn State(comptime endian: std.builtin.Endian) type { |
| 152 | 152 | |
| 153 | 153 | /// Clear the entire state, disabling compiler optimizations. |
| 154 | 154 | pub fn secureZero(self: *Self) void { |
| 155 | std.crypto.utils.secureZero(u64, &self.st); | |
| 155 | std.crypto.secureZero(u64, &self.st); | |
| 156 | 156 | } |
| 157 | 157 | |
| 158 | 158 | /// Apply a reduced-round permutation to the state. |
lib/std/crypto/bcrypt.zig+3-4| ... | ... | @@ -9,7 +9,6 @@ const pwhash = crypto.pwhash; |
| 9 | 9 | const testing = std.testing; |
| 10 | 10 | const HmacSha512 = crypto.auth.hmac.sha2.HmacSha512; |
| 11 | 11 | const Sha512 = crypto.hash.sha2.Sha512; |
| 12 | const utils = crypto.utils; | |
| 13 | 12 | |
| 14 | 13 | const phc_format = @import("phc_encoding.zig"); |
| 15 | 14 | |
| ... | ... | @@ -446,7 +445,7 @@ pub fn bcrypt( |
| 446 | 445 | state.expand0(passwordZ); |
| 447 | 446 | state.expand0(salt[0..]); |
| 448 | 447 | } |
| 449 | utils.secureZero(u8, &password_buf); | |
| 448 | crypto.secureZero(u8, &password_buf); | |
| 450 | 449 | |
| 451 | 450 | var cdata = [6]u32{ 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 }; // "OrpheanBeholderScryDoubt" |
| 452 | 451 | k = 0; |
| ... | ... | @@ -556,8 +555,8 @@ const pbkdf_prf = struct { |
| 556 | 555 | } |
| 557 | 556 | |
| 558 | 557 | // zap |
| 559 | crypto.utils.secureZero(u32, &cdata); | |
| 560 | crypto.utils.secureZero(u32, &state.subkeys); | |
| 558 | crypto.secureZero(u32, &cdata); | |
| 559 | crypto.secureZero(u32, &state.subkeys); | |
| 561 | 560 | |
| 562 | 561 | return out; |
| 563 | 562 | } |
lib/std/crypto/chacha20.zig+2-2| ... | ... | @@ -714,9 +714,9 @@ fn ChaChaPoly1305(comptime rounds_nb: usize) type { |
| 714 | 714 | var computed_tag: [16]u8 = undefined; |
| 715 | 715 | mac.final(computed_tag[0..]); |
| 716 | 716 | |
| 717 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 717 | const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); | |
| 718 | 718 | if (!verify) { |
| 719 | crypto.utils.secureZero(u8, &computed_tag); | |
| 719 | crypto.secureZero(u8, &computed_tag); | |
| 720 | 720 | @memset(m, undefined); |
| 721 | 721 | return error.AuthenticationFailed; |
| 722 | 722 | } |
lib/std/crypto/ff.zig+2-2| ... | ... | @@ -225,12 +225,12 @@ pub fn Uint(comptime max_bits: comptime_int) type { |
| 225 | 225 | |
| 226 | 226 | /// Returns `true` if both integers are equal. |
| 227 | 227 | pub fn eql(x: Self, y: Self) bool { |
| 228 | return crypto.utils.timingSafeEql([max_limbs_count]Limb, x.limbs_buffer, y.limbs_buffer); | |
| 228 | return crypto.timing_safe.eql([max_limbs_count]Limb, x.limbs_buffer, y.limbs_buffer); | |
| 229 | 229 | } |
| 230 | 230 | |
| 231 | 231 | /// Compares two integers. |
| 232 | 232 | pub fn compare(x: Self, y: Self) math.Order { |
| 233 | return crypto.utils.timingSafeCompare( | |
| 233 | return crypto.timing_safe.compare( | |
| 234 | 234 | Limb, |
| 235 | 235 | x.limbsConst(), |
| 236 | 236 | y.limbsConst(), |
lib/std/crypto/ghash_polyval.zig+1-2| ... | ... | @@ -3,7 +3,6 @@ const builtin = @import("builtin"); |
| 3 | 3 | const assert = std.debug.assert; |
| 4 | 4 | const math = std.math; |
| 5 | 5 | const mem = std.mem; |
| 6 | const utils = std.crypto.utils; | |
| 7 | 6 | |
| 8 | 7 | const Precomp = u128; |
| 9 | 8 | |
| ... | ... | @@ -403,7 +402,7 @@ fn Hash(comptime endian: std.builtin.Endian, comptime shift_key: bool) type { |
| 403 | 402 | st.pad(); |
| 404 | 403 | mem.writeInt(u128, out[0..16], st.acc, endian); |
| 405 | 404 | |
| 406 | utils.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Self)]); | |
| 405 | std.crypto.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Self)]); | |
| 407 | 406 | } |
| 408 | 407 | |
| 409 | 408 | /// Compute the GHASH of a message. |
lib/std/crypto/isap.zig+2-2| ... | ... | @@ -158,9 +158,9 @@ pub const IsapA128A = struct { |
| 158 | 158 | /// Contents of `m` are undefined if an error is returned. |
| 159 | 159 | 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 { |
| 160 | 160 | var computed_tag = mac(c, ad, npub, key); |
| 161 | const verify = crypto.utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 161 | const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); | |
| 162 | 162 | if (!verify) { |
| 163 | crypto.utils.secureZero(u8, &computed_tag); | |
| 163 | crypto.secureZero(u8, &computed_tag); | |
| 164 | 164 | @memset(m, undefined); |
| 165 | 165 | return error.AuthenticationFailed; |
| 166 | 166 | } |
lib/std/crypto/keccak_p.zig+1-1| ... | ... | @@ -132,7 +132,7 @@ pub fn KeccakF(comptime f: u11) type { |
| 132 | 132 | |
| 133 | 133 | /// Clear the entire state, disabling compiler optimizations. |
| 134 | 134 | pub fn secureZero(self: *Self) void { |
| 135 | std.crypto.utils.secureZero(T, &self.st); | |
| 135 | std.crypto.secureZero(T, &self.st); | |
| 136 | 136 | } |
| 137 | 137 | |
| 138 | 138 | inline fn round(self: *Self, rc: T) void { |
lib/std/crypto/ml_kem.zig+1-1| ... | ... | @@ -1508,7 +1508,7 @@ fn Mat(comptime K: u8) type { |
| 1508 | 1508 | |
| 1509 | 1509 | // Returns `true` if a ≠ b. |
| 1510 | 1510 | fn ctneq(comptime len: usize, a: [len]u8, b: [len]u8) u1 { |
| 1511 | return 1 - @intFromBool(crypto.utils.timingSafeEql([len]u8, a, b)); | |
| 1511 | return 1 - @intFromBool(crypto.timing_safe.eql([len]u8, a, b)); | |
| 1512 | 1512 | } |
| 1513 | 1513 | |
| 1514 | 1514 | // Copy src into dst given b = 1. |
lib/std/crypto/pcurves/common.zig+1-1| ... | ... | @@ -57,7 +57,7 @@ pub fn Field(comptime params: FieldParams) type { |
| 57 | 57 | mem.writeInt(std.meta.Int(.unsigned, encoded_length * 8), &fos, field_order, .little); |
| 58 | 58 | break :fos fos; |
| 59 | 59 | }; |
| 60 | if (crypto.utils.timingSafeCompare(u8, &s, &field_order_s, .little) != .lt) { | |
| 60 | if (crypto.timing_safe.compare(u8, &s, &field_order_s, .little) != .lt) { | |
| 61 | 61 | return error.NonCanonical; |
| 62 | 62 | } |
| 63 | 63 | } |
lib/std/crypto/poly1305.zig+1-2| ... | ... | @@ -1,5 +1,4 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | const utils = std.crypto.utils; | |
| 3 | 2 | const mem = std.mem; |
| 4 | 3 | const mulWide = std.math.mulWide; |
| 5 | 4 | |
| ... | ... | @@ -185,7 +184,7 @@ pub const Poly1305 = struct { |
| 185 | 184 | mem.writeInt(u64, out[0..8], st.h[0], .little); |
| 186 | 185 | mem.writeInt(u64, out[8..16], st.h[1], .little); |
| 187 | 186 | |
| 188 | utils.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Poly1305)]); | |
| 187 | std.crypto.secureZero(u8, @as([*]u8, @ptrCast(st))[0..@sizeOf(Poly1305)]); | |
| 189 | 188 | } |
| 190 | 189 | |
| 191 | 190 | pub fn create(out: *[mac_length]u8, msg: []const u8, key: *const [key_length]u8) void { |
lib/std/crypto/salsa20.zig+3-4| ... | ... | @@ -4,7 +4,6 @@ const crypto = std.crypto; |
| 4 | 4 | const debug = std.debug; |
| 5 | 5 | const math = std.math; |
| 6 | 6 | const mem = std.mem; |
| 7 | const utils = std.crypto.utils; | |
| 8 | 7 | |
| 9 | 8 | const Poly1305 = crypto.onetimeauth.Poly1305; |
| 10 | 9 | const Blake2b = crypto.hash.blake2.Blake2b; |
| ... | ... | @@ -419,9 +418,9 @@ pub const XSalsa20Poly1305 = struct { |
| 419 | 418 | var computed_tag: [tag_length]u8 = undefined; |
| 420 | 419 | mac.final(&computed_tag); |
| 421 | 420 | |
| 422 | const verify = utils.timingSafeEql([tag_length]u8, computed_tag, tag); | |
| 421 | const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag); | |
| 423 | 422 | if (!verify) { |
| 424 | utils.secureZero(u8, &computed_tag); | |
| 423 | crypto.secureZero(u8, &computed_tag); | |
| 425 | 424 | @memset(m, undefined); |
| 426 | 425 | return error.AuthenticationFailed; |
| 427 | 426 | } |
| ... | ... | @@ -540,7 +539,7 @@ pub const SealedBox = struct { |
| 540 | 539 | const nonce = createNonce(ekp.public_key, public_key); |
| 541 | 540 | c[0..public_length].* = ekp.public_key; |
| 542 | 541 | try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key); |
| 543 | utils.secureZero(u8, ekp.secret_key[0..]); | |
| 542 | crypto.secureZero(u8, ekp.secret_key[0..]); | |
| 544 | 543 | } |
| 545 | 544 | |
| 546 | 545 | /// Decrypt a message using a key pair. |
lib/std/crypto/timing_safe.zig created+197| ... | ... | @@ -0,0 +1,197 @@ |
| 1 | //! Please see this accepted proposal for the long-term plans regarding | |
| 2 | //! constant-time operations in Zig: https://github.com/ziglang/zig/issues/1776 | |
| 3 | ||
| 4 | const std = @import("../std.zig"); | |
| 5 | const assert = std.debug.assert; | |
| 6 | const Endian = std.builtin.Endian; | |
| 7 | const Order = std.math.Order; | |
| 8 | ||
| 9 | /// Compares two arrays in constant time (for a given length) and returns whether they are equal. | |
| 10 | /// This function was designed to compare short cryptographic secrets (MACs, signatures). | |
| 11 | /// For all other applications, use mem.eql() instead. | |
| 12 | pub fn eql(comptime T: type, a: T, b: T) bool { | |
| 13 | switch (@typeInfo(T)) { | |
| 14 | .Array => |info| { | |
| 15 | const C = info.child; | |
| 16 | if (@typeInfo(C) != .Int) { | |
| 17 | @compileError("Elements to be compared must be integers"); | |
| 18 | } | |
| 19 | var acc = @as(C, 0); | |
| 20 | for (a, 0..) |x, i| { | |
| 21 | acc |= x ^ b[i]; | |
| 22 | } | |
| 23 | const s = @typeInfo(C).Int.bits; | |
| 24 | const Cu = std.meta.Int(.unsigned, s); | |
| 25 | const Cext = std.meta.Int(.unsigned, s + 1); | |
| 26 | return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s)))); | |
| 27 | }, | |
| 28 | .Vector => |info| { | |
| 29 | const C = info.child; | |
| 30 | if (@typeInfo(C) != .Int) { | |
| 31 | @compileError("Elements to be compared must be integers"); | |
| 32 | } | |
| 33 | const acc = @reduce(.Or, a ^ b); | |
| 34 | const s = @typeInfo(C).Int.bits; | |
| 35 | const Cu = std.meta.Int(.unsigned, s); | |
| 36 | const Cext = std.meta.Int(.unsigned, s + 1); | |
| 37 | return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s)))); | |
| 38 | }, | |
| 39 | else => { | |
| 40 | @compileError("Only arrays and vectors can be compared"); | |
| 41 | }, | |
| 42 | } | |
| 43 | } | |
| 44 | ||
| 45 | /// Compare two integers serialized as arrays of the same size, in constant time. | |
| 46 | /// Returns .lt if a<b, .gt if a>b and .eq if a=b | |
| 47 | pub fn compare(comptime T: type, a: []const T, b: []const T, endian: Endian) Order { | |
| 48 | assert(a.len == b.len); | |
| 49 | const bits = switch (@typeInfo(T)) { | |
| 50 | .Int => |cinfo| if (cinfo.signedness != .unsigned) @compileError("Elements to be compared must be unsigned") else cinfo.bits, | |
| 51 | else => @compileError("Elements to be compared must be integers"), | |
| 52 | }; | |
| 53 | const Cext = std.meta.Int(.unsigned, bits + 1); | |
| 54 | var gt: T = 0; | |
| 55 | var eq: T = 1; | |
| 56 | if (endian == .little) { | |
| 57 | var i = a.len; | |
| 58 | while (i != 0) { | |
| 59 | i -= 1; | |
| 60 | const x1 = a[i]; | |
| 61 | const x2 = b[i]; | |
| 62 | gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq; | |
| 63 | eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits)); | |
| 64 | } | |
| 65 | } else { | |
| 66 | for (a, 0..) |x1, i| { | |
| 67 | const x2 = b[i]; | |
| 68 | gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq; | |
| 69 | eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits)); | |
| 70 | } | |
| 71 | } | |
| 72 | if (gt != 0) { | |
| 73 | return Order.gt; | |
| 74 | } else if (eq != 0) { | |
| 75 | return Order.eq; | |
| 76 | } | |
| 77 | return Order.lt; | |
| 78 | } | |
| 79 | ||
| 80 | /// Add two integers serialized as arrays of the same size, in constant time. | |
| 81 | /// The result is stored into `result`, and `true` is returned if an overflow occurred. | |
| 82 | pub fn add(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { | |
| 83 | const len = a.len; | |
| 84 | assert(len == b.len and len == result.len); | |
| 85 | var carry: u1 = 0; | |
| 86 | if (endian == .little) { | |
| 87 | var i: usize = 0; | |
| 88 | while (i < len) : (i += 1) { | |
| 89 | const ov1 = @addWithOverflow(a[i], b[i]); | |
| 90 | const ov2 = @addWithOverflow(ov1[0], carry); | |
| 91 | result[i] = ov2[0]; | |
| 92 | carry = ov1[1] | ov2[1]; | |
| 93 | } | |
| 94 | } else { | |
| 95 | var i: usize = len; | |
| 96 | while (i != 0) { | |
| 97 | i -= 1; | |
| 98 | const ov1 = @addWithOverflow(a[i], b[i]); | |
| 99 | const ov2 = @addWithOverflow(ov1[0], carry); | |
| 100 | result[i] = ov2[0]; | |
| 101 | carry = ov1[1] | ov2[1]; | |
| 102 | } | |
| 103 | } | |
| 104 | return @as(bool, @bitCast(carry)); | |
| 105 | } | |
| 106 | ||
| 107 | /// Subtract two integers serialized as arrays of the same size, in constant time. | |
| 108 | /// The result is stored into `result`, and `true` is returned if an underflow occurred. | |
| 109 | pub fn sub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { | |
| 110 | const len = a.len; | |
| 111 | assert(len == b.len and len == result.len); | |
| 112 | var borrow: u1 = 0; | |
| 113 | if (endian == .little) { | |
| 114 | var i: usize = 0; | |
| 115 | while (i < len) : (i += 1) { | |
| 116 | const ov1 = @subWithOverflow(a[i], b[i]); | |
| 117 | const ov2 = @subWithOverflow(ov1[0], borrow); | |
| 118 | result[i] = ov2[0]; | |
| 119 | borrow = ov1[1] | ov2[1]; | |
| 120 | } | |
| 121 | } else { | |
| 122 | var i: usize = len; | |
| 123 | while (i != 0) { | |
| 124 | i -= 1; | |
| 125 | const ov1 = @subWithOverflow(a[i], b[i]); | |
| 126 | const ov2 = @subWithOverflow(ov1[0], borrow); | |
| 127 | result[i] = ov2[0]; | |
| 128 | borrow = ov1[1] | ov2[1]; | |
| 129 | } | |
| 130 | } | |
| 131 | return @as(bool, @bitCast(borrow)); | |
| 132 | } | |
| 133 | ||
| 134 | test eql { | |
| 135 | const random = std.crypto.random; | |
| 136 | const expect = std.testing.expect; | |
| 137 | var a: [100]u8 = undefined; | |
| 138 | var b: [100]u8 = undefined; | |
| 139 | random.bytes(a[0..]); | |
| 140 | random.bytes(b[0..]); | |
| 141 | try expect(!eql([100]u8, a, b)); | |
| 142 | a = b; | |
| 143 | try expect(eql([100]u8, a, b)); | |
| 144 | } | |
| 145 | ||
| 146 | test "eql (vectors)" { | |
| 147 | if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest; | |
| 148 | ||
| 149 | const random = std.crypto.random; | |
| 150 | const expect = std.testing.expect; | |
| 151 | var a: [100]u8 = undefined; | |
| 152 | var b: [100]u8 = undefined; | |
| 153 | random.bytes(a[0..]); | |
| 154 | random.bytes(b[0..]); | |
| 155 | const v1: @Vector(100, u8) = a; | |
| 156 | const v2: @Vector(100, u8) = b; | |
| 157 | try expect(!eql(@Vector(100, u8), v1, v2)); | |
| 158 | const v3: @Vector(100, u8) = a; | |
| 159 | try expect(eql(@Vector(100, u8), v1, v3)); | |
| 160 | } | |
| 161 | ||
| 162 | test compare { | |
| 163 | const expectEqual = std.testing.expectEqual; | |
| 164 | var a = [_]u8{10} ** 32; | |
| 165 | var b = [_]u8{10} ** 32; | |
| 166 | try expectEqual(compare(u8, &a, &b, .big), .eq); | |
| 167 | try expectEqual(compare(u8, &a, &b, .little), .eq); | |
| 168 | a[31] = 1; | |
| 169 | try expectEqual(compare(u8, &a, &b, .big), .lt); | |
| 170 | try expectEqual(compare(u8, &a, &b, .little), .lt); | |
| 171 | a[0] = 20; | |
| 172 | try expectEqual(compare(u8, &a, &b, .big), .gt); | |
| 173 | try expectEqual(compare(u8, &a, &b, .little), .lt); | |
| 174 | } | |
| 175 | ||
| 176 | test "add and sub" { | |
| 177 | const expectEqual = std.testing.expectEqual; | |
| 178 | const expectEqualSlices = std.testing.expectEqualSlices; | |
| 179 | const random = std.crypto.random; | |
| 180 | const len = 32; | |
| 181 | var a: [len]u8 = undefined; | |
| 182 | var b: [len]u8 = undefined; | |
| 183 | var c: [len]u8 = undefined; | |
| 184 | const zero = [_]u8{0} ** len; | |
| 185 | var iterations: usize = 100; | |
| 186 | while (iterations != 0) : (iterations -= 1) { | |
| 187 | random.bytes(&a); | |
| 188 | random.bytes(&b); | |
| 189 | const endian = if (iterations % 2 == 0) Endian.big else Endian.little; | |
| 190 | _ = sub(u8, &a, &b, &c, endian); // a-b | |
| 191 | _ = add(u8, &c, &b, &c, endian); // (a-b)+b | |
| 192 | try expectEqualSlices(u8, &c, &a); | |
| 193 | const borrow = sub(u8, &c, &a, &c, endian); // ((a-b)+b)-a | |
| 194 | try expectEqualSlices(u8, &c, &zero); | |
| 195 | try expectEqual(borrow, false); | |
| 196 | } | |
| 197 | } |
lib/std/crypto/tlcsprng.zig+2-2| ... | ... | @@ -137,7 +137,7 @@ fn childAtForkHandler() callconv(.C) void { |
| 137 | 137 | // The atfork handler is global, this function may be called after |
| 138 | 138 | // fork()-ing threads that never initialized the CSPRNG context. |
| 139 | 139 | if (wipe_mem.len == 0) return; |
| 140 | std.crypto.utils.secureZero(u8, wipe_mem); | |
| 140 | std.crypto.secureZero(u8, wipe_mem); | |
| 141 | 141 | } |
| 142 | 142 | |
| 143 | 143 | fn fillWithCsprng(buffer: []u8) void { |
| ... | ... | @@ -159,7 +159,7 @@ fn initAndFill(buffer: []u8) void { |
| 159 | 159 | |
| 160 | 160 | const ctx = @as(*Context, @ptrCast(wipe_mem.ptr)); |
| 161 | 161 | ctx.rng = Rng.init(seed); |
| 162 | std.crypto.utils.secureZero(u8, &seed); | |
| 162 | std.crypto.secureZero(u8, &seed); | |
| 163 | 163 | |
| 164 | 164 | // This is at the end so that accidental recursive dependencies result |
| 165 | 165 | // in stack overflows instead of invalid random data. |
lib/std/crypto/utils.zig deleted-206| ... | ... | @@ -1,206 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const debug = std.debug; | |
| 3 | const mem = std.mem; | |
| 4 | const random = std.crypto.random; | |
| 5 | const testing = std.testing; | |
| 6 | ||
| 7 | const Endian = std.builtin.Endian; | |
| 8 | const Order = std.math.Order; | |
| 9 | ||
| 10 | /// Compares two arrays in constant time (for a given length) and returns whether they are equal. | |
| 11 | /// This function was designed to compare short cryptographic secrets (MACs, signatures). | |
| 12 | /// For all other applications, use mem.eql() instead. | |
| 13 | pub fn timingSafeEql(comptime T: type, a: T, b: T) bool { | |
| 14 | switch (@typeInfo(T)) { | |
| 15 | .Array => |info| { | |
| 16 | const C = info.child; | |
| 17 | if (@typeInfo(C) != .Int) { | |
| 18 | @compileError("Elements to be compared must be integers"); | |
| 19 | } | |
| 20 | var acc = @as(C, 0); | |
| 21 | for (a, 0..) |x, i| { | |
| 22 | acc |= x ^ b[i]; | |
| 23 | } | |
| 24 | const s = @typeInfo(C).Int.bits; | |
| 25 | const Cu = std.meta.Int(.unsigned, s); | |
| 26 | const Cext = std.meta.Int(.unsigned, s + 1); | |
| 27 | return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s)))); | |
| 28 | }, | |
| 29 | .Vector => |info| { | |
| 30 | const C = info.child; | |
| 31 | if (@typeInfo(C) != .Int) { | |
| 32 | @compileError("Elements to be compared must be integers"); | |
| 33 | } | |
| 34 | const acc = @reduce(.Or, a ^ b); | |
| 35 | const s = @typeInfo(C).Int.bits; | |
| 36 | const Cu = std.meta.Int(.unsigned, s); | |
| 37 | const Cext = std.meta.Int(.unsigned, s + 1); | |
| 38 | return @as(bool, @bitCast(@as(u1, @truncate((@as(Cext, @as(Cu, @bitCast(acc))) -% 1) >> s)))); | |
| 39 | }, | |
| 40 | else => { | |
| 41 | @compileError("Only arrays and vectors can be compared"); | |
| 42 | }, | |
| 43 | } | |
| 44 | } | |
| 45 | ||
| 46 | /// Compare two integers serialized as arrays of the same size, in constant time. | |
| 47 | /// Returns .lt if a<b, .gt if a>b and .eq if a=b | |
| 48 | pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: Endian) Order { | |
| 49 | debug.assert(a.len == b.len); | |
| 50 | const bits = switch (@typeInfo(T)) { | |
| 51 | .Int => |cinfo| if (cinfo.signedness != .unsigned) @compileError("Elements to be compared must be unsigned") else cinfo.bits, | |
| 52 | else => @compileError("Elements to be compared must be integers"), | |
| 53 | }; | |
| 54 | const Cext = std.meta.Int(.unsigned, bits + 1); | |
| 55 | var gt: T = 0; | |
| 56 | var eq: T = 1; | |
| 57 | if (endian == .little) { | |
| 58 | var i = a.len; | |
| 59 | while (i != 0) { | |
| 60 | i -= 1; | |
| 61 | const x1 = a[i]; | |
| 62 | const x2 = b[i]; | |
| 63 | gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq; | |
| 64 | eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits)); | |
| 65 | } | |
| 66 | } else { | |
| 67 | for (a, 0..) |x1, i| { | |
| 68 | const x2 = b[i]; | |
| 69 | gt |= @as(T, @truncate((@as(Cext, x2) -% @as(Cext, x1)) >> bits)) & eq; | |
| 70 | eq &= @as(T, @truncate((@as(Cext, (x2 ^ x1)) -% 1) >> bits)); | |
| 71 | } | |
| 72 | } | |
| 73 | if (gt != 0) { | |
| 74 | return Order.gt; | |
| 75 | } else if (eq != 0) { | |
| 76 | return Order.eq; | |
| 77 | } | |
| 78 | return Order.lt; | |
| 79 | } | |
| 80 | ||
| 81 | /// Add two integers serialized as arrays of the same size, in constant time. | |
| 82 | /// The result is stored into `result`, and `true` is returned if an overflow occurred. | |
| 83 | pub fn timingSafeAdd(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { | |
| 84 | const len = a.len; | |
| 85 | debug.assert(len == b.len and len == result.len); | |
| 86 | var carry: u1 = 0; | |
| 87 | if (endian == .little) { | |
| 88 | var i: usize = 0; | |
| 89 | while (i < len) : (i += 1) { | |
| 90 | const ov1 = @addWithOverflow(a[i], b[i]); | |
| 91 | const ov2 = @addWithOverflow(ov1[0], carry); | |
| 92 | result[i] = ov2[0]; | |
| 93 | carry = ov1[1] | ov2[1]; | |
| 94 | } | |
| 95 | } else { | |
| 96 | var i: usize = len; | |
| 97 | while (i != 0) { | |
| 98 | i -= 1; | |
| 99 | const ov1 = @addWithOverflow(a[i], b[i]); | |
| 100 | const ov2 = @addWithOverflow(ov1[0], carry); | |
| 101 | result[i] = ov2[0]; | |
| 102 | carry = ov1[1] | ov2[1]; | |
| 103 | } | |
| 104 | } | |
| 105 | return @as(bool, @bitCast(carry)); | |
| 106 | } | |
| 107 | ||
| 108 | /// Subtract two integers serialized as arrays of the same size, in constant time. | |
| 109 | /// The result is stored into `result`, and `true` is returned if an underflow occurred. | |
| 110 | pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T, endian: Endian) bool { | |
| 111 | const len = a.len; | |
| 112 | debug.assert(len == b.len and len == result.len); | |
| 113 | var borrow: u1 = 0; | |
| 114 | if (endian == .little) { | |
| 115 | var i: usize = 0; | |
| 116 | while (i < len) : (i += 1) { | |
| 117 | const ov1 = @subWithOverflow(a[i], b[i]); | |
| 118 | const ov2 = @subWithOverflow(ov1[0], borrow); | |
| 119 | result[i] = ov2[0]; | |
| 120 | borrow = ov1[1] | ov2[1]; | |
| 121 | } | |
| 122 | } else { | |
| 123 | var i: usize = len; | |
| 124 | while (i != 0) { | |
| 125 | i -= 1; | |
| 126 | const ov1 = @subWithOverflow(a[i], b[i]); | |
| 127 | const ov2 = @subWithOverflow(ov1[0], borrow); | |
| 128 | result[i] = ov2[0]; | |
| 129 | borrow = ov1[1] | ov2[1]; | |
| 130 | } | |
| 131 | } | |
| 132 | return @as(bool, @bitCast(borrow)); | |
| 133 | } | |
| 134 | ||
| 135 | /// Sets a slice to zeroes. | |
| 136 | /// Prevents the store from being optimized out. | |
| 137 | pub inline fn secureZero(comptime T: type, s: []T) void { | |
| 138 | @memset(@as([]volatile T, s), 0); | |
| 139 | } | |
| 140 | ||
| 141 | test timingSafeEql { | |
| 142 | var a: [100]u8 = undefined; | |
| 143 | var b: [100]u8 = undefined; | |
| 144 | random.bytes(a[0..]); | |
| 145 | random.bytes(b[0..]); | |
| 146 | try testing.expect(!timingSafeEql([100]u8, a, b)); | |
| 147 | a = b; | |
| 148 | try testing.expect(timingSafeEql([100]u8, a, b)); | |
| 149 | } | |
| 150 | ||
| 151 | test "timingSafeEql (vectors)" { | |
| 152 | if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest; | |
| 153 | ||
| 154 | var a: [100]u8 = undefined; | |
| 155 | var b: [100]u8 = undefined; | |
| 156 | random.bytes(a[0..]); | |
| 157 | random.bytes(b[0..]); | |
| 158 | const v1: @Vector(100, u8) = a; | |
| 159 | const v2: @Vector(100, u8) = b; | |
| 160 | try testing.expect(!timingSafeEql(@Vector(100, u8), v1, v2)); | |
| 161 | const v3: @Vector(100, u8) = a; | |
| 162 | try testing.expect(timingSafeEql(@Vector(100, u8), v1, v3)); | |
| 163 | } | |
| 164 | ||
| 165 | test timingSafeCompare { | |
| 166 | var a = [_]u8{10} ** 32; | |
| 167 | var b = [_]u8{10} ** 32; | |
| 168 | try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .eq); | |
| 169 | try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .eq); | |
| 170 | a[31] = 1; | |
| 171 | try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .lt); | |
| 172 | try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .lt); | |
| 173 | a[0] = 20; | |
| 174 | try testing.expectEqual(timingSafeCompare(u8, &a, &b, .big), .gt); | |
| 175 | try testing.expectEqual(timingSafeCompare(u8, &a, &b, .little), .lt); | |
| 176 | } | |
| 177 | ||
| 178 | test "timingSafe{Add,Sub}" { | |
| 179 | const len = 32; | |
| 180 | var a: [len]u8 = undefined; | |
| 181 | var b: [len]u8 = undefined; | |
| 182 | var c: [len]u8 = undefined; | |
| 183 | const zero = [_]u8{0} ** len; | |
| 184 | var iterations: usize = 100; | |
| 185 | while (iterations != 0) : (iterations -= 1) { | |
| 186 | random.bytes(&a); | |
| 187 | random.bytes(&b); | |
| 188 | const endian = if (iterations % 2 == 0) Endian.big else Endian.little; | |
| 189 | _ = timingSafeSub(u8, &a, &b, &c, endian); // a-b | |
| 190 | _ = timingSafeAdd(u8, &c, &b, &c, endian); // (a-b)+b | |
| 191 | try testing.expectEqualSlices(u8, &c, &a); | |
| 192 | const borrow = timingSafeSub(u8, &c, &a, &c, endian); // ((a-b)+b)-a | |
| 193 | try testing.expectEqualSlices(u8, &c, &zero); | |
| 194 | try testing.expectEqual(borrow, false); | |
| 195 | } | |
| 196 | } | |
| 197 | ||
| 198 | test secureZero { | |
| 199 | var a = [_]u8{0xfe} ** 8; | |
| 200 | var b = [_]u8{0xfe} ** 8; | |
| 201 | ||
| 202 | @memset(a[0..], 0); | |
| 203 | secureZero(u8, b[0..]); | |
| 204 | ||
| 205 | try testing.expectEqualSlices(u8, a[0..], b[0..]); | |
| 206 | } |