aboutsummaryrefslogtreecommitdiff
path: root/lib/std/crypto
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2020-12-18 23:57:46 -0500
committerGitHub <noreply@github.com>2020-12-18 23:57:46 -0500
commit506af7e52e0985b410ea089bf5fa3247ab2377cb (patch)
tree2ec26d70f41a1382b736b606ebfa094ace62573e /lib/std/crypto
parentce65533985caa9e2da567948e36d7d4ba0185005 (diff)
parentf416535768fc30195cad6cd481f73fd1e80082aa (diff)
downloadzig-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.zig8
-rw-r--r--lib/std/crypto/25519/edwards25519.zig4
-rw-r--r--lib/std/crypto/25519/x25519.zig2
-rw-r--r--lib/std/crypto/bcrypt.zig4
-rw-r--r--lib/std/crypto/salsa20.zig18
-rw-r--r--lib/std/crypto/tlcsprng.zig158
-rw-r--r--lib/std/crypto/utils.zig8
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));