From 013efaf13987acfa6b41d40f07900c1ea77f5bda Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 17 Dec 2020 20:03:41 -0700 Subject: std: introduce a thread-local CSPRNG for general use std.crypto.random * cross platform, even freestanding * can't fail. on initialization for some systems requires calling os.getrandom(), in which case there are rare but theoretically possible errors. The code panics in these cases, however the application may choose to override the default seed function and then handle the failure another way. * thread-safe * supports the full Random interface * cryptographically secure * no syscall required to initialize on Linux (AT_RANDOM) * calls arc4random on systems that support it `std.crypto.randomBytes` is removed in favor of `std.crypto.random.bytes`. I moved some of the Random implementations into their own files in the interest of organization. stage2 no longer requires passing a RNG; instead it uses this API. Closes #6704 --- lib/std/crypto/25519/ed25519.zig | 8 ++--- lib/std/crypto/25519/edwards25519.zig | 4 +-- lib/std/crypto/25519/x25519.zig | 2 +- lib/std/crypto/bcrypt.zig | 4 +-- lib/std/crypto/salsa20.zig | 18 +++++----- lib/std/crypto/tlcsprng.zig | 62 +++++++++++++++++++++++++++++++++++ lib/std/crypto/utils.zig | 8 ++--- 7 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 lib/std/crypto/tlcsprng.zig (limited to 'lib/std/crypto') 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..0105cf0ce8 --- /dev/null +++ b/lib/std/crypto/tlcsprng.zig @@ -0,0 +1,62 @@ +// 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 }; +pub threadlocal var csprng_state: std.crypto.core.Gimli = undefined; +pub threadlocal var csprng_state_initialized = false; +fn tlsCsprngFill(r: *std.rand.Random, buf: []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(buf.ptr, buf.len); + } + if (!csprng_state_initialized) { + var seed: [seed_len]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 { + defaultSeed(&seed); + } + init(seed); + } + if (buf.len != 0) { + csprng_state.squeeze(buf); + } else { + csprng_state.permute(); + } + mem.set(u8, csprng_state.toSlice()[0..std.crypto.core.Gimli.RATE], 0); +} + +fn defaultSeed(buffer: *[seed_len]u8) void { + std.os.getrandom(buffer) catch @panic("getrandom() failed to seed thread-local CSPRNG"); +} + +pub const seed_len = 32; + +pub fn init(seed: [seed_len]u8) void { + var initial_state: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined; + mem.copy(u8, initial_state[0..seed_len], &seed); + mem.set(u8, initial_state[seed_len..], 0); + csprng_state = std.crypto.core.Gimli.init(initial_state); + + // This is at the end so that accidental recursive dependencies result + // in stack overflows instead of invalid random data. + csprng_state_initialized = true; +} 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)); -- cgit v1.2.3