commit 62d6bbc7dc36dbb2f45da08e767076157deeb259 (tree)
parent 867501d9d2f757f99af59b1904b251aa63262e23
Author: Andrew Kelley <andrew@ziglang.org>
Date: Wed, 7 Jan 2026 20:04:26 +0100
Merge pull request 'std.Io: implement entropy (randomness)' (#30709) from random into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30709
Diffstat:
66 files changed, 1301 insertions(+), 803 deletions(-)
diff --git a/build.zig b/build.zig
@@ -593,15 +593,7 @@ pub fn build(b: *std.Build) !void {
.x86_64 => 3_756_422_348,
else => 3_800_000_000,
},
- .linux => switch (b.graph.host.result.cpu.arch) {
- .aarch64 => 6_732_817_203,
- .loongarch64 => 3_216_349_593,
- .powerpc64le => 3_090_179_276,
- .riscv64 => 4_052_670_054,
- .s390x => 3_652_514_201,
- .x86_64 => 3_249_546_854,
- else => 6_800_000_000,
- },
+ .linux => 6_800_000_000,
.macos => switch (b.graph.host.result.cpu.arch) {
.aarch64 => 8_273_795_481,
else => 8_300_000_000,
diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig
@@ -1217,11 +1217,12 @@ pub fn getDepFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u
}
fn getRandomFilename(d: *Driver, buf: *[std.fs.max_name_bytes]u8, extension: []const u8) ![]const u8 {
+ const io = d.comp.io;
const random_bytes_count = 12;
const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
var random_bytes: [random_bytes_count]u8 = undefined;
- std.crypto.random.bytes(&random_bytes);
+ io.random(&random_bytes);
var random_name: [sub_path_len]u8 = undefined;
_ = std.fs.base64_encoder.encode(&random_name, &random_bytes);
diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig
@@ -21,7 +21,6 @@ pub const dependencies = @import("@dependencies");
pub const std_options: std.Options = .{
.side_channels_mitigations = .none,
.http_disable_tls = true,
- .crypto_fork_safety = false,
};
pub fn main(init: process.Init.Minimal) !void {
diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig
@@ -111,12 +111,7 @@ pub const TestResults = struct {
pub const MakeOptions = struct {
progress_node: std.Progress.Node,
watch: bool,
- web_server: switch (builtin.target.cpu.arch) {
- else => ?*Build.WebServer,
- // WASM code references `Build.abi` which happens to incidentally reference this type, but
- // it currently breaks because `std.net.Address` doesn't work there. Work around for now.
- .wasm32 => void,
- },
+ web_server: ?*Build.WebServer,
/// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.
unit_test_timeout_ns: ?u64,
/// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`.
diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig
@@ -1706,18 +1706,29 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
// The args file is already present from a previous run.
} else |err| switch (err) {
error.FileNotFound => {
- try b.cache_root.handle.createDirPath(io, "tmp");
- const rand_int = std.crypto.random.int(u64);
- const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
- try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args });
- defer b.cache_root.handle.deleteFile(io, tmp_path) catch {
- // It's fine if the temporary file can't be cleaned up.
+ var af = b.cache_root.handle.createFileAtomic(io, args_file, .{
+ .replace = false,
+ .make_path = true,
+ }) catch |e| return step.fail("failed creating tmp args file {f}{s}: {t}", .{
+ b.cache_root, args_file, e,
+ });
+ defer af.deinit(io);
+
+ af.file.writeStreamingAll(io, args) catch |e| {
+ return step.fail("failed writing args data to tmp file {f}{s}: {t}", .{
+ b.cache_root, args_file, e,
+ });
};
- b.cache_root.handle.rename(tmp_path, b.cache_root.handle, args_file, io) catch |rename_err| switch (rename_err) {
+ // Note we can't clean up this file, not even after build
+ // success, because that might interfere with another build
+ // process that needs the same file.
+ af.link(io) catch |e| switch (e) {
error.PathAlreadyExists => {
// The args file was created by another concurrent build process.
},
- else => |other_err| return other_err,
+ else => |other_err| return step.fail("failed linking tmp file {f}{s}: {t}", .{
+ b.cache_root, args_file, other_err,
+ }),
};
},
else => |other_err| return other_err,
diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig
@@ -476,46 +476,28 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
return;
} else |outer_err| switch (outer_err) {
error.FileNotFound => {
- const sub_dirname = fs.path.dirname(sub_path).?;
- b.cache_root.handle.createDirPath(io, sub_dirname) catch |e|
- return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e });
-
- const rand_int = std.crypto.random.int(u64);
- const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
- std.fmt.hex(rand_int) ++ fs.path.sep_str ++
- basename;
- const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
-
- b.cache_root.handle.createDirPath(io, tmp_sub_path_dirname) catch |err| {
- return step.fail("unable to make temporary directory '{f}{s}': {t}", .{
- b.cache_root, tmp_sub_path_dirname, err,
- });
- };
+ var atomic_file = b.cache_root.handle.createFileAtomic(io, sub_path, .{
+ .replace = false,
+ .make_path = true,
+ }) catch |err| return step.fail("failed to create temporary path for '{f}{s}': {t}", .{
+ b.cache_root, sub_path, err,
+ });
+ defer atomic_file.deinit(io);
- b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
- return step.fail("unable to write options to '{f}{s}': {t}", .{
- b.cache_root, tmp_sub_path, err,
+ atomic_file.file.writeStreamingAll(io, options.contents.items) catch |err| {
+ return step.fail("failed to write options to temporary path for '{f}{s}': {t}", .{
+ b.cache_root, sub_path, err,
});
};
- b.cache_root.handle.rename(tmp_sub_path, b.cache_root.handle, sub_path, io) catch |err| switch (err) {
+ atomic_file.link(io) catch |err| switch (err) {
error.PathAlreadyExists => {
- // Other process beat us to it. Clean up the temp file.
- b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| {
- try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{
- b.cache_root, tmp_sub_path, e,
- });
- };
step.result_cached = true;
return;
},
- else => {
- return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{
- b.cache_root, tmp_sub_path,
- b.cache_root, sub_path,
- err,
- });
- },
+ else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{
+ b.cache_root, sub_path, err,
+ }),
};
},
else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig
@@ -984,7 +984,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
};
// We do not know the final output paths yet, use temp paths to run the command.
- const rand_int = std.crypto.random.int(u64);
+ var rand_int: u64 = undefined;
+ io.random(@ptrCast(&rand_int));
const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
for (output_placeholders.items) |placeholder| {
@@ -1128,7 +1129,8 @@ pub fn rerunInFuzzMode(
}
const has_side_effects = false;
- const rand_int = std.crypto.random.int(u64);
+ var rand_int: u64 = undefined;
+ io.random(@ptrCast(&rand_int));
const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
.progress_node = prog_node,
diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig
@@ -293,7 +293,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
.tmp => {
step.result_cached = false;
- const rand_int = std.crypto.random.int(u64);
+ var rand_int: u64 = undefined;
+ io.random(@ptrCast(&rand_int));
const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path});
diff --git a/lib/std/Io.zig b/lib/std/Io.zig
@@ -676,6 +676,7 @@ pub const VTable = struct {
dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,
dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,
dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
+ dirRenamePreserve: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenamePreserveError!void,
dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void,
dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
@@ -731,6 +732,9 @@ pub const VTable = struct {
now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
+ random: *const fn (?*anyopaque, buffer: []u8) void,
+ randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void,
+
netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
@@ -2242,3 +2246,36 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel
pub fn unlockStderr(io: Io) void {
return io.vtable.unlockStderr(io.userdata);
}
+
+/// Obtains entropy from a cryptographically secure pseudo-random number
+/// generator.
+///
+/// The implementation *may* store RNG state in process memory and use it to
+/// fill `buffer`.
+///
+/// The randomness is seeded by `randomSecure`, or a less secure mechanism upon
+/// failure.
+///
+/// Threadsafe.
+///
+/// See also `randomSecure`.
+pub fn random(io: Io, buffer: []u8) void {
+ return io.vtable.random(io.userdata, buffer);
+}
+
+pub const RandomSecureError = error{EntropyUnavailable} || Cancelable;
+
+/// Obtains cryptographically secure entropy from outside the process.
+///
+/// Always makes a syscall, or otherwise avoids dependency on process memory,
+/// in order to obtain fresh randomness. Does not rely on stored RNG state.
+///
+/// Does not have any fallback mechanisms; returns `error.EntropyUnavailable`
+/// if any problems occur.
+///
+/// Threadsafe.
+///
+/// See also `random`.
+pub fn randomSecure(io: Io, buffer: []u8) RandomSecureError!void {
+ return io.vtable.randomSecure(io.userdata, buffer);
+}
diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig
@@ -936,10 +936,9 @@ pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void
pub const RenameError = error{
/// In WASI, this error may occur when the file descriptor does
/// not hold the required rights to rename a resource by path relative to it.
- ///
- /// On Windows, this error may be returned instead of PathAlreadyExists when
- /// renaming a directory over an existing directory.
AccessDenied,
+ /// Attempted to replace a nonempty directory.
+ DirNotEmpty,
PermissionDenied,
FileBusy,
DiskQuota,
@@ -950,9 +949,8 @@ pub const RenameError = error{
NotDir,
SystemResources,
NoSpaceLeft,
- PathAlreadyExists,
ReadOnlyFileSystem,
- RenameAcrossMountPoints,
+ CrossDevice,
NoDevice,
SharingViolation,
PipeBusy,
@@ -964,6 +962,7 @@ pub const RenameError = error{
/// intercepts file system operations and makes them significantly slower
/// in addition to possibly failing with this error code.
AntivirusInterference,
+ HardwareFailure,
} || PathNameError || Io.Cancelable || Io.UnexpectedError;
/// Change the name or location of a file or directory.
@@ -973,9 +972,9 @@ pub const RenameError = error{
/// Renaming a file over an existing directory or a directory over an existing
/// file will fail with `error.IsDir` or `error.NotDir`
///
-/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
-/// On WASI, both paths should be encoded as valid UTF-8.
-/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
+/// * On WASI, both paths should be encoded as valid UTF-8.
+/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn rename(
old_dir: Dir,
old_sub_path: []const u8,
@@ -993,6 +992,39 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename
return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
}
+pub const RenamePreserveError = error{
+ /// In WASI, this error may occur when the file descriptor does
+ /// not hold the required rights to rename a resource by path relative to it.
+ ///
+ /// On Windows, this error may be returned instead of PathAlreadyExists when
+ /// renaming a directory over an existing directory.
+ AccessDenied,
+ PathAlreadyExists,
+ /// Operating system or file system does not support atomic nonreplacing
+ /// rename.
+ OperationUnsupported,
+} || RenameError;
+
+/// Change the name or location of a file or directory.
+///
+/// If `new_sub_path` already exists, `error.PathAlreadyExists` will be returned.
+///
+/// Renaming a file over an existing directory or a directory over an existing
+/// file will fail with `error.IsDir` or `error.NotDir`
+///
+/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
+/// * On WASI, both paths should be encoded as valid UTF-8.
+/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
+pub fn renamePreserve(
+ old_dir: Dir,
+ old_sub_path: []const u8,
+ new_dir: Dir,
+ new_sub_path: []const u8,
+ io: Io,
+) RenamePreserveError!void {
+ return io.vtable.dirRenamePreserve(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
+}
+
pub const HardLinkOptions = File.HardLinkOptions;
pub const HardLinkError = File.HardLinkError;
@@ -1098,8 +1130,10 @@ pub fn symLinkAtomic(
const temp_path = temp_path_buf[0..temp_path_len];
+ var random_integer: u64 = undefined;
+
while (true) {
- const random_integer = std.crypto.random.int(u64);
+ io.random(@ptrCast(&random_integer));
temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
if (dir.symLink(io, target_path, temp_path, flags)) {
diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig
@@ -709,7 +709,7 @@ pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
}
pub const HardLinkOptions = struct {
- follow_symlinks: bool = true,
+ follow_symlinks: bool = false,
};
pub const HardLinkError = error{
@@ -726,7 +726,7 @@ pub const HardLinkError = error{
SystemResources,
NoSpaceLeft,
ReadOnlyFileSystem,
- NotSameFileSystem,
+ CrossDevice,
NotDir,
} || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError;
diff --git a/lib/std/Io/File/Atomic.zig b/lib/std/Io/File/Atomic.zig
@@ -37,10 +37,14 @@ pub fn deinit(af: *Atomic, io: Io) void {
af.* = undefined;
}
-pub const LinkError = Dir.HardLinkError;
+pub const LinkError = File.HardLinkError || Dir.RenamePreserveError;
/// Atomically materializes the file into place, failing with
/// `error.PathAlreadyExists` if something already exists there.
+///
+/// If this operation could not be done with an unnamed temporary file, the
+/// named temporary file will be deleted in a following operation, which may
+/// independently fail. The result of that operation is stored in `delete_err`.
pub fn link(af: *Atomic, io: Io) LinkError!void {
if (af.file_exists) {
if (af.file_open) {
@@ -48,8 +52,7 @@ pub fn link(af: *Atomic, io: Io) LinkError!void {
af.file_open = false;
}
const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
- try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{});
- af.dir.deleteFile(io, &tmp_sub_path) catch {};
+ try af.dir.renamePreserve(&tmp_sub_path, af.dir, af.dest_sub_path, io);
af.file_exists = false;
} else {
assert(af.file_open);
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
@@ -65,6 +65,22 @@ argv0: Argv0,
environ: Environ,
null_file: NullFile = .{},
+random_file: RandomFile = .{},
+
+csprng: Csprng = .{},
+
+pub const Csprng = struct {
+ rng: std.Random.DefaultCsprng = .{
+ .state = undefined,
+ .offset = std.math.maxInt(usize),
+ },
+
+ pub const seed_len = std.Random.DefaultCsprng.secret_seed_length;
+
+ pub fn isInitialized(c: *const Csprng) bool {
+ return c.rng.offset != std.math.maxInt(usize);
+ }
+};
pub const Argv0 = switch (native_os) {
.openbsd, .haiku => struct {
@@ -151,6 +167,15 @@ pub const NullFile = switch (native_os) {
},
};
+pub const RandomFile = switch (native_os) {
+ .windows => NullFile,
+ else => if (use_dev_urandom) NullFile else struct {
+ fn deinit(this: @This()) void {
+ _ = this;
+ }
+ },
+};
+
pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
unknown = 0,
_,
@@ -585,6 +610,8 @@ const Thread = struct {
/// Always released when `Status.cancelation` is set to `.parked`.
futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
+ csprng: Csprng,
+
const Handle = Handle: {
if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE;
@@ -1285,6 +1312,7 @@ pub fn deinit(t: *Threaded) void {
if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
}
t.null_file.deinit();
+ t.random_file.deinit();
t.* = undefined;
}
@@ -1313,6 +1341,7 @@ fn worker(t: *Threaded) void {
}),
.cancel_protection = .unblocked,
.futex_waiter = undefined,
+ .csprng = .{},
};
Thread.current = &thread;
@@ -1413,6 +1442,7 @@ pub fn io(t: *Threaded) Io {
.dirDeleteFile = dirDeleteFile,
.dirDeleteDir = dirDeleteDir,
.dirRename = dirRename,
+ .dirRenamePreserve = dirRenamePreserve,
.dirSymLink = dirSymLink,
.dirReadLink = dirReadLink,
.dirSetOwner = dirSetOwner,
@@ -1466,6 +1496,9 @@ pub fn io(t: *Threaded) Io {
.now = now,
.sleep = sleep,
+ .random = random,
+ .randomSecure = randomSecure,
+
.netListenIp = switch (native_os) {
.windows => netListenIpWindows,
else => netListenIpPosix,
@@ -1561,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {
.dirDeleteFile = dirDeleteFile,
.dirDeleteDir = dirDeleteDir,
.dirRename = dirRename,
+ .dirRenamePreserve = dirRenamePreserve,
.dirSymLink = dirSymLink,
.dirReadLink = dirReadLink,
.dirSetOwner = dirSetOwner,
@@ -1614,6 +1648,9 @@ pub fn ioBasic(t: *Threaded) Io {
.now = now,
.sleep = sleep,
+ .random = random,
+ .randomSecure = randomSecure,
+
.netListenIp = netListenIpUnavailable,
.netListenUnix = netListenUnixUnavailable,
.netAccept = netAcceptUnavailable,
@@ -1704,6 +1741,23 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid
});
const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
+const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
+ .{ .major = 30, .minor = 0, .patch = 0 }
+else
+ .{ .major = 2, .minor = 28, .patch = 0 });
+
+const use_libc_getrandom = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
+ .major = 28,
+ .minor = 0,
+ .patch = 0,
+} else .{
+ .major = 2,
+ .minor = 25,
+ .patch = 0,
+});
+
+const use_dev_urandom = @TypeOf(posix.system.getrandom) == void and native_os == .linux;
+
fn async(
userdata: ?*anyopaque,
result: []u8,
@@ -2342,11 +2396,11 @@ fn dirCreateDirPath(
status = .created;
} else |err| switch (err) {
error.PathAlreadyExists => {
- // stat the file and return an error if it's not a directory
- // this is important because otherwise a dangling symlink
- // could cause an infinite loop
- const fstat = try dirStatFile(t, dir, component.path, .{});
- if (fstat.kind != .directory) return error.NotDir;
+ // It is important to return an error if it's not a directory
+ // because otherwise a dangling symlink could cause an infinite
+ // loop.
+ const kind = try filePathKind(t, dir, component.path);
+ if (kind != .directory) return error.NotDir;
},
error.FileNotFound => |e| {
component = it.previous() orelse return e;
@@ -2538,11 +2592,7 @@ fn dirStatFileLinux(
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
const linux = std.os.linux;
- const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
- .{ .major = 30, .minor = 0, .patch = 0 }
- else
- .{ .major = 2, .minor = 28, .patch = 0 });
- const sys = if (use_c) std.c else std.os.linux;
+ const sys = if (statx_use_c) std.c else std.os.linux;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
@@ -2691,6 +2741,35 @@ fn dirStatFileWasi(
}
}
+fn filePathKind(t: *Threaded, dir: Dir, sub_path: []const u8) !File.Kind {
+ if (native_os == .linux) {
+ var path_buffer: [posix.PATH_MAX]u8 = undefined;
+ const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
+
+ const linux = std.os.linux;
+ const syscall: Syscall = try .start();
+ while (true) {
+ var statx = std.mem.zeroes(linux.Statx);
+ switch (linux.errno(linux.statx(dir.handle, sub_path_posix, 0, .{ .TYPE = true }, &statx))) {
+ .SUCCESS => {
+ syscall.finish();
+ if (!statx.mask.TYPE) return error.Unexpected;
+ return statxKind(statx.mode);
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .NOMEM => return syscall.fail(error.SystemResources),
+ else => |err| return syscall.unexpectedErrno(err),
+ }
+ }
+ }
+
+ const stat = try dirStatFile(t, dir, sub_path, .{});
+ return stat.kind;
+}
+
fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
const t: *Threaded = @ptrCast(@alignCast(userdata));
@@ -2778,11 +2857,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
const linux = std.os.linux;
- const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
- .{ .major = 30, .minor = 0, .patch = 0 }
- else
- .{ .major = 2, .minor = 28, .patch = 0 });
- const sys = if (use_c) std.c else std.os.linux;
+ const sys = if (statx_use_c) std.c else std.os.linux;
const syscall: Syscall = try .start();
while (true) {
@@ -3450,7 +3525,7 @@ fn dirCreateFileAtomic(
if (dest_dirname) |dirname| {
// This has a nice side effect of preemptively triggering EISDIR or
// ENOENT, avoiding the ambiguity below.
- dir.createDirPath(t_io, dirname) catch |err| switch (err) {
+ if (options.make_path) dir.createDirPath(t_io, dirname) catch |err| switch (err) {
// None of these make sense in this context.
error.IsDir,
error.Streaming,
@@ -3553,8 +3628,9 @@ fn atomicFileInit(
dir: Dir,
close_dir_on_deinit: bool,
) Dir.CreateFileAtomicError!File.Atomic {
+ var random_integer: u64 = undefined;
while (true) {
- const random_integer = std.crypto.random.int(u64);
+ t_io.random(@ptrCast(&random_integer));
const tmp_sub_path = std.fmt.hex(random_integer);
const file = dir.createFile(t_io, &tmp_sub_path, .{
.permissions = permissions,
@@ -3636,10 +3712,12 @@ fn dirOpenFilePosix(
},
};
+ const mode: posix.mode_t = 0;
+
const fd: posix.fd_t = fd: {
const syscall: Syscall = try .start();
while (true) {
- const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0));
+ const rc = openat_sym(dir.handle, sub_path_posix, os_flags, mode);
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
@@ -4068,9 +4146,11 @@ fn dirOpenDirPosix(
if (@hasField(posix.O, "PATH") and !options.iterate)
flags.PATH = true;
+ const mode: posix.mode_t = 0;
+
const syscall: Syscall = try .start();
while (true) {
- const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0));
+ const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
@@ -5169,12 +5249,21 @@ fn fileHardLink(
var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
- const flags: u32 = if (!options.follow_symlinks)
- posix.AT.SYMLINK_NOFOLLOW | posix.AT.EMPTY_PATH
+ const flags: u32 = if (options.follow_symlinks)
+ posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH
else
posix.AT.EMPTY_PATH;
- return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags);
+ return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) {
+ error.FileNotFound => {
+ if (options.follow_symlinks) return error.FileNotFound;
+ var proc_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
+ const proc_path = std.fmt.bufPrintSentinel(&proc_buf, "/proc/self/fd/{d}", .{file.handle}, 0) catch
+ unreachable;
+ return linkat(posix.AT.FDCWD, proc_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW);
+ },
+ else => |e| return e,
+ };
}
fn linkat(
@@ -5205,7 +5294,7 @@ fn linkat(
.NOTDIR => return syscall.fail(error.NotDir),
.PERM => return syscall.fail(error.PermissionDenied),
.ROFS => return syscall.fail(error.ReadOnlyFileSystem),
- .XDEV => return syscall.fail(error.NotSameFileSystem),
+ .XDEV => return syscall.fail(error.CrossDevice),
.ILSEQ => return syscall.fail(error.BadPathName),
.FAULT => |err| return syscall.errnoBug(err),
.INVAL => |err| return syscall.errnoBug(err),
@@ -5614,15 +5703,44 @@ fn dirRenameWindows(
new_dir: Dir,
new_sub_path: []const u8,
) Dir.RenameError!void {
- const w = windows;
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
+ return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, true) catch |err| switch (err) {
+ error.PathAlreadyExists => return error.Unexpected,
+ error.OperationUnsupported => return error.Unexpected,
+ else => |e| return e,
+ };
+}
+fn dirRenamePreserve(
+ userdata: ?*anyopaque,
+ old_dir: Dir,
+ old_sub_path: []const u8,
+ new_dir: Dir,
+ new_sub_path: []const u8,
+) Dir.RenamePreserveError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false);
+ if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path);
+ // Make a hard link then delete the original.
+ try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false });
+ const prev = swapCancelProtection(t, .blocked);
+ defer _ = swapCancelProtection(t, prev);
+ dirDeleteFile(t, old_dir, old_sub_path) catch {};
+}
+
+fn dirRenameWindowsInner(
+ old_dir: Dir,
+ old_sub_path: []const u8,
+ new_dir: Dir,
+ new_sub_path: []const u8,
+ replace_if_exists: bool,
+) Dir.RenamePreserveError!void {
+ const w = windows;
const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
const old_path_w = old_path_w_buf.span();
const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path);
const new_path_w = new_path_w_buf.span();
- const replace_if_exists = true;
const src_fd = src_fd: {
const syscall: Syscall = try .start();
@@ -5724,9 +5842,9 @@ fn dirRenameWindows(
.ACCESS_DENIED => return error.AccessDenied,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
+ .NOT_SAME_DEVICE => return error.CrossDevice,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
- .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
+ .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
.FILE_IS_A_DIRECTORY => return error.IsDir,
.NOT_A_DIRECTORY => return error.NotDir,
else => return w.unexpectedStatus(rc),
@@ -5770,10 +5888,10 @@ fn dirRenameWasi(
.NOTDIR => return error.NotDir,
.NOMEM => return error.SystemResources,
.NOSPC => return error.NoSpaceLeft,
- .EXIST => return error.PathAlreadyExists,
- .NOTEMPTY => return error.PathAlreadyExists,
+ .EXIST => return error.DirNotEmpty,
+ .NOTEMPTY => return error.DirNotEmpty,
.ROFS => return error.ReadOnlyFileSystem,
- .XDEV => return error.RenameAcrossMountPoints,
+ .XDEV => return error.CrossDevice,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
@@ -5799,9 +5917,105 @@ fn dirRenamePosix(
const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
+ return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix);
+}
+
+fn dirRenamePreserveLinux(
+ old_dir: Dir,
+ old_sub_path: []const u8,
+ new_dir: Dir,
+ new_sub_path: []const u8,
+) Dir.RenamePreserveError!void {
+ const linux = std.os.linux;
+
+ var old_path_buffer: [linux.PATH_MAX]u8 = undefined;
+ var new_path_buffer: [linux.PATH_MAX]u8 = undefined;
+
+ const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
+ const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
+
+ const syscall: Syscall = try .start();
+ while (true) switch (linux.errno(linux.renameat2(
+ old_dir.handle,
+ old_sub_path_posix,
+ new_dir.handle,
+ new_sub_path_posix,
+ .{ .NOREPLACE = true },
+ ))) {
+ .SUCCESS => return syscall.finish(),
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .ACCES => return syscall.fail(error.AccessDenied),
+ .PERM => return syscall.fail(error.PermissionDenied),
+ .BUSY => return syscall.fail(error.FileBusy),
+ .DQUOT => return syscall.fail(error.DiskQuota),
+ .ISDIR => return syscall.fail(error.IsDir),
+ .LOOP => return syscall.fail(error.SymLinkLoop),
+ .MLINK => return syscall.fail(error.LinkQuotaExceeded),
+ .NAMETOOLONG => return syscall.fail(error.NameTooLong),
+ .NOENT => return syscall.fail(error.FileNotFound),
+ .NOTDIR => return syscall.fail(error.NotDir),
+ .NOMEM => return syscall.fail(error.SystemResources),
+ .NOSPC => return syscall.fail(error.NoSpaceLeft),
+ .EXIST => return syscall.fail(error.PathAlreadyExists),
+ .NOTEMPTY => return syscall.fail(error.DirNotEmpty),
+ .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
+ .XDEV => return syscall.fail(error.CrossDevice),
+ .ILSEQ => return syscall.fail(error.BadPathName),
+ .FAULT => |err| return syscall.errnoBug(err),
+ .INVAL => |err| return syscall.errnoBug(err),
+ else => |err| return syscall.unexpectedErrno(err),
+ };
+}
+
+fn renameat(
+ old_dir: posix.fd_t,
+ old_sub_path: [*:0]const u8,
+ new_dir: posix.fd_t,
+ new_sub_path: [*:0]const u8,
+) Dir.RenameError!void {
+ const syscall: Syscall = try .start();
+ while (true) switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
+ .SUCCESS => return syscall.finish(),
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .ACCES => return syscall.fail(error.AccessDenied),
+ .PERM => return syscall.fail(error.PermissionDenied),
+ .BUSY => return syscall.fail(error.FileBusy),
+ .DQUOT => return syscall.fail(error.DiskQuota),
+ .ISDIR => return syscall.fail(error.IsDir),
+ .IO => return syscall.fail(error.HardwareFailure),
+ .LOOP => return syscall.fail(error.SymLinkLoop),
+ .MLINK => return syscall.fail(error.LinkQuotaExceeded),
+ .NAMETOOLONG => return syscall.fail(error.NameTooLong),
+ .NOENT => return syscall.fail(error.FileNotFound),
+ .NOTDIR => return syscall.fail(error.NotDir),
+ .NOMEM => return syscall.fail(error.SystemResources),
+ .NOSPC => return syscall.fail(error.NoSpaceLeft),
+ .EXIST => return syscall.fail(error.DirNotEmpty),
+ .NOTEMPTY => return syscall.fail(error.DirNotEmpty),
+ .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
+ .XDEV => return syscall.fail(error.CrossDevice),
+ .ILSEQ => return syscall.fail(error.BadPathName),
+ .FAULT => |err| return syscall.errnoBug(err),
+ .INVAL => |err| return syscall.errnoBug(err),
+ else => |err| return syscall.unexpectedErrno(err),
+ };
+}
+
+fn renameatPreserve(
+ old_dir: posix.fd_t,
+ old_sub_path: [*:0]const u8,
+ new_dir: posix.fd_t,
+ new_sub_path: [*:0]const u8,
+) Dir.RenameError!void {
const syscall: Syscall = try .start();
while (true) {
- switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {
+ switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
.SUCCESS => return syscall.finish(),
.INTR => {
try syscall.checkCancel();
@@ -5827,7 +6041,7 @@ fn dirRenamePosix(
.EXIST => return error.PathAlreadyExists,
.NOTEMPTY => return error.PathAlreadyExists,
.ROFS => return error.ReadOnlyFileSystem,
- .XDEV => return error.RenameAcrossMountPoints,
+ .XDEV => return error.CrossDevice,
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
}
@@ -6318,11 +6532,6 @@ fn fchmodatFallback(
mode: posix.mode_t,
) Dir.SetFilePermissionsError!void {
comptime assert(native_os == .linux);
- const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
- .{ .major = 30, .minor = 0, .patch = 0 }
- else
- .{ .major = 2, .minor = 28, .patch = 0 });
- const sys = if (use_c) std.c else std.os.linux;
// Fallback to changing permissions using procfs:
//
@@ -6369,6 +6578,7 @@ fn fchmodatFallback(
defer posix.close(path_fd);
const path_mode = mode: {
+ const sys = if (statx_use_c) std.c else std.os.linux;
const syscall: Syscall = try .start();
while (true) {
var statx = std.mem.zeroes(std.os.linux.Statx);
@@ -7612,7 +7822,7 @@ fn dirHardLink(
.NOTDIR => return error.NotDir,
.PERM => return error.PermissionDenied,
.ROFS => return error.ReadOnlyFileSystem,
- .XDEV => return error.NotSameFileSystem,
+ .XDEV => return error.CrossDevice,
.INVAL => |err| return errnoBug(err),
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
@@ -7628,7 +7838,7 @@ fn dirHardLink(
const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
- const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
+ const flags: u32 = if (options.follow_symlinks) posix.AT.SYMLINK_FOLLOW else 0;
return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags);
}
@@ -12268,16 +12478,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
.nlink = stx.nlink,
.size = stx.size,
.permissions = .fromMode(stx.mode),
- .kind = switch (stx.mode & std.os.linux.S.IFMT) {
- std.os.linux.S.IFDIR => .directory,
- std.os.linux.S.IFCHR => .character_device,
- std.os.linux.S.IFBLK => .block_device,
- std.os.linux.S.IFREG => .file,
- std.os.linux.S.IFIFO => .named_pipe,
- std.os.linux.S.IFLNK => .sym_link,
- std.os.linux.S.IFSOCK => .unix_domain_socket,
- else => .unknown,
- },
+ .kind = statxKind(stx.mode),
.atime = if (!stx.mask.ATIME) null else .{
.nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec),
},
@@ -12286,6 +12487,19 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
};
}
+fn statxKind(stx_mode: u16) File.Kind {
+ return switch (stx_mode & std.os.linux.S.IFMT) {
+ std.os.linux.S.IFDIR => .directory,
+ std.os.linux.S.IFCHR => .character_device,
+ std.os.linux.S.IFBLK => .block_device,
+ std.os.linux.S.IFREG => .file,
+ std.os.linux.S.IFIFO => .named_pipe,
+ std.os.linux.S.IFLNK => .sym_link,
+ std.os.linux.S.IFSOCK => .unix_domain_socket,
+ else => .unknown,
+ };
+}
+
fn statFromPosix(st: *const posix.Stat) File.Stat {
const atime = st.atime();
const mtime = st.mtime();
@@ -12441,7 +12655,8 @@ fn lookupDns(
for (family_records) |fr| {
if (options.family != fr.af) {
- const entropy = std.crypto.random.array(u8, 2);
+ var entropy: [2]u8 = undefined;
+ random(t, &entropy);
const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
queries_buffer[nq] = query_buffers[nq][0..len];
nq += 1;
@@ -13853,6 +14068,62 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
};
}
+fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
+ {
+ t.mutex.lock();
+ defer t.mutex.unlock();
+ if (t.random_file.handle) |handle| return handle;
+ }
+
+ const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
+
+ var nt_name: windows.UNICODE_STRING = .{
+ .Length = device_path.len * 2,
+ .MaximumLength = 0,
+ .Buffer = @constCast(&device_path),
+ };
+ var fresh_handle: windows.HANDLE = undefined;
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+ var syscall: Syscall = try .start();
+ while (true) switch (windows.ntdll.NtOpenFile(
+ &fresh_handle,
+ .{
+ .STANDARD = .{ .SYNCHRONIZE = true },
+ .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
+ },
+ &.{
+ .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
+ .RootDirectory = null,
+ .ObjectName = &nt_name,
+ .Attributes = .{},
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ },
+ &io_status_block,
+ .VALID_FLAGS,
+ .{ .IO = .SYNCHRONOUS_NONALERT },
+ )) {
+ .SUCCESS => {
+ syscall.finish();
+ t.mutex.lock(); // Another thread might have won the race.
+ defer t.mutex.unlock();
+ if (t.random_file.handle) |prev_handle| {
+ _ = windows.ntdll.NtClose(fresh_handle);
+ return prev_handle;
+ } else {
+ t.random_file.handle = fresh_handle;
+ return fresh_handle;
+ }
+ },
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.EntropyUnavailable), // Observed on wine 10.0
+ else => return syscall.fail(error.EntropyUnavailable),
+ };
+}
+
fn getNulHandle(t: *Threaded) !windows.HANDLE {
{
t.mutex.lock();
@@ -14935,6 +15206,305 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
return @field(t.environ.string, name);
}
+fn random(userdata: ?*anyopaque, buffer: []u8) void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const thread = Thread.current orelse return randomMainThread(t, buffer);
+ if (!thread.csprng.isInitialized()) {
+ @branchHint(.unlikely);
+ var seed: [Csprng.seed_len]u8 = undefined;
+ randomMainThread(t, &seed);
+ thread.csprng.rng = .init(seed);
+ }
+ thread.csprng.rng.fill(buffer);
+}
+
+fn randomMainThread(t: *Threaded, buffer: []u8) void {
+ t.mutex.lock();
+ defer t.mutex.unlock();
+
+ if (!t.csprng.isInitialized()) {
+ @branchHint(.unlikely);
+ var seed: [Csprng.seed_len]u8 = undefined;
+ {
+ t.mutex.unlock();
+ defer t.mutex.lock();
+
+ const prev = swapCancelProtection(t, .blocked);
+ defer _ = swapCancelProtection(t, prev);
+
+ randomSecure(t, &seed) catch |err| switch (err) {
+ error.Canceled => unreachable,
+ error.EntropyUnavailable => {
+ @memset(&seed, 0);
+ const aslr_addr = @intFromPtr(t);
+ std.mem.writeInt(usize, seed[seed.len - @sizeOf(usize) ..][0..@sizeOf(usize)], aslr_addr, .native);
+ switch (native_os) {
+ .windows => fallbackSeedWindows(&seed),
+ .wasi => if (builtin.link_libc) fallbackSeedPosix(&seed) else fallbackSeedWasi(&seed),
+ else => fallbackSeedPosix(&seed),
+ }
+ },
+ };
+ }
+ t.csprng.rng = .init(seed);
+ }
+
+ t.csprng.rng.fill(buffer);
+}
+
+fn fallbackSeedPosix(seed: *[Csprng.seed_len]u8) void {
+ std.mem.writeInt(posix.pid_t, seed[0..@sizeOf(posix.pid_t)], posix.system.getpid(), .native);
+ const i_1 = @sizeOf(posix.pid_t);
+
+ var ts: posix.timespec = undefined;
+ const Sec = @TypeOf(ts.sec);
+ const Nsec = @TypeOf(ts.nsec);
+ const i_2 = i_1 + @sizeOf(Sec);
+ switch (posix.errno(posix.system.clock_gettime(.REALTIME, &ts))) {
+ .SUCCESS => {
+ std.mem.writeInt(Sec, seed[i_1..][0..@sizeOf(Sec)], ts.sec, .native);
+ std.mem.writeInt(Nsec, seed[i_2..][0..@sizeOf(Nsec)], ts.nsec, .native);
+ },
+ else => {},
+ }
+}
+
+fn fallbackSeedWindows(seed: *[Csprng.seed_len]u8) void {
+ var pc: windows.LARGE_INTEGER = undefined;
+ _ = windows.ntdll.RtlQueryPerformanceCounter(&pc);
+ std.mem.writeInt(windows.LARGE_INTEGER, seed[0..@sizeOf(windows.LARGE_INTEGER)], pc, .native);
+}
+
+fn fallbackSeedWasi(seed: *[Csprng.seed_len]u8) void {
+ var ts: std.os.wasi.timestamp_t = undefined;
+ if (std.os.wasi.clock_time_get(.REALTIME, 1, &ts) == .SUCCESS) {
+ std.mem.writeInt(std.os.wasi.timestamp_t, seed[0..@sizeOf(std.os.wasi.timestamp_t)], ts, .native);
+ }
+}
+
+fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+
+ if (is_windows) {
+ if (buffer.len == 0) return;
+ // ProcessPrng from bcryptprimitives.dll has the following properties:
+ // * introduces a dependency on bcryptprimitives.dll, which apparently
+ // runs a test suite every time it is loaded
+ // * heap allocates a 48-byte buffer, handling failure by returning NO_MEMORY in a BOOL
+ // despite the function being documented to always return TRUE
+ // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
+ // Therefore, that function is avoided in favor of using the device directly.
+ const cng_device = try getCngHandle(t);
+ var io_status_block: windows.IO_STATUS_BLOCK = undefined;
+ var i: usize = 0;
+ const syscall: Syscall = try .start();
+ while (true) {
+ const remaining_len = std.math.lossyCast(u32, buffer.len - i);
+ switch (windows.ntdll.NtDeviceIoControlFile(
+ cng_device,
+ null,
+ null,
+ null,
+ &io_status_block,
+ windows.IOCTL.KSEC.GEN_RANDOM,
+ null,
+ 0,
+ buffer[i..].ptr,
+ remaining_len,
+ )) {
+ .SUCCESS => {
+ i += remaining_len;
+ if (buffer.len - i == 0) {
+ return syscall.finish();
+ } else {
+ try syscall.checkCancel();
+ continue;
+ }
+ },
+ .CANCELLED => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => return syscall.fail(error.EntropyUnavailable),
+ }
+ }
+ }
+
+ if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) {
+ if (buffer.len == 0) return;
+ posix.system.arc4random_buf(buffer.ptr, buffer.len);
+ return;
+ }
+
+ if (native_os == .wasi) {
+ if (buffer.len == 0) return;
+ const syscall: Syscall = try .start();
+ while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) {
+ .SUCCESS => return syscall.finish(),
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => return syscall.fail(error.EntropyUnavailable),
+ };
+ }
+
+ if (@TypeOf(posix.system.getrandom) != void) {
+ const getrandom = if (use_libc_getrandom) std.c.getrandom else std.os.linux.getrandom;
+ var i: usize = 0;
+ const syscall: Syscall = try .start();
+ while (buffer.len - i != 0) {
+ const buf = buffer[i..];
+ const rc = getrandom(buf.ptr, buf.len, 0);
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ syscall.finish();
+ const n: usize = @intCast(rc);
+ i += n;
+ continue;
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => return syscall.fail(error.EntropyUnavailable),
+ }
+ }
+ return;
+ }
+
+ if (native_os == .emscripten) {
+ if (buffer.len == 0) return;
+ const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len));
+ switch (err) {
+ .SUCCESS => return,
+ else => return error.EntropyUnavailable,
+ }
+ }
+
+ if (native_os == .linux) {
+ comptime assert(use_dev_urandom);
+ const urandom_fd = try getRandomFd(t);
+
+ var i: usize = 0;
+ while (buffer.len - i != 0) {
+ const syscall: Syscall = try .start();
+ const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i);
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ syscall.finish();
+ const n: usize = @intCast(rc);
+ if (n == 0) return error.EntropyUnavailable;
+ i += n;
+ continue;
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => return syscall.fail(error.EntropyUnavailable),
+ }
+ }
+ }
+
+ return error.EntropyUnavailable;
+}
+
+fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
+ {
+ t.mutex.lock();
+ defer t.mutex.unlock();
+
+ if (t.random_file.fd == -2) return error.EntropyUnavailable;
+ if (t.random_file.fd != -1) return t.random_file.fd;
+ }
+
+ const mode: posix.mode_t = 0;
+
+ const fd: posix.fd_t = fd: {
+ const syscall: Syscall = try .start();
+ while (true) {
+ const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{
+ .ACCMODE = .RDONLY,
+ .CLOEXEC = true,
+ }, mode);
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ syscall.finish();
+ break :fd @intCast(rc);
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => return syscall.fail(error.EntropyUnavailable),
+ }
+ }
+ };
+ errdefer posix.close(fd);
+
+ switch (native_os) {
+ .linux => {
+ const sys = if (statx_use_c) std.c else std.os.linux;
+ const syscall: Syscall = try .start();
+ while (true) {
+ var statx = std.mem.zeroes(std.os.linux.Statx);
+ switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
+ .SUCCESS => {
+ syscall.finish();
+ if (!statx.mask.TYPE) return error.EntropyUnavailable;
+ t.mutex.lock(); // Another thread might have won the race.
+ defer t.mutex.unlock();
+ if (t.random_file.fd >= 0) {
+ posix.close(fd);
+ return t.random_file.fd;
+ } else if (!posix.S.ISCHR(statx.mode)) {
+ t.random_file.fd = -2;
+ return error.EntropyUnavailable;
+ } else {
+ t.random_file.fd = fd;
+ return fd;
+ }
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => return syscall.fail(error.EntropyUnavailable),
+ }
+ }
+ },
+ else => {
+ const syscall: Syscall = try .start();
+ while (true) {
+ var stat = std.mem.zeroes(posix.Stat);
+ switch (posix.errno(fstat_sym(fd, &stat))) {
+ .SUCCESS => {
+ syscall.finish();
+ t.mutex.lock(); // Another thread might have won the race.
+ defer t.mutex.unlock();
+ if (t.random_file.fd >= 0) {
+ posix.close(fd);
+ return t.random_file.fd;
+ } else if (!posix.S.ISCHR(stat.mode)) {
+ t.random_file.fd = -2;
+ return error.EntropyUnavailable;
+ } else {
+ t.random_file.fd = fd;
+ return fd;
+ }
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ else => return syscall.fail(error.EntropyUnavailable),
+ }
+ }
+ },
+ }
+}
+
test {
_ = @import("Threaded/test.zig");
}
diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig
@@ -275,7 +275,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
const io = testing.io;
- const socket_path = try generateFileName("socket.unix");
+ const socket_path = try generateFileName(io, "socket.unix");
defer testing.allocator.free(socket_path);
const socket_addr = try net.UnixAddress.init(socket_path);
@@ -308,11 +308,11 @@ test "listen on a unix socket, send bytes, receive bytes" {
try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
}
-fn generateFileName(base_name: []const u8) ![]const u8 {
+fn generateFileName(io: Io, base_name: []const u8) ![]const u8 {
const random_bytes_count = 12;
const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
var random_bytes: [12]u8 = undefined;
- std.crypto.random.bytes(&random_bytes);
+ io.random(&random_bytes);
var sub_path: [sub_path_len]u8 = undefined;
_ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig
@@ -564,3 +564,29 @@ test "tasks spawned in group after Group.cancel are canceled" {
try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up
try group.concurrent(io, global.waitThenSpawn, .{ io, &group });
}
+
+test "random" {
+ const io = testing.io;
+
+ var a: u64 = undefined;
+ var b: u64 = undefined;
+ var c: u64 = undefined;
+
+ io.random(@ptrCast(&a));
+ io.random(@ptrCast(&b));
+ io.random(@ptrCast(&c));
+
+ try std.testing.expect(a ^ b ^ c != 0);
+}
+
+test "randomSecure" {
+ const io = testing.io;
+
+ var buf_a: [50]u8 = undefined;
+ var buf_b: [50]u8 = undefined;
+ try io.randomSecure(&buf_a);
+ try io.randomSecure(&buf_b);
+ // If this test fails the chance is significantly higher that there is a bug than
+ // that two sets of 50 bytes were equal.
+ try expect(!mem.eql(u8, &buf_a, &buf_b));
+}
diff --git a/lib/std/Random.zig b/lib/std/Random.zig
@@ -1,15 +1,13 @@
//! The engines provided here should be initialized from an external source.
-//! For a thread-local cryptographically secure pseudo random number generator,
-//! use `std.crypto.random`.
//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will
//! be faster and use substantially less stack space.
+const Random = @This();
const std = @import("std.zig");
const math = std.math;
const mem = std.mem;
const assert = std.debug.assert;
const maxInt = std.math.maxInt;
-const Random = @This();
/// Fast unbiased random numbers.
pub const DefaultPrng = Xoshiro256;
@@ -35,6 +33,22 @@ pub const ziggurat = @import("Random/ziggurat.zig");
ptr: *anyopaque,
fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,
+pub const IoSource = struct {
+ io: std.Io,
+
+ pub fn interface(this: *const @This()) std.Random {
+ return .{
+ .ptr = @constCast(this),
+ .fillFn = fill,
+ };
+ }
+
+ fn fill(ptr: *anyopaque, buffer: []u8) void {
+ const this: *const @This() = @ptrCast(@alignCast(ptr));
+ this.io.random(buffer);
+ }
+};
+
pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {
const Ptr = @TypeOf(pointer);
assert(@typeInfo(Ptr) == .pointer); // Must be a pointer
diff --git a/lib/std/Random/ChaCha.zig b/lib/std/Random/ChaCha.zig
@@ -20,7 +20,7 @@ pub const secret_seed_length = Cipher.key_length;
/// The seed must be uniform, secret and `secret_seed_length` bytes long.
pub fn init(secret_seed: [secret_seed_length]u8) Self {
- var self = Self{ .state = undefined, .offset = 0 };
+ var self: Self = .{ .state = undefined, .offset = 0 };
Cipher.stream(&self.state, 0, secret_seed, nonce);
return self;
}
diff --git a/lib/std/Random/test.zig b/lib/std/Random/test.zig
@@ -436,8 +436,9 @@ fn testRangeBias(r: Random, start: i8, end: i8, biased: bool) !void {
}
test "CSPRNG" {
+ const io = std.testing.io;
var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined;
- std.crypto.random.bytes(&secret_seed);
+ io.random(&secret_seed);
var csprng = DefaultCsprng.init(secret_seed);
const random = csprng.random();
const a = random.int(u64);
diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig
@@ -235,9 +235,6 @@ pub const nacl = struct {
/// Finite-field arithmetic.
pub const ff = @import("crypto/ff.zig");
-/// This is a thread-local, cryptographically secure pseudo random number generator.
-pub const random = @import("crypto/tlcsprng.zig").interface;
-
/// Encoding and decoding
pub const codecs = @import("crypto/codecs.zig");
@@ -306,6 +303,9 @@ test {
_ = dh.X25519;
_ = kem.kyber_d00;
+ _ = kem.hybrid;
+ _ = kem.kyber_d00;
+ _ = kem.ml_kem;
_ = ecc.Curve25519;
_ = ecc.Edwards25519;
@@ -343,6 +343,7 @@ test {
_ = sign.Ed25519;
_ = sign.ecdsa;
+ _ = sign.mldsa;
_ = stream.chacha.ChaCha20IETF;
_ = stream.chacha.ChaCha12IETF;
@@ -364,20 +365,12 @@ test {
_ = secureZero;
_ = timing_safe;
_ = ff;
- _ = random;
_ = errors;
_ = tls;
_ = Certificate;
_ = codecs;
}
-test "CSPRNG" {
- const a = random.int(u64);
- const b = random.int(u64);
- const c = random.int(u64);
- try std.testing.expect(a ^ b ^ c != 0);
-}
-
test "issue #4532: no index out of bounds" {
const types = [_]type{
hash.Md5,
diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig
@@ -333,12 +333,10 @@ pub const Ed25519 = struct {
}
/// Generate a new, random key pair.
- ///
- /// `crypto.random.bytes` must be supported by the target.
- pub fn generate() KeyPair {
+ pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
- crypto.random.bytes(&random_seed);
+ io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
@@ -389,18 +387,21 @@ pub const Ed25519 = struct {
/// Create a Signer, that can be used for incremental signing.
/// Note that the signature is not deterministic.
- /// The noise parameter, if set, should be something unique for each message,
- /// such as a random nonce, or a counter.
- pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer {
+ pub fn signer(
+ key_pair: KeyPair,
+ /// If set, should be something unique for each message, such as a
+ /// random nonce, or a counter.
+ noise: ?[noise_length]u8,
+ /// Filled with cryptographically secure randomness.
+ entropy: *const [noise_length]u8,
+ ) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer {
if (!mem.eql(u8, &key_pair.secret_key.publicKeyBytes(), &key_pair.public_key.toBytes())) {
return error.KeyMismatch;
}
const scalar_and_prefix = key_pair.secret_key.scalarAndPrefix();
var h = Sha512.init(.{});
h.update(&scalar_and_prefix.prefix);
- var noise2: [noise_length]u8 = undefined;
- crypto.random.bytes(&noise2);
- h.update(&noise2);
+ h.update(entropy);
if (noise) |*z| {
h.update(z);
}
@@ -420,7 +421,7 @@ pub const Ed25519 = struct {
};
/// Verify several signatures in a single operation, much faster than verifying signatures one-by-one
- pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void {
+ pub fn verifyBatch(io: std.Io, comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void {
var r_batch: [count]CompressedScalar = undefined;
var s_batch: [count]CompressedScalar = undefined;
var a_batch: [count]Curve = undefined;
@@ -454,7 +455,7 @@ pub const Ed25519 = struct {
var z_batch: [count]Curve.scalar.CompressedScalar = undefined;
for (&z_batch) |*z| {
- crypto.random.bytes(z[0..16]);
+ io.random(z[0..16]);
@memset(z[16..], 0);
}
@@ -587,12 +588,14 @@ test "signature" {
}
test "batch verification" {
+ const io = std.testing.io;
+
for (0..16) |_| {
- const key_pair = Ed25519.KeyPair.generate();
+ const key_pair = Ed25519.KeyPair.generate(io);
var msg1: [32]u8 = undefined;
var msg2: [32]u8 = undefined;
- crypto.random.bytes(&msg1);
- crypto.random.bytes(&msg2);
+ io.random(&msg1);
+ io.random(&msg2);
const sig1 = try key_pair.sign(&msg1, null);
const sig2 = try key_pair.sign(&msg2, null);
var signature_batch = [_]Ed25519.BatchElement{
@@ -607,10 +610,10 @@ test "batch verification" {
.public_key = key_pair.public_key,
},
};
- try Ed25519.verifyBatch(2, signature_batch);
+ try Ed25519.verifyBatch(io, 2, signature_batch);
signature_batch[1].sig = sig1;
- try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(signature_batch.len, signature_batch));
+ try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(io, signature_batch.len, signature_batch));
}
}
@@ -718,14 +721,15 @@ test "test vectors" {
}
test "with blind keys" {
+ const io = std.testing.io;
const BlindKeyPair = Ed25519.key_blinding.BlindKeyPair;
// Create a standard Ed25519 key pair
- const kp = Ed25519.KeyPair.generate();
+ const kp = Ed25519.KeyPair.generate(io);
// Create a random blinding seed
var blind: [32]u8 = undefined;
- crypto.random.bytes(&blind);
+ io.random(&blind);
// Blind the key pair
const blind_kp = try BlindKeyPair.init(kp, blind, "ctx");
@@ -741,9 +745,12 @@ test "with blind keys" {
}
test "signatures with streaming" {
- const kp = Ed25519.KeyPair.generate();
+ const io = std.testing.io;
+ const kp = Ed25519.KeyPair.generate(io);
- var signer = try kp.signer(null);
+ var entropy: [Ed25519.noise_length]u8 = undefined;
+ io.random(&entropy);
+ var signer = try kp.signer(null, &entropy);
signer.update("mes");
signer.update("sage");
const sig = signer.finalize();
@@ -757,7 +764,8 @@ test "signatures with streaming" {
}
test "key pair from secret key" {
- const kp = Ed25519.KeyPair.generate();
+ const io = std.testing.io;
+ const kp = Ed25519.KeyPair.generate(io);
const kp2 = try Ed25519.KeyPair.fromSecretKey(kp.secret_key);
try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes());
try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes());
@@ -788,7 +796,8 @@ test "cofactored vs cofactorless verification" {
}
test "regular signature verifies with both verify and verifyStrict" {
- const kp = Ed25519.KeyPair.generate();
+ const io = std.testing.io;
+ const kp = Ed25519.KeyPair.generate(io);
const msg = "test message";
const sig = try kp.sign(msg, null);
try sig.verify(msg, kp.public_key);
diff --git a/lib/std/crypto/25519/edwards25519.zig b/lib/std/crypto/25519/edwards25519.zig
@@ -575,10 +575,11 @@ test "packing/unpacking" {
}
test "point addition/subtraction" {
+ const io = std.testing.io;
var s1: [32]u8 = undefined;
var s2: [32]u8 = undefined;
- crypto.random.bytes(&s1);
- crypto.random.bytes(&s2);
+ io.random(&s1);
+ io.random(&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);
@@ -622,9 +623,10 @@ test "implicit reduction of invalid scalars" {
}
test "subgroup check" {
+ const io = std.testing.io;
for (0..100) |_| {
var p = Edwards25519.basePoint;
- const s = Edwards25519.scalar.random();
+ const s = Edwards25519.scalar.random(io);
p = try p.mulPublic(s);
try p.rejectUnexpectedSubgroup();
}
diff --git a/lib/std/crypto/25519/scalar.zig b/lib/std/crypto/25519/scalar.zig
@@ -101,8 +101,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar) CompressedScalar {
}
/// Return a random scalar < L
-pub fn random() CompressedScalar {
- return Scalar.random().toBytes();
+pub fn random(io: std.Io) CompressedScalar {
+ return Scalar.random(io).toBytes();
}
/// A scalar in unpacked representation
@@ -560,10 +560,10 @@ pub const Scalar = struct {
}
/// Return a random scalar < L.
- pub fn random() Scalar {
+ pub fn random(io: std.Io) Scalar {
var s: [64]u8 = undefined;
while (true) {
- crypto.random.bytes(&s);
+ io.random(&s);
const n = Scalar.fromBytes64(s);
if (!n.isZero()) {
return n;
@@ -879,8 +879,9 @@ test "scalar field inversion" {
}
test "random scalar" {
- const s1 = random();
- const s2 = random();
+ const io = std.testing.io;
+ const s1 = random(io);
+ const s2 = random(io);
try std.testing.expect(!mem.eql(u8, &s1, &s2));
}
diff --git a/lib/std/crypto/25519/x25519.zig b/lib/std/crypto/25519/x25519.zig
@@ -41,10 +41,10 @@ pub const X25519 = struct {
}
/// Generate a new, random key pair.
- pub fn generate() KeyPair {
+ pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
- crypto.random.bytes(&random_seed);
+ io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
diff --git a/lib/std/crypto/argon2.zig b/lib/std/crypto/argon2.zig
@@ -533,7 +533,7 @@ const PhcFormatHasher = struct {
if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding;
var salt: [default_salt_len]u8 = undefined;
- crypto.random.bytes(&salt);
+ io.random(&salt);
var hash: [default_hash_len]u8 = undefined;
try kdf(allocator, &hash, password, &salt, params, mode, io);
diff --git a/lib/std/crypto/bcrypt.zig b/lib/std/crypto/bcrypt.zig
@@ -17,7 +17,7 @@ const HasherError = pwhash.HasherError;
const EncodingError = phc_format.Error;
const Error = pwhash.Error;
-const salt_length: usize = 16;
+pub const salt_length: usize = 16;
const salt_str_length: usize = 22;
const ct_str_length: usize = 31;
const ct_length: usize = 24;
@@ -426,7 +426,7 @@ pub const Params = struct {
fn bcryptWithTruncation(
password: []const u8,
- salt: [salt_length]u8,
+ salt: *const [salt_length]u8,
params: Params,
) [dk_length]u8 {
var state = State{};
@@ -435,13 +435,13 @@ fn bcryptWithTruncation(
@memcpy(password_buf[0..trimmed_len], password[0..trimmed_len]);
password_buf[trimmed_len] = 0;
const passwordZ = password_buf[0 .. trimmed_len + 1];
- state.expand(salt[0..], passwordZ);
+ state.expand(salt, passwordZ);
const rounds: u64 = @as(u64, 1) << params.rounds_log;
var k: u64 = 0;
while (k < rounds) : (k += 1) {
state.expand0(passwordZ);
- state.expand0(salt[0..]);
+ state.expand0(salt);
}
crypto.secureZero(u8, &password_buf);
@@ -467,7 +467,7 @@ fn bcryptWithTruncation(
/// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead.
pub fn bcrypt(
password: []const u8,
- salt: [salt_length]u8,
+ salt: *const [salt_length]u8,
params: Params,
) [dk_length]u8 {
if (password.len <= 72 or params.silently_truncate_password) {
@@ -475,7 +475,7 @@ pub fn bcrypt(
}
var pre_hash: [HmacSha512.mac_length]u8 = undefined;
- HmacSha512.create(&pre_hash, password, &salt);
+ HmacSha512.create(&pre_hash, password, salt);
const Encoder = crypt_format.Codec.Encoder;
var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined;
@@ -623,16 +623,16 @@ const crypt_format = struct {
fn strHashInternal(
password: []const u8,
- salt: [salt_length]u8,
+ salt: *const [salt_length]u8,
params: Params,
) [hash_length]u8 {
var dk = bcrypt(password, salt, params);
var salt_str: [salt_str_length]u8 = undefined;
- _ = Codec.Encoder.encode(salt_str[0..], salt[0..]);
+ _ = Codec.Encoder.encode(&salt_str, salt);
var ct_str: [ct_str_length]u8 = undefined;
- _ = Codec.Encoder.encode(ct_str[0..], dk[0..]);
+ _ = Codec.Encoder.encode(&ct_str, dk[0..]);
var s_buf: [hash_length]u8 = undefined;
const s = fmt.bufPrint(
@@ -657,21 +657,20 @@ const PhcFormatHasher = struct {
hash: BinValue(dk_length),
};
- /// Return a non-deterministic hash of the password encoded as a PHC-format string
+ /// Return a non-deterministic hash of the password encoded as a PHC-format string.
fn create(
password: []const u8,
params: Params,
buf: []u8,
+ /// Filled with cryptographically secure entropy.
+ salt: *const [salt_length]u8,
) HasherError![]const u8 {
- var salt: [salt_length]u8 = undefined;
- crypto.random.bytes(&salt);
-
const hash = bcrypt(password, salt, params);
return phc_format.serialize(HashResult{
.alg_id = alg_id,
.r = params.rounds_log,
- .salt = try BinValue(salt_length).fromSlice(&salt),
+ .salt = try BinValue(salt_length).fromSlice(salt),
.hash = try BinValue(dk_length).fromSlice(&hash),
}, buf);
}
@@ -688,11 +687,11 @@ const PhcFormatHasher = struct {
if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length)
return HasherError.InvalidEncoding;
- const params = Params{
+ const params: Params = .{
.rounds_log = hash_result.r,
.silently_truncate_password = silently_truncate_password,
};
- const hash = bcrypt(password, hash_result.salt.buf, params);
+ const hash = bcrypt(password, &hash_result.salt.buf, params);
const expected_hash = hash_result.hash.constSlice();
if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed;
@@ -709,12 +708,11 @@ const CryptFormatHasher = struct {
password: []const u8,
params: Params,
buf: []u8,
+ /// Filled with cryptographically secure entropy.
+ salt: *const [salt_length]u8,
) HasherError![]const u8 {
if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft;
- var salt: [salt_length]u8 = undefined;
- crypto.random.bytes(&salt);
-
const hash = crypt_format.strHashInternal(password, salt, params);
@memcpy(buf[0..hash.len], &hash);
@@ -736,9 +734,9 @@ const CryptFormatHasher = struct {
const salt_str = str[7..][0..salt_str_length];
var salt: [salt_length]u8 = undefined;
- crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding;
+ crypt_format.Codec.Decoder.decode(&salt, salt_str) catch return HasherError.InvalidEncoding;
- const wanted_s = crypt_format.strHashInternal(password, salt, .{
+ const wanted_s = crypt_format.strHashInternal(password, &salt, .{
.rounds_log = rounds_log,
.silently_truncate_password = silently_truncate_password,
});
@@ -756,21 +754,28 @@ pub const HashOptions = struct {
encoding: pwhash.Encoding,
};
-/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
-/// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches.
+/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key
+/// stretching function.
+///
+/// bcrypt is a computationally expensive and cache-hard function, explicitly
+/// designed to slow down exhaustive searches.
///
-/// The function returns a string that includes all the parameters required for verification.
+/// The function returns a string that includes all the parameters required for
+/// verification.
///
-/// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes.
-/// If this is an issue for your application, set the `silently_truncate_password` option to `false`.
+/// By design, bcrypt silently truncates passwords to 72 bytes. If this is an
+/// issue for your application, set the `silently_truncate_password` option to
+/// `false`.
pub fn strHash(
password: []const u8,
options: HashOptions,
out: []u8,
+ /// Filled with cryptographically secure entropy.
+ salt: *const [salt_length]u8,
) Error![]const u8 {
switch (options.encoding) {
- .phc => return PhcFormatHasher.create(password, options.params, out),
- .crypt => return CryptFormatHasher.create(password, options.params, out),
+ .phc => return PhcFormatHasher.create(password, options.params, out, salt),
+ .crypt => return CryptFormatHasher.create(password, options.params, out, salt),
}
}
@@ -796,8 +801,9 @@ pub fn strVerify(
}
test "bcrypt codec" {
+ const io = testing.io;
var salt: [salt_length]u8 = undefined;
- crypto.random.bytes(&salt);
+ io.random(&salt);
var salt_str: [salt_str_length]u8 = undefined;
_ = crypt_format.Codec.Encoder.encode(salt_str[0..], salt[0..]);
var salt2: [salt_length]u8 = undefined;
@@ -806,14 +812,20 @@ test "bcrypt codec" {
}
test "bcrypt crypt format" {
- var hash_options = HashOptions{
+ const io = testing.io;
+
+ var hash_options: HashOptions = .{
.params = .{ .rounds_log = 5, .silently_truncate_password = false },
.encoding = .crypt,
};
- var verify_options = VerifyOptions{ .silently_truncate_password = false };
+ var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
var buf: [hash_length]u8 = undefined;
- const s = try strHash("password", hash_options, &buf);
+ const s = s: {
+ var salt: [salt_length]u8 = undefined;
+ io.random(&salt);
+ break :s try strHash("password", hash_options, &buf, &salt);
+ };
try testing.expect(mem.startsWith(u8, s, crypt_format.prefix));
try strVerify(s, "password", verify_options);
@@ -823,7 +835,11 @@ test "bcrypt crypt format" {
);
var long_buf: [hash_length]u8 = undefined;
- var long_s = try strHash("password" ** 100, hash_options, &long_buf);
+ var long_s = s: {
+ var salt: [salt_length]u8 = undefined;
+ io.random(&salt);
+ break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
+ };
try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix));
try strVerify(long_s, "password" ** 100, verify_options);
@@ -834,7 +850,11 @@ test "bcrypt crypt format" {
hash_options.params.silently_truncate_password = true;
verify_options.silently_truncate_password = true;
- long_s = try strHash("password" ** 100, hash_options, &long_buf);
+ long_s = s: {
+ var salt: [salt_length]u8 = undefined;
+ io.random(&salt);
+ break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
+ };
try strVerify(long_s, "password" ** 101, verify_options);
try strVerify(
@@ -845,15 +865,20 @@ test "bcrypt crypt format" {
}
test "bcrypt phc format" {
- var hash_options = HashOptions{
+ const io = testing.io;
+ var hash_options: HashOptions = .{
.params = .{ .rounds_log = 5, .silently_truncate_password = false },
.encoding = .phc,
};
- var verify_options = VerifyOptions{ .silently_truncate_password = false };
+ var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
const prefix = "$bcrypt$";
var buf: [hash_length * 2]u8 = undefined;
- const s = try strHash("password", hash_options, &buf);
+ const s = s: {
+ var salt: [salt_length]u8 = undefined;
+ io.random(&salt);
+ break :s try strHash("password", hash_options, &buf, &salt);
+ };
try testing.expect(mem.startsWith(u8, s, prefix));
try strVerify(s, "password", verify_options);
@@ -863,7 +888,11 @@ test "bcrypt phc format" {
);
var long_buf: [hash_length * 2]u8 = undefined;
- var long_s = try strHash("password" ** 100, hash_options, &long_buf);
+ var long_s = s: {
+ var salt: [salt_length]u8 = undefined;
+ io.random(&salt);
+ break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
+ };
try testing.expect(mem.startsWith(u8, long_s, prefix));
try strVerify(long_s, "password" ** 100, verify_options);
@@ -874,7 +903,11 @@ test "bcrypt phc format" {
hash_options.params.silently_truncate_password = true;
verify_options.silently_truncate_password = true;
- long_s = try strHash("password" ** 100, hash_options, &long_buf);
+ long_s = s: {
+ var salt: [salt_length]u8 = undefined;
+ io.random(&salt);
+ break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
+ };
try strVerify(long_s, "password" ** 101, verify_options);
try strVerify(
diff --git a/lib/std/crypto/ecdsa.zig b/lib/std/crypto/ecdsa.zig
@@ -323,10 +323,10 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
}
/// Generate a new, random key pair.
- pub fn generate() KeyPair {
+ pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
- crypto.random.bytes(&random_seed);
+ io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
@@ -417,12 +417,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
test "Basic operations over EcdsaP384Sha384" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+ const io = testing.io;
const Scheme = EcdsaP384Sha384;
- const kp = Scheme.KeyPair.generate();
+ const kp = Scheme.KeyPair.generate(io);
const msg = "test";
var noise: [Scheme.noise_length]u8 = undefined;
- crypto.random.bytes(&noise);
+ io.random(&noise);
const sig = try kp.sign(msg, noise);
try sig.verify(msg, kp.public_key);
@@ -433,12 +434,13 @@ test "Basic operations over EcdsaP384Sha384" {
test "Basic operations over Secp256k1" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+ const io = testing.io;
const Scheme = EcdsaSecp256k1Sha256oSha256;
- const kp = Scheme.KeyPair.generate();
+ const kp = Scheme.KeyPair.generate(io);
const msg = "test";
var noise: [Scheme.noise_length]u8 = undefined;
- crypto.random.bytes(&noise);
+ io.random(&noise);
const sig = try kp.sign(msg, noise);
try sig.verify(msg, kp.public_key);
@@ -449,12 +451,13 @@ test "Basic operations over Secp256k1" {
test "Basic operations over EcdsaP384Sha256" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+ const io = testing.io;
const Scheme = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha256);
- const kp = Scheme.KeyPair.generate();
+ const kp = Scheme.KeyPair.generate(io);
const msg = "test";
var noise: [Scheme.noise_length]u8 = undefined;
- crypto.random.bytes(&noise);
+ io.random(&noise);
const sig = try kp.sign(msg, noise);
try sig.verify(msg, kp.public_key);
@@ -502,8 +505,10 @@ test "Verifying a existing signature with EcdsaP384Sha256" {
test "Prehashed message operations" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+ const io = testing.io;
+
const Scheme = EcdsaP256Sha256;
- const kp = Scheme.KeyPair.generate();
+ const kp = Scheme.KeyPair.generate(io);
const msg = "test message for prehashed signing";
const Hash = crypto.hash.sha2.Sha256;
@@ -518,7 +523,7 @@ test "Prehashed message operations" {
try testing.expectError(error.SignatureVerificationFailed, sig.verifyPrehashed(bad_hash, kp.public_key));
var noise: [Scheme.noise_length]u8 = undefined;
- crypto.random.bytes(&noise);
+ io.random(&noise);
const sig_with_noise = try kp.signPrehashed(msg_hash, noise);
try sig_with_noise.verifyPrehashed(msg_hash, kp.public_key);
@@ -1628,8 +1633,9 @@ fn tvTry(comptime Scheme: type, vector: TestVector) !void {
test "Sec1 encoding/decoding" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+ const io = testing.io;
const Scheme = EcdsaP384Sha384;
- const kp = Scheme.KeyPair.generate();
+ const kp = Scheme.KeyPair.generate(io);
const pk = kp.public_key;
const pk_compressed_sec1 = pk.toCompressedSec1();
const pk_recovered1 = try Scheme.PublicKey.fromSec1(&pk_compressed_sec1);
diff --git a/lib/std/crypto/hybrid_kem.zig b/lib/std/crypto/hybrid_kem.zig
@@ -174,43 +174,56 @@ pub fn HybridKem(comptime params: Params) type {
return .{ .bytes = buf.* };
}
- /// Generates a shared secret and encapsulates it for the public key.
- /// If `seed` is `null`, uses random bytes from `std.crypto.random`.
- /// If `seed` is set, encapsulation is deterministic (for testing only).
- pub fn encaps(self: PublicKey, seed: ?[]const u8) !EncapsulatedSecret {
- const pq_nek = params.PqKem.PublicKey.encoded_length;
- const ek_pq = try params.PqKem.PublicKey.fromBytes(self.bytes[0..pq_nek]);
- const ek_t = self.bytes[pq_nek..][0..params.Group.element_length];
-
+ /// Generates a shared secret, encapsulated for the public key,
+ /// using random bytes.
+ ///
+ /// This is recommended over `encapsDeterministic`.
+ pub fn encaps(pk: PublicKey, io: std.Io) !EncapsulatedSecret {
var seed_pq: [32]u8 = undefined;
- var seed_t_expanded: [params.Group.seed_length]u8 = undefined;
+ io.random(&seed_pq);
+ var seed_t: [32]u8 = undefined;
+ io.random(&seed_t);
+ var seed_t_expanded: [params.Group.seed_length]u8 = try expandRandomnessSeed(seed_t);
+ return encapsInner(pk, &seed_pq, &seed_t_expanded);
+ }
- if (seed) |r| {
- if (r.len < 32) return error.InsufficientRandomness;
- seed_pq = r[0..32].*;
+ /// Generates a shared secret, encapsulated for the public key,
+ /// using the provided seed.
+ ///
+ /// Calling `encaps` instead is recommended.
+ pub fn encapsDeterministic(pk: PublicKey, seed: []const u8) !EncapsulatedSecret {
+ if (seed.len < 32) return error.InsufficientRandomness;
+ var seed_pq: [32]u8 = seed[0..32].*;
+ var seed_t_expanded: [params.Group.seed_length]u8 = undefined;
- const t_randomness = r[32..];
+ const t_randomness = seed[32..];
+ if (t_randomness.len < params.Group.seed_length) {
+ // Provided randomness is shorter than seed_length, use it directly
+ // (test vectors provide just enough for randomScalar)
+ @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness);
+ // Pad the rest with zeros if needed (shouldn't be used by randomScalar)
if (t_randomness.len < params.Group.seed_length) {
- // Provided randomness is shorter than seed_length, use it directly
- // (test vectors provide just enough for randomScalar)
- @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness);
- // Pad the rest with zeros if needed (shouldn't be used by randomScalar)
- if (t_randomness.len < params.Group.seed_length) {
- @memset(seed_t_expanded[t_randomness.len..], 0);
- }
- } else {
- // Full randomness provided
- @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]);
+ @memset(seed_t_expanded[t_randomness.len..], 0);
}
} else {
- crypto.random.bytes(&seed_pq);
- var seed_t: [32]u8 = undefined;
- crypto.random.bytes(&seed_t);
- seed_t_expanded = try expandRandomnessSeed(seed_t);
+ // Full randomness provided
+ @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]);
}
- const pq_encap = ek_pq.encaps(seed_pq);
- const sk_e = try params.Group.randomScalar(&seed_t_expanded);
+ return encapsInner(pk, &seed_pq, &seed_t_expanded);
+ }
+
+ fn encapsInner(
+ pk: PublicKey,
+ seed_pq: *[32]u8,
+ seed_t_expanded: *[params.Group.seed_length]u8,
+ ) !EncapsulatedSecret {
+ const pq_nek = params.PqKem.PublicKey.encoded_length;
+ const ek_pq = try params.PqKem.PublicKey.fromBytes(pk.bytes[0..pq_nek]);
+ const ek_t = pk.bytes[pq_nek..][0..params.Group.element_length];
+
+ const pq_encap = ek_pq.encapsDeterministic(seed_pq);
+ const sk_e = try params.Group.randomScalar(seed_t_expanded);
const ct_t_point = try params.Group.mulBase(sk_e);
const ct_t = if (is_nist_curve) params.Group.encodePoint(ct_t_point) else ct_t_point;
@@ -280,9 +293,9 @@ pub fn HybridKem(comptime params: Params) type {
}
/// Generates a new random key pair.
- pub fn generate() !KeyPair {
+ pub fn generate(io: std.Io) !KeyPair {
var seed: [params.Nseed]u8 = undefined;
- crypto.random.bytes(&seed);
+ io.random(&seed);
return generateDeterministic(seed);
}
};
@@ -386,7 +399,7 @@ test "MLKEM768-X25519 basic round trip" {
var enc_seed: [64]u8 = undefined;
@memset(&enc_seed, 0x43);
- const encap_result = try kp.public_key.encaps(&enc_seed);
+ const encap_result = try kp.public_key.encapsDeterministic(&enc_seed);
const ss_decap = try kp.secret_key.decaps(&encap_result.ciphertext);
try testing.expectEqualSlices(u8, &encap_result.shared_secret, &ss_decap);
@@ -408,7 +421,7 @@ test "MLKEM768-X25519 test vector 0" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -432,7 +445,7 @@ test "MLKEM768-X25519 test vector 1" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -456,7 +469,7 @@ test "MLKEM768-X25519 test vector 2" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -480,7 +493,7 @@ test "MLKEM768-X25519 test vector 3" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -504,7 +517,7 @@ test "MLKEM768-X25519 test vector 4" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -528,7 +541,7 @@ test "MLKEM768-X25519 test vector 5" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -552,7 +565,7 @@ test "MLKEM768-X25519 test vector 6" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -576,7 +589,7 @@ test "MLKEM768-X25519 test vector 7" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -600,7 +613,7 @@ test "MLKEM768-X25519 test vector 8" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -624,7 +637,7 @@ test "MLKEM768-X25519 test vector 9" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -648,7 +661,7 @@ test "MLKEM768-P256 test vector 0" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -672,7 +685,7 @@ test "MLKEM768-P256 test vector 1" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -696,7 +709,7 @@ test "MLKEM768-P256 test vector 2" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -720,7 +733,7 @@ test "MLKEM768-P256 test vector 3" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -744,7 +757,7 @@ test "MLKEM768-P256 test vector 4" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -768,7 +781,7 @@ test "MLKEM768-P256 test vector 5" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -792,7 +805,7 @@ test "MLKEM768-P256 test vector 6" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -816,7 +829,7 @@ test "MLKEM768-P256 test vector 7" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -840,7 +853,7 @@ test "MLKEM768-P256 test vector 8" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -864,7 +877,7 @@ test "MLKEM768-P256 test vector 9" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -888,7 +901,7 @@ test "MLKEM1024-P384 test vector 0" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -912,7 +925,7 @@ test "MLKEM1024-P384 test vector 1" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -936,7 +949,7 @@ test "MLKEM1024-P384 test vector 2" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -960,7 +973,7 @@ test "MLKEM1024-P384 test vector 3" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -984,7 +997,7 @@ test "MLKEM1024-P384 test vector 4" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -1008,7 +1021,7 @@ test "MLKEM1024-P384 test vector 5" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -1032,7 +1045,7 @@ test "MLKEM1024-P384 test vector 6" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -1056,7 +1069,7 @@ test "MLKEM1024-P384 test vector 7" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@@ -1080,7 +1093,7 @@ test "MLKEM1024-P384 test vector 8" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
- const enc_result = try kp.public_key.encaps(&randomness);
+ const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
diff --git a/lib/std/crypto/ml_dsa.zig b/lib/std/crypto/ml_dsa.zig
@@ -2019,12 +2019,9 @@ fn MLDSAImpl(comptime p: Params) type {
secret_key: SecretKey,
/// Generate a new random key pair.
- /// This uses the system's cryptographically secure random number generator.
- ///
- /// `crypto.random.bytes` must be supported by the target.
- pub fn generate() KeyPair {
+ pub fn generate(io: std.Io) KeyPair {
var seed: [Self.seed_length]u8 = undefined;
- crypto.random.bytes(&seed);
+ io.random(&seed);
return generateDeterministic(seed) catch unreachable;
}
@@ -3198,8 +3195,9 @@ test "ML-DSA-87 KAT test vector 0" {
}
test "KeyPair API - generate and sign" {
+ const io = std.testing.io;
// Test the new KeyPair API with random generation
- const kp = MLDSA44.KeyPair.generate();
+ const kp = MLDSA44.KeyPair.generate(io);
const msg = "Test message for KeyPair API";
// Sign with deterministic mode (no noise)
@@ -3222,8 +3220,9 @@ test "KeyPair API - generateDeterministic" {
}
test "KeyPair API - fromSecretKey" {
+ const io = std.testing.io;
// Generate a key pair
- const kp1 = MLDSA44.KeyPair.generate();
+ const kp1 = MLDSA44.KeyPair.generate(io);
// Derive public key from secret key
const kp2 = try MLDSA44.KeyPair.fromSecretKey(kp1.secret_key);
@@ -3235,8 +3234,9 @@ test "KeyPair API - fromSecretKey" {
}
test "Signature verification with noise" {
+ const io = std.testing.io;
// Test signing with randomness (hedged signatures)
- const kp = MLDSA65.KeyPair.generate();
+ const kp = MLDSA65.KeyPair.generate(io);
const msg = "Message to be signed with randomness";
// Create some noise
@@ -3250,8 +3250,9 @@ test "Signature verification with noise" {
}
test "Signature verification failure" {
+ const io = std.testing.io;
// Test that invalid signatures are rejected
- const kp = MLDSA44.KeyPair.generate();
+ const kp = MLDSA44.KeyPair.generate(io);
const msg = "Original message";
const sig = try kp.sign(msg, null);
diff --git a/lib/std/crypto/ml_kem.zig b/lib/std/crypto/ml_kem.zig
@@ -244,32 +244,41 @@ fn Kyber(comptime p: Params) type {
/// Size of a serialized representation of the key, in bytes.
pub const encoded_length = InnerPk.encoded_length;
- /// Generates a shared secret, and encapsulates it for the public key.
- /// If `seed` is `null`, a random seed is used. This is recommended.
- /// If `seed` is set, encapsulation is deterministic.
- pub fn encaps(pk: PublicKey, seed_: ?[encaps_seed_length]u8) EncapsulatedSecret {
+ /// Generates a shared secret, encapsulated for the public key,
+ /// using random bytes.
+ ///
+ /// This is recommended over `encapsDeterministic`.
+ pub fn encaps(pk: PublicKey, io: std.Io) EncapsulatedSecret {
var m: [inner_plaintext_length]u8 = undefined;
+ io.random(&m);
+ return encapsInner(pk, &m);
+ }
- if (seed_) |seed| {
- if (p.ml_kem) {
- @memcpy(&m, &seed);
- } else {
- // m = H(seed)
- sha3.Sha3_256.hash(&seed, &m, .{});
- }
+ /// Generates a shared secret, encapsulated for the public key,
+ /// using the provided seed.
+ ///
+ /// Calling `encaps` instead is recommended.
+ pub fn encapsDeterministic(pk: PublicKey, seed: *const [encaps_seed_length]u8) EncapsulatedSecret {
+ var m: [inner_plaintext_length]u8 = undefined;
+ if (p.ml_kem) {
+ @memcpy(&m, seed);
} else {
- crypto.random.bytes(&m);
+ // m = H(seed)
+ sha3.Sha3_256.hash(seed, &m, .{});
}
+ return encapsInner(pk, &m);
+ }
+ fn encapsInner(pk: PublicKey, m: *[inner_plaintext_length]u8) EncapsulatedSecret {
// (K', r) = G(m ‖ H(pk))
var kr: [inner_plaintext_length + h_length]u8 = undefined;
var g = sha3.Sha3_512.init(.{});
- g.update(&m);
+ g.update(m);
g.update(&pk.hpk);
g.final(&kr);
// c = innerEncrypt(pk, m, r)
- const ct = pk.pk.encrypt(&m, kr[32..64]);
+ const ct = pk.pk.encrypt(m, kr[32..64]);
if (p.ml_kem) {
return EncapsulatedSecret{
@@ -398,10 +407,10 @@ fn Kyber(comptime p: Params) type {
}
/// Generate a new, random key pair.
- pub fn generate() KeyPair {
+ pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
- crypto.random.bytes(&random_seed);
+ io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
@@ -1634,15 +1643,15 @@ test "Test happy flow" {
}
inline for (modes) |mode| {
for (0..10) |i| {
- seed[0] = @as(u8, @intCast(i));
+ seed[0] = @intCast(i);
const kp = try mode.KeyPair.generateDeterministic(seed);
const sk = try mode.SecretKey.fromBytes(&kp.secret_key.toBytes());
try testing.expectEqual(sk, kp.secret_key);
const pk = try mode.PublicKey.fromBytes(&kp.public_key.toBytes());
try testing.expectEqual(pk, kp.public_key);
for (0..10) |j| {
- seed[1] = @as(u8, @intCast(j));
- const e = pk.encaps(seed[0..32].*);
+ seed[1] = @intCast(j);
+ const e = pk.encapsDeterministic(seed[0..32]);
try testing.expectEqual(e.shared_secret, try sk.decaps(&e.ciphertext));
}
}
@@ -1695,7 +1704,7 @@ fn testNistKat(mode: type, hash: []const u8) !void {
g2.fill(kseed[32..64]);
g2.fill(&eseed);
const kp = try mode.KeyPair.generateDeterministic(kseed);
- const e = kp.public_key.encaps(eseed);
+ const e = kp.public_key.encapsDeterministic(&eseed);
const ss2 = try kp.secret_key.decaps(&e.ciphertext);
try testing.expectEqual(ss2, e.shared_secret);
try fw.writer.print("pk = {X}\n", .{&kp.public_key.toBytes()});
diff --git a/lib/std/crypto/pcurves/p256.zig b/lib/std/crypto/pcurves/p256.zig
@@ -122,8 +122,8 @@ pub const P256 = struct {
}
/// Return a random point.
- pub fn random() P256 {
- const n = scalar.random(.little);
+ pub fn random(io: std.Io) P256 {
+ const n = scalar.random(io, .little);
return basePoint.mul(n, .little) catch unreachable;
}
diff --git a/lib/std/crypto/pcurves/p256/scalar.zig b/lib/std/crypto/pcurves/p256/scalar.zig
@@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
}
/// Return a random scalar
-pub fn random(endian: std.builtin.Endian) CompressedScalar {
- return Scalar.random().toBytes(endian);
+pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
+ return Scalar.random(io).toBytes(endian);
}
/// A scalar in unpacked representation.
@@ -170,10 +170,10 @@ pub const Scalar = struct {
}
/// Return a random scalar < L.
- pub fn random() Scalar {
+ pub fn random(io: std.Io) Scalar {
var s: [48]u8 = undefined;
while (true) {
- crypto.random.bytes(&s);
+ io.random(&s);
const n = Scalar.fromBytes48(s, .little);
if (!n.isZero()) {
return n;
diff --git a/lib/std/crypto/pcurves/p384.zig b/lib/std/crypto/pcurves/p384.zig
@@ -122,8 +122,8 @@ pub const P384 = struct {
}
/// Return a random point.
- pub fn random() P384 {
- const n = scalar.random(.little);
+ pub fn random(io: std.Io) P384 {
+ const n = scalar.random(io, .little);
return basePoint.mul(n, .little) catch unreachable;
}
diff --git a/lib/std/crypto/pcurves/p384/scalar.zig b/lib/std/crypto/pcurves/p384/scalar.zig
@@ -63,8 +63,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
}
/// Return a random scalar
-pub fn random(endian: std.builtin.Endian) CompressedScalar {
- return Scalar.random().toBytes(endian);
+pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
+ return Scalar.random(io).toBytes(endian);
}
/// A scalar in unpacked representation.
@@ -159,10 +159,10 @@ pub const Scalar = struct {
}
/// Return a random scalar < L.
- pub fn random() Scalar {
+ pub fn random(io: std.Io) Scalar {
var s: [64]u8 = undefined;
while (true) {
- crypto.random.bytes(&s);
+ io.random(&s);
const n = Scalar.fromBytes64(s, .little);
if (!n.isZero()) {
return n;
diff --git a/lib/std/crypto/pcurves/secp256k1.zig b/lib/std/crypto/pcurves/secp256k1.zig
@@ -175,8 +175,8 @@ pub const Secp256k1 = struct {
}
/// Return a random point.
- pub fn random() Secp256k1 {
- const n = scalar.random(.little);
+ pub fn random(io: std.Io) Secp256k1 {
+ const n = scalar.random(io, .little);
return basePoint.mul(n, .little) catch unreachable;
}
diff --git a/lib/std/crypto/pcurves/secp256k1/scalar.zig b/lib/std/crypto/pcurves/secp256k1/scalar.zig
@@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
}
/// Return a random scalar
-pub fn random(endian: std.builtin.Endian) CompressedScalar {
- return Scalar.random().toBytes(endian);
+pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
+ return Scalar.random(io).toBytes(endian);
}
/// A scalar in unpacked representation.
@@ -170,10 +170,10 @@ pub const Scalar = struct {
}
/// Return a random scalar < L.
- pub fn random() Scalar {
+ pub fn random(io: std.Io) Scalar {
var s: [48]u8 = undefined;
while (true) {
- crypto.random.bytes(&s);
+ io.random(&s);
const n = Scalar.fromBytes48(s, .little);
if (!n.isZero()) {
return n;
diff --git a/lib/std/crypto/pcurves/tests/p256.zig b/lib/std/crypto/pcurves/tests/p256.zig
@@ -5,8 +5,9 @@ const testing = std.testing;
const P256 = @import("../p256.zig").P256;
test "p256 ECDH key exchange" {
- const dha = P256.scalar.random(.little);
- const dhb = P256.scalar.random(.little);
+ const io = testing.io;
+ const dha = P256.scalar.random(io, .little);
+ const dhb = P256.scalar.random(io, .little);
const dhA = try P256.basePoint.mul(dha, .little);
const dhB = try P256.basePoint.mul(dhb, .little);
const shareda = try dhA.mul(dhb, .little);
@@ -66,28 +67,32 @@ test "p256 test vectors - doubling" {
}
test "p256 compressed sec1 encoding/decoding" {
- const p = P256.random();
+ const io = testing.io;
+ const p = P256.random(io);
const s = p.toCompressedSec1();
const q = try P256.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "p256 uncompressed sec1 encoding/decoding" {
- const p = P256.random();
+ const io = testing.io;
+ const p = P256.random(io);
const s = p.toUncompressedSec1();
const q = try P256.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "p256 public key is the neutral element" {
+ const io = testing.io;
const n = P256.scalar.Scalar.zero.toBytes(.little);
- const p = P256.random();
+ const p = P256.random(io);
try testing.expectError(error.IdentityElement, p.mul(n, .little));
}
test "p256 public key is the neutral element (public verification)" {
+ const io = testing.io;
const n = P256.scalar.Scalar.zero.toBytes(.little);
- const p = P256.random();
+ const p = P256.random(io);
try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
}
diff --git a/lib/std/crypto/pcurves/tests/p384.zig b/lib/std/crypto/pcurves/tests/p384.zig
@@ -5,8 +5,9 @@ const testing = std.testing;
const P384 = @import("../p384.zig").P384;
test "p384 ECDH key exchange" {
- const dha = P384.scalar.random(.little);
- const dhb = P384.scalar.random(.little);
+ const io = testing.io;
+ const dha = P384.scalar.random(io, .little);
+ const dhb = P384.scalar.random(io, .little);
const dhA = try P384.basePoint.mul(dha, .little);
const dhB = try P384.basePoint.mul(dhb, .little);
const shareda = try dhA.mul(dhb, .little);
@@ -67,7 +68,8 @@ test "p384 test vectors - doubling" {
}
test "p384 compressed sec1 encoding/decoding" {
- const p = P384.random();
+ const io = testing.io;
+ const p = P384.random(io);
const s0 = p.toUncompressedSec1();
const s = p.toCompressedSec1();
try testing.expectEqualSlices(u8, s0[1..49], s[1..49]);
@@ -76,21 +78,24 @@ test "p384 compressed sec1 encoding/decoding" {
}
test "p384 uncompressed sec1 encoding/decoding" {
- const p = P384.random();
+ const io = testing.io;
+ const p = P384.random(io);
const s = p.toUncompressedSec1();
const q = try P384.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "p384 public key is the neutral element" {
+ const io = testing.io;
const n = P384.scalar.Scalar.zero.toBytes(.little);
- const p = P384.random();
+ const p = P384.random(io);
try testing.expectError(error.IdentityElement, p.mul(n, .little));
}
test "p384 public key is the neutral element (public verification)" {
+ const io = testing.io;
const n = P384.scalar.Scalar.zero.toBytes(.little);
- const p = P384.random();
+ const p = P384.random(io);
try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
}
diff --git a/lib/std/crypto/pcurves/tests/secp256k1.zig b/lib/std/crypto/pcurves/tests/secp256k1.zig
@@ -5,8 +5,9 @@ const testing = std.testing;
const Secp256k1 = @import("../secp256k1.zig").Secp256k1;
test "secp256k1 ECDH key exchange" {
- const dha = Secp256k1.scalar.random(.little);
- const dhb = Secp256k1.scalar.random(.little);
+ const io = testing.io;
+ const dha = Secp256k1.scalar.random(io, .little);
+ const dhb = Secp256k1.scalar.random(io, .little);
const dhA = try Secp256k1.basePoint.mul(dha, .little);
const dhB = try Secp256k1.basePoint.mul(dhb, .little);
const shareda = try dhA.mul(dhb, .little);
@@ -15,8 +16,9 @@ test "secp256k1 ECDH key exchange" {
}
test "secp256k1 ECDH key exchange including public multiplication" {
- const dha = Secp256k1.scalar.random(.little);
- const dhb = Secp256k1.scalar.random(.little);
+ const io = testing.io;
+ const dha = Secp256k1.scalar.random(io, .little);
+ const dhb = Secp256k1.scalar.random(io, .little);
const dhA = try Secp256k1.basePoint.mul(dha, .little);
const dhB = try Secp256k1.basePoint.mulPublic(dhb, .little);
const shareda = try dhA.mul(dhb, .little);
@@ -77,28 +79,32 @@ test "secp256k1 test vectors - doubling" {
}
test "secp256k1 compressed sec1 encoding/decoding" {
- const p = Secp256k1.random();
+ const io = testing.io;
+ const p = Secp256k1.random(io);
const s = p.toCompressedSec1();
const q = try Secp256k1.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "secp256k1 uncompressed sec1 encoding/decoding" {
- const p = Secp256k1.random();
+ const io = testing.io;
+ const p = Secp256k1.random(io);
const s = p.toUncompressedSec1();
const q = try Secp256k1.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "secp256k1 public key is the neutral element" {
+ const io = testing.io;
const n = Secp256k1.scalar.Scalar.zero.toBytes(.little);
- const p = Secp256k1.random();
+ const p = Secp256k1.random(io);
try testing.expectError(error.IdentityElement, p.mul(n, .little));
}
test "secp256k1 public key is the neutral element (public verification)" {
+ const io = testing.io;
const n = Secp256k1.scalar.Scalar.zero.toBytes(.little);
- const p = Secp256k1.random();
+ const p = Secp256k1.random(io);
try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
}
diff --git a/lib/std/crypto/salsa20.zig b/lib/std/crypto/salsa20.zig
@@ -533,9 +533,9 @@ pub const SealedBox = struct {
/// Encrypt a message `m` for a recipient whose public key is `public_key`.
/// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
- pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
+ pub fn seal(io: std.Io, c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
debug.assert(c.len == m.len + seal_length);
- var ekp = KeyPair.generate();
+ var ekp = KeyPair.generate(io);
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);
@@ -573,29 +573,31 @@ test "(x)salsa20" {
}
test "xsalsa20poly1305" {
+ const io = std.testing.io;
var msg: [100]u8 = undefined;
var msg2: [msg.len]u8 = undefined;
var c: [msg.len]u8 = undefined;
var key: [XSalsa20Poly1305.key_length]u8 = undefined;
var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
- crypto.random.bytes(&msg);
- crypto.random.bytes(&key);
- crypto.random.bytes(&nonce);
+ io.random(&msg);
+ io.random(&key);
+ io.random(&nonce);
XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
}
test "xsalsa20poly1305 secretbox" {
+ const io = std.testing.io;
var msg: [100]u8 = undefined;
var msg2: [msg.len]u8 = undefined;
var key: [XSalsa20Poly1305.key_length]u8 = undefined;
var nonce: [Box.nonce_length]u8 = undefined;
var boxed: [msg.len + Box.tag_length]u8 = undefined;
- crypto.random.bytes(&msg);
- crypto.random.bytes(&key);
- crypto.random.bytes(&nonce);
+ io.random(&msg);
+ io.random(&key);
+ io.random(&nonce);
SecretBox.seal(boxed[0..], msg[0..], nonce, key);
try SecretBox.open(msg2[0..], boxed[0..], nonce, key);
@@ -604,15 +606,16 @@ test "xsalsa20poly1305 secretbox" {
test "xsalsa20poly1305 box" {
if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
+ const io = std.testing.io;
var msg: [100]u8 = undefined;
var msg2: [msg.len]u8 = undefined;
var nonce: [Box.nonce_length]u8 = undefined;
var boxed: [msg.len + Box.tag_length]u8 = undefined;
- crypto.random.bytes(&msg);
- crypto.random.bytes(&nonce);
+ io.random(&msg);
+ io.random(&nonce);
- const kp1 = Box.KeyPair.generate();
- const kp2 = Box.KeyPair.generate();
+ const kp1 = Box.KeyPair.generate(io);
+ const kp2 = Box.KeyPair.generate(io);
try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
}
@@ -620,13 +623,14 @@ test "xsalsa20poly1305 box" {
test "xsalsa20poly1305 sealedbox" {
if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
+ const io = std.testing.io;
var msg: [100]u8 = undefined;
var msg2: [msg.len]u8 = undefined;
var boxed: [msg.len + SealedBox.seal_length]u8 = undefined;
- crypto.random.bytes(&msg);
+ io.random(&msg);
- const kp = Box.KeyPair.generate();
- try SealedBox.seal(boxed[0..], msg[0..], kp.public_key);
+ const kp = Box.KeyPair.generate(io);
+ try SealedBox.seal(io, boxed[0..], msg[0..], kp.public_key);
try SealedBox.open(msg2[0..], boxed[0..], kp);
}
diff --git a/lib/std/crypto/scrypt.zig b/lib/std/crypto/scrypt.zig
@@ -20,7 +20,7 @@ const Error = pwhash.Error;
const max_size = math.maxInt(usize);
const max_int = max_size >> 1;
-const default_salt_len = 32;
+pub const default_salt_len = 32;
const default_hash_len = 32;
const max_salt_len = 64;
const max_hash_len = 64;
@@ -417,10 +417,9 @@ const PhcFormatHasher = struct {
password: []const u8,
params: Params,
buf: []u8,
+ /// Filled with cryptographically secure entropy.
+ salt: []const u8,
) HasherError![]const u8 {
- var salt: [default_salt_len]u8 = undefined;
- crypto.random.bytes(&salt);
-
var hash: [default_hash_len]u8 = undefined;
try kdf(allocator, &hash, password, &salt, params);
@@ -466,9 +465,9 @@ const CryptFormatHasher = struct {
password: []const u8,
params: Params,
buf: []u8,
+ /// Filled with cryptographically secure entropy.
+ salt_bin: []const u8,
) HasherError![]const u8 {
- var salt_bin: [default_salt_len]u8 = undefined;
- crypto.random.bytes(&salt_bin);
const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin);
var hash: [default_hash_len]u8 = undefined;
diff --git a/lib/std/crypto/timing_safe.zig b/lib/std/crypto/timing_safe.zig
@@ -180,24 +180,24 @@ pub fn declassify(ptr: anytype) void {
}
test eql {
- const random = std.crypto.random;
+ const io = std.testing.io;
const expect = std.testing.expect;
var a: [100]u8 = undefined;
var b: [100]u8 = undefined;
- random.bytes(a[0..]);
- random.bytes(b[0..]);
+ io.random(&a);
+ io.random(&b);
try expect(!eql([100]u8, a, b));
a = b;
try expect(eql([100]u8, a, b));
}
test "eql (vectors)" {
- const random = std.crypto.random;
+ const io = std.testing.io;
const expect = std.testing.expect;
var a: [100]u8 = undefined;
var b: [100]u8 = undefined;
- random.bytes(a[0..]);
- random.bytes(b[0..]);
+ io.random(&a);
+ io.random(&b);
const v1: @Vector(100, u8) = a;
const v2: @Vector(100, u8) = b;
try expect(!eql(@Vector(100, u8), v1, v2));
@@ -220,9 +220,10 @@ test compare {
}
test "add and sub" {
+ const io = std.testing.io;
+
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;
@@ -230,8 +231,8 @@ test "add and sub" {
const zero = [_]u8{0} ** len;
var iterations: usize = 100;
while (iterations != 0) : (iterations -= 1) {
- random.bytes(&a);
- random.bytes(&b);
+ io.random(&a);
+ io.random(&b);
const endian = if (iterations % 2 == 0) Endian.big else Endian.little;
_ = sub(u8, &a, &b, &c, endian); // a-b
_ = add(u8, &c, &b, &c, endian); // (a-b)+b
@@ -243,11 +244,11 @@ test "add and sub" {
}
test classify {
- const random = std.crypto.random;
+ const io = std.testing.io;
const expect = std.testing.expect;
var secret: [32]u8 = undefined;
- random.bytes(&secret);
+ io.random(&secret);
// Input of the hash function is marked as secret
classify(&secret);
diff --git a/lib/std/crypto/tlcsprng.zig b/lib/std/crypto/tlcsprng.zig
@@ -1,169 +0,0 @@
-//! 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 builtin = @import("builtin");
-const mem = std.mem;
-const native_os = builtin.os.tag;
-const posix = std.posix;
-
-/// We use this as a layer of indirection because global const pointers cannot
-/// point to thread-local variables.
-pub const interface: std.Random = .{
- .ptr = undefined,
- .fillFn = tlsCsprngFill,
-};
-
-const os_has_fork = @TypeOf(posix.fork) != void;
-const os_has_arc4random = builtin.link_libc and (@TypeOf(std.c.arc4random_buf) != void);
-const want_fork_safety = os_has_fork and !os_has_arc4random and std.options.crypto_fork_safety;
-const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{
- .major = 4,
- .minor = 14,
- .patch = 0,
-}) orelse true;
-
-const Rng = std.Random.DefaultCsprng;
-
-const Context = struct {
- init_state: enum(u8) { uninitialized = 0, initialized, failed },
- rng: Rng,
-};
-
-var install_atfork_handler = std.once(struct {
- // Install the global handler only once.
- // The same handler is shared among threads and is inherinted by fork()-ed
- // processes.
- fn do() void {
- const r = std.c.pthread_atfork(null, null, childAtForkHandler);
- std.debug.assert(r == 0);
- }
-}.do);
-
-threadlocal var wipe_mem: []align(std.heap.page_size_min) u8 = &[_]u8{};
-
-fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
- if (os_has_arc4random) {
- // 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 (std.options.crypto_always_getrandom) {
- return std.options.cryptoRandomSeed(buffer);
- }
-
- if (wipe_mem.len == 0) {
- // Not initialized yet.
- if (want_fork_safety and maybe_have_wipe_on_fork) {
- // Allocate a per-process page, madvise operates with page
- // granularity.
- wipe_mem = posix.mmap(
- null,
- @sizeOf(Context),
- posix.PROT.READ | posix.PROT.WRITE,
- .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
- -1,
- 0,
- ) catch {
- // Could not allocate memory for the local state, fall back to
- // the OS syscall.
- return std.options.cryptoRandomSeed(buffer);
- };
- // The memory is already zero-initialized.
- } else {
- // Use a static thread-local buffer.
- const S = struct {
- threadlocal var buf: Context align(std.heap.page_size_min) = .{
- .init_state = .uninitialized,
- .rng = undefined,
- };
- };
- wipe_mem = mem.asBytes(&S.buf);
- }
- }
- const ctx: *Context = @ptrCast(wipe_mem.ptr);
-
- switch (ctx.init_state) {
- .uninitialized => {
- if (!want_fork_safety) {
- return initAndFill(buffer);
- }
-
- if (maybe_have_wipe_on_fork) wof: {
- // Qemu user-mode emulation ignores any valid/invalid madvise
- // hint and returns success. Check if this is the case by
- // passing bogus parameters, we expect EINVAL as result.
- if (posix.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| {
- break :wof;
- } else |_| {}
-
- if (posix.madvise(wipe_mem.ptr, wipe_mem.len, posix.MADV.WIPEONFORK)) |_| {
- return initAndFill(buffer);
- } else |_| {}
- }
-
- if (std.Thread.use_pthreads) {
- return setupPthreadAtforkAndFill(buffer);
- }
-
- // Since we failed to set up fork safety, we fall back to always
- // calling getrandom every time.
- ctx.init_state = .failed;
- return std.options.cryptoRandomSeed(buffer);
- },
- .initialized => {
- return fillWithCsprng(buffer);
- },
- .failed => {
- if (want_fork_safety) {
- return std.options.cryptoRandomSeed(buffer);
- } else {
- unreachable;
- }
- },
- }
-}
-
-fn setupPthreadAtforkAndFill(buffer: []u8) void {
- install_atfork_handler.call();
- return initAndFill(buffer);
-}
-
-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.secureZero(u8, wipe_mem);
-}
-
-fn fillWithCsprng(buffer: []u8) void {
- const ctx: *Context = @ptrCast(wipe_mem.ptr);
- return ctx.rng.fill(buffer);
-}
-
-pub fn defaultRandomSeed(buffer: []u8) void {
- posix.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
-}
-
-fn initAndFill(buffer: []u8) void {
- var seed: [Rng.secret_seed_length]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 `std.options.cryptoRandomSeed` function is provided.
- std.options.cryptoRandomSeed(&seed);
-
- const ctx: *Context = @ptrCast(wipe_mem.ptr);
- ctx.rng = Rng.init(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.
- ctx.init_state = .initialized;
-
- return fillWithCsprng(buffer);
-}
diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig
@@ -109,7 +109,7 @@ pub const Options = struct {
read_buffer: []u8,
/// Cryptographically secure random bytes. The pointer is not captured; data is only
/// read during `init`.
- entropy: *const [176]u8,
+ entropy: *const [entropy_len]u8,
/// Current time according to the wall clock / calendar, in seconds.
realtime_now_seconds: i64,
@@ -130,6 +130,8 @@ pub const Options = struct {
allow_truncation_attacks: bool = false,
/// Populated when `error.TlsAlert` is returned from `init`.
alert: ?*tls.Alert = null,
+
+ pub const entropy_len = 240;
};
const InitError = error{
@@ -200,7 +202,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
var server_hello_rand: [32]u8 = undefined;
const legacy_session_id = options.entropy[32..64].*;
- var key_share = KeyShare.init(options.entropy[64..176].*) catch |err| switch (err) {
+ var key_share = KeyShare.init(options.entropy[64..240]) catch |err| switch (err) {
// Only possible to happen if the seed is all zeroes.
error.IdentityElement => return error.InsufficientEntropy,
};
@@ -1330,12 +1332,12 @@ const KeyShare = struct {
crypto.dh.X25519.shared_length,
);
- fn init(seed: [112]u8) error{IdentityElement}!KeyShare {
+ fn init(seed: *const [176]u8) error{IdentityElement}!KeyShare {
return .{
- .ml_kem768_kp = .generate(),
- .secp256r1_kp = try .generateDeterministic(seed[0..32].*),
- .secp384r1_kp = try .generateDeterministic(seed[32..80].*),
- .x25519_kp = try .generateDeterministic(seed[80..112].*),
+ .ml_kem768_kp = try .generateDeterministic(seed[0..64].*),
+ .secp256r1_kp = try .generateDeterministic(seed[64..96].*),
+ .secp384r1_kp = try .generateDeterministic(seed[96..144].*),
+ .x25519_kp = try .generateDeterministic(seed[144..176].*),
.sk_buf = undefined,
.sk_len = 0,
};
diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig
@@ -1022,8 +1022,7 @@ test "Dir.rename directory onto non-empty dir" {
file.close(io);
target_dir.close(io);
- // Rename should fail with PathAlreadyExists if target_dir is non-empty
- try expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
+ try expectError(error.DirNotEmpty, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
// Ensure the directory was not renamed
var dir = try ctx.dir.openDir(io, test_dir_path, .{});
@@ -1651,6 +1650,21 @@ test "AtomicFile" {
\\ this is a test file
;
+ // link() succeeds with no file already present
+ {
+ var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = false });
+ defer af.deinit(io);
+ try af.file.writeStreamingAll(io, test_content);
+ try af.link(io);
+ }
+ // link() returns error.PathAlreadyExists if file already present
+ {
+ var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = false });
+ defer af.deinit(io);
+ try af.file.writeStreamingAll(io, test_content);
+ try expectError(error.PathAlreadyExists, af.link(io));
+ }
+ // replace() succeeds if file already present
{
var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true });
defer af.deinit(io);
@@ -1761,7 +1775,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
const io = testing.io;
var random_bytes: [12]u8 = undefined;
- std.crypto.random.bytes(&random_bytes);
+ io.random(&random_bytes);
var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
_ = std.fs.base64_encoder.encode(&random_b64, &random_bytes);
diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig
@@ -321,8 +321,8 @@ pub const Connection = struct {
assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
@memcpy(host_buffer, remote_host.bytes);
const tls: *Tls = @ptrCast(base);
- var random_buffer: [176]u8 = undefined;
- std.crypto.random.bytes(&random_buffer);
+ var random_buffer: [std.crypto.tls.Client.Options.entropy_len]u8 = undefined;
+ io.random(&random_buffer);
tls.* = .{
.connection = .{
.client = client,
diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig
@@ -501,6 +501,15 @@ pub const O = switch (native_arch) {
else => @compileError("missing std.os.linux.O constants for this architecture"),
};
+pub const RENAME = packed struct(u32) {
+ /// Cannot be set together with `EXCHANGE`.
+ NOREPLACE: bool = false,
+ /// Cannot be set together with `NOREPLACE`.
+ EXCHANGE: bool = false,
+ WHITEOUT: bool = false,
+ _: u29 = 0,
+};
+
/// Set by startup code, used by `getauxval`.
pub var elf_aux_maybe: ?[*]std.elf.Auxv = null;
@@ -1346,9 +1355,22 @@ pub fn rename(old: [*:0]const u8, new: [*:0]const u8) usize {
if (@hasField(SYS, "rename")) {
return syscall2(.rename, @intFromPtr(old), @intFromPtr(new));
} else if (@hasField(SYS, "renameat")) {
- return syscall4(.renameat, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new));
+ return syscall4(
+ .renameat,
+ @as(usize, @bitCast(@as(isize, AT.FDCWD))),
+ @intFromPtr(old),
+ @as(usize, @bitCast(@as(isize, AT.FDCWD))),
+ @intFromPtr(new),
+ );
} else {
- return syscall5(.renameat2, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new), 0);
+ return syscall5(
+ .renameat2,
+ @as(usize, @bitCast(@as(isize, AT.FDCWD))),
+ @intFromPtr(old),
+ @as(usize, @bitCast(@as(isize, AT.FDCWD))),
+ @intFromPtr(new),
+ 0,
+ );
}
}
@@ -1373,14 +1395,14 @@ pub fn renameat(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]co
}
}
-pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: u32) usize {
+pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: RENAME) usize {
return syscall5(
.renameat2,
@as(usize, @bitCast(@as(isize, oldfd))),
@intFromPtr(oldpath),
@as(usize, @bitCast(@as(isize, newfd))),
@intFromPtr(newpath),
- flags,
+ @as(u32, @bitCast(flags)),
);
}
diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig
@@ -531,13 +531,11 @@ pub fn prepareArea(area: []u8) usize {
};
}
-/// The main motivation for the size chosen here is that this is how much ends up being requested for
-/// the thread-local variables of the `std.crypto.random` implementation. I'm not sure why it ends up
-/// being so much; the struct itself is only 64 bytes. I think it has to do with being page-aligned
-/// and LLVM or LLD is not smart enough to lay out the TLS data in a space-conserving way. Anyway, I
-/// think it's fine because it's less than 3 pages of memory, and putting it in the ELF like this is
-/// equivalent to moving the `mmap` call below into the kernel, avoiding syscall overhead.
-var main_thread_area_buffer: [0x2100]u8 align(page_size_min) = undefined;
+/// The main motivation for the size chosen here is to be larger than total
+/// amount of thread-local variables for most programs. Putting this allocation
+/// in the ELF like this is equivalent to moving the `mmap` call below into the
+/// kernel, avoiding syscall overhead.
+var main_thread_area_buffer: [0x1000]u8 align(page_size_min) = undefined;
/// Computes the layout of the static TLS area, allocates the area, initializes all of its fields,
/// and assigns the architecture-specific value to the TP register.
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -2647,62 +2647,6 @@ pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInfor
}
}
-/// An alternate implementation of ProcessPrng from bcryptprimitives.dll
-/// This one has the following differences:
-/// * does not heap allocate `buffer`
-/// * does not introduce a dependency on bcryptprimitives.dll, which apparently
-/// runs a test suite every time it is loaded
-/// * reads buffer.len bytes from "\\Device\\CNG" rather than seeding a per-CPU
-/// AES csprng with 48 bytes.
-pub fn ProcessPrng(buffer: []u8) error{Unexpected}!void {
- const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
- var nt_name: UNICODE_STRING = .{
- .Length = device_path.len * 2,
- .MaximumLength = 0,
- .Buffer = @constCast(&device_path),
- };
- var cng_device: HANDLE = undefined;
- var io_status_block: IO_STATUS_BLOCK = undefined;
- switch (ntdll.NtOpenFile(
- &cng_device,
- .{
- .STANDARD = .{ .SYNCHRONIZE = true },
- .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
- },
- &.{
- .Length = @sizeOf(OBJECT_ATTRIBUTES),
- .RootDirectory = null,
- .ObjectName = &nt_name,
- .Attributes = .{},
- .SecurityDescriptor = null,
- .SecurityQualityOfService = null,
- },
- &io_status_block,
- .VALID_FLAGS,
- .{ .IO = .SYNCHRONOUS_NONALERT },
- )) {
- .SUCCESS => {},
- .OBJECT_NAME_NOT_FOUND => return error.Unexpected, // Observed on wine 10.0
- else => |status| return unexpectedStatus(status),
- }
- defer _ = ntdll.NtClose(cng_device);
- switch (ntdll.NtDeviceIoControlFile(
- cng_device,
- null,
- null,
- null,
- &io_status_block,
- IOCTL.KSEC.GEN_RANDOM,
- null,
- 0,
- buffer.ptr,
- @intCast(buffer.len),
- )) {
- .SUCCESS => {},
- else => |status| return unexpectedStatus(status),
- }
-}
-
pub const WaitForSingleObjectError = error{
WaitAbandoned,
WaitTimeOut,
@@ -3250,7 +3194,7 @@ pub const RenameError = error{
NetworkNotFound,
AntivirusInterference,
BadPathName,
- RenameAcrossMountPoints,
+ CrossDevice,
} || UnexpectedError;
pub fn RenameFile(
@@ -3351,7 +3295,7 @@ pub fn RenameFile(
.ACCESS_DENIED => return error.AccessDenied,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
- .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
+ .NOT_SAME_DEVICE => return error.CrossDevice,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
.FILE_IS_A_DIRECTORY => return error.IsDir,
diff --git a/lib/std/posix.zig b/lib/std/posix.zig
@@ -361,107 +361,6 @@ pub fn reboot(cmd: RebootCommand) RebootError!void {
}
}
-pub const GetRandomError = OpenError;
-
-/// Obtain a series of random bytes. These bytes can be used to seed user-space
-/// random number generators or for cryptographic purposes.
-/// When linking against libc, this calls the
-/// appropriate OS-specific library call. Otherwise it uses the zig standard
-/// library implementation.
-pub fn getrandom(buffer: []u8) GetRandomError!void {
- if (native_os == .windows) {
- return windows.ProcessPrng(buffer);
- }
- if (builtin.link_libc and @TypeOf(system.arc4random_buf) != void) {
- system.arc4random_buf(buffer.ptr, buffer.len);
- return;
- }
- if (native_os == .wasi) switch (wasi.random_get(buffer.ptr, buffer.len)) {
- .SUCCESS => return,
- else => |err| return unexpectedErrno(err),
- };
- if (@TypeOf(system.getrandom) != void) {
- var buf = buffer;
- const use_c = native_os != .linux or
- std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 28, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 25, .patch = 0 });
-
- while (buf.len != 0) {
- const num_read: usize, const err = if (use_c) res: {
- const rc = std.c.getrandom(buf.ptr, buf.len, 0);
- break :res .{ @bitCast(rc), errno(rc) };
- } else res: {
- const rc = linux.getrandom(buf.ptr, buf.len, 0);
- break :res .{ rc, linux.errno(rc) };
- };
-
- switch (err) {
- .SUCCESS => buf = buf[num_read..],
- .INVAL => unreachable,
- .FAULT => unreachable,
- .INTR => continue,
- else => return unexpectedErrno(err),
- }
- }
- return;
- }
- if (native_os == .emscripten) {
- const err = errno(std.c.getentropy(buffer.ptr, buffer.len));
- switch (err) {
- .SUCCESS => return,
- else => return unexpectedErrno(err),
- }
- }
- return getRandomBytesDevURandom(buffer);
-}
-
-fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {
- const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
- defer close(fd);
-
- switch (native_os) {
- .linux => {
- var stx = std.mem.zeroes(linux.Statx);
- const rc = linux.statx(
- fd,
- "",
- linux.AT.EMPTY_PATH,
- .{ .TYPE = true },
- &stx,
- );
- switch (errno(rc)) {
- .SUCCESS => {},
- .ACCES => unreachable,
- .BADF => unreachable,
- .FAULT => unreachable,
- .INVAL => unreachable,
- .LOOP => unreachable,
- .NAMETOOLONG => unreachable,
- .NOENT => unreachable,
- .NOMEM => return error.SystemResources,
- .NOTDIR => unreachable,
- else => |err| return unexpectedErrno(err),
- }
- if (!S.ISCHR(stx.mode)) {
- return error.NoDevice;
- }
- },
- else => {
- const st = fstat(fd) catch |err| switch (err) {
- error.Streaming => return error.NoDevice,
- else => |e| return e,
- };
- if (!S.ISCHR(st.mode)) {
- return error.NoDevice;
- }
- },
- }
-
- var i: usize = 0;
- while (i < buf.len) {
- i += read(fd, buf[i..]) catch return error.Unexpected;
- }
-}
-
pub const RaiseError = UnexpectedError;
pub fn raise(sig: SIG) RaiseError!void {
@@ -1695,7 +1594,7 @@ pub const FanotifyMarkError = error{
NotDir,
OperationUnsupported,
PermissionDenied,
- NotSameFileSystem,
+ CrossDevice,
NameTooLong,
} || UnexpectedError;
@@ -1735,7 +1634,7 @@ pub fn fanotify_markZ(
.NOTDIR => return error.NotDir,
.OPNOTSUPP => return error.OperationUnsupported,
.PERM => return error.PermissionDenied,
- .XDEV => return error.NotSameFileSystem,
+ .XDEV => return error.CrossDevice,
else => |err| return unexpectedErrno(err),
}
}
diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig
@@ -33,16 +33,6 @@ test "check WASI CWD" {
}
}
-test "getrandom" {
- var buf_a: [50]u8 = undefined;
- var buf_b: [50]u8 = undefined;
- try posix.getrandom(&buf_a);
- try posix.getrandom(&buf_b);
- // If this test fails the chance is significantly higher that there is a bug than
- // that two sets of 50 bytes were equal.
- try expect(!mem.eql(u8, &buf_a, &buf_b));
-}
-
test "getuid" {
if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
_ = posix.getuid();
diff --git a/lib/std/std.zig b/lib/std/std.zig
@@ -137,12 +137,6 @@ pub const Options = struct {
fmt_max_depth: usize = fmt.default_max_depth,
- cryptoRandomSeed: fn (buffer: []u8) void = @import("crypto/tlcsprng.zig").defaultRandomSeed,
-
- crypto_always_getrandom: bool = false,
-
- crypto_fork_safety: bool = true,
-
/// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
/// disable TLS support.
///
diff --git a/lib/std/testing.zig b/lib/std/testing.zig
@@ -631,7 +631,7 @@ pub const TmpDir = struct {
pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir {
comptime assert(builtin.is_test);
var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
- std.crypto.random.bytes(&random_bytes);
+ io.random(&random_bytes);
var sub_path: [TmpDir.sub_path_len]u8 = undefined;
_ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
diff --git a/src/Compilation.zig b/src/Compilation.zig
@@ -2942,7 +2942,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
.none => |none| {
assert(none.tmp_artifact_directory == null);
none.tmp_artifact_directory = d: {
- tmp_dir_rand_int = std.crypto.random.int(u64);
+ io.random(@ptrCast(&tmp_dir_rand_int));
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| {
@@ -3023,7 +3023,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
// Compile the artifacts to a temporary directory.
whole.tmp_artifact_directory = d: {
- tmp_dir_rand_int = std.crypto.random.int(u64);
+ io.random(@ptrCast(&tmp_dir_rand_int));
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| {
@@ -3460,7 +3460,7 @@ fn renameTmpIntoCache(
},
else => return error.AccessDenied,
},
- error.PathAlreadyExists => {
+ error.DirNotEmpty => {
try cache_directory.handle.deleteTree(io, o_sub_path);
continue;
},
@@ -5759,7 +5759,11 @@ pub fn translateC(
const gpa = comp.gpa;
const io = comp.io;
- const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
+ const tmp_basename = r: {
+ var x: u64 = undefined;
+ io.random(@ptrCast(&x));
+ break :r std.fmt.hex(x);
+ };
const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
const cache_dir = comp.dirs.local_cache.handle;
var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{});
@@ -6889,8 +6893,13 @@ fn spawnZigRc(
}
pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
+ const io = comp.io;
+ const rand_int = r: {
+ var x: u64 = undefined;
+ io.random(@ptrCast(&x));
+ break :r x;
+ };
const s = fs.path.sep_str;
- const rand_int = std.crypto.random.int(u64);
if (comp.dirs.local_cache.path) |p| {
return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix });
} else {
diff --git a/src/Package.zig b/src/Package.zig
@@ -14,9 +14,9 @@ pub const Fingerprint = packed struct(u64) {
id: u32,
checksum: u32,
- pub fn generate(name: []const u8) Fingerprint {
+ pub fn generate(rng: std.Random, name: []const u8) Fingerprint {
return .{
- .id = std.crypto.random.intRangeLessThan(u32, 1, 0xffffffff),
+ .id = rng.intRangeLessThan(u32, 1, 0xffffffff),
.checksum = std.hash.Crc32.hash(name),
};
}
diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig
@@ -494,7 +494,11 @@ fn runResource(
const eb = &f.error_bundle;
const s = fs.path.sep_str;
const cache_root = f.job_queue.global_cache;
- const rand_int = std.crypto.random.int(u64);
+ const rand_int = r: {
+ var x: u64 = undefined;
+ io.random(@ptrCast(&x));
+ break :r x;
+ };
const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(rand_int);
const package_sub_path = blk: {
@@ -690,7 +694,9 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
return error.FetchFailed;
}
- f.manifest = try Manifest.parse(arena, ast.*, .{
+ const rng: std.Random.IoSource = .{ .io = io };
+
+ f.manifest = try Manifest.parse(arena, ast.*, rng.interface(), .{
.allow_missing_paths_field = f.allow_missing_paths_field,
.allow_missing_fingerprint = f.allow_missing_fingerprint,
.allow_name_string = f.allow_name_string,
@@ -1305,7 +1311,11 @@ fn unzip(
zip_path[prefix.len + random_len ..].* = suffix.*;
var zip_file = while (true) {
- const random_integer = std.crypto.random.int(u64);
+ const random_integer = r: {
+ var x: u64 = undefined;
+ io.random(@ptrCast(&x));
+ break :r x;
+ };
zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer);
break cache_root.handle.createFile(io, &zip_path, .{
@@ -1466,7 +1476,7 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u
};
continue;
},
- error.PathAlreadyExists, error.AccessDenied => {
+ error.DirNotEmpty, error.AccessDenied => {
// Package has been already downloaded and may already be in use on the system.
cache_dir.deleteTree(io, tmp_dir_sub_path) catch {
// Garbage files leftover in zig-cache/tmp/ is, as they say
diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig
@@ -57,7 +57,7 @@ pub const ParseOptions = struct {
pub const Error = Allocator.Error;
-pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest {
+pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) Error!Manifest {
const main_node_index = ast.nodeData(.root).node;
var arena_instance = std.heap.ArenaAllocator.init(gpa);
@@ -87,7 +87,7 @@ pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest {
defer p.dependencies.deinit(gpa);
defer p.paths.deinit(gpa);
- p.parseRoot(main_node_index) catch |err| switch (err) {
+ p.parseRoot(main_node_index, rng) catch |err| switch (err) {
error.ParseFailure => assert(p.errors.items.len > 0),
else => |e| return e,
};
@@ -157,7 +157,7 @@ const Parse = struct {
const InnerError = error{ ParseFailure, OutOfMemory };
- fn parseRoot(p: *Parse, node: Ast.Node.Index) !void {
+ fn parseRoot(p: *Parse, node: Ast.Node.Index, rng: std.Random) !void {
const ast = p.ast;
const main_token = ast.nodeMainToken(node);
@@ -217,13 +217,13 @@ const Parse = struct {
if (fingerprint) |n| {
if (!n.validate(p.name)) {
return fail(p, main_token, "invalid fingerprint: 0x{x}; if this is a new or forked package, use this value: 0x{x}", .{
- n.int(), Package.Fingerprint.generate(p.name).int(),
+ n.int(), Package.Fingerprint.generate(rng, p.name).int(),
});
}
p.id = n.id;
} else if (!p.allow_missing_fingerprint) {
try appendError(p, main_token, "missing top-level 'fingerprint' field; suggested value: 0x{x}", .{
- Package.Fingerprint.generate(p.name).int(),
+ Package.Fingerprint.generate(rng, p.name).int(),
});
} else {
p.id = 0;
@@ -623,7 +623,9 @@ test "basic" {
try testing.expect(ast.errors.len == 0);
- var manifest = try Manifest.parse(gpa, ast, .{});
+ var rng = std.Random.DefaultPrng.init(0);
+
+ var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
defer manifest.deinit(gpa);
try testing.expect(manifest.errors.len == 0);
@@ -666,7 +668,9 @@ test "minimum_zig_version" {
try testing.expect(ast.errors.len == 0);
- var manifest = try Manifest.parse(gpa, ast, .{});
+ var rng = std.Random.DefaultPrng.init(0);
+
+ var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
defer manifest.deinit(gpa);
try testing.expect(manifest.errors.len == 0);
@@ -698,7 +702,9 @@ test "minimum_zig_version - invalid version" {
try testing.expect(ast.errors.len == 0);
- var manifest = try Manifest.parse(gpa, ast, .{});
+ var rng = std.Random.DefaultPrng.init(0);
+
+ var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
defer manifest.deinit(gpa);
try testing.expect(manifest.errors.len == 1);
diff --git a/src/link.zig b/src/link.zig
@@ -616,8 +616,13 @@ pub const File = struct {
// it will return ETXTBSY. So instead, we copy the file, atomically rename it
// over top of the exe path, and then proceed normally. This changes the inode,
// avoiding the error.
+ const random_integer = r: {
+ var x: u32 = undefined;
+ io.random(@ptrCast(&x));
+ break :r x;
+ };
const tmp_sub_path = try std.fmt.allocPrint(gpa, "{s}-{x}", .{
- emit.sub_path, std.crypto.random.int(u32),
+ emit.sub_path, random_integer,
});
defer gpa.free(tmp_sub_path);
try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{});
diff --git a/src/link/Lld.zig b/src/link/Lld.zig
@@ -1636,7 +1636,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
const err = switch (first_err) {
error.NameTooLong => err: {
const s = fs.path.sep_str;
- const rand_int = std.crypto.random.int(u64);
+ const rand_int = r: {
+ var x: u64 = undefined;
+ io.random(@ptrCast(&x));
+ break :r x;
+ };
const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";
const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{});
diff --git a/src/main.zig b/src/main.zig
@@ -3395,7 +3395,7 @@ fn buildOutputType(
// "-" is stdin. Dump it to a real file.
const sep = fs.path.sep_str;
const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{
- std.crypto.random.int(u64), ext.canonicalName(target),
+ randInt(io, u64), ext.canonicalName(target),
});
try dirs.local_cache.handle.createDirPath(io, "tmp");
@@ -4433,7 +4433,7 @@ fn runOrTest(
try argv.append(exe_path);
if (arg_mode == .zig_test) {
try argv.append(
- try std.fmt.allocPrint(arena, "--seed=0x{x}", .{std.crypto.random.int(u32)}),
+ try std.fmt.allocPrint(arena, "--seed=0x{x}", .{randInt(io, u32)}),
);
}
} else {
@@ -4763,7 +4763,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
const cwd_basename = fs.path.basename(cwd_path);
const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename);
- const fingerprint: Package.Fingerprint = .generate(sanitized_root_name);
+ const rng: std.Random.IoSource = .{ .io = io };
+ const fingerprint: Package.Fingerprint = .generate(rng.interface(), sanitized_root_name);
switch (template) {
.example => {
@@ -4919,7 +4920,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
try child_argv.appendSlice(&.{
"--seed",
- try std.fmt.allocPrint(arena, "0x{x}", .{std.crypto.random.int(u32)}),
+ try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}),
});
const argv_index_seed = child_argv.items.len - 1;
@@ -4937,7 +4938,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
// the strategy is to choose a temporary file name ahead of time, and then
// read this file in the parent to obtain the results, in the case the child
// exits with code 3.
- const results_tmp_file_nonce = std.fmt.hex(std.crypto.random.int(u64));
+ const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64));
try child_argv.append("-Z" ++ results_tmp_file_nonce);
var color: Color = .auto;
@@ -7223,7 +7224,7 @@ fn createDependenciesModule(
) !*Package.Module {
// Atomically create the file in a directory named after the hash of its contents.
const basename = "dependencies.zig";
- const rand_int = std.crypto.random.int(u64);
+ const rand_int = randInt(io, u64);
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
{
var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{});
@@ -7339,6 +7340,8 @@ fn loadManifest(
io: Io,
options: LoadManifestOptions,
) !struct { Package.Manifest, Ast } {
+ const rng: std.Random.IoSource = .{ .io = io };
+
const manifest_bytes = while (true) {
break options.dir.readFileAllocOptions(
io,
@@ -7360,15 +7363,13 @@ fn loadManifest(
, .{
options.root_name,
build_options.version,
- Package.Fingerprint.generate(options.root_name).int(),
+ Package.Fingerprint.generate(rng.interface(), options.root_name).int(),
}) catch |e| {
- fatal("unable to write {s}: {s}", .{ Package.Manifest.basename, @errorName(e) });
+ fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e });
};
continue;
},
- else => |e| fatal("unable to load {s}: {s}", .{
- Package.Manifest.basename, @errorName(e),
- }),
+ else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }),
};
};
var ast = try Ast.parse(gpa, manifest_bytes, .zon);
@@ -7379,7 +7380,7 @@ fn loadManifest(
process.exit(2);
}
- var manifest = try Package.Manifest.parse(gpa, ast, .{});
+ var manifest = try Package.Manifest.parse(gpa, ast, rng.interface(), .{});
errdefer manifest.deinit(gpa);
if (manifest.errors.len > 0) {
@@ -7632,3 +7633,9 @@ fn setThreadLimit(n: usize) void {
threaded_impl_ptr.setAsyncLimit(limit);
threaded_impl_ptr.concurrent_limit = limit;
}
+
+fn randInt(io: Io, comptime T: type) T {
+ var x: T = undefined;
+ io.random(@ptrCast(&x));
+ return x;
+}
diff --git a/test/standalone/simple/guess_number/main.zig b/test/standalone/simple/guess_number/main.zig
@@ -10,7 +10,8 @@ pub fn main(init: std.process.Init) !void {
try out.writeAll("Welcome to the Guess Number Game in Zig.\n");
- const answer = std.crypto.random.intRangeLessThan(u8, 0, 100) + 1;
+ var rng: std.Random.IoSource = .{ .io = init.io };
+ const answer = rng.interface().intRangeLessThan(u8, 0, 100) + 1;
while (true) {
try out.writeAll("\nGuess a number between 1 and 100: ");
diff --git a/test/standalone/windows_argv/build.zig b/test/standalone/windows_argv/build.zig
@@ -52,7 +52,7 @@ pub fn build(b: *std.Build) !void {
const fuzz_seed = b.option(u64, "seed", "Seed to use for the PRNG (default: random)") orelse seed: {
var buf: [8]u8 = undefined;
- try std.posix.getrandom(&buf);
+ b.graph.io.random(&buf);
break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
};
const fuzz_seed_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_seed}) catch @panic("oom");
diff --git a/test/standalone/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig
@@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
+ const io = init.io;
const args = try init.minimal.args.toSlice(init.arena.allocator());
if (args.len < 2) return error.MissingArgs;
@@ -23,7 +24,7 @@ pub fn main(init: std.process.Init) !void {
if (args.len < 4) {
rand_seed = true;
var buf: [8]u8 = undefined;
- try std.posix.getrandom(&buf);
+ io.random(&buf);
break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
}
break :seed try std.fmt.parseUnsigned(u64, args[3], 10);
diff --git a/test/standalone/windows_bat_args/build.zig b/test/standalone/windows_bat_args/build.zig
@@ -65,7 +65,7 @@ pub fn build(b: *std.Build) !void {
const fuzz_seed = b.option(u64, "seed", "Seed to use for the PRNG (default: random)") orelse seed: {
var buf: [8]u8 = undefined;
- try std.posix.getrandom(&buf);
+ b.graph.io.random(&buf);
break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
};
const fuzz_seed_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_seed}) catch @panic("oom");
diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig
@@ -22,7 +22,7 @@ pub fn main(init: std.process.Init) !void {
const seed_arg = it.next() orelse {
rand_seed = true;
var buf: [8]u8 = undefined;
- try std.posix.getrandom(&buf);
+ io.random(&buf);
break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
};
break :seed try std.fmt.parseUnsigned(u64, seed_arg, 10);
diff --git a/tools/doctest.zig b/tools/doctest.zig
@@ -78,9 +78,10 @@ pub fn main(init: std.process.Init) !void {
const code = try parseManifest(arena, source_bytes);
const source = stripManifest(source_bytes);
- const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{
- cache_root, std.crypto.random.int(u64),
- });
+ var random_integer: u64 = undefined;
+ io.random(@ptrCast(&random_integer));
+
+ const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ cache_root, random_integer });
Dir.cwd().createDirPath(io, tmp_dir_path) catch |err|
fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err });
defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{
diff --git a/tools/incr-check.zig b/tools/incr-check.zig
@@ -100,7 +100,7 @@ pub fn main(init: std.process.Init) !void {
const prog_node = std.Progress.start(io, .{});
defer prog_node.end();
- const rand_int = std.crypto.random.int(u64);
+ const rand_int = rand64(io);
const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{});
defer {
@@ -452,20 +452,19 @@ const Eval = struct {
std.debug.assert(eval.target.backend == .sema);
return;
};
+ const io = eval.io;
const binary_path = switch (eval.target.backend) {
.sema => unreachable,
.selfhosted, .llvm => emitted_path,
.cbe => bin: {
- const rand_int = std.crypto.random.int(u64);
+ const rand_int = rand64(io);
const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);
try eval.buildCOutput(emitted_path, out_bin_name, prog_node);
break :bin out_bin_name;
},
};
- const io = eval.io;
-
var argv_buf: [2][]const u8 = undefined;
const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor(
io,
@@ -957,3 +956,9 @@ fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError {
.msg = message,
};
}
+
+fn rand64(io: Io) u64 {
+ var x: u64 = undefined;
+ io.random(@ptrCast(&x));
+ return x;
+}