authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-07 20:04:26+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-07 20:04:26+01:00
log62d6bbc7dc36dbb2f45da08e767076157deeb259
tree6f9e22b97a6cfee83aecf3fcc3bc5cd5f2a76806
parent867501d9d2f757f99af59b1904b251aa63262e23
parent006afece53af66401b941e25eba03efab6e9251f

Merge pull request 'std.Io: implement entropy (randomness)' (#30709) from random into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30709

66 files changed, 1301 insertions(+), 803 deletions(-)

build.zig+1-9
......@@ -593,15 +593,7 @@ pub fn build(b: *std.Build) !void {
593593 .x86_64 => 3_756_422_348,
594594 else => 3_800_000_000,
595595 },
596 .linux => switch (b.graph.host.result.cpu.arch) {
597 .aarch64 => 6_732_817_203,
598 .loongarch64 => 3_216_349_593,
599 .powerpc64le => 3_090_179_276,
600 .riscv64 => 4_052_670_054,
601 .s390x => 3_652_514_201,
602 .x86_64 => 3_249_546_854,
603 else => 6_800_000_000,
604 },
596 .linux => 6_800_000_000,
605597 .macos => switch (b.graph.host.result.cpu.arch) {
606598 .aarch64 => 8_273_795_481,
607599 else => 8_300_000_000,
lib/compiler/aro/aro/Driver.zig+2-1
......@@ -1217,11 +1217,12 @@ pub fn getDepFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u
12171217}
12181218
12191219fn getRandomFilename(d: *Driver, buf: *[std.fs.max_name_bytes]u8, extension: []const u8) ![]const u8 {
1220 const io = d.comp.io;
12201221 const random_bytes_count = 12;
12211222 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
12221223
12231224 var random_bytes: [random_bytes_count]u8 = undefined;
1224 std.crypto.random.bytes(&random_bytes);
1225 io.random(&random_bytes);
12251226 var random_name: [sub_path_len]u8 = undefined;
12261227 _ = std.fs.base64_encoder.encode(&random_name, &random_bytes);
12271228
lib/compiler/build_runner.zig-1
......@@ -21,7 +21,6 @@ pub const dependencies = @import("@dependencies");
2121pub const std_options: std.Options = .{
2222 .side_channels_mitigations = .none,
2323 .http_disable_tls = true,
24 .crypto_fork_safety = false,
2524};
2625
2726pub fn main(init: process.Init.Minimal) !void {
lib/std/Build/Step.zig+1-6
......@@ -111,12 +111,7 @@ pub const TestResults = struct {
111111pub const MakeOptions = struct {
112112 progress_node: std.Progress.Node,
113113 watch: bool,
114 web_server: switch (builtin.target.cpu.arch) {
115 else => ?*Build.WebServer,
116 // WASM code references `Build.abi` which happens to incidentally reference this type, but
117 // it currently breaks because `std.net.Address` doesn't work there. Work around for now.
118 .wasm32 => void,
119 },
114 web_server: ?*Build.WebServer,
120115 /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.
121116 unit_test_timeout_ns: ?u64,
122117 /// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`.
lib/std/Build/Step/Compile.zig+19-8
......@@ -1706,18 +1706,29 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
17061706 // The args file is already present from a previous run.
17071707 } else |err| switch (err) {
17081708 error.FileNotFound => {
1709 try b.cache_root.handle.createDirPath(io, "tmp");
1710 const rand_int = std.crypto.random.int(u64);
1711 const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
1712 try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args });
1713 defer b.cache_root.handle.deleteFile(io, tmp_path) catch {
1714 // It's fine if the temporary file can't be cleaned up.
1709 var af = b.cache_root.handle.createFileAtomic(io, args_file, .{
1710 .replace = false,
1711 .make_path = true,
1712 }) catch |e| return step.fail("failed creating tmp args file {f}{s}: {t}", .{
1713 b.cache_root, args_file, e,
1714 });
1715 defer af.deinit(io);
1716
1717 af.file.writeStreamingAll(io, args) catch |e| {
1718 return step.fail("failed writing args data to tmp file {f}{s}: {t}", .{
1719 b.cache_root, args_file, e,
1720 });
17151721 };
1716 b.cache_root.handle.rename(tmp_path, b.cache_root.handle, args_file, io) catch |rename_err| switch (rename_err) {
1722 // Note we can't clean up this file, not even after build
1723 // success, because that might interfere with another build
1724 // process that needs the same file.
1725 af.link(io) catch |e| switch (e) {
17171726 error.PathAlreadyExists => {
17181727 // The args file was created by another concurrent build process.
17191728 },
1720 else => |other_err| return other_err,
1729 else => |other_err| return step.fail("failed linking tmp file {f}{s}: {t}", .{
1730 b.cache_root, args_file, other_err,
1731 }),
17211732 };
17221733 },
17231734 else => |other_err| return other_err,
lib/std/Build/Step/Options.zig+14-32
......@@ -476,46 +476,28 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
476476 return;
477477 } else |outer_err| switch (outer_err) {
478478 error.FileNotFound => {
479 const sub_dirname = fs.path.dirname(sub_path).?;
480 b.cache_root.handle.createDirPath(io, sub_dirname) catch |e|
481 return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e });
482
483 const rand_int = std.crypto.random.int(u64);
484 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
485 std.fmt.hex(rand_int) ++ fs.path.sep_str ++
486 basename;
487 const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
488
489 b.cache_root.handle.createDirPath(io, tmp_sub_path_dirname) catch |err| {
490 return step.fail("unable to make temporary directory '{f}{s}': {t}", .{
491 b.cache_root, tmp_sub_path_dirname, err,
492 });
493 };
479 var atomic_file = b.cache_root.handle.createFileAtomic(io, sub_path, .{
480 .replace = false,
481 .make_path = true,
482 }) catch |err| return step.fail("failed to create temporary path for '{f}{s}': {t}", .{
483 b.cache_root, sub_path, err,
484 });
485 defer atomic_file.deinit(io);
494486
495 b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
496 return step.fail("unable to write options to '{f}{s}': {t}", .{
497 b.cache_root, tmp_sub_path, err,
487 atomic_file.file.writeStreamingAll(io, options.contents.items) catch |err| {
488 return step.fail("failed to write options to temporary path for '{f}{s}': {t}", .{
489 b.cache_root, sub_path, err,
498490 });
499491 };
500492
501 b.cache_root.handle.rename(tmp_sub_path, b.cache_root.handle, sub_path, io) catch |err| switch (err) {
493 atomic_file.link(io) catch |err| switch (err) {
502494 error.PathAlreadyExists => {
503 // Other process beat us to it. Clean up the temp file.
504 b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| {
505 try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{
506 b.cache_root, tmp_sub_path, e,
507 });
508 };
509495 step.result_cached = true;
510496 return;
511497 },
512 else => {
513 return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{
514 b.cache_root, tmp_sub_path,
515 b.cache_root, sub_path,
516 err,
517 });
518 },
498 else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{
499 b.cache_root, sub_path, err,
500 }),
519501 };
520502 },
521503 else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{
lib/std/Build/Step/Run.zig+4-2
......@@ -984,7 +984,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
984984 };
985985
986986 // We do not know the final output paths yet, use temp paths to run the command.
987 const rand_int = std.crypto.random.int(u64);
987 var rand_int: u64 = undefined;
988 io.random(@ptrCast(&rand_int));
988989 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
989990
990991 for (output_placeholders.items) |placeholder| {
......@@ -1128,7 +1129,8 @@ pub fn rerunInFuzzMode(
11281129 }
11291130
11301131 const has_side_effects = false;
1131 const rand_int = std.crypto.random.int(u64);
1132 var rand_int: u64 = undefined;
1133 io.random(@ptrCast(&rand_int));
11321134 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
11331135 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
11341136 .progress_node = prog_node,
lib/std/Build/Step/WriteFile.zig+2-1
......@@ -293,7 +293,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
293293 .tmp => {
294294 step.result_cached = false;
295295
296 const rand_int = std.crypto.random.int(u64);
296 var rand_int: u64 = undefined;
297 io.random(@ptrCast(&rand_int));
297298 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
298299
299300 write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path});
lib/std/Io.zig+37
......@@ -676,6 +676,7 @@ pub const VTable = struct {
676676 dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,
677677 dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,
678678 dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
679 dirRenamePreserve: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenamePreserveError!void,
679680 dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void,
680681 dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
681682 dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
......@@ -731,6 +732,9 @@ pub const VTable = struct {
731732 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
732733 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
733734
735 random: *const fn (?*anyopaque, buffer: []u8) void,
736 randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void,
737
734738 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
735739 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
736740 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
22422246pub fn unlockStderr(io: Io) void {
22432247 return io.vtable.unlockStderr(io.userdata);
22442248}
2249
2250/// Obtains entropy from a cryptographically secure pseudo-random number
2251/// generator.
2252///
2253/// The implementation *may* store RNG state in process memory and use it to
2254/// fill `buffer`.
2255///
2256/// The randomness is seeded by `randomSecure`, or a less secure mechanism upon
2257/// failure.
2258///
2259/// Threadsafe.
2260///
2261/// See also `randomSecure`.
2262pub fn random(io: Io, buffer: []u8) void {
2263 return io.vtable.random(io.userdata, buffer);
2264}
2265
2266pub const RandomSecureError = error{EntropyUnavailable} || Cancelable;
2267
2268/// Obtains cryptographically secure entropy from outside the process.
2269///
2270/// Always makes a syscall, or otherwise avoids dependency on process memory,
2271/// in order to obtain fresh randomness. Does not rely on stored RNG state.
2272///
2273/// Does not have any fallback mechanisms; returns `error.EntropyUnavailable`
2274/// if any problems occur.
2275///
2276/// Threadsafe.
2277///
2278/// See also `random`.
2279pub fn randomSecure(io: Io, buffer: []u8) RandomSecureError!void {
2280 return io.vtable.randomSecure(io.userdata, buffer);
2281}
lib/std/Io/Dir.zig+43-9
......@@ -936,10 +936,9 @@ pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void
936936pub const RenameError = error{
937937 /// In WASI, this error may occur when the file descriptor does
938938 /// not hold the required rights to rename a resource by path relative to it.
939 ///
940 /// On Windows, this error may be returned instead of PathAlreadyExists when
941 /// renaming a directory over an existing directory.
942939 AccessDenied,
940 /// Attempted to replace a nonempty directory.
941 DirNotEmpty,
943942 PermissionDenied,
944943 FileBusy,
945944 DiskQuota,
......@@ -950,9 +949,8 @@ pub const RenameError = error{
950949 NotDir,
951950 SystemResources,
952951 NoSpaceLeft,
953 PathAlreadyExists,
954952 ReadOnlyFileSystem,
955 RenameAcrossMountPoints,
953 CrossDevice,
956954 NoDevice,
957955 SharingViolation,
958956 PipeBusy,
......@@ -964,6 +962,7 @@ pub const RenameError = error{
964962 /// intercepts file system operations and makes them significantly slower
965963 /// in addition to possibly failing with this error code.
966964 AntivirusInterference,
965 HardwareFailure,
967966} || PathNameError || Io.Cancelable || Io.UnexpectedError;
968967
969968/// Change the name or location of a file or directory.
......@@ -973,9 +972,9 @@ pub const RenameError = error{
973972/// Renaming a file over an existing directory or a directory over an existing
974973/// file will fail with `error.IsDir` or `error.NotDir`
975974///
976/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
977/// On WASI, both paths should be encoded as valid UTF-8.
978/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
975/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
976/// * On WASI, both paths should be encoded as valid UTF-8.
977/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
979978pub fn rename(
980979 old_dir: Dir,
981980 old_sub_path: []const u8,
......@@ -993,6 +992,39 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename
993992 return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
994993}
995994
995pub const RenamePreserveError = error{
996 /// In WASI, this error may occur when the file descriptor does
997 /// not hold the required rights to rename a resource by path relative to it.
998 ///
999 /// On Windows, this error may be returned instead of PathAlreadyExists when
1000 /// renaming a directory over an existing directory.
1001 AccessDenied,
1002 PathAlreadyExists,
1003 /// Operating system or file system does not support atomic nonreplacing
1004 /// rename.
1005 OperationUnsupported,
1006} || RenameError;
1007
1008/// Change the name or location of a file or directory.
1009///
1010/// If `new_sub_path` already exists, `error.PathAlreadyExists` will be returned.
1011///
1012/// Renaming a file over an existing directory or a directory over an existing
1013/// file will fail with `error.IsDir` or `error.NotDir`
1014///
1015/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
1016/// * On WASI, both paths should be encoded as valid UTF-8.
1017/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
1018pub fn renamePreserve(
1019 old_dir: Dir,
1020 old_sub_path: []const u8,
1021 new_dir: Dir,
1022 new_sub_path: []const u8,
1023 io: Io,
1024) RenamePreserveError!void {
1025 return io.vtable.dirRenamePreserve(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
1026}
1027
9961028pub const HardLinkOptions = File.HardLinkOptions;
9971029
9981030pub const HardLinkError = File.HardLinkError;
......@@ -1098,8 +1130,10 @@ pub fn symLinkAtomic(
10981130
10991131 const temp_path = temp_path_buf[0..temp_path_len];
11001132
1133 var random_integer: u64 = undefined;
1134
11011135 while (true) {
1102 const random_integer = std.crypto.random.int(u64);
1136 io.random(@ptrCast(&random_integer));
11031137 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
11041138
11051139 if (dir.symLink(io, target_path, temp_path, flags)) {
lib/std/Io/File.zig+2-2
......@@ -709,7 +709,7 @@ pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
709709}
710710
711711pub const HardLinkOptions = struct {
712 follow_symlinks: bool = true,
712 follow_symlinks: bool = false,
713713};
714714
715715pub const HardLinkError = error{
......@@ -726,7 +726,7 @@ pub const HardLinkError = error{
726726 SystemResources,
727727 NoSpaceLeft,
728728 ReadOnlyFileSystem,
729 NotSameFileSystem,
729 CrossDevice,
730730 NotDir,
731731} || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError;
732732
lib/std/Io/File/Atomic.zig+6-3
......@@ -37,10 +37,14 @@ pub fn deinit(af: *Atomic, io: Io) void {
3737 af.* = undefined;
3838}
3939
40pub const LinkError = Dir.HardLinkError;
40pub const LinkError = File.HardLinkError || Dir.RenamePreserveError;
4141
4242/// Atomically materializes the file into place, failing with
4343/// `error.PathAlreadyExists` if something already exists there.
44///
45/// If this operation could not be done with an unnamed temporary file, the
46/// named temporary file will be deleted in a following operation, which may
47/// independently fail. The result of that operation is stored in `delete_err`.
4448pub fn link(af: *Atomic, io: Io) LinkError!void {
4549 if (af.file_exists) {
4650 if (af.file_open) {
......@@ -48,8 +52,7 @@ pub fn link(af: *Atomic, io: Io) LinkError!void {
4852 af.file_open = false;
4953 }
5054 const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
51 try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{});
52 af.dir.deleteFile(io, &tmp_sub_path) catch {};
55 try af.dir.renamePreserve(&tmp_sub_path, af.dir, af.dest_sub_path, io);
5356 af.file_exists = false;
5457 } else {
5558 assert(af.file_open);
lib/std/Io/Threaded.zig+620-50
......@@ -65,6 +65,22 @@ argv0: Argv0,
6565environ: Environ,
6666
6767null_file: NullFile = .{},
68random_file: RandomFile = .{},
69
70csprng: Csprng = .{},
71
72pub const Csprng = struct {
73 rng: std.Random.DefaultCsprng = .{
74 .state = undefined,
75 .offset = std.math.maxInt(usize),
76 },
77
78 pub const seed_len = std.Random.DefaultCsprng.secret_seed_length;
79
80 pub fn isInitialized(c: *const Csprng) bool {
81 return c.rng.offset != std.math.maxInt(usize);
82 }
83};
6884
6985pub const Argv0 = switch (native_os) {
7086 .openbsd, .haiku => struct {
......@@ -151,6 +167,15 @@ pub const NullFile = switch (native_os) {
151167 },
152168};
153169
170pub const RandomFile = switch (native_os) {
171 .windows => NullFile,
172 else => if (use_dev_urandom) NullFile else struct {
173 fn deinit(this: @This()) void {
174 _ = this;
175 }
176 },
177};
178
154179pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
155180 unknown = 0,
156181 _,
......@@ -585,6 +610,8 @@ const Thread = struct {
585610 /// Always released when `Status.cancelation` is set to `.parked`.
586611 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
587612
613 csprng: Csprng,
614
588615 const Handle = Handle: {
589616 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
590617 if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE;
......@@ -1285,6 +1312,7 @@ pub fn deinit(t: *Threaded) void {
12851312 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
12861313 }
12871314 t.null_file.deinit();
1315 t.random_file.deinit();
12881316 t.* = undefined;
12891317}
12901318
......@@ -1313,6 +1341,7 @@ fn worker(t: *Threaded) void {
13131341 }),
13141342 .cancel_protection = .unblocked,
13151343 .futex_waiter = undefined,
1344 .csprng = .{},
13161345 };
13171346 Thread.current = &thread;
13181347
......@@ -1413,6 +1442,7 @@ pub fn io(t: *Threaded) Io {
14131442 .dirDeleteFile = dirDeleteFile,
14141443 .dirDeleteDir = dirDeleteDir,
14151444 .dirRename = dirRename,
1445 .dirRenamePreserve = dirRenamePreserve,
14161446 .dirSymLink = dirSymLink,
14171447 .dirReadLink = dirReadLink,
14181448 .dirSetOwner = dirSetOwner,
......@@ -1466,6 +1496,9 @@ pub fn io(t: *Threaded) Io {
14661496 .now = now,
14671497 .sleep = sleep,
14681498
1499 .random = random,
1500 .randomSecure = randomSecure,
1501
14691502 .netListenIp = switch (native_os) {
14701503 .windows => netListenIpWindows,
14711504 else => netListenIpPosix,
......@@ -1561,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {
15611594 .dirDeleteFile = dirDeleteFile,
15621595 .dirDeleteDir = dirDeleteDir,
15631596 .dirRename = dirRename,
1597 .dirRenamePreserve = dirRenamePreserve,
15641598 .dirSymLink = dirSymLink,
15651599 .dirReadLink = dirReadLink,
15661600 .dirSetOwner = dirSetOwner,
......@@ -1614,6 +1648,9 @@ pub fn ioBasic(t: *Threaded) Io {
16141648 .now = now,
16151649 .sleep = sleep,
16161650
1651 .random = random,
1652 .randomSecure = randomSecure,
1653
16171654 .netListenIp = netListenIpUnavailable,
16181655 .netListenUnix = netListenUnixUnavailable,
16191656 .netAccept = netAcceptUnavailable,
......@@ -1704,6 +1741,23 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid
17041741});
17051742const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
17061743
1744const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
1745 .{ .major = 30, .minor = 0, .patch = 0 }
1746else
1747 .{ .major = 2, .minor = 28, .patch = 0 });
1748
1749const use_libc_getrandom = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
1750 .major = 28,
1751 .minor = 0,
1752 .patch = 0,
1753} else .{
1754 .major = 2,
1755 .minor = 25,
1756 .patch = 0,
1757});
1758
1759const use_dev_urandom = @TypeOf(posix.system.getrandom) == void and native_os == .linux;
1760
17071761fn async(
17081762 userdata: ?*anyopaque,
17091763 result: []u8,
......@@ -2342,11 +2396,11 @@ fn dirCreateDirPath(
23422396 status = .created;
23432397 } else |err| switch (err) {
23442398 error.PathAlreadyExists => {
2345 // stat the file and return an error if it's not a directory
2346 // this is important because otherwise a dangling symlink
2347 // could cause an infinite loop
2348 const fstat = try dirStatFile(t, dir, component.path, .{});
2349 if (fstat.kind != .directory) return error.NotDir;
2399 // It is important to return an error if it's not a directory
2400 // because otherwise a dangling symlink could cause an infinite
2401 // loop.
2402 const kind = try filePathKind(t, dir, component.path);
2403 if (kind != .directory) return error.NotDir;
23502404 },
23512405 error.FileNotFound => |e| {
23522406 component = it.previous() orelse return e;
......@@ -2538,11 +2592,7 @@ fn dirStatFileLinux(
25382592 const t: *Threaded = @ptrCast(@alignCast(userdata));
25392593 _ = t;
25402594 const linux = std.os.linux;
2541 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
2542 .{ .major = 30, .minor = 0, .patch = 0 }
2543 else
2544 .{ .major = 2, .minor = 28, .patch = 0 });
2545 const sys = if (use_c) std.c else std.os.linux;
2595 const sys = if (statx_use_c) std.c else std.os.linux;
25462596
25472597 var path_buffer: [posix.PATH_MAX]u8 = undefined;
25482598 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
......@@ -2691,6 +2741,35 @@ fn dirStatFileWasi(
26912741 }
26922742}
26932743
2744fn filePathKind(t: *Threaded, dir: Dir, sub_path: []const u8) !File.Kind {
2745 if (native_os == .linux) {
2746 var path_buffer: [posix.PATH_MAX]u8 = undefined;
2747 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
2748
2749 const linux = std.os.linux;
2750 const syscall: Syscall = try .start();
2751 while (true) {
2752 var statx = std.mem.zeroes(linux.Statx);
2753 switch (linux.errno(linux.statx(dir.handle, sub_path_posix, 0, .{ .TYPE = true }, &statx))) {
2754 .SUCCESS => {
2755 syscall.finish();
2756 if (!statx.mask.TYPE) return error.Unexpected;
2757 return statxKind(statx.mode);
2758 },
2759 .INTR => {
2760 try syscall.checkCancel();
2761 continue;
2762 },
2763 .NOMEM => return syscall.fail(error.SystemResources),
2764 else => |err| return syscall.unexpectedErrno(err),
2765 }
2766 }
2767 }
2768
2769 const stat = try dirStatFile(t, dir, sub_path, .{});
2770 return stat.kind;
2771}
2772
26942773fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
26952774 const t: *Threaded = @ptrCast(@alignCast(userdata));
26962775
......@@ -2778,11 +2857,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
27782857 const t: *Threaded = @ptrCast(@alignCast(userdata));
27792858 _ = t;
27802859 const linux = std.os.linux;
2781 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
2782 .{ .major = 30, .minor = 0, .patch = 0 }
2783 else
2784 .{ .major = 2, .minor = 28, .patch = 0 });
2785 const sys = if (use_c) std.c else std.os.linux;
2860 const sys = if (statx_use_c) std.c else std.os.linux;
27862861
27872862 const syscall: Syscall = try .start();
27882863 while (true) {
......@@ -3450,7 +3525,7 @@ fn dirCreateFileAtomic(
34503525 if (dest_dirname) |dirname| {
34513526 // This has a nice side effect of preemptively triggering EISDIR or
34523527 // ENOENT, avoiding the ambiguity below.
3453 dir.createDirPath(t_io, dirname) catch |err| switch (err) {
3528 if (options.make_path) dir.createDirPath(t_io, dirname) catch |err| switch (err) {
34543529 // None of these make sense in this context.
34553530 error.IsDir,
34563531 error.Streaming,
......@@ -3553,8 +3628,9 @@ fn atomicFileInit(
35533628 dir: Dir,
35543629 close_dir_on_deinit: bool,
35553630) Dir.CreateFileAtomicError!File.Atomic {
3631 var random_integer: u64 = undefined;
35563632 while (true) {
3557 const random_integer = std.crypto.random.int(u64);
3633 t_io.random(@ptrCast(&random_integer));
35583634 const tmp_sub_path = std.fmt.hex(random_integer);
35593635 const file = dir.createFile(t_io, &tmp_sub_path, .{
35603636 .permissions = permissions,
......@@ -3636,10 +3712,12 @@ fn dirOpenFilePosix(
36363712 },
36373713 };
36383714
3715 const mode: posix.mode_t = 0;
3716
36393717 const fd: posix.fd_t = fd: {
36403718 const syscall: Syscall = try .start();
36413719 while (true) {
3642 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0));
3720 const rc = openat_sym(dir.handle, sub_path_posix, os_flags, mode);
36433721 switch (posix.errno(rc)) {
36443722 .SUCCESS => {
36453723 syscall.finish();
......@@ -4068,9 +4146,11 @@ fn dirOpenDirPosix(
40684146 if (@hasField(posix.O, "PATH") and !options.iterate)
40694147 flags.PATH = true;
40704148
4149 const mode: posix.mode_t = 0;
4150
40714151 const syscall: Syscall = try .start();
40724152 while (true) {
4073 const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0));
4153 const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
40744154 switch (posix.errno(rc)) {
40754155 .SUCCESS => {
40764156 syscall.finish();
......@@ -5169,12 +5249,21 @@ fn fileHardLink(
51695249 var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
51705250 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
51715251
5172 const flags: u32 = if (!options.follow_symlinks)
5173 posix.AT.SYMLINK_NOFOLLOW | posix.AT.EMPTY_PATH
5252 const flags: u32 = if (options.follow_symlinks)
5253 posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH
51745254 else
51755255 posix.AT.EMPTY_PATH;
51765256
5177 return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags);
5257 return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) {
5258 error.FileNotFound => {
5259 if (options.follow_symlinks) return error.FileNotFound;
5260 var proc_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
5261 const proc_path = std.fmt.bufPrintSentinel(&proc_buf, "/proc/self/fd/{d}", .{file.handle}, 0) catch
5262 unreachable;
5263 return linkat(posix.AT.FDCWD, proc_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW);
5264 },
5265 else => |e| return e,
5266 };
51785267}
51795268
51805269fn linkat(
......@@ -5205,7 +5294,7 @@ fn linkat(
52055294 .NOTDIR => return syscall.fail(error.NotDir),
52065295 .PERM => return syscall.fail(error.PermissionDenied),
52075296 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
5208 .XDEV => return syscall.fail(error.NotSameFileSystem),
5297 .XDEV => return syscall.fail(error.CrossDevice),
52095298 .ILSEQ => return syscall.fail(error.BadPathName),
52105299 .FAULT => |err| return syscall.errnoBug(err),
52115300 .INVAL => |err| return syscall.errnoBug(err),
......@@ -5614,15 +5703,44 @@ fn dirRenameWindows(
56145703 new_dir: Dir,
56155704 new_sub_path: []const u8,
56165705) Dir.RenameError!void {
5617 const w = windows;
56185706 const t: *Threaded = @ptrCast(@alignCast(userdata));
56195707 _ = t;
5708 return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, true) catch |err| switch (err) {
5709 error.PathAlreadyExists => return error.Unexpected,
5710 error.OperationUnsupported => return error.Unexpected,
5711 else => |e| return e,
5712 };
5713}
56205714
5715fn dirRenamePreserve(
5716 userdata: ?*anyopaque,
5717 old_dir: Dir,
5718 old_sub_path: []const u8,
5719 new_dir: Dir,
5720 new_sub_path: []const u8,
5721) Dir.RenamePreserveError!void {
5722 const t: *Threaded = @ptrCast(@alignCast(userdata));
5723 if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false);
5724 if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path);
5725 // Make a hard link then delete the original.
5726 try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false });
5727 const prev = swapCancelProtection(t, .blocked);
5728 defer _ = swapCancelProtection(t, prev);
5729 dirDeleteFile(t, old_dir, old_sub_path) catch {};
5730}
5731
5732fn dirRenameWindowsInner(
5733 old_dir: Dir,
5734 old_sub_path: []const u8,
5735 new_dir: Dir,
5736 new_sub_path: []const u8,
5737 replace_if_exists: bool,
5738) Dir.RenamePreserveError!void {
5739 const w = windows;
56215740 const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
56225741 const old_path_w = old_path_w_buf.span();
56235742 const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path);
56245743 const new_path_w = new_path_w_buf.span();
5625 const replace_if_exists = true;
56265744
56275745 const src_fd = src_fd: {
56285746 const syscall: Syscall = try .start();
......@@ -5724,9 +5842,9 @@ fn dirRenameWindows(
57245842 .ACCESS_DENIED => return error.AccessDenied,
57255843 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
57265844 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
5727 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
5845 .NOT_SAME_DEVICE => return error.CrossDevice,
57285846 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
5729 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
5847 .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
57305848 .FILE_IS_A_DIRECTORY => return error.IsDir,
57315849 .NOT_A_DIRECTORY => return error.NotDir,
57325850 else => return w.unexpectedStatus(rc),
......@@ -5770,10 +5888,10 @@ fn dirRenameWasi(
57705888 .NOTDIR => return error.NotDir,
57715889 .NOMEM => return error.SystemResources,
57725890 .NOSPC => return error.NoSpaceLeft,
5773 .EXIST => return error.PathAlreadyExists,
5774 .NOTEMPTY => return error.PathAlreadyExists,
5891 .EXIST => return error.DirNotEmpty,
5892 .NOTEMPTY => return error.DirNotEmpty,
57755893 .ROFS => return error.ReadOnlyFileSystem,
5776 .XDEV => return error.RenameAcrossMountPoints,
5894 .XDEV => return error.CrossDevice,
57775895 .NOTCAPABLE => return error.AccessDenied,
57785896 .ILSEQ => return error.BadPathName,
57795897 else => |err| return posix.unexpectedErrno(err),
......@@ -5799,9 +5917,105 @@ fn dirRenamePosix(
57995917 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
58005918 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
58015919
5920 return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix);
5921}
5922
5923fn dirRenamePreserveLinux(
5924 old_dir: Dir,
5925 old_sub_path: []const u8,
5926 new_dir: Dir,
5927 new_sub_path: []const u8,
5928) Dir.RenamePreserveError!void {
5929 const linux = std.os.linux;
5930
5931 var old_path_buffer: [linux.PATH_MAX]u8 = undefined;
5932 var new_path_buffer: [linux.PATH_MAX]u8 = undefined;
5933
5934 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
5935 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
5936
5937 const syscall: Syscall = try .start();
5938 while (true) switch (linux.errno(linux.renameat2(
5939 old_dir.handle,
5940 old_sub_path_posix,
5941 new_dir.handle,
5942 new_sub_path_posix,
5943 .{ .NOREPLACE = true },
5944 ))) {
5945 .SUCCESS => return syscall.finish(),
5946 .INTR => {
5947 try syscall.checkCancel();
5948 continue;
5949 },
5950 .ACCES => return syscall.fail(error.AccessDenied),
5951 .PERM => return syscall.fail(error.PermissionDenied),
5952 .BUSY => return syscall.fail(error.FileBusy),
5953 .DQUOT => return syscall.fail(error.DiskQuota),
5954 .ISDIR => return syscall.fail(error.IsDir),
5955 .LOOP => return syscall.fail(error.SymLinkLoop),
5956 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
5957 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
5958 .NOENT => return syscall.fail(error.FileNotFound),
5959 .NOTDIR => return syscall.fail(error.NotDir),
5960 .NOMEM => return syscall.fail(error.SystemResources),
5961 .NOSPC => return syscall.fail(error.NoSpaceLeft),
5962 .EXIST => return syscall.fail(error.PathAlreadyExists),
5963 .NOTEMPTY => return syscall.fail(error.DirNotEmpty),
5964 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
5965 .XDEV => return syscall.fail(error.CrossDevice),
5966 .ILSEQ => return syscall.fail(error.BadPathName),
5967 .FAULT => |err| return syscall.errnoBug(err),
5968 .INVAL => |err| return syscall.errnoBug(err),
5969 else => |err| return syscall.unexpectedErrno(err),
5970 };
5971}
5972
5973fn renameat(
5974 old_dir: posix.fd_t,
5975 old_sub_path: [*:0]const u8,
5976 new_dir: posix.fd_t,
5977 new_sub_path: [*:0]const u8,
5978) Dir.RenameError!void {
5979 const syscall: Syscall = try .start();
5980 while (true) switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
5981 .SUCCESS => return syscall.finish(),
5982 .INTR => {
5983 try syscall.checkCancel();
5984 continue;
5985 },
5986 .ACCES => return syscall.fail(error.AccessDenied),
5987 .PERM => return syscall.fail(error.PermissionDenied),
5988 .BUSY => return syscall.fail(error.FileBusy),
5989 .DQUOT => return syscall.fail(error.DiskQuota),
5990 .ISDIR => return syscall.fail(error.IsDir),
5991 .IO => return syscall.fail(error.HardwareFailure),
5992 .LOOP => return syscall.fail(error.SymLinkLoop),
5993 .MLINK => return syscall.fail(error.LinkQuotaExceeded),
5994 .NAMETOOLONG => return syscall.fail(error.NameTooLong),
5995 .NOENT => return syscall.fail(error.FileNotFound),
5996 .NOTDIR => return syscall.fail(error.NotDir),
5997 .NOMEM => return syscall.fail(error.SystemResources),
5998 .NOSPC => return syscall.fail(error.NoSpaceLeft),
5999 .EXIST => return syscall.fail(error.DirNotEmpty),
6000 .NOTEMPTY => return syscall.fail(error.DirNotEmpty),
6001 .ROFS => return syscall.fail(error.ReadOnlyFileSystem),
6002 .XDEV => return syscall.fail(error.CrossDevice),
6003 .ILSEQ => return syscall.fail(error.BadPathName),
6004 .FAULT => |err| return syscall.errnoBug(err),
6005 .INVAL => |err| return syscall.errnoBug(err),
6006 else => |err| return syscall.unexpectedErrno(err),
6007 };
6008}
6009
6010fn renameatPreserve(
6011 old_dir: posix.fd_t,
6012 old_sub_path: [*:0]const u8,
6013 new_dir: posix.fd_t,
6014 new_sub_path: [*:0]const u8,
6015) Dir.RenameError!void {
58026016 const syscall: Syscall = try .start();
58036017 while (true) {
5804 switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {
6018 switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
58056019 .SUCCESS => return syscall.finish(),
58066020 .INTR => {
58076021 try syscall.checkCancel();
......@@ -5827,7 +6041,7 @@ fn dirRenamePosix(
58276041 .EXIST => return error.PathAlreadyExists,
58286042 .NOTEMPTY => return error.PathAlreadyExists,
58296043 .ROFS => return error.ReadOnlyFileSystem,
5830 .XDEV => return error.RenameAcrossMountPoints,
6044 .XDEV => return error.CrossDevice,
58316045 .ILSEQ => return error.BadPathName,
58326046 else => |err| return posix.unexpectedErrno(err),
58336047 }
......@@ -6318,11 +6532,6 @@ fn fchmodatFallback(
63186532 mode: posix.mode_t,
63196533) Dir.SetFilePermissionsError!void {
63206534 comptime assert(native_os == .linux);
6321 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
6322 .{ .major = 30, .minor = 0, .patch = 0 }
6323 else
6324 .{ .major = 2, .minor = 28, .patch = 0 });
6325 const sys = if (use_c) std.c else std.os.linux;
63266535
63276536 // Fallback to changing permissions using procfs:
63286537 //
......@@ -6369,6 +6578,7 @@ fn fchmodatFallback(
63696578 defer posix.close(path_fd);
63706579
63716580 const path_mode = mode: {
6581 const sys = if (statx_use_c) std.c else std.os.linux;
63726582 const syscall: Syscall = try .start();
63736583 while (true) {
63746584 var statx = std.mem.zeroes(std.os.linux.Statx);
......@@ -7612,7 +7822,7 @@ fn dirHardLink(
76127822 .NOTDIR => return error.NotDir,
76137823 .PERM => return error.PermissionDenied,
76147824 .ROFS => return error.ReadOnlyFileSystem,
7615 .XDEV => return error.NotSameFileSystem,
7825 .XDEV => return error.CrossDevice,
76167826 .INVAL => |err| return errnoBug(err),
76177827 .ILSEQ => return error.BadPathName,
76187828 else => |err| return posix.unexpectedErrno(err),
......@@ -7628,7 +7838,7 @@ fn dirHardLink(
76287838 const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
76297839 const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
76307840
7631 const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
7841 const flags: u32 = if (options.follow_symlinks) posix.AT.SYMLINK_FOLLOW else 0;
76327842 return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags);
76337843}
76347844
......@@ -12268,16 +12478,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
1226812478 .nlink = stx.nlink,
1226912479 .size = stx.size,
1227012480 .permissions = .fromMode(stx.mode),
12271 .kind = switch (stx.mode & std.os.linux.S.IFMT) {
12272 std.os.linux.S.IFDIR => .directory,
12273 std.os.linux.S.IFCHR => .character_device,
12274 std.os.linux.S.IFBLK => .block_device,
12275 std.os.linux.S.IFREG => .file,
12276 std.os.linux.S.IFIFO => .named_pipe,
12277 std.os.linux.S.IFLNK => .sym_link,
12278 std.os.linux.S.IFSOCK => .unix_domain_socket,
12279 else => .unknown,
12280 },
12481 .kind = statxKind(stx.mode),
1228112482 .atime = if (!stx.mask.ATIME) null else .{
1228212483 .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec),
1228312484 },
......@@ -12286,6 +12487,19 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
1228612487 };
1228712488}
1228812489
12490fn statxKind(stx_mode: u16) File.Kind {
12491 return switch (stx_mode & std.os.linux.S.IFMT) {
12492 std.os.linux.S.IFDIR => .directory,
12493 std.os.linux.S.IFCHR => .character_device,
12494 std.os.linux.S.IFBLK => .block_device,
12495 std.os.linux.S.IFREG => .file,
12496 std.os.linux.S.IFIFO => .named_pipe,
12497 std.os.linux.S.IFLNK => .sym_link,
12498 std.os.linux.S.IFSOCK => .unix_domain_socket,
12499 else => .unknown,
12500 };
12501}
12502
1228912503fn statFromPosix(st: *const posix.Stat) File.Stat {
1229012504 const atime = st.atime();
1229112505 const mtime = st.mtime();
......@@ -12441,7 +12655,8 @@ fn lookupDns(
1244112655
1244212656 for (family_records) |fr| {
1244312657 if (options.family != fr.af) {
12444 const entropy = std.crypto.random.array(u8, 2);
12658 var entropy: [2]u8 = undefined;
12659 random(t, &entropy);
1244512660 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
1244612661 queries_buffer[nq] = query_buffers[nq][0..len];
1244712662 nq += 1;
......@@ -13853,6 +14068,62 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1385314068 };
1385414069}
1385514070
14071fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
14072 {
14073 t.mutex.lock();
14074 defer t.mutex.unlock();
14075 if (t.random_file.handle) |handle| return handle;
14076 }
14077
14078 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
14079
14080 var nt_name: windows.UNICODE_STRING = .{
14081 .Length = device_path.len * 2,
14082 .MaximumLength = 0,
14083 .Buffer = @constCast(&device_path),
14084 };
14085 var fresh_handle: windows.HANDLE = undefined;
14086 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
14087 var syscall: Syscall = try .start();
14088 while (true) switch (windows.ntdll.NtOpenFile(
14089 &fresh_handle,
14090 .{
14091 .STANDARD = .{ .SYNCHRONIZE = true },
14092 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
14093 },
14094 &.{
14095 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
14096 .RootDirectory = null,
14097 .ObjectName = &nt_name,
14098 .Attributes = .{},
14099 .SecurityDescriptor = null,
14100 .SecurityQualityOfService = null,
14101 },
14102 &io_status_block,
14103 .VALID_FLAGS,
14104 .{ .IO = .SYNCHRONOUS_NONALERT },
14105 )) {
14106 .SUCCESS => {
14107 syscall.finish();
14108 t.mutex.lock(); // Another thread might have won the race.
14109 defer t.mutex.unlock();
14110 if (t.random_file.handle) |prev_handle| {
14111 _ = windows.ntdll.NtClose(fresh_handle);
14112 return prev_handle;
14113 } else {
14114 t.random_file.handle = fresh_handle;
14115 return fresh_handle;
14116 }
14117 },
14118 .CANCELLED => {
14119 try syscall.checkCancel();
14120 continue;
14121 },
14122 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.EntropyUnavailable), // Observed on wine 10.0
14123 else => return syscall.fail(error.EntropyUnavailable),
14124 };
14125}
14126
1385614127fn getNulHandle(t: *Threaded) !windows.HANDLE {
1385714128 {
1385814129 t.mutex.lock();
......@@ -14935,6 +15206,305 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
1493515206 return @field(t.environ.string, name);
1493615207}
1493715208
15209fn random(userdata: ?*anyopaque, buffer: []u8) void {
15210 const t: *Threaded = @ptrCast(@alignCast(userdata));
15211 const thread = Thread.current orelse return randomMainThread(t, buffer);
15212 if (!thread.csprng.isInitialized()) {
15213 @branchHint(.unlikely);
15214 var seed: [Csprng.seed_len]u8 = undefined;
15215 randomMainThread(t, &seed);
15216 thread.csprng.rng = .init(seed);
15217 }
15218 thread.csprng.rng.fill(buffer);
15219}
15220
15221fn randomMainThread(t: *Threaded, buffer: []u8) void {
15222 t.mutex.lock();
15223 defer t.mutex.unlock();
15224
15225 if (!t.csprng.isInitialized()) {
15226 @branchHint(.unlikely);
15227 var seed: [Csprng.seed_len]u8 = undefined;
15228 {
15229 t.mutex.unlock();
15230 defer t.mutex.lock();
15231
15232 const prev = swapCancelProtection(t, .blocked);
15233 defer _ = swapCancelProtection(t, prev);
15234
15235 randomSecure(t, &seed) catch |err| switch (err) {
15236 error.Canceled => unreachable,
15237 error.EntropyUnavailable => {
15238 @memset(&seed, 0);
15239 const aslr_addr = @intFromPtr(t);
15240 std.mem.writeInt(usize, seed[seed.len - @sizeOf(usize) ..][0..@sizeOf(usize)], aslr_addr, .native);
15241 switch (native_os) {
15242 .windows => fallbackSeedWindows(&seed),
15243 .wasi => if (builtin.link_libc) fallbackSeedPosix(&seed) else fallbackSeedWasi(&seed),
15244 else => fallbackSeedPosix(&seed),
15245 }
15246 },
15247 };
15248 }
15249 t.csprng.rng = .init(seed);
15250 }
15251
15252 t.csprng.rng.fill(buffer);
15253}
15254
15255fn fallbackSeedPosix(seed: *[Csprng.seed_len]u8) void {
15256 std.mem.writeInt(posix.pid_t, seed[0..@sizeOf(posix.pid_t)], posix.system.getpid(), .native);
15257 const i_1 = @sizeOf(posix.pid_t);
15258
15259 var ts: posix.timespec = undefined;
15260 const Sec = @TypeOf(ts.sec);
15261 const Nsec = @TypeOf(ts.nsec);
15262 const i_2 = i_1 + @sizeOf(Sec);
15263 switch (posix.errno(posix.system.clock_gettime(.REALTIME, &ts))) {
15264 .SUCCESS => {
15265 std.mem.writeInt(Sec, seed[i_1..][0..@sizeOf(Sec)], ts.sec, .native);
15266 std.mem.writeInt(Nsec, seed[i_2..][0..@sizeOf(Nsec)], ts.nsec, .native);
15267 },
15268 else => {},
15269 }
15270}
15271
15272fn fallbackSeedWindows(seed: *[Csprng.seed_len]u8) void {
15273 var pc: windows.LARGE_INTEGER = undefined;
15274 _ = windows.ntdll.RtlQueryPerformanceCounter(&pc);
15275 std.mem.writeInt(windows.LARGE_INTEGER, seed[0..@sizeOf(windows.LARGE_INTEGER)], pc, .native);
15276}
15277
15278fn fallbackSeedWasi(seed: *[Csprng.seed_len]u8) void {
15279 var ts: std.os.wasi.timestamp_t = undefined;
15280 if (std.os.wasi.clock_time_get(.REALTIME, 1, &ts) == .SUCCESS) {
15281 std.mem.writeInt(std.os.wasi.timestamp_t, seed[0..@sizeOf(std.os.wasi.timestamp_t)], ts, .native);
15282 }
15283}
15284
15285fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
15286 const t: *Threaded = @ptrCast(@alignCast(userdata));
15287
15288 if (is_windows) {
15289 if (buffer.len == 0) return;
15290 // ProcessPrng from bcryptprimitives.dll has the following properties:
15291 // * introduces a dependency on bcryptprimitives.dll, which apparently
15292 // runs a test suite every time it is loaded
15293 // * heap allocates a 48-byte buffer, handling failure by returning NO_MEMORY in a BOOL
15294 // despite the function being documented to always return TRUE
15295 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
15296 // Therefore, that function is avoided in favor of using the device directly.
15297 const cng_device = try getCngHandle(t);
15298 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
15299 var i: usize = 0;
15300 const syscall: Syscall = try .start();
15301 while (true) {
15302 const remaining_len = std.math.lossyCast(u32, buffer.len - i);
15303 switch (windows.ntdll.NtDeviceIoControlFile(
15304 cng_device,
15305 null,
15306 null,
15307 null,
15308 &io_status_block,
15309 windows.IOCTL.KSEC.GEN_RANDOM,
15310 null,
15311 0,
15312 buffer[i..].ptr,
15313 remaining_len,
15314 )) {
15315 .SUCCESS => {
15316 i += remaining_len;
15317 if (buffer.len - i == 0) {
15318 return syscall.finish();
15319 } else {
15320 try syscall.checkCancel();
15321 continue;
15322 }
15323 },
15324 .CANCELLED => {
15325 try syscall.checkCancel();
15326 continue;
15327 },
15328 else => return syscall.fail(error.EntropyUnavailable),
15329 }
15330 }
15331 }
15332
15333 if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) {
15334 if (buffer.len == 0) return;
15335 posix.system.arc4random_buf(buffer.ptr, buffer.len);
15336 return;
15337 }
15338
15339 if (native_os == .wasi) {
15340 if (buffer.len == 0) return;
15341 const syscall: Syscall = try .start();
15342 while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) {
15343 .SUCCESS => return syscall.finish(),
15344 .INTR => {
15345 try syscall.checkCancel();
15346 continue;
15347 },
15348 else => return syscall.fail(error.EntropyUnavailable),
15349 };
15350 }
15351
15352 if (@TypeOf(posix.system.getrandom) != void) {
15353 const getrandom = if (use_libc_getrandom) std.c.getrandom else std.os.linux.getrandom;
15354 var i: usize = 0;
15355 const syscall: Syscall = try .start();
15356 while (buffer.len - i != 0) {
15357 const buf = buffer[i..];
15358 const rc = getrandom(buf.ptr, buf.len, 0);
15359 switch (posix.errno(rc)) {
15360 .SUCCESS => {
15361 syscall.finish();
15362 const n: usize = @intCast(rc);
15363 i += n;
15364 continue;
15365 },
15366 .INTR => {
15367 try syscall.checkCancel();
15368 continue;
15369 },
15370 else => return syscall.fail(error.EntropyUnavailable),
15371 }
15372 }
15373 return;
15374 }
15375
15376 if (native_os == .emscripten) {
15377 if (buffer.len == 0) return;
15378 const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len));
15379 switch (err) {
15380 .SUCCESS => return,
15381 else => return error.EntropyUnavailable,
15382 }
15383 }
15384
15385 if (native_os == .linux) {
15386 comptime assert(use_dev_urandom);
15387 const urandom_fd = try getRandomFd(t);
15388
15389 var i: usize = 0;
15390 while (buffer.len - i != 0) {
15391 const syscall: Syscall = try .start();
15392 const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i);
15393 switch (posix.errno(rc)) {
15394 .SUCCESS => {
15395 syscall.finish();
15396 const n: usize = @intCast(rc);
15397 if (n == 0) return error.EntropyUnavailable;
15398 i += n;
15399 continue;
15400 },
15401 .INTR => {
15402 try syscall.checkCancel();
15403 continue;
15404 },
15405 else => return syscall.fail(error.EntropyUnavailable),
15406 }
15407 }
15408 }
15409
15410 return error.EntropyUnavailable;
15411}
15412
15413fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
15414 {
15415 t.mutex.lock();
15416 defer t.mutex.unlock();
15417
15418 if (t.random_file.fd == -2) return error.EntropyUnavailable;
15419 if (t.random_file.fd != -1) return t.random_file.fd;
15420 }
15421
15422 const mode: posix.mode_t = 0;
15423
15424 const fd: posix.fd_t = fd: {
15425 const syscall: Syscall = try .start();
15426 while (true) {
15427 const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{
15428 .ACCMODE = .RDONLY,
15429 .CLOEXEC = true,
15430 }, mode);
15431 switch (posix.errno(rc)) {
15432 .SUCCESS => {
15433 syscall.finish();
15434 break :fd @intCast(rc);
15435 },
15436 .INTR => {
15437 try syscall.checkCancel();
15438 continue;
15439 },
15440 else => return syscall.fail(error.EntropyUnavailable),
15441 }
15442 }
15443 };
15444 errdefer posix.close(fd);
15445
15446 switch (native_os) {
15447 .linux => {
15448 const sys = if (statx_use_c) std.c else std.os.linux;
15449 const syscall: Syscall = try .start();
15450 while (true) {
15451 var statx = std.mem.zeroes(std.os.linux.Statx);
15452 switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
15453 .SUCCESS => {
15454 syscall.finish();
15455 if (!statx.mask.TYPE) return error.EntropyUnavailable;
15456 t.mutex.lock(); // Another thread might have won the race.
15457 defer t.mutex.unlock();
15458 if (t.random_file.fd >= 0) {
15459 posix.close(fd);
15460 return t.random_file.fd;
15461 } else if (!posix.S.ISCHR(statx.mode)) {
15462 t.random_file.fd = -2;
15463 return error.EntropyUnavailable;
15464 } else {
15465 t.random_file.fd = fd;
15466 return fd;
15467 }
15468 },
15469 .INTR => {
15470 try syscall.checkCancel();
15471 continue;
15472 },
15473 else => return syscall.fail(error.EntropyUnavailable),
15474 }
15475 }
15476 },
15477 else => {
15478 const syscall: Syscall = try .start();
15479 while (true) {
15480 var stat = std.mem.zeroes(posix.Stat);
15481 switch (posix.errno(fstat_sym(fd, &stat))) {
15482 .SUCCESS => {
15483 syscall.finish();
15484 t.mutex.lock(); // Another thread might have won the race.
15485 defer t.mutex.unlock();
15486 if (t.random_file.fd >= 0) {
15487 posix.close(fd);
15488 return t.random_file.fd;
15489 } else if (!posix.S.ISCHR(stat.mode)) {
15490 t.random_file.fd = -2;
15491 return error.EntropyUnavailable;
15492 } else {
15493 t.random_file.fd = fd;
15494 return fd;
15495 }
15496 },
15497 .INTR => {
15498 try syscall.checkCancel();
15499 continue;
15500 },
15501 else => return syscall.fail(error.EntropyUnavailable),
15502 }
15503 }
15504 },
15505 }
15506}
15507
1493815508test {
1493915509 _ = @import("Threaded/test.zig");
1494015510}
lib/std/Io/net/test.zig+3-3
......@@ -275,7 +275,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
275275
276276 const io = testing.io;
277277
278 const socket_path = try generateFileName("socket.unix");
278 const socket_path = try generateFileName(io, "socket.unix");
279279 defer testing.allocator.free(socket_path);
280280
281281 const socket_addr = try net.UnixAddress.init(socket_path);
......@@ -308,11 +308,11 @@ test "listen on a unix socket, send bytes, receive bytes" {
308308 try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
309309}
310310
311fn generateFileName(base_name: []const u8) ![]const u8 {
311fn generateFileName(io: Io, base_name: []const u8) ![]const u8 {
312312 const random_bytes_count = 12;
313313 const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
314314 var random_bytes: [12]u8 = undefined;
315 std.crypto.random.bytes(&random_bytes);
315 io.random(&random_bytes);
316316 var sub_path: [sub_path_len]u8 = undefined;
317317 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
318318 return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });
lib/std/Io/test.zig+26
......@@ -564,3 +564,29 @@ test "tasks spawned in group after Group.cancel are canceled" {
564564 try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up
565565 try group.concurrent(io, global.waitThenSpawn, .{ io, &group });
566566}
567
568test "random" {
569 const io = testing.io;
570
571 var a: u64 = undefined;
572 var b: u64 = undefined;
573 var c: u64 = undefined;
574
575 io.random(@ptrCast(&a));
576 io.random(@ptrCast(&b));
577 io.random(@ptrCast(&c));
578
579 try std.testing.expect(a ^ b ^ c != 0);
580}
581
582test "randomSecure" {
583 const io = testing.io;
584
585 var buf_a: [50]u8 = undefined;
586 var buf_b: [50]u8 = undefined;
587 try io.randomSecure(&buf_a);
588 try io.randomSecure(&buf_b);
589 // If this test fails the chance is significantly higher that there is a bug than
590 // that two sets of 50 bytes were equal.
591 try expect(!mem.eql(u8, &buf_a, &buf_b));
592}
lib/std/Random.zig+17-3
......@@ -1,15 +1,13 @@
11//! The engines provided here should be initialized from an external source.
2//! For a thread-local cryptographically secure pseudo random number generator,
3//! use `std.crypto.random`.
42//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will
53//! be faster and use substantially less stack space.
4const Random = @This();
65
76const std = @import("std.zig");
87const math = std.math;
98const mem = std.mem;
109const assert = std.debug.assert;
1110const maxInt = std.math.maxInt;
12const Random = @This();
1311
1412/// Fast unbiased random numbers.
1513pub const DefaultPrng = Xoshiro256;
......@@ -35,6 +33,22 @@ pub const ziggurat = @import("Random/ziggurat.zig");
3533ptr: *anyopaque,
3634fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,
3735
36pub const IoSource = struct {
37 io: std.Io,
38
39 pub fn interface(this: *const @This()) std.Random {
40 return .{
41 .ptr = @constCast(this),
42 .fillFn = fill,
43 };
44 }
45
46 fn fill(ptr: *anyopaque, buffer: []u8) void {
47 const this: *const @This() = @ptrCast(@alignCast(ptr));
48 this.io.random(buffer);
49 }
50};
51
3852pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {
3953 const Ptr = @TypeOf(pointer);
4054 assert(@typeInfo(Ptr) == .pointer); // Must be a pointer
lib/std/Random/ChaCha.zig+1-1
......@@ -20,7 +20,7 @@ pub const secret_seed_length = Cipher.key_length;
2020
2121/// The seed must be uniform, secret and `secret_seed_length` bytes long.
2222pub fn init(secret_seed: [secret_seed_length]u8) Self {
23 var self = Self{ .state = undefined, .offset = 0 };
23 var self: Self = .{ .state = undefined, .offset = 0 };
2424 Cipher.stream(&self.state, 0, secret_seed, nonce);
2525 return self;
2626}
lib/std/Random/test.zig+2-1
......@@ -436,8 +436,9 @@ fn testRangeBias(r: Random, start: i8, end: i8, biased: bool) !void {
436436}
437437
438438test "CSPRNG" {
439 const io = std.testing.io;
439440 var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined;
440 std.crypto.random.bytes(&secret_seed);
441 io.random(&secret_seed);
441442 var csprng = DefaultCsprng.init(secret_seed);
442443 const random = csprng.random();
443444 const a = random.int(u64);
lib/std/crypto.zig+4-11
......@@ -235,9 +235,6 @@ pub const nacl = struct {
235235/// Finite-field arithmetic.
236236pub const ff = @import("crypto/ff.zig");
237237
238/// This is a thread-local, cryptographically secure pseudo random number generator.
239pub const random = @import("crypto/tlcsprng.zig").interface;
240
241238/// Encoding and decoding
242239pub const codecs = @import("crypto/codecs.zig");
243240
......@@ -306,6 +303,9 @@ test {
306303 _ = dh.X25519;
307304
308305 _ = kem.kyber_d00;
306 _ = kem.hybrid;
307 _ = kem.kyber_d00;
308 _ = kem.ml_kem;
309309
310310 _ = ecc.Curve25519;
311311 _ = ecc.Edwards25519;
......@@ -343,6 +343,7 @@ test {
343343
344344 _ = sign.Ed25519;
345345 _ = sign.ecdsa;
346 _ = sign.mldsa;
346347
347348 _ = stream.chacha.ChaCha20IETF;
348349 _ = stream.chacha.ChaCha12IETF;
......@@ -364,20 +365,12 @@ test {
364365 _ = secureZero;
365366 _ = timing_safe;
366367 _ = ff;
367 _ = random;
368368 _ = errors;
369369 _ = tls;
370370 _ = Certificate;
371371 _ = codecs;
372372}
373373
374test "CSPRNG" {
375 const a = random.int(u64);
376 const b = random.int(u64);
377 const c = random.int(u64);
378 try std.testing.expect(a ^ b ^ c != 0);
379}
380
381374test "issue #4532: no index out of bounds" {
382375 const types = [_]type{
383376 hash.Md5,
lib/std/crypto/25519/ed25519.zig+32-23
......@@ -333,12 +333,10 @@ pub const Ed25519 = struct {
333333 }
334334
335335 /// Generate a new, random key pair.
336 ///
337 /// `crypto.random.bytes` must be supported by the target.
338 pub fn generate() KeyPair {
336 pub fn generate(io: std.Io) KeyPair {
339337 var random_seed: [seed_length]u8 = undefined;
340338 while (true) {
341 crypto.random.bytes(&random_seed);
339 io.random(&random_seed);
342340 return generateDeterministic(random_seed) catch {
343341 @branchHint(.unlikely);
344342 continue;
......@@ -389,18 +387,21 @@ pub const Ed25519 = struct {
389387
390388 /// Create a Signer, that can be used for incremental signing.
391389 /// Note that the signature is not deterministic.
392 /// The noise parameter, if set, should be something unique for each message,
393 /// such as a random nonce, or a counter.
394 pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer {
390 pub fn signer(
391 key_pair: KeyPair,
392 /// If set, should be something unique for each message, such as a
393 /// random nonce, or a counter.
394 noise: ?[noise_length]u8,
395 /// Filled with cryptographically secure randomness.
396 entropy: *const [noise_length]u8,
397 ) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer {
395398 if (!mem.eql(u8, &key_pair.secret_key.publicKeyBytes(), &key_pair.public_key.toBytes())) {
396399 return error.KeyMismatch;
397400 }
398401 const scalar_and_prefix = key_pair.secret_key.scalarAndPrefix();
399402 var h = Sha512.init(.{});
400403 h.update(&scalar_and_prefix.prefix);
401 var noise2: [noise_length]u8 = undefined;
402 crypto.random.bytes(&noise2);
403 h.update(&noise2);
404 h.update(entropy);
404405 if (noise) |*z| {
405406 h.update(z);
406407 }
......@@ -420,7 +421,7 @@ pub const Ed25519 = struct {
420421 };
421422
422423 /// Verify several signatures in a single operation, much faster than verifying signatures one-by-one
423 pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void {
424 pub fn verifyBatch(io: std.Io, comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void {
424425 var r_batch: [count]CompressedScalar = undefined;
425426 var s_batch: [count]CompressedScalar = undefined;
426427 var a_batch: [count]Curve = undefined;
......@@ -454,7 +455,7 @@ pub const Ed25519 = struct {
454455
455456 var z_batch: [count]Curve.scalar.CompressedScalar = undefined;
456457 for (&z_batch) |*z| {
457 crypto.random.bytes(z[0..16]);
458 io.random(z[0..16]);
458459 @memset(z[16..], 0);
459460 }
460461
......@@ -587,12 +588,14 @@ test "signature" {
587588}
588589
589590test "batch verification" {
591 const io = std.testing.io;
592
590593 for (0..16) |_| {
591 const key_pair = Ed25519.KeyPair.generate();
594 const key_pair = Ed25519.KeyPair.generate(io);
592595 var msg1: [32]u8 = undefined;
593596 var msg2: [32]u8 = undefined;
594 crypto.random.bytes(&msg1);
595 crypto.random.bytes(&msg2);
597 io.random(&msg1);
598 io.random(&msg2);
596599 const sig1 = try key_pair.sign(&msg1, null);
597600 const sig2 = try key_pair.sign(&msg2, null);
598601 var signature_batch = [_]Ed25519.BatchElement{
......@@ -607,10 +610,10 @@ test "batch verification" {
607610 .public_key = key_pair.public_key,
608611 },
609612 };
610 try Ed25519.verifyBatch(2, signature_batch);
613 try Ed25519.verifyBatch(io, 2, signature_batch);
611614
612615 signature_batch[1].sig = sig1;
613 try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(signature_batch.len, signature_batch));
616 try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(io, signature_batch.len, signature_batch));
614617 }
615618}
616619
......@@ -718,14 +721,15 @@ test "test vectors" {
718721}
719722
720723test "with blind keys" {
724 const io = std.testing.io;
721725 const BlindKeyPair = Ed25519.key_blinding.BlindKeyPair;
722726
723727 // Create a standard Ed25519 key pair
724 const kp = Ed25519.KeyPair.generate();
728 const kp = Ed25519.KeyPair.generate(io);
725729
726730 // Create a random blinding seed
727731 var blind: [32]u8 = undefined;
728 crypto.random.bytes(&blind);
732 io.random(&blind);
729733
730734 // Blind the key pair
731735 const blind_kp = try BlindKeyPair.init(kp, blind, "ctx");
......@@ -741,9 +745,12 @@ test "with blind keys" {
741745}
742746
743747test "signatures with streaming" {
744 const kp = Ed25519.KeyPair.generate();
748 const io = std.testing.io;
749 const kp = Ed25519.KeyPair.generate(io);
745750
746 var signer = try kp.signer(null);
751 var entropy: [Ed25519.noise_length]u8 = undefined;
752 io.random(&entropy);
753 var signer = try kp.signer(null, &entropy);
747754 signer.update("mes");
748755 signer.update("sage");
749756 const sig = signer.finalize();
......@@ -757,7 +764,8 @@ test "signatures with streaming" {
757764}
758765
759766test "key pair from secret key" {
760 const kp = Ed25519.KeyPair.generate();
767 const io = std.testing.io;
768 const kp = Ed25519.KeyPair.generate(io);
761769 const kp2 = try Ed25519.KeyPair.fromSecretKey(kp.secret_key);
762770 try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes());
763771 try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes());
......@@ -788,7 +796,8 @@ test "cofactored vs cofactorless verification" {
788796}
789797
790798test "regular signature verifies with both verify and verifyStrict" {
791 const kp = Ed25519.KeyPair.generate();
799 const io = std.testing.io;
800 const kp = Ed25519.KeyPair.generate(io);
792801 const msg = "test message";
793802 const sig = try kp.sign(msg, null);
794803 try sig.verify(msg, kp.public_key);
lib/std/crypto/25519/edwards25519.zig+5-3
......@@ -575,10 +575,11 @@ test "packing/unpacking" {
575575}
576576
577577test "point addition/subtraction" {
578 const io = std.testing.io;
578579 var s1: [32]u8 = undefined;
579580 var s2: [32]u8 = undefined;
580 crypto.random.bytes(&s1);
581 crypto.random.bytes(&s2);
581 io.random(&s1);
582 io.random(&s2);
582583 const p = try Edwards25519.basePoint.clampedMul(s1);
583584 const q = try Edwards25519.basePoint.clampedMul(s2);
584585 const r = p.add(q).add(q).sub(q).sub(q);
......@@ -622,9 +623,10 @@ test "implicit reduction of invalid scalars" {
622623}
623624
624625test "subgroup check" {
626 const io = std.testing.io;
625627 for (0..100) |_| {
626628 var p = Edwards25519.basePoint;
627 const s = Edwards25519.scalar.random();
629 const s = Edwards25519.scalar.random(io);
628630 p = try p.mulPublic(s);
629631 try p.rejectUnexpectedSubgroup();
630632 }
lib/std/crypto/25519/scalar.zig+7-6
......@@ -101,8 +101,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar) CompressedScalar {
101101}
102102
103103/// Return a random scalar < L
104pub fn random() CompressedScalar {
105 return Scalar.random().toBytes();
104pub fn random(io: std.Io) CompressedScalar {
105 return Scalar.random(io).toBytes();
106106}
107107
108108/// A scalar in unpacked representation
......@@ -560,10 +560,10 @@ pub const Scalar = struct {
560560 }
561561
562562 /// Return a random scalar < L.
563 pub fn random() Scalar {
563 pub fn random(io: std.Io) Scalar {
564564 var s: [64]u8 = undefined;
565565 while (true) {
566 crypto.random.bytes(&s);
566 io.random(&s);
567567 const n = Scalar.fromBytes64(s);
568568 if (!n.isZero()) {
569569 return n;
......@@ -879,8 +879,9 @@ test "scalar field inversion" {
879879}
880880
881881test "random scalar" {
882 const s1 = random();
883 const s2 = random();
882 const io = std.testing.io;
883 const s1 = random(io);
884 const s2 = random(io);
884885 try std.testing.expect(!mem.eql(u8, &s1, &s2));
885886}
886887
lib/std/crypto/25519/x25519.zig+2-2
......@@ -41,10 +41,10 @@ pub const X25519 = struct {
4141 }
4242
4343 /// Generate a new, random key pair.
44 pub fn generate() KeyPair {
44 pub fn generate(io: std.Io) KeyPair {
4545 var random_seed: [seed_length]u8 = undefined;
4646 while (true) {
47 crypto.random.bytes(&random_seed);
47 io.random(&random_seed);
4848 return generateDeterministic(random_seed) catch {
4949 @branchHint(.unlikely);
5050 continue;
lib/std/crypto/argon2.zig+1-1
......@@ -533,7 +533,7 @@ const PhcFormatHasher = struct {
533533 if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding;
534534
535535 var salt: [default_salt_len]u8 = undefined;
536 crypto.random.bytes(&salt);
536 io.random(&salt);
537537
538538 var hash: [default_hash_len]u8 = undefined;
539539 try kdf(allocator, &hash, password, &salt, params, mode, io);
lib/std/crypto/bcrypt.zig+72-39
......@@ -17,7 +17,7 @@ const HasherError = pwhash.HasherError;
1717const EncodingError = phc_format.Error;
1818const Error = pwhash.Error;
1919
20const salt_length: usize = 16;
20pub const salt_length: usize = 16;
2121const salt_str_length: usize = 22;
2222const ct_str_length: usize = 31;
2323const ct_length: usize = 24;
......@@ -426,7 +426,7 @@ pub const Params = struct {
426426
427427fn bcryptWithTruncation(
428428 password: []const u8,
429 salt: [salt_length]u8,
429 salt: *const [salt_length]u8,
430430 params: Params,
431431) [dk_length]u8 {
432432 var state = State{};
......@@ -435,13 +435,13 @@ fn bcryptWithTruncation(
435435 @memcpy(password_buf[0..trimmed_len], password[0..trimmed_len]);
436436 password_buf[trimmed_len] = 0;
437437 const passwordZ = password_buf[0 .. trimmed_len + 1];
438 state.expand(salt[0..], passwordZ);
438 state.expand(salt, passwordZ);
439439
440440 const rounds: u64 = @as(u64, 1) << params.rounds_log;
441441 var k: u64 = 0;
442442 while (k < rounds) : (k += 1) {
443443 state.expand0(passwordZ);
444 state.expand0(salt[0..]);
444 state.expand0(salt);
445445 }
446446 crypto.secureZero(u8, &password_buf);
447447
......@@ -467,7 +467,7 @@ fn bcryptWithTruncation(
467467/// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead.
468468pub fn bcrypt(
469469 password: []const u8,
470 salt: [salt_length]u8,
470 salt: *const [salt_length]u8,
471471 params: Params,
472472) [dk_length]u8 {
473473 if (password.len <= 72 or params.silently_truncate_password) {
......@@ -475,7 +475,7 @@ pub fn bcrypt(
475475 }
476476
477477 var pre_hash: [HmacSha512.mac_length]u8 = undefined;
478 HmacSha512.create(&pre_hash, password, &salt);
478 HmacSha512.create(&pre_hash, password, salt);
479479
480480 const Encoder = crypt_format.Codec.Encoder;
481481 var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined;
......@@ -623,16 +623,16 @@ const crypt_format = struct {
623623
624624 fn strHashInternal(
625625 password: []const u8,
626 salt: [salt_length]u8,
626 salt: *const [salt_length]u8,
627627 params: Params,
628628 ) [hash_length]u8 {
629629 var dk = bcrypt(password, salt, params);
630630
631631 var salt_str: [salt_str_length]u8 = undefined;
632 _ = Codec.Encoder.encode(salt_str[0..], salt[0..]);
632 _ = Codec.Encoder.encode(&salt_str, salt);
633633
634634 var ct_str: [ct_str_length]u8 = undefined;
635 _ = Codec.Encoder.encode(ct_str[0..], dk[0..]);
635 _ = Codec.Encoder.encode(&ct_str, dk[0..]);
636636
637637 var s_buf: [hash_length]u8 = undefined;
638638 const s = fmt.bufPrint(
......@@ -657,21 +657,20 @@ const PhcFormatHasher = struct {
657657 hash: BinValue(dk_length),
658658 };
659659
660 /// Return a non-deterministic hash of the password encoded as a PHC-format string
660 /// Return a non-deterministic hash of the password encoded as a PHC-format string.
661661 fn create(
662662 password: []const u8,
663663 params: Params,
664664 buf: []u8,
665 /// Filled with cryptographically secure entropy.
666 salt: *const [salt_length]u8,
665667 ) HasherError![]const u8 {
666 var salt: [salt_length]u8 = undefined;
667 crypto.random.bytes(&salt);
668
669668 const hash = bcrypt(password, salt, params);
670669
671670 return phc_format.serialize(HashResult{
672671 .alg_id = alg_id,
673672 .r = params.rounds_log,
674 .salt = try BinValue(salt_length).fromSlice(&salt),
673 .salt = try BinValue(salt_length).fromSlice(salt),
675674 .hash = try BinValue(dk_length).fromSlice(&hash),
676675 }, buf);
677676 }
......@@ -688,11 +687,11 @@ const PhcFormatHasher = struct {
688687 if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length)
689688 return HasherError.InvalidEncoding;
690689
691 const params = Params{
690 const params: Params = .{
692691 .rounds_log = hash_result.r,
693692 .silently_truncate_password = silently_truncate_password,
694693 };
695 const hash = bcrypt(password, hash_result.salt.buf, params);
694 const hash = bcrypt(password, &hash_result.salt.buf, params);
696695 const expected_hash = hash_result.hash.constSlice();
697696
698697 if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed;
......@@ -709,12 +708,11 @@ const CryptFormatHasher = struct {
709708 password: []const u8,
710709 params: Params,
711710 buf: []u8,
711 /// Filled with cryptographically secure entropy.
712 salt: *const [salt_length]u8,
712713 ) HasherError![]const u8 {
713714 if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft;
714715
715 var salt: [salt_length]u8 = undefined;
716 crypto.random.bytes(&salt);
717
718716 const hash = crypt_format.strHashInternal(password, salt, params);
719717 @memcpy(buf[0..hash.len], &hash);
720718
......@@ -736,9 +734,9 @@ const CryptFormatHasher = struct {
736734
737735 const salt_str = str[7..][0..salt_str_length];
738736 var salt: [salt_length]u8 = undefined;
739 crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding;
737 crypt_format.Codec.Decoder.decode(&salt, salt_str) catch return HasherError.InvalidEncoding;
740738
741 const wanted_s = crypt_format.strHashInternal(password, salt, .{
739 const wanted_s = crypt_format.strHashInternal(password, &salt, .{
742740 .rounds_log = rounds_log,
743741 .silently_truncate_password = silently_truncate_password,
744742 });
......@@ -756,21 +754,28 @@ pub const HashOptions = struct {
756754 encoding: pwhash.Encoding,
757755};
758756
759/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
760/// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches.
757/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key
758/// stretching function.
759///
760/// bcrypt is a computationally expensive and cache-hard function, explicitly
761/// designed to slow down exhaustive searches.
761762///
762/// The function returns a string that includes all the parameters required for verification.
763/// The function returns a string that includes all the parameters required for
764/// verification.
763765///
764/// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes.
765/// If this is an issue for your application, set the `silently_truncate_password` option to `false`.
766/// By design, bcrypt silently truncates passwords to 72 bytes. If this is an
767/// issue for your application, set the `silently_truncate_password` option to
768/// `false`.
766769pub fn strHash(
767770 password: []const u8,
768771 options: HashOptions,
769772 out: []u8,
773 /// Filled with cryptographically secure entropy.
774 salt: *const [salt_length]u8,
770775) Error![]const u8 {
771776 switch (options.encoding) {
772 .phc => return PhcFormatHasher.create(password, options.params, out),
773 .crypt => return CryptFormatHasher.create(password, options.params, out),
777 .phc => return PhcFormatHasher.create(password, options.params, out, salt),
778 .crypt => return CryptFormatHasher.create(password, options.params, out, salt),
774779 }
775780}
776781
......@@ -796,8 +801,9 @@ pub fn strVerify(
796801}
797802
798803test "bcrypt codec" {
804 const io = testing.io;
799805 var salt: [salt_length]u8 = undefined;
800 crypto.random.bytes(&salt);
806 io.random(&salt);
801807 var salt_str: [salt_str_length]u8 = undefined;
802808 _ = crypt_format.Codec.Encoder.encode(salt_str[0..], salt[0..]);
803809 var salt2: [salt_length]u8 = undefined;
......@@ -806,14 +812,20 @@ test "bcrypt codec" {
806812}
807813
808814test "bcrypt crypt format" {
809 var hash_options = HashOptions{
815 const io = testing.io;
816
817 var hash_options: HashOptions = .{
810818 .params = .{ .rounds_log = 5, .silently_truncate_password = false },
811819 .encoding = .crypt,
812820 };
813 var verify_options = VerifyOptions{ .silently_truncate_password = false };
821 var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
814822
815823 var buf: [hash_length]u8 = undefined;
816 const s = try strHash("password", hash_options, &buf);
824 const s = s: {
825 var salt: [salt_length]u8 = undefined;
826 io.random(&salt);
827 break :s try strHash("password", hash_options, &buf, &salt);
828 };
817829
818830 try testing.expect(mem.startsWith(u8, s, crypt_format.prefix));
819831 try strVerify(s, "password", verify_options);
......@@ -823,7 +835,11 @@ test "bcrypt crypt format" {
823835 );
824836
825837 var long_buf: [hash_length]u8 = undefined;
826 var long_s = try strHash("password" ** 100, hash_options, &long_buf);
838 var long_s = s: {
839 var salt: [salt_length]u8 = undefined;
840 io.random(&salt);
841 break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
842 };
827843
828844 try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix));
829845 try strVerify(long_s, "password" ** 100, verify_options);
......@@ -834,7 +850,11 @@ test "bcrypt crypt format" {
834850
835851 hash_options.params.silently_truncate_password = true;
836852 verify_options.silently_truncate_password = true;
837 long_s = try strHash("password" ** 100, hash_options, &long_buf);
853 long_s = s: {
854 var salt: [salt_length]u8 = undefined;
855 io.random(&salt);
856 break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
857 };
838858 try strVerify(long_s, "password" ** 101, verify_options);
839859
840860 try strVerify(
......@@ -845,15 +865,20 @@ test "bcrypt crypt format" {
845865}
846866
847867test "bcrypt phc format" {
848 var hash_options = HashOptions{
868 const io = testing.io;
869 var hash_options: HashOptions = .{
849870 .params = .{ .rounds_log = 5, .silently_truncate_password = false },
850871 .encoding = .phc,
851872 };
852 var verify_options = VerifyOptions{ .silently_truncate_password = false };
873 var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
853874 const prefix = "$bcrypt$";
854875
855876 var buf: [hash_length * 2]u8 = undefined;
856 const s = try strHash("password", hash_options, &buf);
877 const s = s: {
878 var salt: [salt_length]u8 = undefined;
879 io.random(&salt);
880 break :s try strHash("password", hash_options, &buf, &salt);
881 };
857882
858883 try testing.expect(mem.startsWith(u8, s, prefix));
859884 try strVerify(s, "password", verify_options);
......@@ -863,7 +888,11 @@ test "bcrypt phc format" {
863888 );
864889
865890 var long_buf: [hash_length * 2]u8 = undefined;
866 var long_s = try strHash("password" ** 100, hash_options, &long_buf);
891 var long_s = s: {
892 var salt: [salt_length]u8 = undefined;
893 io.random(&salt);
894 break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
895 };
867896
868897 try testing.expect(mem.startsWith(u8, long_s, prefix));
869898 try strVerify(long_s, "password" ** 100, verify_options);
......@@ -874,7 +903,11 @@ test "bcrypt phc format" {
874903
875904 hash_options.params.silently_truncate_password = true;
876905 verify_options.silently_truncate_password = true;
877 long_s = try strHash("password" ** 100, hash_options, &long_buf);
906 long_s = s: {
907 var salt: [salt_length]u8 = undefined;
908 io.random(&salt);
909 break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
910 };
878911 try strVerify(long_s, "password" ** 101, verify_options);
879912
880913 try strVerify(
lib/std/crypto/ecdsa.zig+17-11
......@@ -323,10 +323,10 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
323323 }
324324
325325 /// Generate a new, random key pair.
326 pub fn generate() KeyPair {
326 pub fn generate(io: std.Io) KeyPair {
327327 var random_seed: [seed_length]u8 = undefined;
328328 while (true) {
329 crypto.random.bytes(&random_seed);
329 io.random(&random_seed);
330330 return generateDeterministic(random_seed) catch {
331331 @branchHint(.unlikely);
332332 continue;
......@@ -417,12 +417,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
417417test "Basic operations over EcdsaP384Sha384" {
418418 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
419419
420 const io = testing.io;
420421 const Scheme = EcdsaP384Sha384;
421 const kp = Scheme.KeyPair.generate();
422 const kp = Scheme.KeyPair.generate(io);
422423 const msg = "test";
423424
424425 var noise: [Scheme.noise_length]u8 = undefined;
425 crypto.random.bytes(&noise);
426 io.random(&noise);
426427 const sig = try kp.sign(msg, noise);
427428 try sig.verify(msg, kp.public_key);
428429
......@@ -433,12 +434,13 @@ test "Basic operations over EcdsaP384Sha384" {
433434test "Basic operations over Secp256k1" {
434435 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
435436
437 const io = testing.io;
436438 const Scheme = EcdsaSecp256k1Sha256oSha256;
437 const kp = Scheme.KeyPair.generate();
439 const kp = Scheme.KeyPair.generate(io);
438440 const msg = "test";
439441
440442 var noise: [Scheme.noise_length]u8 = undefined;
441 crypto.random.bytes(&noise);
443 io.random(&noise);
442444 const sig = try kp.sign(msg, noise);
443445 try sig.verify(msg, kp.public_key);
444446
......@@ -449,12 +451,13 @@ test "Basic operations over Secp256k1" {
449451test "Basic operations over EcdsaP384Sha256" {
450452 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
451453
454 const io = testing.io;
452455 const Scheme = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha256);
453 const kp = Scheme.KeyPair.generate();
456 const kp = Scheme.KeyPair.generate(io);
454457 const msg = "test";
455458
456459 var noise: [Scheme.noise_length]u8 = undefined;
457 crypto.random.bytes(&noise);
460 io.random(&noise);
458461 const sig = try kp.sign(msg, noise);
459462 try sig.verify(msg, kp.public_key);
460463
......@@ -502,8 +505,10 @@ test "Verifying a existing signature with EcdsaP384Sha256" {
502505test "Prehashed message operations" {
503506 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
504507
508 const io = testing.io;
509
505510 const Scheme = EcdsaP256Sha256;
506 const kp = Scheme.KeyPair.generate();
511 const kp = Scheme.KeyPair.generate(io);
507512 const msg = "test message for prehashed signing";
508513
509514 const Hash = crypto.hash.sha2.Sha256;
......@@ -518,7 +523,7 @@ test "Prehashed message operations" {
518523 try testing.expectError(error.SignatureVerificationFailed, sig.verifyPrehashed(bad_hash, kp.public_key));
519524
520525 var noise: [Scheme.noise_length]u8 = undefined;
521 crypto.random.bytes(&noise);
526 io.random(&noise);
522527 const sig_with_noise = try kp.signPrehashed(msg_hash, noise);
523528 try sig_with_noise.verifyPrehashed(msg_hash, kp.public_key);
524529
......@@ -1628,8 +1633,9 @@ fn tvTry(comptime Scheme: type, vector: TestVector) !void {
16281633test "Sec1 encoding/decoding" {
16291634 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
16301635
1636 const io = testing.io;
16311637 const Scheme = EcdsaP384Sha384;
1632 const kp = Scheme.KeyPair.generate();
1638 const kp = Scheme.KeyPair.generate(io);
16331639 const pk = kp.public_key;
16341640 const pk_compressed_sec1 = pk.toCompressedSec1();
16351641 const pk_recovered1 = try Scheme.PublicKey.fromSec1(&pk_compressed_sec1);
lib/std/crypto/hybrid_kem.zig+74-61
......@@ -174,43 +174,56 @@ pub fn HybridKem(comptime params: Params) type {
174174 return .{ .bytes = buf.* };
175175 }
176176
177 /// Generates a shared secret and encapsulates it for the public key.
178 /// If `seed` is `null`, uses random bytes from `std.crypto.random`.
179 /// If `seed` is set, encapsulation is deterministic (for testing only).
180 pub fn encaps(self: PublicKey, seed: ?[]const u8) !EncapsulatedSecret {
181 const pq_nek = params.PqKem.PublicKey.encoded_length;
182 const ek_pq = try params.PqKem.PublicKey.fromBytes(self.bytes[0..pq_nek]);
183 const ek_t = self.bytes[pq_nek..][0..params.Group.element_length];
184
177 /// Generates a shared secret, encapsulated for the public key,
178 /// using random bytes.
179 ///
180 /// This is recommended over `encapsDeterministic`.
181 pub fn encaps(pk: PublicKey, io: std.Io) !EncapsulatedSecret {
185182 var seed_pq: [32]u8 = undefined;
186 var seed_t_expanded: [params.Group.seed_length]u8 = undefined;
183 io.random(&seed_pq);
184 var seed_t: [32]u8 = undefined;
185 io.random(&seed_t);
186 var seed_t_expanded: [params.Group.seed_length]u8 = try expandRandomnessSeed(seed_t);
187 return encapsInner(pk, &seed_pq, &seed_t_expanded);
188 }
187189
188 if (seed) |r| {
189 if (r.len < 32) return error.InsufficientRandomness;
190 seed_pq = r[0..32].*;
190 /// Generates a shared secret, encapsulated for the public key,
191 /// using the provided seed.
192 ///
193 /// Calling `encaps` instead is recommended.
194 pub fn encapsDeterministic(pk: PublicKey, seed: []const u8) !EncapsulatedSecret {
195 if (seed.len < 32) return error.InsufficientRandomness;
196 var seed_pq: [32]u8 = seed[0..32].*;
197 var seed_t_expanded: [params.Group.seed_length]u8 = undefined;
191198
192 const t_randomness = r[32..];
199 const t_randomness = seed[32..];
200 if (t_randomness.len < params.Group.seed_length) {
201 // Provided randomness is shorter than seed_length, use it directly
202 // (test vectors provide just enough for randomScalar)
203 @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness);
204 // Pad the rest with zeros if needed (shouldn't be used by randomScalar)
193205 if (t_randomness.len < params.Group.seed_length) {
194 // Provided randomness is shorter than seed_length, use it directly
195 // (test vectors provide just enough for randomScalar)
196 @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness);
197 // Pad the rest with zeros if needed (shouldn't be used by randomScalar)
198 if (t_randomness.len < params.Group.seed_length) {
199 @memset(seed_t_expanded[t_randomness.len..], 0);
200 }
201 } else {
202 // Full randomness provided
203 @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]);
206 @memset(seed_t_expanded[t_randomness.len..], 0);
204207 }
205208 } else {
206 crypto.random.bytes(&seed_pq);
207 var seed_t: [32]u8 = undefined;
208 crypto.random.bytes(&seed_t);
209 seed_t_expanded = try expandRandomnessSeed(seed_t);
209 // Full randomness provided
210 @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]);
210211 }
211212
212 const pq_encap = ek_pq.encaps(seed_pq);
213 const sk_e = try params.Group.randomScalar(&seed_t_expanded);
213 return encapsInner(pk, &seed_pq, &seed_t_expanded);
214 }
215
216 fn encapsInner(
217 pk: PublicKey,
218 seed_pq: *[32]u8,
219 seed_t_expanded: *[params.Group.seed_length]u8,
220 ) !EncapsulatedSecret {
221 const pq_nek = params.PqKem.PublicKey.encoded_length;
222 const ek_pq = try params.PqKem.PublicKey.fromBytes(pk.bytes[0..pq_nek]);
223 const ek_t = pk.bytes[pq_nek..][0..params.Group.element_length];
224
225 const pq_encap = ek_pq.encapsDeterministic(seed_pq);
226 const sk_e = try params.Group.randomScalar(seed_t_expanded);
214227 const ct_t_point = try params.Group.mulBase(sk_e);
215228 const ct_t = if (is_nist_curve) params.Group.encodePoint(ct_t_point) else ct_t_point;
216229
......@@ -280,9 +293,9 @@ pub fn HybridKem(comptime params: Params) type {
280293 }
281294
282295 /// Generates a new random key pair.
283 pub fn generate() !KeyPair {
296 pub fn generate(io: std.Io) !KeyPair {
284297 var seed: [params.Nseed]u8 = undefined;
285 crypto.random.bytes(&seed);
298 io.random(&seed);
286299 return generateDeterministic(seed);
287300 }
288301 };
......@@ -386,7 +399,7 @@ test "MLKEM768-X25519 basic round trip" {
386399 var enc_seed: [64]u8 = undefined;
387400 @memset(&enc_seed, 0x43);
388401
389 const encap_result = try kp.public_key.encaps(&enc_seed);
402 const encap_result = try kp.public_key.encapsDeterministic(&enc_seed);
390403 const ss_decap = try kp.secret_key.decaps(&encap_result.ciphertext);
391404
392405 try testing.expectEqualSlices(u8, &encap_result.shared_secret, &ss_decap);
......@@ -408,7 +421,7 @@ test "MLKEM768-X25519 test vector 0" {
408421 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
409422 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
410423
411 const enc_result = try kp.public_key.encaps(&randomness);
424 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
412425 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
413426 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
414427
......@@ -432,7 +445,7 @@ test "MLKEM768-X25519 test vector 1" {
432445 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
433446 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
434447
435 const enc_result = try kp.public_key.encaps(&randomness);
448 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
436449 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
437450 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
438451
......@@ -456,7 +469,7 @@ test "MLKEM768-X25519 test vector 2" {
456469 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
457470 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
458471
459 const enc_result = try kp.public_key.encaps(&randomness);
472 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
460473 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
461474 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
462475
......@@ -480,7 +493,7 @@ test "MLKEM768-X25519 test vector 3" {
480493 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
481494 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
482495
483 const enc_result = try kp.public_key.encaps(&randomness);
496 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
484497 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
485498 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
486499
......@@ -504,7 +517,7 @@ test "MLKEM768-X25519 test vector 4" {
504517 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
505518 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
506519
507 const enc_result = try kp.public_key.encaps(&randomness);
520 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
508521 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
509522 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
510523
......@@ -528,7 +541,7 @@ test "MLKEM768-X25519 test vector 5" {
528541 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
529542 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
530543
531 const enc_result = try kp.public_key.encaps(&randomness);
544 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
532545 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
533546 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
534547
......@@ -552,7 +565,7 @@ test "MLKEM768-X25519 test vector 6" {
552565 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
553566 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
554567
555 const enc_result = try kp.public_key.encaps(&randomness);
568 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
556569 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
557570 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
558571
......@@ -576,7 +589,7 @@ test "MLKEM768-X25519 test vector 7" {
576589 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
577590 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
578591
579 const enc_result = try kp.public_key.encaps(&randomness);
592 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
580593 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
581594 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
582595
......@@ -600,7 +613,7 @@ test "MLKEM768-X25519 test vector 8" {
600613 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
601614 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
602615
603 const enc_result = try kp.public_key.encaps(&randomness);
616 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
604617 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
605618 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
606619
......@@ -624,7 +637,7 @@ test "MLKEM768-X25519 test vector 9" {
624637 const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
625638 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
626639
627 const enc_result = try kp.public_key.encaps(&randomness);
640 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
628641 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
629642 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
630643
......@@ -648,7 +661,7 @@ test "MLKEM768-P256 test vector 0" {
648661 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
649662 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
650663
651 const enc_result = try kp.public_key.encaps(&randomness);
664 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
652665 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
653666 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
654667
......@@ -672,7 +685,7 @@ test "MLKEM768-P256 test vector 1" {
672685 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
673686 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
674687
675 const enc_result = try kp.public_key.encaps(&randomness);
688 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
676689 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
677690 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
678691
......@@ -696,7 +709,7 @@ test "MLKEM768-P256 test vector 2" {
696709 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
697710 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
698711
699 const enc_result = try kp.public_key.encaps(&randomness);
712 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
700713 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
701714 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
702715
......@@ -720,7 +733,7 @@ test "MLKEM768-P256 test vector 3" {
720733 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
721734 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
722735
723 const enc_result = try kp.public_key.encaps(&randomness);
736 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
724737 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
725738 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
726739
......@@ -744,7 +757,7 @@ test "MLKEM768-P256 test vector 4" {
744757 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
745758 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
746759
747 const enc_result = try kp.public_key.encaps(&randomness);
760 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
748761 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
749762 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
750763
......@@ -768,7 +781,7 @@ test "MLKEM768-P256 test vector 5" {
768781 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
769782 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
770783
771 const enc_result = try kp.public_key.encaps(&randomness);
784 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
772785 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
773786 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
774787
......@@ -792,7 +805,7 @@ test "MLKEM768-P256 test vector 6" {
792805 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
793806 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
794807
795 const enc_result = try kp.public_key.encaps(&randomness);
808 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
796809 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
797810 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
798811
......@@ -816,7 +829,7 @@ test "MLKEM768-P256 test vector 7" {
816829 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
817830 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
818831
819 const enc_result = try kp.public_key.encaps(&randomness);
832 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
820833 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
821834 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
822835
......@@ -840,7 +853,7 @@ test "MLKEM768-P256 test vector 8" {
840853 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
841854 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
842855
843 const enc_result = try kp.public_key.encaps(&randomness);
856 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
844857 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
845858 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
846859
......@@ -864,7 +877,7 @@ test "MLKEM768-P256 test vector 9" {
864877 const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
865878 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
866879
867 const enc_result = try kp.public_key.encaps(&randomness);
880 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
868881 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
869882 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
870883
......@@ -888,7 +901,7 @@ test "MLKEM1024-P384 test vector 0" {
888901 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
889902 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
890903
891 const enc_result = try kp.public_key.encaps(&randomness);
904 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
892905 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
893906 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
894907
......@@ -912,7 +925,7 @@ test "MLKEM1024-P384 test vector 1" {
912925 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
913926 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
914927
915 const enc_result = try kp.public_key.encaps(&randomness);
928 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
916929 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
917930 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
918931
......@@ -936,7 +949,7 @@ test "MLKEM1024-P384 test vector 2" {
936949 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
937950 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
938951
939 const enc_result = try kp.public_key.encaps(&randomness);
952 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
940953 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
941954 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
942955
......@@ -960,7 +973,7 @@ test "MLKEM1024-P384 test vector 3" {
960973 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
961974 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
962975
963 const enc_result = try kp.public_key.encaps(&randomness);
976 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
964977 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
965978 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
966979
......@@ -984,7 +997,7 @@ test "MLKEM1024-P384 test vector 4" {
984997 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
985998 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
986999
987 const enc_result = try kp.public_key.encaps(&randomness);
1000 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
9881001 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
9891002 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
9901003
......@@ -1008,7 +1021,7 @@ test "MLKEM1024-P384 test vector 5" {
10081021 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
10091022 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
10101023
1011 const enc_result = try kp.public_key.encaps(&randomness);
1024 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
10121025 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
10131026 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
10141027
......@@ -1032,7 +1045,7 @@ test "MLKEM1024-P384 test vector 6" {
10321045 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
10331046 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
10341047
1035 const enc_result = try kp.public_key.encaps(&randomness);
1048 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
10361049 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
10371050 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
10381051
......@@ -1056,7 +1069,7 @@ test "MLKEM1024-P384 test vector 7" {
10561069 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
10571070 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
10581071
1059 const enc_result = try kp.public_key.encaps(&randomness);
1072 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
10601073 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
10611074 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
10621075
......@@ -1080,7 +1093,7 @@ test "MLKEM1024-P384 test vector 8" {
10801093 const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
10811094 try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
10821095
1083 const enc_result = try kp.public_key.encaps(&randomness);
1096 const enc_result = try kp.public_key.encapsDeterministic(&randomness);
10841097 try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
10851098 try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
10861099
lib/std/crypto/ml_dsa.zig+10-9
......@@ -2019,12 +2019,9 @@ fn MLDSAImpl(comptime p: Params) type {
20192019 secret_key: SecretKey,
20202020
20212021 /// Generate a new random key pair.
2022 /// This uses the system's cryptographically secure random number generator.
2023 ///
2024 /// `crypto.random.bytes` must be supported by the target.
2025 pub fn generate() KeyPair {
2022 pub fn generate(io: std.Io) KeyPair {
20262023 var seed: [Self.seed_length]u8 = undefined;
2027 crypto.random.bytes(&seed);
2024 io.random(&seed);
20282025 return generateDeterministic(seed) catch unreachable;
20292026 }
20302027
......@@ -3198,8 +3195,9 @@ test "ML-DSA-87 KAT test vector 0" {
31983195}
31993196
32003197test "KeyPair API - generate and sign" {
3198 const io = std.testing.io;
32013199 // Test the new KeyPair API with random generation
3202 const kp = MLDSA44.KeyPair.generate();
3200 const kp = MLDSA44.KeyPair.generate(io);
32033201 const msg = "Test message for KeyPair API";
32043202
32053203 // Sign with deterministic mode (no noise)
......@@ -3222,8 +3220,9 @@ test "KeyPair API - generateDeterministic" {
32223220}
32233221
32243222test "KeyPair API - fromSecretKey" {
3223 const io = std.testing.io;
32253224 // Generate a key pair
3226 const kp1 = MLDSA44.KeyPair.generate();
3225 const kp1 = MLDSA44.KeyPair.generate(io);
32273226
32283227 // Derive public key from secret key
32293228 const kp2 = try MLDSA44.KeyPair.fromSecretKey(kp1.secret_key);
......@@ -3235,8 +3234,9 @@ test "KeyPair API - fromSecretKey" {
32353234}
32363235
32373236test "Signature verification with noise" {
3237 const io = std.testing.io;
32383238 // Test signing with randomness (hedged signatures)
3239 const kp = MLDSA65.KeyPair.generate();
3239 const kp = MLDSA65.KeyPair.generate(io);
32403240 const msg = "Message to be signed with randomness";
32413241
32423242 // Create some noise
......@@ -3250,8 +3250,9 @@ test "Signature verification with noise" {
32503250}
32513251
32523252test "Signature verification failure" {
3253 const io = std.testing.io;
32533254 // Test that invalid signatures are rejected
3254 const kp = MLDSA44.KeyPair.generate();
3255 const kp = MLDSA44.KeyPair.generate(io);
32553256 const msg = "Original message";
32563257 const sig = try kp.sign(msg, null);
32573258
lib/std/crypto/ml_kem.zig+29-20
......@@ -244,32 +244,41 @@ fn Kyber(comptime p: Params) type {
244244 /// Size of a serialized representation of the key, in bytes.
245245 pub const encoded_length = InnerPk.encoded_length;
246246
247 /// Generates a shared secret, and encapsulates it for the public key.
248 /// If `seed` is `null`, a random seed is used. This is recommended.
249 /// If `seed` is set, encapsulation is deterministic.
250 pub fn encaps(pk: PublicKey, seed_: ?[encaps_seed_length]u8) EncapsulatedSecret {
247 /// Generates a shared secret, encapsulated for the public key,
248 /// using random bytes.
249 ///
250 /// This is recommended over `encapsDeterministic`.
251 pub fn encaps(pk: PublicKey, io: std.Io) EncapsulatedSecret {
251252 var m: [inner_plaintext_length]u8 = undefined;
253 io.random(&m);
254 return encapsInner(pk, &m);
255 }
252256
253 if (seed_) |seed| {
254 if (p.ml_kem) {
255 @memcpy(&m, &seed);
256 } else {
257 // m = H(seed)
258 sha3.Sha3_256.hash(&seed, &m, .{});
259 }
257 /// Generates a shared secret, encapsulated for the public key,
258 /// using the provided seed.
259 ///
260 /// Calling `encaps` instead is recommended.
261 pub fn encapsDeterministic(pk: PublicKey, seed: *const [encaps_seed_length]u8) EncapsulatedSecret {
262 var m: [inner_plaintext_length]u8 = undefined;
263 if (p.ml_kem) {
264 @memcpy(&m, seed);
260265 } else {
261 crypto.random.bytes(&m);
266 // m = H(seed)
267 sha3.Sha3_256.hash(seed, &m, .{});
262268 }
269 return encapsInner(pk, &m);
270 }
263271
272 fn encapsInner(pk: PublicKey, m: *[inner_plaintext_length]u8) EncapsulatedSecret {
264273 // (K', r) = G(m ‖ H(pk))
265274 var kr: [inner_plaintext_length + h_length]u8 = undefined;
266275 var g = sha3.Sha3_512.init(.{});
267 g.update(&m);
276 g.update(m);
268277 g.update(&pk.hpk);
269278 g.final(&kr);
270279
271280 // c = innerEncrypt(pk, m, r)
272 const ct = pk.pk.encrypt(&m, kr[32..64]);
281 const ct = pk.pk.encrypt(m, kr[32..64]);
273282
274283 if (p.ml_kem) {
275284 return EncapsulatedSecret{
......@@ -398,10 +407,10 @@ fn Kyber(comptime p: Params) type {
398407 }
399408
400409 /// Generate a new, random key pair.
401 pub fn generate() KeyPair {
410 pub fn generate(io: std.Io) KeyPair {
402411 var random_seed: [seed_length]u8 = undefined;
403412 while (true) {
404 crypto.random.bytes(&random_seed);
413 io.random(&random_seed);
405414 return generateDeterministic(random_seed) catch {
406415 @branchHint(.unlikely);
407416 continue;
......@@ -1634,15 +1643,15 @@ test "Test happy flow" {
16341643 }
16351644 inline for (modes) |mode| {
16361645 for (0..10) |i| {
1637 seed[0] = @as(u8, @intCast(i));
1646 seed[0] = @intCast(i);
16381647 const kp = try mode.KeyPair.generateDeterministic(seed);
16391648 const sk = try mode.SecretKey.fromBytes(&kp.secret_key.toBytes());
16401649 try testing.expectEqual(sk, kp.secret_key);
16411650 const pk = try mode.PublicKey.fromBytes(&kp.public_key.toBytes());
16421651 try testing.expectEqual(pk, kp.public_key);
16431652 for (0..10) |j| {
1644 seed[1] = @as(u8, @intCast(j));
1645 const e = pk.encaps(seed[0..32].*);
1653 seed[1] = @intCast(j);
1654 const e = pk.encapsDeterministic(seed[0..32]);
16461655 try testing.expectEqual(e.shared_secret, try sk.decaps(&e.ciphertext));
16471656 }
16481657 }
......@@ -1695,7 +1704,7 @@ fn testNistKat(mode: type, hash: []const u8) !void {
16951704 g2.fill(kseed[32..64]);
16961705 g2.fill(&eseed);
16971706 const kp = try mode.KeyPair.generateDeterministic(kseed);
1698 const e = kp.public_key.encaps(eseed);
1707 const e = kp.public_key.encapsDeterministic(&eseed);
16991708 const ss2 = try kp.secret_key.decaps(&e.ciphertext);
17001709 try testing.expectEqual(ss2, e.shared_secret);
17011710 try fw.writer.print("pk = {X}\n", .{&kp.public_key.toBytes()});
lib/std/crypto/pcurves/p256.zig+2-2
......@@ -122,8 +122,8 @@ pub const P256 = struct {
122122 }
123123
124124 /// Return a random point.
125 pub fn random() P256 {
126 const n = scalar.random(.little);
125 pub fn random(io: std.Io) P256 {
126 const n = scalar.random(io, .little);
127127 return basePoint.mul(n, .little) catch unreachable;
128128 }
129129
lib/std/crypto/pcurves/p256/scalar.zig+4-4
......@@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
6868}
6969
7070/// Return a random scalar
71pub fn random(endian: std.builtin.Endian) CompressedScalar {
72 return Scalar.random().toBytes(endian);
71pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
72 return Scalar.random(io).toBytes(endian);
7373}
7474
7575/// A scalar in unpacked representation.
......@@ -170,10 +170,10 @@ pub const Scalar = struct {
170170 }
171171
172172 /// Return a random scalar < L.
173 pub fn random() Scalar {
173 pub fn random(io: std.Io) Scalar {
174174 var s: [48]u8 = undefined;
175175 while (true) {
176 crypto.random.bytes(&s);
176 io.random(&s);
177177 const n = Scalar.fromBytes48(s, .little);
178178 if (!n.isZero()) {
179179 return n;
lib/std/crypto/pcurves/p384.zig+2-2
......@@ -122,8 +122,8 @@ pub const P384 = struct {
122122 }
123123
124124 /// Return a random point.
125 pub fn random() P384 {
126 const n = scalar.random(.little);
125 pub fn random(io: std.Io) P384 {
126 const n = scalar.random(io, .little);
127127 return basePoint.mul(n, .little) catch unreachable;
128128 }
129129
lib/std/crypto/pcurves/p384/scalar.zig+4-4
......@@ -63,8 +63,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
6363}
6464
6565/// Return a random scalar
66pub fn random(endian: std.builtin.Endian) CompressedScalar {
67 return Scalar.random().toBytes(endian);
66pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
67 return Scalar.random(io).toBytes(endian);
6868}
6969
7070/// A scalar in unpacked representation.
......@@ -159,10 +159,10 @@ pub const Scalar = struct {
159159 }
160160
161161 /// Return a random scalar < L.
162 pub fn random() Scalar {
162 pub fn random(io: std.Io) Scalar {
163163 var s: [64]u8 = undefined;
164164 while (true) {
165 crypto.random.bytes(&s);
165 io.random(&s);
166166 const n = Scalar.fromBytes64(s, .little);
167167 if (!n.isZero()) {
168168 return n;
lib/std/crypto/pcurves/secp256k1.zig+2-2
......@@ -175,8 +175,8 @@ pub const Secp256k1 = struct {
175175 }
176176
177177 /// Return a random point.
178 pub fn random() Secp256k1 {
179 const n = scalar.random(.little);
178 pub fn random(io: std.Io) Secp256k1 {
179 const n = scalar.random(io, .little);
180180 return basePoint.mul(n, .little) catch unreachable;
181181 }
182182
lib/std/crypto/pcurves/secp256k1/scalar.zig+4-4
......@@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
6868}
6969
7070/// Return a random scalar
71pub fn random(endian: std.builtin.Endian) CompressedScalar {
72 return Scalar.random().toBytes(endian);
71pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
72 return Scalar.random(io).toBytes(endian);
7373}
7474
7575/// A scalar in unpacked representation.
......@@ -170,10 +170,10 @@ pub const Scalar = struct {
170170 }
171171
172172 /// Return a random scalar < L.
173 pub fn random() Scalar {
173 pub fn random(io: std.Io) Scalar {
174174 var s: [48]u8 = undefined;
175175 while (true) {
176 crypto.random.bytes(&s);
176 io.random(&s);
177177 const n = Scalar.fromBytes48(s, .little);
178178 if (!n.isZero()) {
179179 return n;
lib/std/crypto/pcurves/tests/p256.zig+11-6
......@@ -5,8 +5,9 @@ const testing = std.testing;
55const P256 = @import("../p256.zig").P256;
66
77test "p256 ECDH key exchange" {
8 const dha = P256.scalar.random(.little);
9 const dhb = P256.scalar.random(.little);
8 const io = testing.io;
9 const dha = P256.scalar.random(io, .little);
10 const dhb = P256.scalar.random(io, .little);
1011 const dhA = try P256.basePoint.mul(dha, .little);
1112 const dhB = try P256.basePoint.mul(dhb, .little);
1213 const shareda = try dhA.mul(dhb, .little);
......@@ -66,28 +67,32 @@ test "p256 test vectors - doubling" {
6667}
6768
6869test "p256 compressed sec1 encoding/decoding" {
69 const p = P256.random();
70 const io = testing.io;
71 const p = P256.random(io);
7072 const s = p.toCompressedSec1();
7173 const q = try P256.fromSec1(&s);
7274 try testing.expect(p.equivalent(q));
7375}
7476
7577test "p256 uncompressed sec1 encoding/decoding" {
76 const p = P256.random();
78 const io = testing.io;
79 const p = P256.random(io);
7780 const s = p.toUncompressedSec1();
7881 const q = try P256.fromSec1(&s);
7982 try testing.expect(p.equivalent(q));
8083}
8184
8285test "p256 public key is the neutral element" {
86 const io = testing.io;
8387 const n = P256.scalar.Scalar.zero.toBytes(.little);
84 const p = P256.random();
88 const p = P256.random(io);
8589 try testing.expectError(error.IdentityElement, p.mul(n, .little));
8690}
8791
8892test "p256 public key is the neutral element (public verification)" {
93 const io = testing.io;
8994 const n = P256.scalar.Scalar.zero.toBytes(.little);
90 const p = P256.random();
95 const p = P256.random(io);
9196 try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
9297}
9398
lib/std/crypto/pcurves/tests/p384.zig+11-6
......@@ -5,8 +5,9 @@ const testing = std.testing;
55const P384 = @import("../p384.zig").P384;
66
77test "p384 ECDH key exchange" {
8 const dha = P384.scalar.random(.little);
9 const dhb = P384.scalar.random(.little);
8 const io = testing.io;
9 const dha = P384.scalar.random(io, .little);
10 const dhb = P384.scalar.random(io, .little);
1011 const dhA = try P384.basePoint.mul(dha, .little);
1112 const dhB = try P384.basePoint.mul(dhb, .little);
1213 const shareda = try dhA.mul(dhb, .little);
......@@ -67,7 +68,8 @@ test "p384 test vectors - doubling" {
6768}
6869
6970test "p384 compressed sec1 encoding/decoding" {
70 const p = P384.random();
71 const io = testing.io;
72 const p = P384.random(io);
7173 const s0 = p.toUncompressedSec1();
7274 const s = p.toCompressedSec1();
7375 try testing.expectEqualSlices(u8, s0[1..49], s[1..49]);
......@@ -76,21 +78,24 @@ test "p384 compressed sec1 encoding/decoding" {
7678}
7779
7880test "p384 uncompressed sec1 encoding/decoding" {
79 const p = P384.random();
81 const io = testing.io;
82 const p = P384.random(io);
8083 const s = p.toUncompressedSec1();
8184 const q = try P384.fromSec1(&s);
8285 try testing.expect(p.equivalent(q));
8386}
8487
8588test "p384 public key is the neutral element" {
89 const io = testing.io;
8690 const n = P384.scalar.Scalar.zero.toBytes(.little);
87 const p = P384.random();
91 const p = P384.random(io);
8892 try testing.expectError(error.IdentityElement, p.mul(n, .little));
8993}
9094
9195test "p384 public key is the neutral element (public verification)" {
96 const io = testing.io;
9297 const n = P384.scalar.Scalar.zero.toBytes(.little);
93 const p = P384.random();
98 const p = P384.random(io);
9499 try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
95100}
96101
lib/std/crypto/pcurves/tests/secp256k1.zig+14-8
......@@ -5,8 +5,9 @@ const testing = std.testing;
55const Secp256k1 = @import("../secp256k1.zig").Secp256k1;
66
77test "secp256k1 ECDH key exchange" {
8 const dha = Secp256k1.scalar.random(.little);
9 const dhb = Secp256k1.scalar.random(.little);
8 const io = testing.io;
9 const dha = Secp256k1.scalar.random(io, .little);
10 const dhb = Secp256k1.scalar.random(io, .little);
1011 const dhA = try Secp256k1.basePoint.mul(dha, .little);
1112 const dhB = try Secp256k1.basePoint.mul(dhb, .little);
1213 const shareda = try dhA.mul(dhb, .little);
......@@ -15,8 +16,9 @@ test "secp256k1 ECDH key exchange" {
1516}
1617
1718test "secp256k1 ECDH key exchange including public multiplication" {
18 const dha = Secp256k1.scalar.random(.little);
19 const dhb = Secp256k1.scalar.random(.little);
19 const io = testing.io;
20 const dha = Secp256k1.scalar.random(io, .little);
21 const dhb = Secp256k1.scalar.random(io, .little);
2022 const dhA = try Secp256k1.basePoint.mul(dha, .little);
2123 const dhB = try Secp256k1.basePoint.mulPublic(dhb, .little);
2224 const shareda = try dhA.mul(dhb, .little);
......@@ -77,28 +79,32 @@ test "secp256k1 test vectors - doubling" {
7779}
7880
7981test "secp256k1 compressed sec1 encoding/decoding" {
80 const p = Secp256k1.random();
82 const io = testing.io;
83 const p = Secp256k1.random(io);
8184 const s = p.toCompressedSec1();
8285 const q = try Secp256k1.fromSec1(&s);
8386 try testing.expect(p.equivalent(q));
8487}
8588
8689test "secp256k1 uncompressed sec1 encoding/decoding" {
87 const p = Secp256k1.random();
90 const io = testing.io;
91 const p = Secp256k1.random(io);
8892 const s = p.toUncompressedSec1();
8993 const q = try Secp256k1.fromSec1(&s);
9094 try testing.expect(p.equivalent(q));
9195}
9296
9397test "secp256k1 public key is the neutral element" {
98 const io = testing.io;
9499 const n = Secp256k1.scalar.Scalar.zero.toBytes(.little);
95 const p = Secp256k1.random();
100 const p = Secp256k1.random(io);
96101 try testing.expectError(error.IdentityElement, p.mul(n, .little));
97102}
98103
99104test "secp256k1 public key is the neutral element (public verification)" {
105 const io = testing.io;
100106 const n = Secp256k1.scalar.Scalar.zero.toBytes(.little);
101 const p = Secp256k1.random();
107 const p = Secp256k1.random(io);
102108 try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
103109}
104110
lib/std/crypto/salsa20.zig+19-15
......@@ -533,9 +533,9 @@ pub const SealedBox = struct {
533533
534534 /// Encrypt a message `m` for a recipient whose public key is `public_key`.
535535 /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
536 pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
536 pub fn seal(io: std.Io, c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
537537 debug.assert(c.len == m.len + seal_length);
538 var ekp = KeyPair.generate();
538 var ekp = KeyPair.generate(io);
539539 const nonce = createNonce(ekp.public_key, public_key);
540540 c[0..public_length].* = ekp.public_key;
541541 try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);
......@@ -573,29 +573,31 @@ test "(x)salsa20" {
573573}
574574
575575test "xsalsa20poly1305" {
576 const io = std.testing.io;
576577 var msg: [100]u8 = undefined;
577578 var msg2: [msg.len]u8 = undefined;
578579 var c: [msg.len]u8 = undefined;
579580 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
580581 var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
581582 var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
582 crypto.random.bytes(&msg);
583 crypto.random.bytes(&key);
584 crypto.random.bytes(&nonce);
583 io.random(&msg);
584 io.random(&key);
585 io.random(&nonce);
585586
586587 XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
587588 try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
588589}
589590
590591test "xsalsa20poly1305 secretbox" {
592 const io = std.testing.io;
591593 var msg: [100]u8 = undefined;
592594 var msg2: [msg.len]u8 = undefined;
593595 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
594596 var nonce: [Box.nonce_length]u8 = undefined;
595597 var boxed: [msg.len + Box.tag_length]u8 = undefined;
596 crypto.random.bytes(&msg);
597 crypto.random.bytes(&key);
598 crypto.random.bytes(&nonce);
598 io.random(&msg);
599 io.random(&key);
600 io.random(&nonce);
599601
600602 SecretBox.seal(boxed[0..], msg[0..], nonce, key);
601603 try SecretBox.open(msg2[0..], boxed[0..], nonce, key);
......@@ -604,15 +606,16 @@ test "xsalsa20poly1305 secretbox" {
604606test "xsalsa20poly1305 box" {
605607 if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
606608
609 const io = std.testing.io;
607610 var msg: [100]u8 = undefined;
608611 var msg2: [msg.len]u8 = undefined;
609612 var nonce: [Box.nonce_length]u8 = undefined;
610613 var boxed: [msg.len + Box.tag_length]u8 = undefined;
611 crypto.random.bytes(&msg);
612 crypto.random.bytes(&nonce);
614 io.random(&msg);
615 io.random(&nonce);
613616
614 const kp1 = Box.KeyPair.generate();
615 const kp2 = Box.KeyPair.generate();
617 const kp1 = Box.KeyPair.generate(io);
618 const kp2 = Box.KeyPair.generate(io);
616619 try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
617620 try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
618621}
......@@ -620,13 +623,14 @@ test "xsalsa20poly1305 box" {
620623test "xsalsa20poly1305 sealedbox" {
621624 if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
622625
626 const io = std.testing.io;
623627 var msg: [100]u8 = undefined;
624628 var msg2: [msg.len]u8 = undefined;
625629 var boxed: [msg.len + SealedBox.seal_length]u8 = undefined;
626 crypto.random.bytes(&msg);
630 io.random(&msg);
627631
628 const kp = Box.KeyPair.generate();
629 try SealedBox.seal(boxed[0..], msg[0..], kp.public_key);
632 const kp = Box.KeyPair.generate(io);
633 try SealedBox.seal(io, boxed[0..], msg[0..], kp.public_key);
630634 try SealedBox.open(msg2[0..], boxed[0..], kp);
631635}
632636
lib/std/crypto/scrypt.zig+5-6
......@@ -20,7 +20,7 @@ const Error = pwhash.Error;
2020
2121const max_size = math.maxInt(usize);
2222const max_int = max_size >> 1;
23const default_salt_len = 32;
23pub const default_salt_len = 32;
2424const default_hash_len = 32;
2525const max_salt_len = 64;
2626const max_hash_len = 64;
......@@ -417,10 +417,9 @@ const PhcFormatHasher = struct {
417417 password: []const u8,
418418 params: Params,
419419 buf: []u8,
420 /// Filled with cryptographically secure entropy.
421 salt: []const u8,
420422 ) HasherError![]const u8 {
421 var salt: [default_salt_len]u8 = undefined;
422 crypto.random.bytes(&salt);
423
424423 var hash: [default_hash_len]u8 = undefined;
425424 try kdf(allocator, &hash, password, &salt, params);
426425
......@@ -466,9 +465,9 @@ const CryptFormatHasher = struct {
466465 password: []const u8,
467466 params: Params,
468467 buf: []u8,
468 /// Filled with cryptographically secure entropy.
469 salt_bin: []const u8,
469470 ) HasherError![]const u8 {
470 var salt_bin: [default_salt_len]u8 = undefined;
471 crypto.random.bytes(&salt_bin);
472471 const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin);
473472
474473 var hash: [default_hash_len]u8 = undefined;
lib/std/crypto/timing_safe.zig+12-11
......@@ -180,24 +180,24 @@ pub fn declassify(ptr: anytype) void {
180180}
181181
182182test eql {
183 const random = std.crypto.random;
183 const io = std.testing.io;
184184 const expect = std.testing.expect;
185185 var a: [100]u8 = undefined;
186186 var b: [100]u8 = undefined;
187 random.bytes(a[0..]);
188 random.bytes(b[0..]);
187 io.random(&a);
188 io.random(&b);
189189 try expect(!eql([100]u8, a, b));
190190 a = b;
191191 try expect(eql([100]u8, a, b));
192192}
193193
194194test "eql (vectors)" {
195 const random = std.crypto.random;
195 const io = std.testing.io;
196196 const expect = std.testing.expect;
197197 var a: [100]u8 = undefined;
198198 var b: [100]u8 = undefined;
199 random.bytes(a[0..]);
200 random.bytes(b[0..]);
199 io.random(&a);
200 io.random(&b);
201201 const v1: @Vector(100, u8) = a;
202202 const v2: @Vector(100, u8) = b;
203203 try expect(!eql(@Vector(100, u8), v1, v2));
......@@ -220,9 +220,10 @@ test compare {
220220}
221221
222222test "add and sub" {
223 const io = std.testing.io;
224
223225 const expectEqual = std.testing.expectEqual;
224226 const expectEqualSlices = std.testing.expectEqualSlices;
225 const random = std.crypto.random;
226227 const len = 32;
227228 var a: [len]u8 = undefined;
228229 var b: [len]u8 = undefined;
......@@ -230,8 +231,8 @@ test "add and sub" {
230231 const zero = [_]u8{0} ** len;
231232 var iterations: usize = 100;
232233 while (iterations != 0) : (iterations -= 1) {
233 random.bytes(&a);
234 random.bytes(&b);
234 io.random(&a);
235 io.random(&b);
235236 const endian = if (iterations % 2 == 0) Endian.big else Endian.little;
236237 _ = sub(u8, &a, &b, &c, endian); // a-b
237238 _ = add(u8, &c, &b, &c, endian); // (a-b)+b
......@@ -243,11 +244,11 @@ test "add and sub" {
243244}
244245
245246test classify {
246 const random = std.crypto.random;
247 const io = std.testing.io;
247248 const expect = std.testing.expect;
248249
249250 var secret: [32]u8 = undefined;
250 random.bytes(&secret);
251 io.random(&secret);
251252
252253 // Input of the hash function is marked as secret
253254 classify(&secret);
lib/std/crypto/tlcsprng.zig deleted-169
......@@ -1,169 +0,0 @@
1//! Thread-local cryptographically secure pseudo-random number generator.
2//! This file has public declarations that are intended to be used internally
3//! by the standard library; this namespace is not intended to be exposed
4//! directly to standard library users.
5
6const std = @import("std");
7const builtin = @import("builtin");
8const mem = std.mem;
9const native_os = builtin.os.tag;
10const posix = std.posix;
11
12/// We use this as a layer of indirection because global const pointers cannot
13/// point to thread-local variables.
14pub const interface: std.Random = .{
15 .ptr = undefined,
16 .fillFn = tlsCsprngFill,
17};
18
19const os_has_fork = @TypeOf(posix.fork) != void;
20const os_has_arc4random = builtin.link_libc and (@TypeOf(std.c.arc4random_buf) != void);
21const want_fork_safety = os_has_fork and !os_has_arc4random and std.options.crypto_fork_safety;
22const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{
23 .major = 4,
24 .minor = 14,
25 .patch = 0,
26}) orelse true;
27
28const Rng = std.Random.DefaultCsprng;
29
30const Context = struct {
31 init_state: enum(u8) { uninitialized = 0, initialized, failed },
32 rng: Rng,
33};
34
35var install_atfork_handler = std.once(struct {
36 // Install the global handler only once.
37 // The same handler is shared among threads and is inherinted by fork()-ed
38 // processes.
39 fn do() void {
40 const r = std.c.pthread_atfork(null, null, childAtForkHandler);
41 std.debug.assert(r == 0);
42 }
43}.do);
44
45threadlocal var wipe_mem: []align(std.heap.page_size_min) u8 = &[_]u8{};
46
47fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
48 if (os_has_arc4random) {
49 // arc4random is already a thread-local CSPRNG.
50 return std.c.arc4random_buf(buffer.ptr, buffer.len);
51 }
52 // Allow applications to decide they would prefer to have every call to
53 // std.crypto.random always make an OS syscall, rather than rely on an
54 // application implementation of a CSPRNG.
55 if (std.options.crypto_always_getrandom) {
56 return std.options.cryptoRandomSeed(buffer);
57 }
58
59 if (wipe_mem.len == 0) {
60 // Not initialized yet.
61 if (want_fork_safety and maybe_have_wipe_on_fork) {
62 // Allocate a per-process page, madvise operates with page
63 // granularity.
64 wipe_mem = posix.mmap(
65 null,
66 @sizeOf(Context),
67 posix.PROT.READ | posix.PROT.WRITE,
68 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
69 -1,
70 0,
71 ) catch {
72 // Could not allocate memory for the local state, fall back to
73 // the OS syscall.
74 return std.options.cryptoRandomSeed(buffer);
75 };
76 // The memory is already zero-initialized.
77 } else {
78 // Use a static thread-local buffer.
79 const S = struct {
80 threadlocal var buf: Context align(std.heap.page_size_min) = .{
81 .init_state = .uninitialized,
82 .rng = undefined,
83 };
84 };
85 wipe_mem = mem.asBytes(&S.buf);
86 }
87 }
88 const ctx: *Context = @ptrCast(wipe_mem.ptr);
89
90 switch (ctx.init_state) {
91 .uninitialized => {
92 if (!want_fork_safety) {
93 return initAndFill(buffer);
94 }
95
96 if (maybe_have_wipe_on_fork) wof: {
97 // Qemu user-mode emulation ignores any valid/invalid madvise
98 // hint and returns success. Check if this is the case by
99 // passing bogus parameters, we expect EINVAL as result.
100 if (posix.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| {
101 break :wof;
102 } else |_| {}
103
104 if (posix.madvise(wipe_mem.ptr, wipe_mem.len, posix.MADV.WIPEONFORK)) |_| {
105 return initAndFill(buffer);
106 } else |_| {}
107 }
108
109 if (std.Thread.use_pthreads) {
110 return setupPthreadAtforkAndFill(buffer);
111 }
112
113 // Since we failed to set up fork safety, we fall back to always
114 // calling getrandom every time.
115 ctx.init_state = .failed;
116 return std.options.cryptoRandomSeed(buffer);
117 },
118 .initialized => {
119 return fillWithCsprng(buffer);
120 },
121 .failed => {
122 if (want_fork_safety) {
123 return std.options.cryptoRandomSeed(buffer);
124 } else {
125 unreachable;
126 }
127 },
128 }
129}
130
131fn setupPthreadAtforkAndFill(buffer: []u8) void {
132 install_atfork_handler.call();
133 return initAndFill(buffer);
134}
135
136fn childAtForkHandler() callconv(.c) void {
137 // The atfork handler is global, this function may be called after
138 // fork()-ing threads that never initialized the CSPRNG context.
139 if (wipe_mem.len == 0) return;
140 std.crypto.secureZero(u8, wipe_mem);
141}
142
143fn fillWithCsprng(buffer: []u8) void {
144 const ctx: *Context = @ptrCast(wipe_mem.ptr);
145 return ctx.rng.fill(buffer);
146}
147
148pub fn defaultRandomSeed(buffer: []u8) void {
149 posix.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
150}
151
152fn initAndFill(buffer: []u8) void {
153 var seed: [Rng.secret_seed_length]u8 = undefined;
154 // Because we panic on getrandom() failing, we provide the opportunity
155 // to override the default seed function. This also makes
156 // `std.crypto.random` available on freestanding targets, provided that
157 // the `std.options.cryptoRandomSeed` function is provided.
158 std.options.cryptoRandomSeed(&seed);
159
160 const ctx: *Context = @ptrCast(wipe_mem.ptr);
161 ctx.rng = Rng.init(seed);
162 std.crypto.secureZero(u8, &seed);
163
164 // This is at the end so that accidental recursive dependencies result
165 // in stack overflows instead of invalid random data.
166 ctx.init_state = .initialized;
167
168 return fillWithCsprng(buffer);
169}
lib/std/crypto/tls/Client.zig+9-7
......@@ -109,7 +109,7 @@ pub const Options = struct {
109109 read_buffer: []u8,
110110 /// Cryptographically secure random bytes. The pointer is not captured; data is only
111111 /// read during `init`.
112 entropy: *const [176]u8,
112 entropy: *const [entropy_len]u8,
113113 /// Current time according to the wall clock / calendar, in seconds.
114114 realtime_now_seconds: i64,
115115
......@@ -130,6 +130,8 @@ pub const Options = struct {
130130 allow_truncation_attacks: bool = false,
131131 /// Populated when `error.TlsAlert` is returned from `init`.
132132 alert: ?*tls.Alert = null,
133
134 pub const entropy_len = 240;
133135};
134136
135137const InitError = error{
......@@ -200,7 +202,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
200202 var server_hello_rand: [32]u8 = undefined;
201203 const legacy_session_id = options.entropy[32..64].*;
202204
203 var key_share = KeyShare.init(options.entropy[64..176].*) catch |err| switch (err) {
205 var key_share = KeyShare.init(options.entropy[64..240]) catch |err| switch (err) {
204206 // Only possible to happen if the seed is all zeroes.
205207 error.IdentityElement => return error.InsufficientEntropy,
206208 };
......@@ -1330,12 +1332,12 @@ const KeyShare = struct {
13301332 crypto.dh.X25519.shared_length,
13311333 );
13321334
1333 fn init(seed: [112]u8) error{IdentityElement}!KeyShare {
1335 fn init(seed: *const [176]u8) error{IdentityElement}!KeyShare {
13341336 return .{
1335 .ml_kem768_kp = .generate(),
1336 .secp256r1_kp = try .generateDeterministic(seed[0..32].*),
1337 .secp384r1_kp = try .generateDeterministic(seed[32..80].*),
1338 .x25519_kp = try .generateDeterministic(seed[80..112].*),
1337 .ml_kem768_kp = try .generateDeterministic(seed[0..64].*),
1338 .secp256r1_kp = try .generateDeterministic(seed[64..96].*),
1339 .secp384r1_kp = try .generateDeterministic(seed[96..144].*),
1340 .x25519_kp = try .generateDeterministic(seed[144..176].*),
13391341 .sk_buf = undefined,
13401342 .sk_len = 0,
13411343 };
lib/std/fs/test.zig+17-3
......@@ -1022,8 +1022,7 @@ test "Dir.rename directory onto non-empty dir" {
10221022 file.close(io);
10231023 target_dir.close(io);
10241024
1025 // Rename should fail with PathAlreadyExists if target_dir is non-empty
1026 try expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
1025 try expectError(error.DirNotEmpty, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
10271026
10281027 // Ensure the directory was not renamed
10291028 var dir = try ctx.dir.openDir(io, test_dir_path, .{});
......@@ -1651,6 +1650,21 @@ test "AtomicFile" {
16511650 \\ this is a test file
16521651 ;
16531652
1653 // link() succeeds with no file already present
1654 {
1655 var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = false });
1656 defer af.deinit(io);
1657 try af.file.writeStreamingAll(io, test_content);
1658 try af.link(io);
1659 }
1660 // link() returns error.PathAlreadyExists if file already present
1661 {
1662 var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = false });
1663 defer af.deinit(io);
1664 try af.file.writeStreamingAll(io, test_content);
1665 try expectError(error.PathAlreadyExists, af.link(io));
1666 }
1667 // replace() succeeds if file already present
16541668 {
16551669 var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true });
16561670 defer af.deinit(io);
......@@ -1761,7 +1775,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
17611775 const io = testing.io;
17621776
17631777 var random_bytes: [12]u8 = undefined;
1764 std.crypto.random.bytes(&random_bytes);
1778 io.random(&random_bytes);
17651779
17661780 var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
17671781 _ = std.fs.base64_encoder.encode(&random_b64, &random_bytes);
lib/std/http/Client.zig+2-2
......@@ -321,8 +321,8 @@ pub const Connection = struct {
321321 assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
322322 @memcpy(host_buffer, remote_host.bytes);
323323 const tls: *Tls = @ptrCast(base);
324 var random_buffer: [176]u8 = undefined;
325 std.crypto.random.bytes(&random_buffer);
324 var random_buffer: [std.crypto.tls.Client.Options.entropy_len]u8 = undefined;
325 io.random(&random_buffer);
326326 tls.* = .{
327327 .connection = .{
328328 .client = client,
lib/std/os/linux.zig+26-4
......@@ -501,6 +501,15 @@ pub const O = switch (native_arch) {
501501 else => @compileError("missing std.os.linux.O constants for this architecture"),
502502};
503503
504pub const RENAME = packed struct(u32) {
505 /// Cannot be set together with `EXCHANGE`.
506 NOREPLACE: bool = false,
507 /// Cannot be set together with `NOREPLACE`.
508 EXCHANGE: bool = false,
509 WHITEOUT: bool = false,
510 _: u29 = 0,
511};
512
504513/// Set by startup code, used by `getauxval`.
505514pub var elf_aux_maybe: ?[*]std.elf.Auxv = null;
506515
......@@ -1346,9 +1355,22 @@ pub fn rename(old: [*:0]const u8, new: [*:0]const u8) usize {
13461355 if (@hasField(SYS, "rename")) {
13471356 return syscall2(.rename, @intFromPtr(old), @intFromPtr(new));
13481357 } else if (@hasField(SYS, "renameat")) {
1349 return syscall4(.renameat, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new));
1358 return syscall4(
1359 .renameat,
1360 @as(usize, @bitCast(@as(isize, AT.FDCWD))),
1361 @intFromPtr(old),
1362 @as(usize, @bitCast(@as(isize, AT.FDCWD))),
1363 @intFromPtr(new),
1364 );
13501365 } else {
1351 return syscall5(.renameat2, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new), 0);
1366 return syscall5(
1367 .renameat2,
1368 @as(usize, @bitCast(@as(isize, AT.FDCWD))),
1369 @intFromPtr(old),
1370 @as(usize, @bitCast(@as(isize, AT.FDCWD))),
1371 @intFromPtr(new),
1372 0,
1373 );
13521374 }
13531375}
13541376
......@@ -1373,14 +1395,14 @@ pub fn renameat(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]co
13731395 }
13741396}
13751397
1376pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: u32) usize {
1398pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: RENAME) usize {
13771399 return syscall5(
13781400 .renameat2,
13791401 @as(usize, @bitCast(@as(isize, oldfd))),
13801402 @intFromPtr(oldpath),
13811403 @as(usize, @bitCast(@as(isize, newfd))),
13821404 @intFromPtr(newpath),
1383 flags,
1405 @as(u32, @bitCast(flags)),
13841406 );
13851407}
13861408
lib/std/os/linux/tls.zig+5-7
......@@ -531,13 +531,11 @@ pub fn prepareArea(area: []u8) usize {
531531 };
532532}
533533
534/// The main motivation for the size chosen here is that this is how much ends up being requested for
535/// the thread-local variables of the `std.crypto.random` implementation. I'm not sure why it ends up
536/// being so much; the struct itself is only 64 bytes. I think it has to do with being page-aligned
537/// and LLVM or LLD is not smart enough to lay out the TLS data in a space-conserving way. Anyway, I
538/// think it's fine because it's less than 3 pages of memory, and putting it in the ELF like this is
539/// equivalent to moving the `mmap` call below into the kernel, avoiding syscall overhead.
540var main_thread_area_buffer: [0x2100]u8 align(page_size_min) = undefined;
534/// The main motivation for the size chosen here is to be larger than total
535/// amount of thread-local variables for most programs. Putting this allocation
536/// in the ELF like this is equivalent to moving the `mmap` call below into the
537/// kernel, avoiding syscall overhead.
538var main_thread_area_buffer: [0x1000]u8 align(page_size_min) = undefined;
541539
542540/// Computes the layout of the static TLS area, allocates the area, initializes all of its fields,
543541/// and assigns the architecture-specific value to the TP register.
lib/std/os/windows.zig+2-58
......@@ -2647,62 +2647,6 @@ pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInfor
26472647 }
26482648}
26492649
2650/// An alternate implementation of ProcessPrng from bcryptprimitives.dll
2651/// This one has the following differences:
2652/// * does not heap allocate `buffer`
2653/// * does not introduce a dependency on bcryptprimitives.dll, which apparently
2654/// runs a test suite every time it is loaded
2655/// * reads buffer.len bytes from "\\Device\\CNG" rather than seeding a per-CPU
2656/// AES csprng with 48 bytes.
2657pub fn ProcessPrng(buffer: []u8) error{Unexpected}!void {
2658 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
2659 var nt_name: UNICODE_STRING = .{
2660 .Length = device_path.len * 2,
2661 .MaximumLength = 0,
2662 .Buffer = @constCast(&device_path),
2663 };
2664 var cng_device: HANDLE = undefined;
2665 var io_status_block: IO_STATUS_BLOCK = undefined;
2666 switch (ntdll.NtOpenFile(
2667 &cng_device,
2668 .{
2669 .STANDARD = .{ .SYNCHRONIZE = true },
2670 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
2671 },
2672 &.{
2673 .Length = @sizeOf(OBJECT_ATTRIBUTES),
2674 .RootDirectory = null,
2675 .ObjectName = &nt_name,
2676 .Attributes = .{},
2677 .SecurityDescriptor = null,
2678 .SecurityQualityOfService = null,
2679 },
2680 &io_status_block,
2681 .VALID_FLAGS,
2682 .{ .IO = .SYNCHRONOUS_NONALERT },
2683 )) {
2684 .SUCCESS => {},
2685 .OBJECT_NAME_NOT_FOUND => return error.Unexpected, // Observed on wine 10.0
2686 else => |status| return unexpectedStatus(status),
2687 }
2688 defer _ = ntdll.NtClose(cng_device);
2689 switch (ntdll.NtDeviceIoControlFile(
2690 cng_device,
2691 null,
2692 null,
2693 null,
2694 &io_status_block,
2695 IOCTL.KSEC.GEN_RANDOM,
2696 null,
2697 0,
2698 buffer.ptr,
2699 @intCast(buffer.len),
2700 )) {
2701 .SUCCESS => {},
2702 else => |status| return unexpectedStatus(status),
2703 }
2704}
2705
27062650pub const WaitForSingleObjectError = error{
27072651 WaitAbandoned,
27082652 WaitTimeOut,
......@@ -3250,7 +3194,7 @@ pub const RenameError = error{
32503194 NetworkNotFound,
32513195 AntivirusInterference,
32523196 BadPathName,
3253 RenameAcrossMountPoints,
3197 CrossDevice,
32543198} || UnexpectedError;
32553199
32563200pub fn RenameFile(
......@@ -3351,7 +3295,7 @@ pub fn RenameFile(
33513295 .ACCESS_DENIED => return error.AccessDenied,
33523296 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
33533297 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
3354 .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
3298 .NOT_SAME_DEVICE => return error.CrossDevice,
33553299 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
33563300 .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
33573301 .FILE_IS_A_DIRECTORY => return error.IsDir,
lib/std/posix.zig+2-103
......@@ -361,107 +361,6 @@ pub fn reboot(cmd: RebootCommand) RebootError!void {
361361 }
362362}
363363
364pub const GetRandomError = OpenError;
365
366/// Obtain a series of random bytes. These bytes can be used to seed user-space
367/// random number generators or for cryptographic purposes.
368/// When linking against libc, this calls the
369/// appropriate OS-specific library call. Otherwise it uses the zig standard
370/// library implementation.
371pub fn getrandom(buffer: []u8) GetRandomError!void {
372 if (native_os == .windows) {
373 return windows.ProcessPrng(buffer);
374 }
375 if (builtin.link_libc and @TypeOf(system.arc4random_buf) != void) {
376 system.arc4random_buf(buffer.ptr, buffer.len);
377 return;
378 }
379 if (native_os == .wasi) switch (wasi.random_get(buffer.ptr, buffer.len)) {
380 .SUCCESS => return,
381 else => |err| return unexpectedErrno(err),
382 };
383 if (@TypeOf(system.getrandom) != void) {
384 var buf = buffer;
385 const use_c = native_os != .linux or
386 std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 28, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 25, .patch = 0 });
387
388 while (buf.len != 0) {
389 const num_read: usize, const err = if (use_c) res: {
390 const rc = std.c.getrandom(buf.ptr, buf.len, 0);
391 break :res .{ @bitCast(rc), errno(rc) };
392 } else res: {
393 const rc = linux.getrandom(buf.ptr, buf.len, 0);
394 break :res .{ rc, linux.errno(rc) };
395 };
396
397 switch (err) {
398 .SUCCESS => buf = buf[num_read..],
399 .INVAL => unreachable,
400 .FAULT => unreachable,
401 .INTR => continue,
402 else => return unexpectedErrno(err),
403 }
404 }
405 return;
406 }
407 if (native_os == .emscripten) {
408 const err = errno(std.c.getentropy(buffer.ptr, buffer.len));
409 switch (err) {
410 .SUCCESS => return,
411 else => return unexpectedErrno(err),
412 }
413 }
414 return getRandomBytesDevURandom(buffer);
415}
416
417fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {
418 const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
419 defer close(fd);
420
421 switch (native_os) {
422 .linux => {
423 var stx = std.mem.zeroes(linux.Statx);
424 const rc = linux.statx(
425 fd,
426 "",
427 linux.AT.EMPTY_PATH,
428 .{ .TYPE = true },
429 &stx,
430 );
431 switch (errno(rc)) {
432 .SUCCESS => {},
433 .ACCES => unreachable,
434 .BADF => unreachable,
435 .FAULT => unreachable,
436 .INVAL => unreachable,
437 .LOOP => unreachable,
438 .NAMETOOLONG => unreachable,
439 .NOENT => unreachable,
440 .NOMEM => return error.SystemResources,
441 .NOTDIR => unreachable,
442 else => |err| return unexpectedErrno(err),
443 }
444 if (!S.ISCHR(stx.mode)) {
445 return error.NoDevice;
446 }
447 },
448 else => {
449 const st = fstat(fd) catch |err| switch (err) {
450 error.Streaming => return error.NoDevice,
451 else => |e| return e,
452 };
453 if (!S.ISCHR(st.mode)) {
454 return error.NoDevice;
455 }
456 },
457 }
458
459 var i: usize = 0;
460 while (i < buf.len) {
461 i += read(fd, buf[i..]) catch return error.Unexpected;
462 }
463}
464
465364pub const RaiseError = UnexpectedError;
466365
467366pub fn raise(sig: SIG) RaiseError!void {
......@@ -1695,7 +1594,7 @@ pub const FanotifyMarkError = error{
16951594 NotDir,
16961595 OperationUnsupported,
16971596 PermissionDenied,
1698 NotSameFileSystem,
1597 CrossDevice,
16991598 NameTooLong,
17001599} || UnexpectedError;
17011600
......@@ -1735,7 +1634,7 @@ pub fn fanotify_markZ(
17351634 .NOTDIR => return error.NotDir,
17361635 .OPNOTSUPP => return error.OperationUnsupported,
17371636 .PERM => return error.PermissionDenied,
1738 .XDEV => return error.NotSameFileSystem,
1637 .XDEV => return error.CrossDevice,
17391638 else => |err| return unexpectedErrno(err),
17401639 }
17411640}
lib/std/posix/test.zig-10
......@@ -33,16 +33,6 @@ test "check WASI CWD" {
3333 }
3434}
3535
36test "getrandom" {
37 var buf_a: [50]u8 = undefined;
38 var buf_b: [50]u8 = undefined;
39 try posix.getrandom(&buf_a);
40 try posix.getrandom(&buf_b);
41 // If this test fails the chance is significantly higher that there is a bug than
42 // that two sets of 50 bytes were equal.
43 try expect(!mem.eql(u8, &buf_a, &buf_b));
44}
45
4636test "getuid" {
4737 if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
4838 _ = posix.getuid();
lib/std/std.zig-6
......@@ -137,12 +137,6 @@ pub const Options = struct {
137137
138138 fmt_max_depth: usize = fmt.default_max_depth,
139139
140 cryptoRandomSeed: fn (buffer: []u8) void = @import("crypto/tlcsprng.zig").defaultRandomSeed,
141
142 crypto_always_getrandom: bool = false,
143
144 crypto_fork_safety: bool = true,
145
146140 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
147141 /// disable TLS support.
148142 ///
lib/std/testing.zig+1-1
......@@ -631,7 +631,7 @@ pub const TmpDir = struct {
631631pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir {
632632 comptime assert(builtin.is_test);
633633 var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
634 std.crypto.random.bytes(&random_bytes);
634 io.random(&random_bytes);
635635 var sub_path: [TmpDir.sub_path_len]u8 = undefined;
636636 _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
637637
src/Compilation.zig+14-5
......@@ -2942,7 +2942,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
29422942 .none => |none| {
29432943 assert(none.tmp_artifact_directory == null);
29442944 none.tmp_artifact_directory = d: {
2945 tmp_dir_rand_int = std.crypto.random.int(u64);
2945 io.random(@ptrCast(&tmp_dir_rand_int));
29462946 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
29472947 const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
29482948 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
30233023
30243024 // Compile the artifacts to a temporary directory.
30253025 whole.tmp_artifact_directory = d: {
3026 tmp_dir_rand_int = std.crypto.random.int(u64);
3026 io.random(@ptrCast(&tmp_dir_rand_int));
30273027 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
30283028 const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
30293029 const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| {
......@@ -3460,7 +3460,7 @@ fn renameTmpIntoCache(
34603460 },
34613461 else => return error.AccessDenied,
34623462 },
3463 error.PathAlreadyExists => {
3463 error.DirNotEmpty => {
34643464 try cache_directory.handle.deleteTree(io, o_sub_path);
34653465 continue;
34663466 },
......@@ -5759,7 +5759,11 @@ pub fn translateC(
57595759
57605760 const gpa = comp.gpa;
57615761 const io = comp.io;
5762 const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
5762 const tmp_basename = r: {
5763 var x: u64 = undefined;
5764 io.random(@ptrCast(&x));
5765 break :r std.fmt.hex(x);
5766 };
57635767 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
57645768 const cache_dir = comp.dirs.local_cache.handle;
57655769 var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{});
......@@ -6889,8 +6893,13 @@ fn spawnZigRc(
68896893}
68906894
68916895pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
6896 const io = comp.io;
6897 const rand_int = r: {
6898 var x: u64 = undefined;
6899 io.random(@ptrCast(&x));
6900 break :r x;
6901 };
68926902 const s = fs.path.sep_str;
6893 const rand_int = std.crypto.random.int(u64);
68946903 if (comp.dirs.local_cache.path) |p| {
68956904 return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix });
68966905 } else {
src/Package.zig+2-2
......@@ -14,9 +14,9 @@ pub const Fingerprint = packed struct(u64) {
1414 id: u32,
1515 checksum: u32,
1616
17 pub fn generate(name: []const u8) Fingerprint {
17 pub fn generate(rng: std.Random, name: []const u8) Fingerprint {
1818 return .{
19 .id = std.crypto.random.intRangeLessThan(u32, 1, 0xffffffff),
19 .id = rng.intRangeLessThan(u32, 1, 0xffffffff),
2020 .checksum = std.hash.Crc32.hash(name),
2121 };
2222 }
src/Package/Fetch.zig+14-4
......@@ -494,7 +494,11 @@ fn runResource(
494494 const eb = &f.error_bundle;
495495 const s = fs.path.sep_str;
496496 const cache_root = f.job_queue.global_cache;
497 const rand_int = std.crypto.random.int(u64);
497 const rand_int = r: {
498 var x: u64 = undefined;
499 io.random(@ptrCast(&x));
500 break :r x;
501 };
498502 const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(rand_int);
499503
500504 const package_sub_path = blk: {
......@@ -690,7 +694,9 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
690694 return error.FetchFailed;
691695 }
692696
693 f.manifest = try Manifest.parse(arena, ast.*, .{
697 const rng: std.Random.IoSource = .{ .io = io };
698
699 f.manifest = try Manifest.parse(arena, ast.*, rng.interface(), .{
694700 .allow_missing_paths_field = f.allow_missing_paths_field,
695701 .allow_missing_fingerprint = f.allow_missing_fingerprint,
696702 .allow_name_string = f.allow_name_string,
......@@ -1305,7 +1311,11 @@ fn unzip(
13051311 zip_path[prefix.len + random_len ..].* = suffix.*;
13061312
13071313 var zip_file = while (true) {
1308 const random_integer = std.crypto.random.int(u64);
1314 const random_integer = r: {
1315 var x: u64 = undefined;
1316 io.random(@ptrCast(&x));
1317 break :r x;
1318 };
13091319 zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer);
13101320
13111321 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
14661476 };
14671477 continue;
14681478 },
1469 error.PathAlreadyExists, error.AccessDenied => {
1479 error.DirNotEmpty, error.AccessDenied => {
14701480 // Package has been already downloaded and may already be in use on the system.
14711481 cache_dir.deleteTree(io, tmp_dir_sub_path) catch {
14721482 // Garbage files leftover in zig-cache/tmp/ is, as they say
src/Package/Manifest.zig+14-8
......@@ -57,7 +57,7 @@ pub const ParseOptions = struct {
5757
5858pub const Error = Allocator.Error;
5959
60pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest {
60pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) Error!Manifest {
6161 const main_node_index = ast.nodeData(.root).node;
6262
6363 var arena_instance = std.heap.ArenaAllocator.init(gpa);
......@@ -87,7 +87,7 @@ pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest {
8787 defer p.dependencies.deinit(gpa);
8888 defer p.paths.deinit(gpa);
8989
90 p.parseRoot(main_node_index) catch |err| switch (err) {
90 p.parseRoot(main_node_index, rng) catch |err| switch (err) {
9191 error.ParseFailure => assert(p.errors.items.len > 0),
9292 else => |e| return e,
9393 };
......@@ -157,7 +157,7 @@ const Parse = struct {
157157
158158 const InnerError = error{ ParseFailure, OutOfMemory };
159159
160 fn parseRoot(p: *Parse, node: Ast.Node.Index) !void {
160 fn parseRoot(p: *Parse, node: Ast.Node.Index, rng: std.Random) !void {
161161 const ast = p.ast;
162162 const main_token = ast.nodeMainToken(node);
163163
......@@ -217,13 +217,13 @@ const Parse = struct {
217217 if (fingerprint) |n| {
218218 if (!n.validate(p.name)) {
219219 return fail(p, main_token, "invalid fingerprint: 0x{x}; if this is a new or forked package, use this value: 0x{x}", .{
220 n.int(), Package.Fingerprint.generate(p.name).int(),
220 n.int(), Package.Fingerprint.generate(rng, p.name).int(),
221221 });
222222 }
223223 p.id = n.id;
224224 } else if (!p.allow_missing_fingerprint) {
225225 try appendError(p, main_token, "missing top-level 'fingerprint' field; suggested value: 0x{x}", .{
226 Package.Fingerprint.generate(p.name).int(),
226 Package.Fingerprint.generate(rng, p.name).int(),
227227 });
228228 } else {
229229 p.id = 0;
......@@ -623,7 +623,9 @@ test "basic" {
623623
624624 try testing.expect(ast.errors.len == 0);
625625
626 var manifest = try Manifest.parse(gpa, ast, .{});
626 var rng = std.Random.DefaultPrng.init(0);
627
628 var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
627629 defer manifest.deinit(gpa);
628630
629631 try testing.expect(manifest.errors.len == 0);
......@@ -666,7 +668,9 @@ test "minimum_zig_version" {
666668
667669 try testing.expect(ast.errors.len == 0);
668670
669 var manifest = try Manifest.parse(gpa, ast, .{});
671 var rng = std.Random.DefaultPrng.init(0);
672
673 var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
670674 defer manifest.deinit(gpa);
671675
672676 try testing.expect(manifest.errors.len == 0);
......@@ -698,7 +702,9 @@ test "minimum_zig_version - invalid version" {
698702
699703 try testing.expect(ast.errors.len == 0);
700704
701 var manifest = try Manifest.parse(gpa, ast, .{});
705 var rng = std.Random.DefaultPrng.init(0);
706
707 var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
702708 defer manifest.deinit(gpa);
703709
704710 try testing.expect(manifest.errors.len == 1);
src/link.zig+6-1
......@@ -616,8 +616,13 @@ pub const File = struct {
616616 // it will return ETXTBSY. So instead, we copy the file, atomically rename it
617617 // over top of the exe path, and then proceed normally. This changes the inode,
618618 // avoiding the error.
619 const random_integer = r: {
620 var x: u32 = undefined;
621 io.random(@ptrCast(&x));
622 break :r x;
623 };
619624 const tmp_sub_path = try std.fmt.allocPrint(gpa, "{s}-{x}", .{
620 emit.sub_path, std.crypto.random.int(u32),
625 emit.sub_path, random_integer,
621626 });
622627 defer gpa.free(tmp_sub_path);
623628 try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{});
src/link/Lld.zig+5-1
......@@ -1636,7 +1636,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
16361636 const err = switch (first_err) {
16371637 error.NameTooLong => err: {
16381638 const s = fs.path.sep_str;
1639 const rand_int = std.crypto.random.int(u64);
1639 const rand_int = r: {
1640 var x: u64 = undefined;
1641 io.random(@ptrCast(&x));
1642 break :r x;
1643 };
16401644 const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";
16411645
16421646 const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{});
src/main.zig+19-12
......@@ -3395,7 +3395,7 @@ fn buildOutputType(
33953395 // "-" is stdin. Dump it to a real file.
33963396 const sep = fs.path.sep_str;
33973397 const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{
3398 std.crypto.random.int(u64), ext.canonicalName(target),
3398 randInt(io, u64), ext.canonicalName(target),
33993399 });
34003400 try dirs.local_cache.handle.createDirPath(io, "tmp");
34013401
......@@ -4433,7 +4433,7 @@ fn runOrTest(
44334433 try argv.append(exe_path);
44344434 if (arg_mode == .zig_test) {
44354435 try argv.append(
4436 try std.fmt.allocPrint(arena, "--seed=0x{x}", .{std.crypto.random.int(u32)}),
4436 try std.fmt.allocPrint(arena, "--seed=0x{x}", .{randInt(io, u32)}),
44374437 );
44384438 }
44394439 } else {
......@@ -4763,7 +4763,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
47634763 const cwd_basename = fs.path.basename(cwd_path);
47644764 const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename);
47654765
4766 const fingerprint: Package.Fingerprint = .generate(sanitized_root_name);
4766 const rng: std.Random.IoSource = .{ .io = io };
4767 const fingerprint: Package.Fingerprint = .generate(rng.interface(), sanitized_root_name);
47674768
47684769 switch (template) {
47694770 .example => {
......@@ -4919,7 +4920,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
49194920
49204921 try child_argv.appendSlice(&.{
49214922 "--seed",
4922 try std.fmt.allocPrint(arena, "0x{x}", .{std.crypto.random.int(u32)}),
4923 try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}),
49234924 });
49244925 const argv_index_seed = child_argv.items.len - 1;
49254926
......@@ -4937,7 +4938,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
49374938 // the strategy is to choose a temporary file name ahead of time, and then
49384939 // read this file in the parent to obtain the results, in the case the child
49394940 // exits with code 3.
4940 const results_tmp_file_nonce = std.fmt.hex(std.crypto.random.int(u64));
4941 const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64));
49414942 try child_argv.append("-Z" ++ results_tmp_file_nonce);
49424943
49434944 var color: Color = .auto;
......@@ -7223,7 +7224,7 @@ fn createDependenciesModule(
72237224) !*Package.Module {
72247225 // Atomically create the file in a directory named after the hash of its contents.
72257226 const basename = "dependencies.zig";
7226 const rand_int = std.crypto.random.int(u64);
7227 const rand_int = randInt(io, u64);
72277228 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
72287229 {
72297230 var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{});
......@@ -7339,6 +7340,8 @@ fn loadManifest(
73397340 io: Io,
73407341 options: LoadManifestOptions,
73417342) !struct { Package.Manifest, Ast } {
7343 const rng: std.Random.IoSource = .{ .io = io };
7344
73427345 const manifest_bytes = while (true) {
73437346 break options.dir.readFileAllocOptions(
73447347 io,
......@@ -7360,15 +7363,13 @@ fn loadManifest(
73607363 , .{
73617364 options.root_name,
73627365 build_options.version,
7363 Package.Fingerprint.generate(options.root_name).int(),
7366 Package.Fingerprint.generate(rng.interface(), options.root_name).int(),
73647367 }) catch |e| {
7365 fatal("unable to write {s}: {s}", .{ Package.Manifest.basename, @errorName(e) });
7368 fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e });
73667369 };
73677370 continue;
73687371 },
7369 else => |e| fatal("unable to load {s}: {s}", .{
7370 Package.Manifest.basename, @errorName(e),
7371 }),
7372 else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }),
73727373 };
73737374 };
73747375 var ast = try Ast.parse(gpa, manifest_bytes, .zon);
......@@ -7379,7 +7380,7 @@ fn loadManifest(
73797380 process.exit(2);
73807381 }
73817382
7382 var manifest = try Package.Manifest.parse(gpa, ast, .{});
7383 var manifest = try Package.Manifest.parse(gpa, ast, rng.interface(), .{});
73837384 errdefer manifest.deinit(gpa);
73847385
73857386 if (manifest.errors.len > 0) {
......@@ -7632,3 +7633,9 @@ fn setThreadLimit(n: usize) void {
76327633 threaded_impl_ptr.setAsyncLimit(limit);
76337634 threaded_impl_ptr.concurrent_limit = limit;
76347635}
7636
7637fn randInt(io: Io, comptime T: type) T {
7638 var x: T = undefined;
7639 io.random(@ptrCast(&x));
7640 return x;
7641}
test/standalone/simple/guess_number/main.zig+2-1
......@@ -10,7 +10,8 @@ pub fn main(init: std.process.Init) !void {
1010
1111 try out.writeAll("Welcome to the Guess Number Game in Zig.\n");
1212
13 const answer = std.crypto.random.intRangeLessThan(u8, 0, 100) + 1;
13 var rng: std.Random.IoSource = .{ .io = init.io };
14 const answer = rng.interface().intRangeLessThan(u8, 0, 100) + 1;
1415
1516 while (true) {
1617 try out.writeAll("\nGuess a number between 1 and 100: ");
test/standalone/windows_argv/build.zig+1-1
......@@ -52,7 +52,7 @@ pub fn build(b: *std.Build) !void {
5252
5353 const fuzz_seed = b.option(u64, "seed", "Seed to use for the PRNG (default: random)") orelse seed: {
5454 var buf: [8]u8 = undefined;
55 try std.posix.getrandom(&buf);
55 b.graph.io.random(&buf);
5656 break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
5757 };
5858 const fuzz_seed_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_seed}) catch @panic("oom");
test/standalone/windows_argv/fuzz.zig+2-1
......@@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
55
66pub fn main(init: std.process.Init) !void {
77 const gpa = init.gpa;
8 const io = init.io;
89 const args = try init.minimal.args.toSlice(init.arena.allocator());
910
1011 if (args.len < 2) return error.MissingArgs;
......@@ -23,7 +24,7 @@ pub fn main(init: std.process.Init) !void {
2324 if (args.len < 4) {
2425 rand_seed = true;
2526 var buf: [8]u8 = undefined;
26 try std.posix.getrandom(&buf);
27 io.random(&buf);
2728 break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
2829 }
2930 break :seed try std.fmt.parseUnsigned(u64, args[3], 10);
test/standalone/windows_bat_args/build.zig+1-1
......@@ -65,7 +65,7 @@ pub fn build(b: *std.Build) !void {
6565
6666 const fuzz_seed = b.option(u64, "seed", "Seed to use for the PRNG (default: random)") orelse seed: {
6767 var buf: [8]u8 = undefined;
68 try std.posix.getrandom(&buf);
68 b.graph.io.random(&buf);
6969 break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
7070 };
7171 const fuzz_seed_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_seed}) catch @panic("oom");
test/standalone/windows_bat_args/fuzz.zig+1-1
......@@ -22,7 +22,7 @@ pub fn main(init: std.process.Init) !void {
2222 const seed_arg = it.next() orelse {
2323 rand_seed = true;
2424 var buf: [8]u8 = undefined;
25 try std.posix.getrandom(&buf);
25 io.random(&buf);
2626 break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
2727 };
2828 break :seed try std.fmt.parseUnsigned(u64, seed_arg, 10);
tools/doctest.zig+4-3
......@@ -78,9 +78,10 @@ pub fn main(init: std.process.Init) !void {
7878 const code = try parseManifest(arena, source_bytes);
7979 const source = stripManifest(source_bytes);
8080
81 const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{
82 cache_root, std.crypto.random.int(u64),
83 });
81 var random_integer: u64 = undefined;
82 io.random(@ptrCast(&random_integer));
83
84 const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ cache_root, random_integer });
8485 Dir.cwd().createDirPath(io, tmp_dir_path) catch |err|
8586 fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err });
8687 defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{
tools/incr-check.zig+9-4
......@@ -100,7 +100,7 @@ pub fn main(init: std.process.Init) !void {
100100 const prog_node = std.Progress.start(io, .{});
101101 defer prog_node.end();
102102
103 const rand_int = std.crypto.random.int(u64);
103 const rand_int = rand64(io);
104104 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
105105 var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{});
106106 defer {
......@@ -452,20 +452,19 @@ const Eval = struct {
452452 std.debug.assert(eval.target.backend == .sema);
453453 return;
454454 };
455 const io = eval.io;
455456
456457 const binary_path = switch (eval.target.backend) {
457458 .sema => unreachable,
458459 .selfhosted, .llvm => emitted_path,
459460 .cbe => bin: {
460 const rand_int = std.crypto.random.int(u64);
461 const rand_int = rand64(io);
461462 const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);
462463 try eval.buildCOutput(emitted_path, out_bin_name, prog_node);
463464 break :bin out_bin_name;
464465 },
465466 };
466467
467 const io = eval.io;
468
469468 var argv_buf: [2][]const u8 = undefined;
470469 const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor(
471470 io,
......@@ -957,3 +956,9 @@ fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError {
957956 .msg = message,
958957 };
959958}
959
960fn rand64(io: Io) u64 {
961 var x: u64 = undefined;
962 io.random(@ptrCast(&x));
963 return x;
964}