diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2020-12-18 23:57:46 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-12-18 23:57:46 -0500 |
| commit | 506af7e52e0985b410ea089bf5fa3247ab2377cb (patch) | |
| tree | 2ec26d70f41a1382b736b606ebfa094ace62573e /lib/std/crypto | |
| parent | ce65533985caa9e2da567948e36d7d4ba0185005 (diff) | |
| parent | f416535768fc30195cad6cd481f73fd1e80082aa (diff) | |
| download | zig-506af7e52e0985b410ea089bf5fa3247ab2377cb.tar.gz zig-506af7e52e0985b410ea089bf5fa3247ab2377cb.zip | |
Merge pull request #7482 from ziglang/tlcsprng
std: introduce a thread-local CSPRNG for general use
Diffstat (limited to 'lib/std/crypto')
| -rw-r--r-- | lib/std/crypto/25519/ed25519.zig | 8 | ||||
| -rw-r--r-- | lib/std/crypto/25519/edwards25519.zig | 4 | ||||
| -rw-r--r-- | lib/std/crypto/25519/x25519.zig | 2 | ||||
| -rw-r--r-- | lib/std/crypto/bcrypt.zig | 4 | ||||
| -rw-r--r-- | lib/std/crypto/salsa20.zig | 18 | ||||
| -rw-r--r-- | lib/std/crypto/tlcsprng.zig | 158 | ||||
| -rw-r--r-- | lib/std/crypto/utils.zig | 8 |
7 files changed, 180 insertions, 22 deletions
diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index 842b08d706..7f90ba584c 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -43,7 +43,7 @@ pub const Ed25519 = struct { pub fn create(seed: ?[seed_length]u8) !KeyPair { const ss = seed orelse ss: { var random_seed: [seed_length]u8 = undefined; - try crypto.randomBytes(&random_seed); + crypto.random.bytes(&random_seed); break :ss random_seed; }; var az: [Sha512.digest_length]u8 = undefined; @@ -179,7 +179,7 @@ pub const Ed25519 = struct { var z_batch: [count]Curve.scalar.CompressedScalar = undefined; for (z_batch) |*z| { - try std.crypto.randomBytes(z[0..16]); + std.crypto.random.bytes(z[0..16]); mem.set(u8, z[16..], 0); } @@ -232,8 +232,8 @@ test "ed25519 batch verification" { const key_pair = try Ed25519.KeyPair.create(null); var msg1: [32]u8 = undefined; var msg2: [32]u8 = undefined; - try std.crypto.randomBytes(&msg1); - try std.crypto.randomBytes(&msg2); + std.crypto.random.bytes(&msg1); + std.crypto.random.bytes(&msg2); const sig1 = try Ed25519.sign(&msg1, key_pair, null); const sig2 = try Ed25519.sign(&msg2, key_pair, null); var signature_batch = [_]Ed25519.BatchElement{ diff --git a/lib/std/crypto/25519/edwards25519.zig b/lib/std/crypto/25519/edwards25519.zig index 008a4535b3..d64f06c421 100644 --- a/lib/std/crypto/25519/edwards25519.zig +++ b/lib/std/crypto/25519/edwards25519.zig @@ -484,8 +484,8 @@ test "edwards25519 packing/unpacking" { test "edwards25519 point addition/substraction" { var s1: [32]u8 = undefined; var s2: [32]u8 = undefined; - try std.crypto.randomBytes(&s1); - try std.crypto.randomBytes(&s2); + std.crypto.random.bytes(&s1); + std.crypto.random.bytes(&s2); const p = try Edwards25519.basePoint.clampedMul(s1); const q = try Edwards25519.basePoint.clampedMul(s2); const r = p.add(q).add(q).sub(q).sub(q); diff --git a/lib/std/crypto/25519/x25519.zig b/lib/std/crypto/25519/x25519.zig index 3b3ff551fe..0bf55d52fc 100644 --- a/lib/std/crypto/25519/x25519.zig +++ b/lib/std/crypto/25519/x25519.zig @@ -34,7 +34,7 @@ pub const X25519 = struct { pub fn create(seed: ?[seed_length]u8) !KeyPair { const sk = seed orelse sk: { var random_seed: [seed_length]u8 = undefined; - try crypto.randomBytes(&random_seed); + crypto.random.bytes(&random_seed); break :sk random_seed; }; var kp: KeyPair = undefined; diff --git a/lib/std/crypto/bcrypt.zig b/lib/std/crypto/bcrypt.zig index 4cec59961b..6813495d25 100644 --- a/lib/std/crypto/bcrypt.zig +++ b/lib/std/crypto/bcrypt.zig @@ -262,7 +262,7 @@ fn strHashInternal(password: []const u8, rounds_log: u6, salt: [salt_length]u8) /// and then use the resulting hash as the password parameter for bcrypt. pub fn strHash(password: []const u8, rounds_log: u6) ![hash_length]u8 { var salt: [salt_length]u8 = undefined; - try crypto.randomBytes(&salt); + crypto.random.bytes(&salt); return strHashInternal(password, rounds_log, salt); } @@ -283,7 +283,7 @@ pub fn strVerify(h: [hash_length]u8, password: []const u8) BcryptError!void { test "bcrypt codec" { var salt: [salt_length]u8 = undefined; - try crypto.randomBytes(&salt); + crypto.random.bytes(&salt); var salt_str: [salt_str_length]u8 = undefined; Codec.encode(salt_str[0..], salt[0..]); var salt2: [salt_length]u8 = undefined; diff --git a/lib/std/crypto/salsa20.zig b/lib/std/crypto/salsa20.zig index dd3e4fe99b..8122e9b25c 100644 --- a/lib/std/crypto/salsa20.zig +++ b/lib/std/crypto/salsa20.zig @@ -571,9 +571,9 @@ test "xsalsa20poly1305" { var key: [XSalsa20Poly1305.key_length]u8 = undefined; var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined; var tag: [XSalsa20Poly1305.tag_length]u8 = undefined; - try crypto.randomBytes(&msg); - try crypto.randomBytes(&key); - try crypto.randomBytes(&nonce); + crypto.random.bytes(&msg); + crypto.random.bytes(&key); + crypto.random.bytes(&nonce); XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key); try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key); @@ -585,9 +585,9 @@ test "xsalsa20poly1305 secretbox" { var key: [XSalsa20Poly1305.key_length]u8 = undefined; var nonce: [Box.nonce_length]u8 = undefined; var boxed: [msg.len + Box.tag_length]u8 = undefined; - try crypto.randomBytes(&msg); - try crypto.randomBytes(&key); - try crypto.randomBytes(&nonce); + crypto.random.bytes(&msg); + crypto.random.bytes(&key); + crypto.random.bytes(&nonce); SecretBox.seal(boxed[0..], msg[0..], nonce, key); try SecretBox.open(msg2[0..], boxed[0..], nonce, key); @@ -598,8 +598,8 @@ test "xsalsa20poly1305 box" { var msg2: [msg.len]u8 = undefined; var nonce: [Box.nonce_length]u8 = undefined; var boxed: [msg.len + Box.tag_length]u8 = undefined; - try crypto.randomBytes(&msg); - try crypto.randomBytes(&nonce); + crypto.random.bytes(&msg); + crypto.random.bytes(&nonce); var kp1 = try Box.KeyPair.create(null); var kp2 = try Box.KeyPair.create(null); @@ -611,7 +611,7 @@ test "xsalsa20poly1305 sealedbox" { var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var boxed: [msg.len + SealedBox.seal_length]u8 = undefined; - try crypto.randomBytes(&msg); + crypto.random.bytes(&msg); var kp = try Box.KeyPair.create(null); try SealedBox.seal(boxed[0..], msg[0..], kp.public_key); diff --git a/lib/std/crypto/tlcsprng.zig b/lib/std/crypto/tlcsprng.zig new file mode 100644 index 0000000000..ee71d6f13e --- /dev/null +++ b/lib/std/crypto/tlcsprng.zig @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2020 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software. + +//! Thread-local cryptographically secure pseudo-random number generator. +//! This file has public declarations that are intended to be used internally +//! by the standard library; this namespace is not intended to be exposed +//! directly to standard library users. + +const std = @import("std"); +const root = @import("root"); +const mem = std.mem; + +/// We use this as a layer of indirection because global const pointers cannot +/// point to thread-local variables. +pub var interface = std.rand.Random{ .fillFn = tlsCsprngFill }; + +const os_has_fork = switch (std.Target.current.os.tag) { + .dragonfly, + .freebsd, + .ios, + .kfreebsd, + .linux, + .macos, + .netbsd, + .openbsd, + .solaris, + .tvos, + .watchos, + => true, + + else => false, +}; +const os_has_arc4random = std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf"); +const want_fork_safety = os_has_fork and !os_has_arc4random and + (std.meta.globalOption("crypto_fork_safety", bool) orelse true); +const maybe_have_wipe_on_fork = std.Target.current.os.isAtLeast(.linux, .{ + .major = 4, + .minor = 14, +}) orelse true; + +const WipeMe = struct { + init_state: enum { uninitialized, initialized, failed }, + gimli: std.crypto.core.Gimli, +}; +const wipe_align = if (maybe_have_wipe_on_fork) mem.page_size else @alignOf(WipeMe); + +threadlocal var wipe_me: WipeMe align(wipe_align) = .{ + .gimli = undefined, + .init_state = .uninitialized, +}; + +fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void { + if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) { + // arc4random is already a thread-local CSPRNG. + return std.c.arc4random_buf(buffer.ptr, buffer.len); + } + // Allow applications to decide they would prefer to have every call to + // std.crypto.random always make an OS syscall, rather than rely on an + // application implementation of a CSPRNG. + if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) { + return fillWithOsEntropy(buffer); + } + switch (wipe_me.init_state) { + .uninitialized => { + if (want_fork_safety) { + if (maybe_have_wipe_on_fork) { + if (std.os.madvise( + @ptrCast([*]align(mem.page_size) u8, &wipe_me), + @sizeOf(@TypeOf(wipe_me)), + std.os.MADV_WIPEONFORK, + )) |_| { + return initAndFill(buffer); + } else |_| if (std.Thread.use_pthreads) { + return setupPthreadAtforkAndFill(buffer); + } else { + // Since we failed to set up fork safety, we fall back to always + // calling getrandom every time. + wipe_me.init_state = .failed; + return fillWithOsEntropy(buffer); + } + } else if (std.Thread.use_pthreads) { + return setupPthreadAtforkAndFill(buffer); + } else { + // We have no mechanism to provide fork safety, but we want fork safety, + // so we fall back to calling getrandom every time. + wipe_me.init_state = .failed; + return fillWithOsEntropy(buffer); + } + } else { + return initAndFill(buffer); + } + }, + .initialized => { + return fillWithCsprng(buffer); + }, + .failed => { + if (want_fork_safety) { + return fillWithOsEntropy(buffer); + } else { + unreachable; + } + }, + } +} + +fn setupPthreadAtforkAndFill(buffer: []u8) void { + const failed = std.c.pthread_atfork(null, null, childAtForkHandler) != 0; + if (failed) { + wipe_me.init_state = .failed; + return fillWithOsEntropy(buffer); + } else { + return initAndFill(buffer); + } +} + +fn childAtForkHandler() callconv(.C) void { + // TODO this is a workaround for https://github.com/ziglang/zig/issues/7495 + var wipe_slice: []u8 = undefined; + wipe_slice = @ptrCast([*]u8, &wipe_me)[0..@sizeOf(@TypeOf(wipe_me))]; + std.crypto.utils.secureZero(u8, wipe_slice); +} + +fn fillWithCsprng(buffer: []u8) void { + if (buffer.len != 0) { + wipe_me.gimli.squeeze(buffer); + } else { + wipe_me.gimli.permute(); + } + mem.set(u8, wipe_me.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0); +} + +fn fillWithOsEntropy(buffer: []u8) void { + std.os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy"); +} + +fn initAndFill(buffer: []u8) void { + var seed: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined; + // Because we panic on getrandom() failing, we provide the opportunity + // to override the default seed function. This also makes + // `std.crypto.random` available on freestanding targets, provided that + // the `cryptoRandomSeed` function is provided. + if (@hasDecl(root, "cryptoRandomSeed")) { + root.cryptoRandomSeed(&seed); + } else { + fillWithOsEntropy(&seed); + } + + wipe_me.gimli = std.crypto.core.Gimli.init(seed); + + // This is at the end so that accidental recursive dependencies result + // in stack overflows instead of invalid random data. + wipe_me.init_state = .initialized; + + return fillWithCsprng(buffer); +} diff --git a/lib/std/crypto/utils.zig b/lib/std/crypto/utils.zig index 33ad6360f6..08271ac9f4 100644 --- a/lib/std/crypto/utils.zig +++ b/lib/std/crypto/utils.zig @@ -51,8 +51,8 @@ pub fn secureZero(comptime T: type, s: []T) void { test "crypto.utils.timingSafeEql" { var a: [100]u8 = undefined; var b: [100]u8 = undefined; - try std.crypto.randomBytes(a[0..]); - try std.crypto.randomBytes(b[0..]); + std.crypto.random.bytes(a[0..]); + std.crypto.random.bytes(b[0..]); testing.expect(!timingSafeEql([100]u8, a, b)); mem.copy(u8, a[0..], b[0..]); testing.expect(timingSafeEql([100]u8, a, b)); @@ -61,8 +61,8 @@ test "crypto.utils.timingSafeEql" { test "crypto.utils.timingSafeEql (vectors)" { var a: [100]u8 = undefined; var b: [100]u8 = undefined; - try std.crypto.randomBytes(a[0..]); - try std.crypto.randomBytes(b[0..]); + std.crypto.random.bytes(a[0..]); + std.crypto.random.bytes(b[0..]); const v1: std.meta.Vector(100, u8) = a; const v2: std.meta.Vector(100, u8) = b; testing.expect(!timingSafeEql(std.meta.Vector(100, u8), v1, v2)); |
