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

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30709
This commit is contained in:
Andrew Kelley 2026-01-07 20:04:26 +01:00
commit 62d6bbc7dc
66 changed files with 1301 additions and 803 deletions

View file

@ -593,15 +593,7 @@ pub fn build(b: *std.Build) !void {
.x86_64 => 3_756_422_348,
else => 3_800_000_000,
},
.linux => switch (b.graph.host.result.cpu.arch) {
.aarch64 => 6_732_817_203,
.loongarch64 => 3_216_349_593,
.powerpc64le => 3_090_179_276,
.riscv64 => 4_052_670_054,
.s390x => 3_652_514_201,
.x86_64 => 3_249_546_854,
else => 6_800_000_000,
},
.linux => 6_800_000_000,
.macos => switch (b.graph.host.result.cpu.arch) {
.aarch64 => 8_273_795_481,
else => 8_300_000_000,

View file

@ -1217,11 +1217,12 @@ pub fn getDepFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u
}
fn getRandomFilename(d: *Driver, buf: *[std.fs.max_name_bytes]u8, extension: []const u8) ![]const u8 {
const io = d.comp.io;
const random_bytes_count = 12;
const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
var random_bytes: [random_bytes_count]u8 = undefined;
std.crypto.random.bytes(&random_bytes);
io.random(&random_bytes);
var random_name: [sub_path_len]u8 = undefined;
_ = std.fs.base64_encoder.encode(&random_name, &random_bytes);

View file

@ -21,7 +21,6 @@ pub const dependencies = @import("@dependencies");
pub const std_options: std.Options = .{
.side_channels_mitigations = .none,
.http_disable_tls = true,
.crypto_fork_safety = false,
};
pub fn main(init: process.Init.Minimal) !void {

View file

@ -111,12 +111,7 @@ pub const TestResults = struct {
pub const MakeOptions = struct {
progress_node: std.Progress.Node,
watch: bool,
web_server: switch (builtin.target.cpu.arch) {
else => ?*Build.WebServer,
// WASM code references `Build.abi` which happens to incidentally reference this type, but
// it currently breaks because `std.net.Address` doesn't work there. Work around for now.
.wasm32 => void,
},
web_server: ?*Build.WebServer,
/// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds.
unit_test_timeout_ns: ?u64,
/// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`.

View file

@ -1706,18 +1706,29 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
// The args file is already present from a previous run.
} else |err| switch (err) {
error.FileNotFound => {
try b.cache_root.handle.createDirPath(io, "tmp");
const rand_int = std.crypto.random.int(u64);
const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args });
defer b.cache_root.handle.deleteFile(io, tmp_path) catch {
// It's fine if the temporary file can't be cleaned up.
var af = b.cache_root.handle.createFileAtomic(io, args_file, .{
.replace = false,
.make_path = true,
}) catch |e| return step.fail("failed creating tmp args file {f}{s}: {t}", .{
b.cache_root, args_file, e,
});
defer af.deinit(io);
af.file.writeStreamingAll(io, args) catch |e| {
return step.fail("failed writing args data to tmp file {f}{s}: {t}", .{
b.cache_root, args_file, e,
});
};
b.cache_root.handle.rename(tmp_path, b.cache_root.handle, args_file, io) catch |rename_err| switch (rename_err) {
// Note we can't clean up this file, not even after build
// success, because that might interfere with another build
// process that needs the same file.
af.link(io) catch |e| switch (e) {
error.PathAlreadyExists => {
// The args file was created by another concurrent build process.
},
else => |other_err| return other_err,
else => |other_err| return step.fail("failed linking tmp file {f}{s}: {t}", .{
b.cache_root, args_file, other_err,
}),
};
},
else => |other_err| return other_err,

View file

@ -476,46 +476,28 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void {
return;
} else |outer_err| switch (outer_err) {
error.FileNotFound => {
const sub_dirname = fs.path.dirname(sub_path).?;
b.cache_root.handle.createDirPath(io, sub_dirname) catch |e|
return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e });
var atomic_file = b.cache_root.handle.createFileAtomic(io, sub_path, .{
.replace = false,
.make_path = true,
}) catch |err| return step.fail("failed to create temporary path for '{f}{s}': {t}", .{
b.cache_root, sub_path, err,
});
defer atomic_file.deinit(io);
const rand_int = std.crypto.random.int(u64);
const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
std.fmt.hex(rand_int) ++ fs.path.sep_str ++
basename;
const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
b.cache_root.handle.createDirPath(io, tmp_sub_path_dirname) catch |err| {
return step.fail("unable to make temporary directory '{f}{s}': {t}", .{
b.cache_root, tmp_sub_path_dirname, err,
atomic_file.file.writeStreamingAll(io, options.contents.items) catch |err| {
return step.fail("failed to write options to temporary path for '{f}{s}': {t}", .{
b.cache_root, sub_path, err,
});
};
b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
return step.fail("unable to write options to '{f}{s}': {t}", .{
b.cache_root, tmp_sub_path, err,
});
};
b.cache_root.handle.rename(tmp_sub_path, b.cache_root.handle, sub_path, io) catch |err| switch (err) {
atomic_file.link(io) catch |err| switch (err) {
error.PathAlreadyExists => {
// Other process beat us to it. Clean up the temp file.
b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| {
try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{
b.cache_root, tmp_sub_path, e,
});
};
step.result_cached = true;
return;
},
else => {
return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{
b.cache_root, tmp_sub_path,
b.cache_root, sub_path,
err,
});
},
else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{
b.cache_root, sub_path, err,
}),
};
},
else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{

View file

@ -984,7 +984,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
};
// We do not know the final output paths yet, use temp paths to run the command.
const rand_int = std.crypto.random.int(u64);
var rand_int: u64 = undefined;
io.random(@ptrCast(&rand_int));
const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
for (output_placeholders.items) |placeholder| {
@ -1128,7 +1129,8 @@ pub fn rerunInFuzzMode(
}
const has_side_effects = false;
const rand_int = std.crypto.random.int(u64);
var rand_int: u64 = undefined;
io.random(@ptrCast(&rand_int));
const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
.progress_node = prog_node,

View file

@ -293,7 +293,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
.tmp => {
step.result_cached = false;
const rand_int = std.crypto.random.int(u64);
var rand_int: u64 = undefined;
io.random(@ptrCast(&rand_int));
const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path});

View file

@ -676,6 +676,7 @@ pub const VTable = struct {
dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void,
dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void,
dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void,
dirRenamePreserve: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenamePreserveError!void,
dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void,
dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize,
dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void,
@ -731,6 +732,9 @@ pub const VTable = struct {
now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
random: *const fn (?*anyopaque, buffer: []u8) void,
randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void,
netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
@ -2242,3 +2246,36 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel
pub fn unlockStderr(io: Io) void {
return io.vtable.unlockStderr(io.userdata);
}
/// Obtains entropy from a cryptographically secure pseudo-random number
/// generator.
///
/// The implementation *may* store RNG state in process memory and use it to
/// fill `buffer`.
///
/// The randomness is seeded by `randomSecure`, or a less secure mechanism upon
/// failure.
///
/// Threadsafe.
///
/// See also `randomSecure`.
pub fn random(io: Io, buffer: []u8) void {
return io.vtable.random(io.userdata, buffer);
}
pub const RandomSecureError = error{EntropyUnavailable} || Cancelable;
/// Obtains cryptographically secure entropy from outside the process.
///
/// Always makes a syscall, or otherwise avoids dependency on process memory,
/// in order to obtain fresh randomness. Does not rely on stored RNG state.
///
/// Does not have any fallback mechanisms; returns `error.EntropyUnavailable`
/// if any problems occur.
///
/// Threadsafe.
///
/// See also `random`.
pub fn randomSecure(io: Io, buffer: []u8) RandomSecureError!void {
return io.vtable.randomSecure(io.userdata, buffer);
}

View file

@ -936,10 +936,9 @@ pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void
pub const RenameError = error{
/// In WASI, this error may occur when the file descriptor does
/// not hold the required rights to rename a resource by path relative to it.
///
/// On Windows, this error may be returned instead of PathAlreadyExists when
/// renaming a directory over an existing directory.
AccessDenied,
/// Attempted to replace a nonempty directory.
DirNotEmpty,
PermissionDenied,
FileBusy,
DiskQuota,
@ -950,9 +949,8 @@ pub const RenameError = error{
NotDir,
SystemResources,
NoSpaceLeft,
PathAlreadyExists,
ReadOnlyFileSystem,
RenameAcrossMountPoints,
CrossDevice,
NoDevice,
SharingViolation,
PipeBusy,
@ -964,6 +962,7 @@ pub const RenameError = error{
/// intercepts file system operations and makes them significantly slower
/// in addition to possibly failing with this error code.
AntivirusInterference,
HardwareFailure,
} || PathNameError || Io.Cancelable || Io.UnexpectedError;
/// Change the name or location of a file or directory.
@ -973,9 +972,9 @@ pub const RenameError = error{
/// Renaming a file over an existing directory or a directory over an existing
/// file will fail with `error.IsDir` or `error.NotDir`
///
/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
/// On WASI, both paths should be encoded as valid UTF-8.
/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
/// * On WASI, both paths should be encoded as valid UTF-8.
/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn rename(
old_dir: Dir,
old_sub_path: []const u8,
@ -993,6 +992,39 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename
return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path);
}
pub const RenamePreserveError = error{
/// In WASI, this error may occur when the file descriptor does
/// not hold the required rights to rename a resource by path relative to it.
///
/// On Windows, this error may be returned instead of PathAlreadyExists when
/// renaming a directory over an existing directory.
AccessDenied,
PathAlreadyExists,
/// Operating system or file system does not support atomic nonreplacing
/// rename.
OperationUnsupported,
} || RenameError;
/// Change the name or location of a file or directory.
///
/// If `new_sub_path` already exists, `error.PathAlreadyExists` will be returned.
///
/// Renaming a file over an existing directory or a directory over an existing
/// file will fail with `error.IsDir` or `error.NotDir`
///
/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/).
/// * On WASI, both paths should be encoded as valid UTF-8.
/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding.
pub fn renamePreserve(
old_dir: Dir,
old_sub_path: []const u8,
new_dir: Dir,
new_sub_path: []const u8,
io: Io,
) RenamePreserveError!void {
return io.vtable.dirRenamePreserve(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path);
}
pub const HardLinkOptions = File.HardLinkOptions;
pub const HardLinkError = File.HardLinkError;
@ -1098,8 +1130,10 @@ pub fn symLinkAtomic(
const temp_path = temp_path_buf[0..temp_path_len];
var random_integer: u64 = undefined;
while (true) {
const random_integer = std.crypto.random.int(u64);
io.random(@ptrCast(&random_integer));
temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
if (dir.symLink(io, target_path, temp_path, flags)) {

View file

@ -709,7 +709,7 @@ pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize {
}
pub const HardLinkOptions = struct {
follow_symlinks: bool = true,
follow_symlinks: bool = false,
};
pub const HardLinkError = error{
@ -726,7 +726,7 @@ pub const HardLinkError = error{
SystemResources,
NoSpaceLeft,
ReadOnlyFileSystem,
NotSameFileSystem,
CrossDevice,
NotDir,
} || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError;

View file

@ -37,10 +37,14 @@ pub fn deinit(af: *Atomic, io: Io) void {
af.* = undefined;
}
pub const LinkError = Dir.HardLinkError;
pub const LinkError = File.HardLinkError || Dir.RenamePreserveError;
/// Atomically materializes the file into place, failing with
/// `error.PathAlreadyExists` if something already exists there.
///
/// If this operation could not be done with an unnamed temporary file, the
/// named temporary file will be deleted in a following operation, which may
/// independently fail. The result of that operation is stored in `delete_err`.
pub fn link(af: *Atomic, io: Io) LinkError!void {
if (af.file_exists) {
if (af.file_open) {
@ -48,8 +52,7 @@ pub fn link(af: *Atomic, io: Io) LinkError!void {
af.file_open = false;
}
const tmp_sub_path = std.fmt.hex(af.file_basename_hex);
try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{});
af.dir.deleteFile(io, &tmp_sub_path) catch {};
try af.dir.renamePreserve(&tmp_sub_path, af.dir, af.dest_sub_path, io);
af.file_exists = false;
} else {
assert(af.file_open);

View file

@ -65,6 +65,22 @@ argv0: Argv0,
environ: Environ,
null_file: NullFile = .{},
random_file: RandomFile = .{},
csprng: Csprng = .{},
pub const Csprng = struct {
rng: std.Random.DefaultCsprng = .{
.state = undefined,
.offset = std.math.maxInt(usize),
},
pub const seed_len = std.Random.DefaultCsprng.secret_seed_length;
pub fn isInitialized(c: *const Csprng) bool {
return c.rng.offset != std.math.maxInt(usize);
}
};
pub const Argv0 = switch (native_os) {
.openbsd, .haiku => struct {
@ -151,6 +167,15 @@ pub const NullFile = switch (native_os) {
},
};
pub const RandomFile = switch (native_os) {
.windows => NullFile,
else => if (use_dev_urandom) NullFile else struct {
fn deinit(this: @This()) void {
_ = this;
}
},
};
pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
unknown = 0,
_,
@ -585,6 +610,8 @@ const Thread = struct {
/// Always released when `Status.cancelation` is set to `.parked`.
futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
csprng: Csprng,
const Handle = Handle: {
if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE;
@ -1285,6 +1312,7 @@ pub fn deinit(t: *Threaded) void {
if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
}
t.null_file.deinit();
t.random_file.deinit();
t.* = undefined;
}
@ -1313,6 +1341,7 @@ fn worker(t: *Threaded) void {
}),
.cancel_protection = .unblocked,
.futex_waiter = undefined,
.csprng = .{},
};
Thread.current = &thread;
@ -1413,6 +1442,7 @@ pub fn io(t: *Threaded) Io {
.dirDeleteFile = dirDeleteFile,
.dirDeleteDir = dirDeleteDir,
.dirRename = dirRename,
.dirRenamePreserve = dirRenamePreserve,
.dirSymLink = dirSymLink,
.dirReadLink = dirReadLink,
.dirSetOwner = dirSetOwner,
@ -1466,6 +1496,9 @@ pub fn io(t: *Threaded) Io {
.now = now,
.sleep = sleep,
.random = random,
.randomSecure = randomSecure,
.netListenIp = switch (native_os) {
.windows => netListenIpWindows,
else => netListenIpPosix,
@ -1561,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io {
.dirDeleteFile = dirDeleteFile,
.dirDeleteDir = dirDeleteDir,
.dirRename = dirRename,
.dirRenamePreserve = dirRenamePreserve,
.dirSymLink = dirSymLink,
.dirReadLink = dirReadLink,
.dirSetOwner = dirSetOwner,
@ -1614,6 +1648,9 @@ pub fn ioBasic(t: *Threaded) Io {
.now = now,
.sleep = sleep,
.random = random,
.randomSecure = randomSecure,
.netListenIp = netListenIpUnavailable,
.netListenUnix = netListenUnixUnavailable,
.netAccept = netAcceptUnavailable,
@ -1704,6 +1741,23 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid
});
const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
.{ .major = 30, .minor = 0, .patch = 0 }
else
.{ .major = 2, .minor = 28, .patch = 0 });
const use_libc_getrandom = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
.major = 28,
.minor = 0,
.patch = 0,
} else .{
.major = 2,
.minor = 25,
.patch = 0,
});
const use_dev_urandom = @TypeOf(posix.system.getrandom) == void and native_os == .linux;
fn async(
userdata: ?*anyopaque,
result: []u8,
@ -2342,11 +2396,11 @@ fn dirCreateDirPath(
status = .created;
} else |err| switch (err) {
error.PathAlreadyExists => {
// stat the file and return an error if it's not a directory
// this is important because otherwise a dangling symlink
// could cause an infinite loop
const fstat = try dirStatFile(t, dir, component.path, .{});
if (fstat.kind != .directory) return error.NotDir;
// It is important to return an error if it's not a directory
// because otherwise a dangling symlink could cause an infinite
// loop.
const kind = try filePathKind(t, dir, component.path);
if (kind != .directory) return error.NotDir;
},
error.FileNotFound => |e| {
component = it.previous() orelse return e;
@ -2538,11 +2592,7 @@ fn dirStatFileLinux(
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
const linux = std.os.linux;
const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
.{ .major = 30, .minor = 0, .patch = 0 }
else
.{ .major = 2, .minor = 28, .patch = 0 });
const sys = if (use_c) std.c else std.os.linux;
const sys = if (statx_use_c) std.c else std.os.linux;
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
@ -2691,6 +2741,35 @@ fn dirStatFileWasi(
}
}
fn filePathKind(t: *Threaded, dir: Dir, sub_path: []const u8) !File.Kind {
if (native_os == .linux) {
var path_buffer: [posix.PATH_MAX]u8 = undefined;
const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
const linux = std.os.linux;
const syscall: Syscall = try .start();
while (true) {
var statx = std.mem.zeroes(linux.Statx);
switch (linux.errno(linux.statx(dir.handle, sub_path_posix, 0, .{ .TYPE = true }, &statx))) {
.SUCCESS => {
syscall.finish();
if (!statx.mask.TYPE) return error.Unexpected;
return statxKind(statx.mode);
},
.INTR => {
try syscall.checkCancel();
continue;
},
.NOMEM => return syscall.fail(error.SystemResources),
else => |err| return syscall.unexpectedErrno(err),
}
}
}
const stat = try dirStatFile(t, dir, sub_path, .{});
return stat.kind;
}
fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
const t: *Threaded = @ptrCast(@alignCast(userdata));
@ -2778,11 +2857,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
const linux = std.os.linux;
const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
.{ .major = 30, .minor = 0, .patch = 0 }
else
.{ .major = 2, .minor = 28, .patch = 0 });
const sys = if (use_c) std.c else std.os.linux;
const sys = if (statx_use_c) std.c else std.os.linux;
const syscall: Syscall = try .start();
while (true) {
@ -3450,7 +3525,7 @@ fn dirCreateFileAtomic(
if (dest_dirname) |dirname| {
// This has a nice side effect of preemptively triggering EISDIR or
// ENOENT, avoiding the ambiguity below.
dir.createDirPath(t_io, dirname) catch |err| switch (err) {
if (options.make_path) dir.createDirPath(t_io, dirname) catch |err| switch (err) {
// None of these make sense in this context.
error.IsDir,
error.Streaming,
@ -3553,8 +3628,9 @@ fn atomicFileInit(
dir: Dir,
close_dir_on_deinit: bool,
) Dir.CreateFileAtomicError!File.Atomic {
var random_integer: u64 = undefined;
while (true) {
const random_integer = std.crypto.random.int(u64);
t_io.random(@ptrCast(&random_integer));
const tmp_sub_path = std.fmt.hex(random_integer);
const file = dir.createFile(t_io, &tmp_sub_path, .{
.permissions = permissions,
@ -3636,10 +3712,12 @@ fn dirOpenFilePosix(
},
};
const mode: posix.mode_t = 0;
const fd: posix.fd_t = fd: {
const syscall: Syscall = try .start();
while (true) {
const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0));
const rc = openat_sym(dir.handle, sub_path_posix, os_flags, mode);
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
@ -4068,9 +4146,11 @@ fn dirOpenDirPosix(
if (@hasField(posix.O, "PATH") and !options.iterate)
flags.PATH = true;
const mode: posix.mode_t = 0;
const syscall: Syscall = try .start();
while (true) {
const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0));
const rc = openat_sym(dir.handle, sub_path_posix, flags, mode);
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
@ -5169,12 +5249,21 @@ fn fileHardLink(
var new_path_buffer: [posix.PATH_MAX]u8 = undefined;
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
const flags: u32 = if (!options.follow_symlinks)
posix.AT.SYMLINK_NOFOLLOW | posix.AT.EMPTY_PATH
const flags: u32 = if (options.follow_symlinks)
posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH
else
posix.AT.EMPTY_PATH;
return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags);
return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) {
error.FileNotFound => {
if (options.follow_symlinks) return error.FileNotFound;
var proc_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
const proc_path = std.fmt.bufPrintSentinel(&proc_buf, "/proc/self/fd/{d}", .{file.handle}, 0) catch
unreachable;
return linkat(posix.AT.FDCWD, proc_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW);
},
else => |e| return e,
};
}
fn linkat(
@ -5205,7 +5294,7 @@ fn linkat(
.NOTDIR => return syscall.fail(error.NotDir),
.PERM => return syscall.fail(error.PermissionDenied),
.ROFS => return syscall.fail(error.ReadOnlyFileSystem),
.XDEV => return syscall.fail(error.NotSameFileSystem),
.XDEV => return syscall.fail(error.CrossDevice),
.ILSEQ => return syscall.fail(error.BadPathName),
.FAULT => |err| return syscall.errnoBug(err),
.INVAL => |err| return syscall.errnoBug(err),
@ -5614,15 +5703,44 @@ fn dirRenameWindows(
new_dir: Dir,
new_sub_path: []const u8,
) Dir.RenameError!void {
const w = windows;
const t: *Threaded = @ptrCast(@alignCast(userdata));
_ = t;
return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, true) catch |err| switch (err) {
error.PathAlreadyExists => return error.Unexpected,
error.OperationUnsupported => return error.Unexpected,
else => |e| return e,
};
}
fn dirRenamePreserve(
userdata: ?*anyopaque,
old_dir: Dir,
old_sub_path: []const u8,
new_dir: Dir,
new_sub_path: []const u8,
) Dir.RenamePreserveError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false);
if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path);
// Make a hard link then delete the original.
try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false });
const prev = swapCancelProtection(t, .blocked);
defer _ = swapCancelProtection(t, prev);
dirDeleteFile(t, old_dir, old_sub_path) catch {};
}
fn dirRenameWindowsInner(
old_dir: Dir,
old_sub_path: []const u8,
new_dir: Dir,
new_sub_path: []const u8,
replace_if_exists: bool,
) Dir.RenamePreserveError!void {
const w = windows;
const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path);
const old_path_w = old_path_w_buf.span();
const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path);
const new_path_w = new_path_w_buf.span();
const replace_if_exists = true;
const src_fd = src_fd: {
const syscall: Syscall = try .start();
@ -5724,9 +5842,9 @@ fn dirRenameWindows(
.ACCESS_DENIED => return error.AccessDenied,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
.NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
.NOT_SAME_DEVICE => return error.CrossDevice,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
.DIRECTORY_NOT_EMPTY => return error.DirNotEmpty,
.FILE_IS_A_DIRECTORY => return error.IsDir,
.NOT_A_DIRECTORY => return error.NotDir,
else => return w.unexpectedStatus(rc),
@ -5770,10 +5888,10 @@ fn dirRenameWasi(
.NOTDIR => return error.NotDir,
.NOMEM => return error.SystemResources,
.NOSPC => return error.NoSpaceLeft,
.EXIST => return error.PathAlreadyExists,
.NOTEMPTY => return error.PathAlreadyExists,
.EXIST => return error.DirNotEmpty,
.NOTEMPTY => return error.DirNotEmpty,
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.RenameAcrossMountPoints,
.XDEV => return error.CrossDevice,
.NOTCAPABLE => return error.AccessDenied,
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
@ -5799,9 +5917,105 @@ fn dirRenamePosix(
const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix);
}
fn dirRenamePreserveLinux(
old_dir: Dir,
old_sub_path: []const u8,
new_dir: Dir,
new_sub_path: []const u8,
) Dir.RenamePreserveError!void {
const linux = std.os.linux;
var old_path_buffer: [linux.PATH_MAX]u8 = undefined;
var new_path_buffer: [linux.PATH_MAX]u8 = undefined;
const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
const syscall: Syscall = try .start();
while (true) switch (linux.errno(linux.renameat2(
old_dir.handle,
old_sub_path_posix,
new_dir.handle,
new_sub_path_posix,
.{ .NOREPLACE = true },
))) {
.SUCCESS => return syscall.finish(),
.INTR => {
try syscall.checkCancel();
continue;
},
.ACCES => return syscall.fail(error.AccessDenied),
.PERM => return syscall.fail(error.PermissionDenied),
.BUSY => return syscall.fail(error.FileBusy),
.DQUOT => return syscall.fail(error.DiskQuota),
.ISDIR => return syscall.fail(error.IsDir),
.LOOP => return syscall.fail(error.SymLinkLoop),
.MLINK => return syscall.fail(error.LinkQuotaExceeded),
.NAMETOOLONG => return syscall.fail(error.NameTooLong),
.NOENT => return syscall.fail(error.FileNotFound),
.NOTDIR => return syscall.fail(error.NotDir),
.NOMEM => return syscall.fail(error.SystemResources),
.NOSPC => return syscall.fail(error.NoSpaceLeft),
.EXIST => return syscall.fail(error.PathAlreadyExists),
.NOTEMPTY => return syscall.fail(error.DirNotEmpty),
.ROFS => return syscall.fail(error.ReadOnlyFileSystem),
.XDEV => return syscall.fail(error.CrossDevice),
.ILSEQ => return syscall.fail(error.BadPathName),
.FAULT => |err| return syscall.errnoBug(err),
.INVAL => |err| return syscall.errnoBug(err),
else => |err| return syscall.unexpectedErrno(err),
};
}
fn renameat(
old_dir: posix.fd_t,
old_sub_path: [*:0]const u8,
new_dir: posix.fd_t,
new_sub_path: [*:0]const u8,
) Dir.RenameError!void {
const syscall: Syscall = try .start();
while (true) switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
.SUCCESS => return syscall.finish(),
.INTR => {
try syscall.checkCancel();
continue;
},
.ACCES => return syscall.fail(error.AccessDenied),
.PERM => return syscall.fail(error.PermissionDenied),
.BUSY => return syscall.fail(error.FileBusy),
.DQUOT => return syscall.fail(error.DiskQuota),
.ISDIR => return syscall.fail(error.IsDir),
.IO => return syscall.fail(error.HardwareFailure),
.LOOP => return syscall.fail(error.SymLinkLoop),
.MLINK => return syscall.fail(error.LinkQuotaExceeded),
.NAMETOOLONG => return syscall.fail(error.NameTooLong),
.NOENT => return syscall.fail(error.FileNotFound),
.NOTDIR => return syscall.fail(error.NotDir),
.NOMEM => return syscall.fail(error.SystemResources),
.NOSPC => return syscall.fail(error.NoSpaceLeft),
.EXIST => return syscall.fail(error.DirNotEmpty),
.NOTEMPTY => return syscall.fail(error.DirNotEmpty),
.ROFS => return syscall.fail(error.ReadOnlyFileSystem),
.XDEV => return syscall.fail(error.CrossDevice),
.ILSEQ => return syscall.fail(error.BadPathName),
.FAULT => |err| return syscall.errnoBug(err),
.INVAL => |err| return syscall.errnoBug(err),
else => |err| return syscall.unexpectedErrno(err),
};
}
fn renameatPreserve(
old_dir: posix.fd_t,
old_sub_path: [*:0]const u8,
new_dir: posix.fd_t,
new_sub_path: [*:0]const u8,
) Dir.RenameError!void {
const syscall: Syscall = try .start();
while (true) {
switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) {
switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) {
.SUCCESS => return syscall.finish(),
.INTR => {
try syscall.checkCancel();
@ -5827,7 +6041,7 @@ fn dirRenamePosix(
.EXIST => return error.PathAlreadyExists,
.NOTEMPTY => return error.PathAlreadyExists,
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.RenameAcrossMountPoints,
.XDEV => return error.CrossDevice,
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
}
@ -6318,11 +6532,6 @@ fn fchmodatFallback(
mode: posix.mode_t,
) Dir.SetFilePermissionsError!void {
comptime assert(native_os == .linux);
const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
.{ .major = 30, .minor = 0, .patch = 0 }
else
.{ .major = 2, .minor = 28, .patch = 0 });
const sys = if (use_c) std.c else std.os.linux;
// Fallback to changing permissions using procfs:
//
@ -6369,6 +6578,7 @@ fn fchmodatFallback(
defer posix.close(path_fd);
const path_mode = mode: {
const sys = if (statx_use_c) std.c else std.os.linux;
const syscall: Syscall = try .start();
while (true) {
var statx = std.mem.zeroes(std.os.linux.Statx);
@ -7612,7 +7822,7 @@ fn dirHardLink(
.NOTDIR => return error.NotDir,
.PERM => return error.PermissionDenied,
.ROFS => return error.ReadOnlyFileSystem,
.XDEV => return error.NotSameFileSystem,
.XDEV => return error.CrossDevice,
.INVAL => |err| return errnoBug(err),
.ILSEQ => return error.BadPathName,
else => |err| return posix.unexpectedErrno(err),
@ -7628,7 +7838,7 @@ fn dirHardLink(
const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer);
const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer);
const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0;
const flags: u32 = if (options.follow_symlinks) posix.AT.SYMLINK_FOLLOW else 0;
return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags);
}
@ -12268,16 +12478,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
.nlink = stx.nlink,
.size = stx.size,
.permissions = .fromMode(stx.mode),
.kind = switch (stx.mode & std.os.linux.S.IFMT) {
std.os.linux.S.IFDIR => .directory,
std.os.linux.S.IFCHR => .character_device,
std.os.linux.S.IFBLK => .block_device,
std.os.linux.S.IFREG => .file,
std.os.linux.S.IFIFO => .named_pipe,
std.os.linux.S.IFLNK => .sym_link,
std.os.linux.S.IFSOCK => .unix_domain_socket,
else => .unknown,
},
.kind = statxKind(stx.mode),
.atime = if (!stx.mask.ATIME) null else .{
.nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec),
},
@ -12286,6 +12487,19 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
};
}
fn statxKind(stx_mode: u16) File.Kind {
return switch (stx_mode & std.os.linux.S.IFMT) {
std.os.linux.S.IFDIR => .directory,
std.os.linux.S.IFCHR => .character_device,
std.os.linux.S.IFBLK => .block_device,
std.os.linux.S.IFREG => .file,
std.os.linux.S.IFIFO => .named_pipe,
std.os.linux.S.IFLNK => .sym_link,
std.os.linux.S.IFSOCK => .unix_domain_socket,
else => .unknown,
};
}
fn statFromPosix(st: *const posix.Stat) File.Stat {
const atime = st.atime();
const mtime = st.mtime();
@ -12441,7 +12655,8 @@ fn lookupDns(
for (family_records) |fr| {
if (options.family != fr.af) {
const entropy = std.crypto.random.array(u8, 2);
var entropy: [2]u8 = undefined;
random(t, &entropy);
const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
queries_buffer[nq] = query_buffers[nq][0..len];
nq += 1;
@ -13853,6 +14068,62 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
};
}
fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
{
t.mutex.lock();
defer t.mutex.unlock();
if (t.random_file.handle) |handle| return handle;
}
const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
var nt_name: windows.UNICODE_STRING = .{
.Length = device_path.len * 2,
.MaximumLength = 0,
.Buffer = @constCast(&device_path),
};
var fresh_handle: windows.HANDLE = undefined;
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var syscall: Syscall = try .start();
while (true) switch (windows.ntdll.NtOpenFile(
&fresh_handle,
.{
.STANDARD = .{ .SYNCHRONIZE = true },
.SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
},
&.{
.Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
.RootDirectory = null,
.ObjectName = &nt_name,
.Attributes = .{},
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
},
&io_status_block,
.VALID_FLAGS,
.{ .IO = .SYNCHRONOUS_NONALERT },
)) {
.SUCCESS => {
syscall.finish();
t.mutex.lock(); // Another thread might have won the race.
defer t.mutex.unlock();
if (t.random_file.handle) |prev_handle| {
_ = windows.ntdll.NtClose(fresh_handle);
return prev_handle;
} else {
t.random_file.handle = fresh_handle;
return fresh_handle;
}
},
.CANCELLED => {
try syscall.checkCancel();
continue;
},
.OBJECT_NAME_NOT_FOUND => return syscall.fail(error.EntropyUnavailable), // Observed on wine 10.0
else => return syscall.fail(error.EntropyUnavailable),
};
}
fn getNulHandle(t: *Threaded) !windows.HANDLE {
{
t.mutex.lock();
@ -14935,6 +15206,305 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
return @field(t.environ.string, name);
}
fn random(userdata: ?*anyopaque, buffer: []u8) void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
const thread = Thread.current orelse return randomMainThread(t, buffer);
if (!thread.csprng.isInitialized()) {
@branchHint(.unlikely);
var seed: [Csprng.seed_len]u8 = undefined;
randomMainThread(t, &seed);
thread.csprng.rng = .init(seed);
}
thread.csprng.rng.fill(buffer);
}
fn randomMainThread(t: *Threaded, buffer: []u8) void {
t.mutex.lock();
defer t.mutex.unlock();
if (!t.csprng.isInitialized()) {
@branchHint(.unlikely);
var seed: [Csprng.seed_len]u8 = undefined;
{
t.mutex.unlock();
defer t.mutex.lock();
const prev = swapCancelProtection(t, .blocked);
defer _ = swapCancelProtection(t, prev);
randomSecure(t, &seed) catch |err| switch (err) {
error.Canceled => unreachable,
error.EntropyUnavailable => {
@memset(&seed, 0);
const aslr_addr = @intFromPtr(t);
std.mem.writeInt(usize, seed[seed.len - @sizeOf(usize) ..][0..@sizeOf(usize)], aslr_addr, .native);
switch (native_os) {
.windows => fallbackSeedWindows(&seed),
.wasi => if (builtin.link_libc) fallbackSeedPosix(&seed) else fallbackSeedWasi(&seed),
else => fallbackSeedPosix(&seed),
}
},
};
}
t.csprng.rng = .init(seed);
}
t.csprng.rng.fill(buffer);
}
fn fallbackSeedPosix(seed: *[Csprng.seed_len]u8) void {
std.mem.writeInt(posix.pid_t, seed[0..@sizeOf(posix.pid_t)], posix.system.getpid(), .native);
const i_1 = @sizeOf(posix.pid_t);
var ts: posix.timespec = undefined;
const Sec = @TypeOf(ts.sec);
const Nsec = @TypeOf(ts.nsec);
const i_2 = i_1 + @sizeOf(Sec);
switch (posix.errno(posix.system.clock_gettime(.REALTIME, &ts))) {
.SUCCESS => {
std.mem.writeInt(Sec, seed[i_1..][0..@sizeOf(Sec)], ts.sec, .native);
std.mem.writeInt(Nsec, seed[i_2..][0..@sizeOf(Nsec)], ts.nsec, .native);
},
else => {},
}
}
fn fallbackSeedWindows(seed: *[Csprng.seed_len]u8) void {
var pc: windows.LARGE_INTEGER = undefined;
_ = windows.ntdll.RtlQueryPerformanceCounter(&pc);
std.mem.writeInt(windows.LARGE_INTEGER, seed[0..@sizeOf(windows.LARGE_INTEGER)], pc, .native);
}
fn fallbackSeedWasi(seed: *[Csprng.seed_len]u8) void {
var ts: std.os.wasi.timestamp_t = undefined;
if (std.os.wasi.clock_time_get(.REALTIME, 1, &ts) == .SUCCESS) {
std.mem.writeInt(std.os.wasi.timestamp_t, seed[0..@sizeOf(std.os.wasi.timestamp_t)], ts, .native);
}
}
fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
const t: *Threaded = @ptrCast(@alignCast(userdata));
if (is_windows) {
if (buffer.len == 0) return;
// ProcessPrng from bcryptprimitives.dll has the following properties:
// * introduces a dependency on bcryptprimitives.dll, which apparently
// runs a test suite every time it is loaded
// * heap allocates a 48-byte buffer, handling failure by returning NO_MEMORY in a BOOL
// despite the function being documented to always return TRUE
// * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
// Therefore, that function is avoided in favor of using the device directly.
const cng_device = try getCngHandle(t);
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
var i: usize = 0;
const syscall: Syscall = try .start();
while (true) {
const remaining_len = std.math.lossyCast(u32, buffer.len - i);
switch (windows.ntdll.NtDeviceIoControlFile(
cng_device,
null,
null,
null,
&io_status_block,
windows.IOCTL.KSEC.GEN_RANDOM,
null,
0,
buffer[i..].ptr,
remaining_len,
)) {
.SUCCESS => {
i += remaining_len;
if (buffer.len - i == 0) {
return syscall.finish();
} else {
try syscall.checkCancel();
continue;
}
},
.CANCELLED => {
try syscall.checkCancel();
continue;
},
else => return syscall.fail(error.EntropyUnavailable),
}
}
}
if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) {
if (buffer.len == 0) return;
posix.system.arc4random_buf(buffer.ptr, buffer.len);
return;
}
if (native_os == .wasi) {
if (buffer.len == 0) return;
const syscall: Syscall = try .start();
while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) {
.SUCCESS => return syscall.finish(),
.INTR => {
try syscall.checkCancel();
continue;
},
else => return syscall.fail(error.EntropyUnavailable),
};
}
if (@TypeOf(posix.system.getrandom) != void) {
const getrandom = if (use_libc_getrandom) std.c.getrandom else std.os.linux.getrandom;
var i: usize = 0;
const syscall: Syscall = try .start();
while (buffer.len - i != 0) {
const buf = buffer[i..];
const rc = getrandom(buf.ptr, buf.len, 0);
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
const n: usize = @intCast(rc);
i += n;
continue;
},
.INTR => {
try syscall.checkCancel();
continue;
},
else => return syscall.fail(error.EntropyUnavailable),
}
}
return;
}
if (native_os == .emscripten) {
if (buffer.len == 0) return;
const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len));
switch (err) {
.SUCCESS => return,
else => return error.EntropyUnavailable,
}
}
if (native_os == .linux) {
comptime assert(use_dev_urandom);
const urandom_fd = try getRandomFd(t);
var i: usize = 0;
while (buffer.len - i != 0) {
const syscall: Syscall = try .start();
const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i);
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
const n: usize = @intCast(rc);
if (n == 0) return error.EntropyUnavailable;
i += n;
continue;
},
.INTR => {
try syscall.checkCancel();
continue;
},
else => return syscall.fail(error.EntropyUnavailable),
}
}
}
return error.EntropyUnavailable;
}
fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t {
{
t.mutex.lock();
defer t.mutex.unlock();
if (t.random_file.fd == -2) return error.EntropyUnavailable;
if (t.random_file.fd != -1) return t.random_file.fd;
}
const mode: posix.mode_t = 0;
const fd: posix.fd_t = fd: {
const syscall: Syscall = try .start();
while (true) {
const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{
.ACCMODE = .RDONLY,
.CLOEXEC = true,
}, mode);
switch (posix.errno(rc)) {
.SUCCESS => {
syscall.finish();
break :fd @intCast(rc);
},
.INTR => {
try syscall.checkCancel();
continue;
},
else => return syscall.fail(error.EntropyUnavailable),
}
}
};
errdefer posix.close(fd);
switch (native_os) {
.linux => {
const sys = if (statx_use_c) std.c else std.os.linux;
const syscall: Syscall = try .start();
while (true) {
var statx = std.mem.zeroes(std.os.linux.Statx);
switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
.SUCCESS => {
syscall.finish();
if (!statx.mask.TYPE) return error.EntropyUnavailable;
t.mutex.lock(); // Another thread might have won the race.
defer t.mutex.unlock();
if (t.random_file.fd >= 0) {
posix.close(fd);
return t.random_file.fd;
} else if (!posix.S.ISCHR(statx.mode)) {
t.random_file.fd = -2;
return error.EntropyUnavailable;
} else {
t.random_file.fd = fd;
return fd;
}
},
.INTR => {
try syscall.checkCancel();
continue;
},
else => return syscall.fail(error.EntropyUnavailable),
}
}
},
else => {
const syscall: Syscall = try .start();
while (true) {
var stat = std.mem.zeroes(posix.Stat);
switch (posix.errno(fstat_sym(fd, &stat))) {
.SUCCESS => {
syscall.finish();
t.mutex.lock(); // Another thread might have won the race.
defer t.mutex.unlock();
if (t.random_file.fd >= 0) {
posix.close(fd);
return t.random_file.fd;
} else if (!posix.S.ISCHR(stat.mode)) {
t.random_file.fd = -2;
return error.EntropyUnavailable;
} else {
t.random_file.fd = fd;
return fd;
}
},
.INTR => {
try syscall.checkCancel();
continue;
},
else => return syscall.fail(error.EntropyUnavailable),
}
}
},
}
}
test {
_ = @import("Threaded/test.zig");
}

View file

@ -275,7 +275,7 @@ test "listen on a unix socket, send bytes, receive bytes" {
const io = testing.io;
const socket_path = try generateFileName("socket.unix");
const socket_path = try generateFileName(io, "socket.unix");
defer testing.allocator.free(socket_path);
const socket_addr = try net.UnixAddress.init(socket_path);
@ -308,11 +308,11 @@ test "listen on a unix socket, send bytes, receive bytes" {
try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
}
fn generateFileName(base_name: []const u8) ![]const u8 {
fn generateFileName(io: Io, base_name: []const u8) ![]const u8 {
const random_bytes_count = 12;
const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);
var random_bytes: [12]u8 = undefined;
std.crypto.random.bytes(&random_bytes);
io.random(&random_bytes);
var sub_path: [sub_path_len]u8 = undefined;
_ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);
return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name });

View file

@ -564,3 +564,29 @@ test "tasks spawned in group after Group.cancel are canceled" {
try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up
try group.concurrent(io, global.waitThenSpawn, .{ io, &group });
}
test "random" {
const io = testing.io;
var a: u64 = undefined;
var b: u64 = undefined;
var c: u64 = undefined;
io.random(@ptrCast(&a));
io.random(@ptrCast(&b));
io.random(@ptrCast(&c));
try std.testing.expect(a ^ b ^ c != 0);
}
test "randomSecure" {
const io = testing.io;
var buf_a: [50]u8 = undefined;
var buf_b: [50]u8 = undefined;
try io.randomSecure(&buf_a);
try io.randomSecure(&buf_b);
// If this test fails the chance is significantly higher that there is a bug than
// that two sets of 50 bytes were equal.
try expect(!mem.eql(u8, &buf_a, &buf_b));
}

View file

@ -1,15 +1,13 @@
//! The engines provided here should be initialized from an external source.
//! For a thread-local cryptographically secure pseudo random number generator,
//! use `std.crypto.random`.
//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will
//! be faster and use substantially less stack space.
const Random = @This();
const std = @import("std.zig");
const math = std.math;
const mem = std.mem;
const assert = std.debug.assert;
const maxInt = std.math.maxInt;
const Random = @This();
/// Fast unbiased random numbers.
pub const DefaultPrng = Xoshiro256;
@ -35,6 +33,22 @@ pub const ziggurat = @import("Random/ziggurat.zig");
ptr: *anyopaque,
fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,
pub const IoSource = struct {
io: std.Io,
pub fn interface(this: *const @This()) std.Random {
return .{
.ptr = @constCast(this),
.fillFn = fill,
};
}
fn fill(ptr: *anyopaque, buffer: []u8) void {
const this: *const @This() = @ptrCast(@alignCast(ptr));
this.io.random(buffer);
}
};
pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {
const Ptr = @TypeOf(pointer);
assert(@typeInfo(Ptr) == .pointer); // Must be a pointer

View file

@ -20,7 +20,7 @@ pub const secret_seed_length = Cipher.key_length;
/// The seed must be uniform, secret and `secret_seed_length` bytes long.
pub fn init(secret_seed: [secret_seed_length]u8) Self {
var self = Self{ .state = undefined, .offset = 0 };
var self: Self = .{ .state = undefined, .offset = 0 };
Cipher.stream(&self.state, 0, secret_seed, nonce);
return self;
}

View file

@ -436,8 +436,9 @@ fn testRangeBias(r: Random, start: i8, end: i8, biased: bool) !void {
}
test "CSPRNG" {
const io = std.testing.io;
var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined;
std.crypto.random.bytes(&secret_seed);
io.random(&secret_seed);
var csprng = DefaultCsprng.init(secret_seed);
const random = csprng.random();
const a = random.int(u64);

View file

@ -235,9 +235,6 @@ pub const nacl = struct {
/// Finite-field arithmetic.
pub const ff = @import("crypto/ff.zig");
/// This is a thread-local, cryptographically secure pseudo random number generator.
pub const random = @import("crypto/tlcsprng.zig").interface;
/// Encoding and decoding
pub const codecs = @import("crypto/codecs.zig");
@ -306,6 +303,9 @@ test {
_ = dh.X25519;
_ = kem.kyber_d00;
_ = kem.hybrid;
_ = kem.kyber_d00;
_ = kem.ml_kem;
_ = ecc.Curve25519;
_ = ecc.Edwards25519;
@ -343,6 +343,7 @@ test {
_ = sign.Ed25519;
_ = sign.ecdsa;
_ = sign.mldsa;
_ = stream.chacha.ChaCha20IETF;
_ = stream.chacha.ChaCha12IETF;
@ -364,20 +365,12 @@ test {
_ = secureZero;
_ = timing_safe;
_ = ff;
_ = random;
_ = errors;
_ = tls;
_ = Certificate;
_ = codecs;
}
test "CSPRNG" {
const a = random.int(u64);
const b = random.int(u64);
const c = random.int(u64);
try std.testing.expect(a ^ b ^ c != 0);
}
test "issue #4532: no index out of bounds" {
const types = [_]type{
hash.Md5,

View file

@ -333,12 +333,10 @@ pub const Ed25519 = struct {
}
/// Generate a new, random key pair.
///
/// `crypto.random.bytes` must be supported by the target.
pub fn generate() KeyPair {
pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
crypto.random.bytes(&random_seed);
io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
@ -389,18 +387,21 @@ pub const Ed25519 = struct {
/// Create a Signer, that can be used for incremental signing.
/// Note that the signature is not deterministic.
/// The noise parameter, if set, should be something unique for each message,
/// such as a random nonce, or a counter.
pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer {
pub fn signer(
key_pair: KeyPair,
/// If set, should be something unique for each message, such as a
/// random nonce, or a counter.
noise: ?[noise_length]u8,
/// Filled with cryptographically secure randomness.
entropy: *const [noise_length]u8,
) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer {
if (!mem.eql(u8, &key_pair.secret_key.publicKeyBytes(), &key_pair.public_key.toBytes())) {
return error.KeyMismatch;
}
const scalar_and_prefix = key_pair.secret_key.scalarAndPrefix();
var h = Sha512.init(.{});
h.update(&scalar_and_prefix.prefix);
var noise2: [noise_length]u8 = undefined;
crypto.random.bytes(&noise2);
h.update(&noise2);
h.update(entropy);
if (noise) |*z| {
h.update(z);
}
@ -420,7 +421,7 @@ pub const Ed25519 = struct {
};
/// Verify several signatures in a single operation, much faster than verifying signatures one-by-one
pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void {
pub fn verifyBatch(io: std.Io, comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void {
var r_batch: [count]CompressedScalar = undefined;
var s_batch: [count]CompressedScalar = undefined;
var a_batch: [count]Curve = undefined;
@ -454,7 +455,7 @@ pub const Ed25519 = struct {
var z_batch: [count]Curve.scalar.CompressedScalar = undefined;
for (&z_batch) |*z| {
crypto.random.bytes(z[0..16]);
io.random(z[0..16]);
@memset(z[16..], 0);
}
@ -587,12 +588,14 @@ test "signature" {
}
test "batch verification" {
const io = std.testing.io;
for (0..16) |_| {
const key_pair = Ed25519.KeyPair.generate();
const key_pair = Ed25519.KeyPair.generate(io);
var msg1: [32]u8 = undefined;
var msg2: [32]u8 = undefined;
crypto.random.bytes(&msg1);
crypto.random.bytes(&msg2);
io.random(&msg1);
io.random(&msg2);
const sig1 = try key_pair.sign(&msg1, null);
const sig2 = try key_pair.sign(&msg2, null);
var signature_batch = [_]Ed25519.BatchElement{
@ -607,10 +610,10 @@ test "batch verification" {
.public_key = key_pair.public_key,
},
};
try Ed25519.verifyBatch(2, signature_batch);
try Ed25519.verifyBatch(io, 2, signature_batch);
signature_batch[1].sig = sig1;
try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(signature_batch.len, signature_batch));
try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(io, signature_batch.len, signature_batch));
}
}
@ -718,14 +721,15 @@ test "test vectors" {
}
test "with blind keys" {
const io = std.testing.io;
const BlindKeyPair = Ed25519.key_blinding.BlindKeyPair;
// Create a standard Ed25519 key pair
const kp = Ed25519.KeyPair.generate();
const kp = Ed25519.KeyPair.generate(io);
// Create a random blinding seed
var blind: [32]u8 = undefined;
crypto.random.bytes(&blind);
io.random(&blind);
// Blind the key pair
const blind_kp = try BlindKeyPair.init(kp, blind, "ctx");
@ -741,9 +745,12 @@ test "with blind keys" {
}
test "signatures with streaming" {
const kp = Ed25519.KeyPair.generate();
const io = std.testing.io;
const kp = Ed25519.KeyPair.generate(io);
var signer = try kp.signer(null);
var entropy: [Ed25519.noise_length]u8 = undefined;
io.random(&entropy);
var signer = try kp.signer(null, &entropy);
signer.update("mes");
signer.update("sage");
const sig = signer.finalize();
@ -757,7 +764,8 @@ test "signatures with streaming" {
}
test "key pair from secret key" {
const kp = Ed25519.KeyPair.generate();
const io = std.testing.io;
const kp = Ed25519.KeyPair.generate(io);
const kp2 = try Ed25519.KeyPair.fromSecretKey(kp.secret_key);
try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes());
try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes());
@ -788,7 +796,8 @@ test "cofactored vs cofactorless verification" {
}
test "regular signature verifies with both verify and verifyStrict" {
const kp = Ed25519.KeyPair.generate();
const io = std.testing.io;
const kp = Ed25519.KeyPair.generate(io);
const msg = "test message";
const sig = try kp.sign(msg, null);
try sig.verify(msg, kp.public_key);

View file

@ -575,10 +575,11 @@ test "packing/unpacking" {
}
test "point addition/subtraction" {
const io = std.testing.io;
var s1: [32]u8 = undefined;
var s2: [32]u8 = undefined;
crypto.random.bytes(&s1);
crypto.random.bytes(&s2);
io.random(&s1);
io.random(&s2);
const p = try Edwards25519.basePoint.clampedMul(s1);
const q = try Edwards25519.basePoint.clampedMul(s2);
const r = p.add(q).add(q).sub(q).sub(q);
@ -622,9 +623,10 @@ test "implicit reduction of invalid scalars" {
}
test "subgroup check" {
const io = std.testing.io;
for (0..100) |_| {
var p = Edwards25519.basePoint;
const s = Edwards25519.scalar.random();
const s = Edwards25519.scalar.random(io);
p = try p.mulPublic(s);
try p.rejectUnexpectedSubgroup();
}

View file

@ -101,8 +101,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar) CompressedScalar {
}
/// Return a random scalar < L
pub fn random() CompressedScalar {
return Scalar.random().toBytes();
pub fn random(io: std.Io) CompressedScalar {
return Scalar.random(io).toBytes();
}
/// A scalar in unpacked representation
@ -560,10 +560,10 @@ pub const Scalar = struct {
}
/// Return a random scalar < L.
pub fn random() Scalar {
pub fn random(io: std.Io) Scalar {
var s: [64]u8 = undefined;
while (true) {
crypto.random.bytes(&s);
io.random(&s);
const n = Scalar.fromBytes64(s);
if (!n.isZero()) {
return n;
@ -879,8 +879,9 @@ test "scalar field inversion" {
}
test "random scalar" {
const s1 = random();
const s2 = random();
const io = std.testing.io;
const s1 = random(io);
const s2 = random(io);
try std.testing.expect(!mem.eql(u8, &s1, &s2));
}

View file

@ -41,10 +41,10 @@ pub const X25519 = struct {
}
/// Generate a new, random key pair.
pub fn generate() KeyPair {
pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
crypto.random.bytes(&random_seed);
io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;

View file

@ -533,7 +533,7 @@ const PhcFormatHasher = struct {
if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding;
var salt: [default_salt_len]u8 = undefined;
crypto.random.bytes(&salt);
io.random(&salt);
var hash: [default_hash_len]u8 = undefined;
try kdf(allocator, &hash, password, &salt, params, mode, io);

View file

@ -17,7 +17,7 @@ const HasherError = pwhash.HasherError;
const EncodingError = phc_format.Error;
const Error = pwhash.Error;
const salt_length: usize = 16;
pub const salt_length: usize = 16;
const salt_str_length: usize = 22;
const ct_str_length: usize = 31;
const ct_length: usize = 24;
@ -426,7 +426,7 @@ pub const Params = struct {
fn bcryptWithTruncation(
password: []const u8,
salt: [salt_length]u8,
salt: *const [salt_length]u8,
params: Params,
) [dk_length]u8 {
var state = State{};
@ -435,13 +435,13 @@ fn bcryptWithTruncation(
@memcpy(password_buf[0..trimmed_len], password[0..trimmed_len]);
password_buf[trimmed_len] = 0;
const passwordZ = password_buf[0 .. trimmed_len + 1];
state.expand(salt[0..], passwordZ);
state.expand(salt, passwordZ);
const rounds: u64 = @as(u64, 1) << params.rounds_log;
var k: u64 = 0;
while (k < rounds) : (k += 1) {
state.expand0(passwordZ);
state.expand0(salt[0..]);
state.expand0(salt);
}
crypto.secureZero(u8, &password_buf);
@ -467,7 +467,7 @@ fn bcryptWithTruncation(
/// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead.
pub fn bcrypt(
password: []const u8,
salt: [salt_length]u8,
salt: *const [salt_length]u8,
params: Params,
) [dk_length]u8 {
if (password.len <= 72 or params.silently_truncate_password) {
@ -475,7 +475,7 @@ pub fn bcrypt(
}
var pre_hash: [HmacSha512.mac_length]u8 = undefined;
HmacSha512.create(&pre_hash, password, &salt);
HmacSha512.create(&pre_hash, password, salt);
const Encoder = crypt_format.Codec.Encoder;
var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined;
@ -623,16 +623,16 @@ const crypt_format = struct {
fn strHashInternal(
password: []const u8,
salt: [salt_length]u8,
salt: *const [salt_length]u8,
params: Params,
) [hash_length]u8 {
var dk = bcrypt(password, salt, params);
var salt_str: [salt_str_length]u8 = undefined;
_ = Codec.Encoder.encode(salt_str[0..], salt[0..]);
_ = Codec.Encoder.encode(&salt_str, salt);
var ct_str: [ct_str_length]u8 = undefined;
_ = Codec.Encoder.encode(ct_str[0..], dk[0..]);
_ = Codec.Encoder.encode(&ct_str, dk[0..]);
var s_buf: [hash_length]u8 = undefined;
const s = fmt.bufPrint(
@ -657,21 +657,20 @@ const PhcFormatHasher = struct {
hash: BinValue(dk_length),
};
/// Return a non-deterministic hash of the password encoded as a PHC-format string
/// Return a non-deterministic hash of the password encoded as a PHC-format string.
fn create(
password: []const u8,
params: Params,
buf: []u8,
/// Filled with cryptographically secure entropy.
salt: *const [salt_length]u8,
) HasherError![]const u8 {
var salt: [salt_length]u8 = undefined;
crypto.random.bytes(&salt);
const hash = bcrypt(password, salt, params);
return phc_format.serialize(HashResult{
.alg_id = alg_id,
.r = params.rounds_log,
.salt = try BinValue(salt_length).fromSlice(&salt),
.salt = try BinValue(salt_length).fromSlice(salt),
.hash = try BinValue(dk_length).fromSlice(&hash),
}, buf);
}
@ -688,11 +687,11 @@ const PhcFormatHasher = struct {
if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length)
return HasherError.InvalidEncoding;
const params = Params{
const params: Params = .{
.rounds_log = hash_result.r,
.silently_truncate_password = silently_truncate_password,
};
const hash = bcrypt(password, hash_result.salt.buf, params);
const hash = bcrypt(password, &hash_result.salt.buf, params);
const expected_hash = hash_result.hash.constSlice();
if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed;
@ -709,12 +708,11 @@ const CryptFormatHasher = struct {
password: []const u8,
params: Params,
buf: []u8,
/// Filled with cryptographically secure entropy.
salt: *const [salt_length]u8,
) HasherError![]const u8 {
if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft;
var salt: [salt_length]u8 = undefined;
crypto.random.bytes(&salt);
const hash = crypt_format.strHashInternal(password, salt, params);
@memcpy(buf[0..hash.len], &hash);
@ -736,9 +734,9 @@ const CryptFormatHasher = struct {
const salt_str = str[7..][0..salt_str_length];
var salt: [salt_length]u8 = undefined;
crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding;
crypt_format.Codec.Decoder.decode(&salt, salt_str) catch return HasherError.InvalidEncoding;
const wanted_s = crypt_format.strHashInternal(password, salt, .{
const wanted_s = crypt_format.strHashInternal(password, &salt, .{
.rounds_log = rounds_log,
.silently_truncate_password = silently_truncate_password,
});
@ -756,21 +754,28 @@ pub const HashOptions = struct {
encoding: pwhash.Encoding,
};
/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
/// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches.
/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key
/// stretching function.
///
/// The function returns a string that includes all the parameters required for verification.
/// bcrypt is a computationally expensive and cache-hard function, explicitly
/// designed to slow down exhaustive searches.
///
/// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes.
/// If this is an issue for your application, set the `silently_truncate_password` option to `false`.
/// The function returns a string that includes all the parameters required for
/// verification.
///
/// By design, bcrypt silently truncates passwords to 72 bytes. If this is an
/// issue for your application, set the `silently_truncate_password` option to
/// `false`.
pub fn strHash(
password: []const u8,
options: HashOptions,
out: []u8,
/// Filled with cryptographically secure entropy.
salt: *const [salt_length]u8,
) Error![]const u8 {
switch (options.encoding) {
.phc => return PhcFormatHasher.create(password, options.params, out),
.crypt => return CryptFormatHasher.create(password, options.params, out),
.phc => return PhcFormatHasher.create(password, options.params, out, salt),
.crypt => return CryptFormatHasher.create(password, options.params, out, salt),
}
}
@ -796,8 +801,9 @@ pub fn strVerify(
}
test "bcrypt codec" {
const io = testing.io;
var salt: [salt_length]u8 = undefined;
crypto.random.bytes(&salt);
io.random(&salt);
var salt_str: [salt_str_length]u8 = undefined;
_ = crypt_format.Codec.Encoder.encode(salt_str[0..], salt[0..]);
var salt2: [salt_length]u8 = undefined;
@ -806,14 +812,20 @@ test "bcrypt codec" {
}
test "bcrypt crypt format" {
var hash_options = HashOptions{
const io = testing.io;
var hash_options: HashOptions = .{
.params = .{ .rounds_log = 5, .silently_truncate_password = false },
.encoding = .crypt,
};
var verify_options = VerifyOptions{ .silently_truncate_password = false };
var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
var buf: [hash_length]u8 = undefined;
const s = try strHash("password", hash_options, &buf);
const s = s: {
var salt: [salt_length]u8 = undefined;
io.random(&salt);
break :s try strHash("password", hash_options, &buf, &salt);
};
try testing.expect(mem.startsWith(u8, s, crypt_format.prefix));
try strVerify(s, "password", verify_options);
@ -823,7 +835,11 @@ test "bcrypt crypt format" {
);
var long_buf: [hash_length]u8 = undefined;
var long_s = try strHash("password" ** 100, hash_options, &long_buf);
var long_s = s: {
var salt: [salt_length]u8 = undefined;
io.random(&salt);
break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
};
try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix));
try strVerify(long_s, "password" ** 100, verify_options);
@ -834,7 +850,11 @@ test "bcrypt crypt format" {
hash_options.params.silently_truncate_password = true;
verify_options.silently_truncate_password = true;
long_s = try strHash("password" ** 100, hash_options, &long_buf);
long_s = s: {
var salt: [salt_length]u8 = undefined;
io.random(&salt);
break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
};
try strVerify(long_s, "password" ** 101, verify_options);
try strVerify(
@ -845,15 +865,20 @@ test "bcrypt crypt format" {
}
test "bcrypt phc format" {
var hash_options = HashOptions{
const io = testing.io;
var hash_options: HashOptions = .{
.params = .{ .rounds_log = 5, .silently_truncate_password = false },
.encoding = .phc,
};
var verify_options = VerifyOptions{ .silently_truncate_password = false };
var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
const prefix = "$bcrypt$";
var buf: [hash_length * 2]u8 = undefined;
const s = try strHash("password", hash_options, &buf);
const s = s: {
var salt: [salt_length]u8 = undefined;
io.random(&salt);
break :s try strHash("password", hash_options, &buf, &salt);
};
try testing.expect(mem.startsWith(u8, s, prefix));
try strVerify(s, "password", verify_options);
@ -863,7 +888,11 @@ test "bcrypt phc format" {
);
var long_buf: [hash_length * 2]u8 = undefined;
var long_s = try strHash("password" ** 100, hash_options, &long_buf);
var long_s = s: {
var salt: [salt_length]u8 = undefined;
io.random(&salt);
break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
};
try testing.expect(mem.startsWith(u8, long_s, prefix));
try strVerify(long_s, "password" ** 100, verify_options);
@ -874,7 +903,11 @@ test "bcrypt phc format" {
hash_options.params.silently_truncate_password = true;
verify_options.silently_truncate_password = true;
long_s = try strHash("password" ** 100, hash_options, &long_buf);
long_s = s: {
var salt: [salt_length]u8 = undefined;
io.random(&salt);
break :s try strHash("password" ** 100, hash_options, &long_buf, &salt);
};
try strVerify(long_s, "password" ** 101, verify_options);
try strVerify(

View file

@ -323,10 +323,10 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
}
/// Generate a new, random key pair.
pub fn generate() KeyPair {
pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
crypto.random.bytes(&random_seed);
io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
@ -417,12 +417,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type {
test "Basic operations over EcdsaP384Sha384" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
const io = testing.io;
const Scheme = EcdsaP384Sha384;
const kp = Scheme.KeyPair.generate();
const kp = Scheme.KeyPair.generate(io);
const msg = "test";
var noise: [Scheme.noise_length]u8 = undefined;
crypto.random.bytes(&noise);
io.random(&noise);
const sig = try kp.sign(msg, noise);
try sig.verify(msg, kp.public_key);
@ -433,12 +434,13 @@ test "Basic operations over EcdsaP384Sha384" {
test "Basic operations over Secp256k1" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
const io = testing.io;
const Scheme = EcdsaSecp256k1Sha256oSha256;
const kp = Scheme.KeyPair.generate();
const kp = Scheme.KeyPair.generate(io);
const msg = "test";
var noise: [Scheme.noise_length]u8 = undefined;
crypto.random.bytes(&noise);
io.random(&noise);
const sig = try kp.sign(msg, noise);
try sig.verify(msg, kp.public_key);
@ -449,12 +451,13 @@ test "Basic operations over Secp256k1" {
test "Basic operations over EcdsaP384Sha256" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
const io = testing.io;
const Scheme = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha256);
const kp = Scheme.KeyPair.generate();
const kp = Scheme.KeyPair.generate(io);
const msg = "test";
var noise: [Scheme.noise_length]u8 = undefined;
crypto.random.bytes(&noise);
io.random(&noise);
const sig = try kp.sign(msg, noise);
try sig.verify(msg, kp.public_key);
@ -502,8 +505,10 @@ test "Verifying a existing signature with EcdsaP384Sha256" {
test "Prehashed message operations" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
const io = testing.io;
const Scheme = EcdsaP256Sha256;
const kp = Scheme.KeyPair.generate();
const kp = Scheme.KeyPair.generate(io);
const msg = "test message for prehashed signing";
const Hash = crypto.hash.sha2.Sha256;
@ -518,7 +523,7 @@ test "Prehashed message operations" {
try testing.expectError(error.SignatureVerificationFailed, sig.verifyPrehashed(bad_hash, kp.public_key));
var noise: [Scheme.noise_length]u8 = undefined;
crypto.random.bytes(&noise);
io.random(&noise);
const sig_with_noise = try kp.signPrehashed(msg_hash, noise);
try sig_with_noise.verifyPrehashed(msg_hash, kp.public_key);
@ -1628,8 +1633,9 @@ fn tvTry(comptime Scheme: type, vector: TestVector) !void {
test "Sec1 encoding/decoding" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
const io = testing.io;
const Scheme = EcdsaP384Sha384;
const kp = Scheme.KeyPair.generate();
const kp = Scheme.KeyPair.generate(io);
const pk = kp.public_key;
const pk_compressed_sec1 = pk.toCompressedSec1();
const pk_recovered1 = try Scheme.PublicKey.fromSec1(&pk_compressed_sec1);

View file

@ -174,43 +174,56 @@ pub fn HybridKem(comptime params: Params) type {
return .{ .bytes = buf.* };
}
/// Generates a shared secret and encapsulates it for the public key.
/// If `seed` is `null`, uses random bytes from `std.crypto.random`.
/// If `seed` is set, encapsulation is deterministic (for testing only).
pub fn encaps(self: PublicKey, seed: ?[]const u8) !EncapsulatedSecret {
const pq_nek = params.PqKem.PublicKey.encoded_length;
const ek_pq = try params.PqKem.PublicKey.fromBytes(self.bytes[0..pq_nek]);
const ek_t = self.bytes[pq_nek..][0..params.Group.element_length];
/// Generates a shared secret, encapsulated for the public key,
/// using random bytes.
///
/// This is recommended over `encapsDeterministic`.
pub fn encaps(pk: PublicKey, io: std.Io) !EncapsulatedSecret {
var seed_pq: [32]u8 = undefined;
io.random(&seed_pq);
var seed_t: [32]u8 = undefined;
io.random(&seed_t);
var seed_t_expanded: [params.Group.seed_length]u8 = try expandRandomnessSeed(seed_t);
return encapsInner(pk, &seed_pq, &seed_t_expanded);
}
/// Generates a shared secret, encapsulated for the public key,
/// using the provided seed.
///
/// Calling `encaps` instead is recommended.
pub fn encapsDeterministic(pk: PublicKey, seed: []const u8) !EncapsulatedSecret {
if (seed.len < 32) return error.InsufficientRandomness;
var seed_pq: [32]u8 = seed[0..32].*;
var seed_t_expanded: [params.Group.seed_length]u8 = undefined;
if (seed) |r| {
if (r.len < 32) return error.InsufficientRandomness;
seed_pq = r[0..32].*;
const t_randomness = r[32..];
const t_randomness = seed[32..];
if (t_randomness.len < params.Group.seed_length) {
// Provided randomness is shorter than seed_length, use it directly
// (test vectors provide just enough for randomScalar)
@memcpy(seed_t_expanded[0..t_randomness.len], t_randomness);
// Pad the rest with zeros if needed (shouldn't be used by randomScalar)
if (t_randomness.len < params.Group.seed_length) {
// Provided randomness is shorter than seed_length, use it directly
// (test vectors provide just enough for randomScalar)
@memcpy(seed_t_expanded[0..t_randomness.len], t_randomness);
// Pad the rest with zeros if needed (shouldn't be used by randomScalar)
if (t_randomness.len < params.Group.seed_length) {
@memset(seed_t_expanded[t_randomness.len..], 0);
}
} else {
// Full randomness provided
@memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]);
@memset(seed_t_expanded[t_randomness.len..], 0);
}
} else {
crypto.random.bytes(&seed_pq);
var seed_t: [32]u8 = undefined;
crypto.random.bytes(&seed_t);
seed_t_expanded = try expandRandomnessSeed(seed_t);
// Full randomness provided
@memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]);
}
const pq_encap = ek_pq.encaps(seed_pq);
const sk_e = try params.Group.randomScalar(&seed_t_expanded);
return encapsInner(pk, &seed_pq, &seed_t_expanded);
}
fn encapsInner(
pk: PublicKey,
seed_pq: *[32]u8,
seed_t_expanded: *[params.Group.seed_length]u8,
) !EncapsulatedSecret {
const pq_nek = params.PqKem.PublicKey.encoded_length;
const ek_pq = try params.PqKem.PublicKey.fromBytes(pk.bytes[0..pq_nek]);
const ek_t = pk.bytes[pq_nek..][0..params.Group.element_length];
const pq_encap = ek_pq.encapsDeterministic(seed_pq);
const sk_e = try params.Group.randomScalar(seed_t_expanded);
const ct_t_point = try params.Group.mulBase(sk_e);
const ct_t = if (is_nist_curve) params.Group.encodePoint(ct_t_point) else ct_t_point;
@ -280,9 +293,9 @@ pub fn HybridKem(comptime params: Params) type {
}
/// Generates a new random key pair.
pub fn generate() !KeyPair {
pub fn generate(io: std.Io) !KeyPair {
var seed: [params.Nseed]u8 = undefined;
crypto.random.bytes(&seed);
io.random(&seed);
return generateDeterministic(seed);
}
};
@ -386,7 +399,7 @@ test "MLKEM768-X25519 basic round trip" {
var enc_seed: [64]u8 = undefined;
@memset(&enc_seed, 0x43);
const encap_result = try kp.public_key.encaps(&enc_seed);
const encap_result = try kp.public_key.encapsDeterministic(&enc_seed);
const ss_decap = try kp.secret_key.decaps(&encap_result.ciphertext);
try testing.expectEqualSlices(u8, &encap_result.shared_secret, &ss_decap);
@ -408,7 +421,7 @@ test "MLKEM768-X25519 test vector 0" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -432,7 +445,7 @@ test "MLKEM768-X25519 test vector 1" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -456,7 +469,7 @@ test "MLKEM768-X25519 test vector 2" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -480,7 +493,7 @@ test "MLKEM768-X25519 test vector 3" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -504,7 +517,7 @@ test "MLKEM768-X25519 test vector 4" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -528,7 +541,7 @@ test "MLKEM768-X25519 test vector 5" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -552,7 +565,7 @@ test "MLKEM768-X25519 test vector 6" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -576,7 +589,7 @@ test "MLKEM768-X25519 test vector 7" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -600,7 +613,7 @@ test "MLKEM768-X25519 test vector 8" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -624,7 +637,7 @@ test "MLKEM768-X25519 test vector 9" {
const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -648,7 +661,7 @@ test "MLKEM768-P256 test vector 0" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -672,7 +685,7 @@ test "MLKEM768-P256 test vector 1" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -696,7 +709,7 @@ test "MLKEM768-P256 test vector 2" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -720,7 +733,7 @@ test "MLKEM768-P256 test vector 3" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -744,7 +757,7 @@ test "MLKEM768-P256 test vector 4" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -768,7 +781,7 @@ test "MLKEM768-P256 test vector 5" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -792,7 +805,7 @@ test "MLKEM768-P256 test vector 6" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -816,7 +829,7 @@ test "MLKEM768-P256 test vector 7" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -840,7 +853,7 @@ test "MLKEM768-P256 test vector 8" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -864,7 +877,7 @@ test "MLKEM768-P256 test vector 9" {
const kp = try MlKem768P256.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -888,7 +901,7 @@ test "MLKEM1024-P384 test vector 0" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -912,7 +925,7 @@ test "MLKEM1024-P384 test vector 1" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -936,7 +949,7 @@ test "MLKEM1024-P384 test vector 2" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -960,7 +973,7 @@ test "MLKEM1024-P384 test vector 3" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -984,7 +997,7 @@ test "MLKEM1024-P384 test vector 4" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -1008,7 +1021,7 @@ test "MLKEM1024-P384 test vector 5" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -1032,7 +1045,7 @@ test "MLKEM1024-P384 test vector 6" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -1056,7 +1069,7 @@ test "MLKEM1024-P384 test vector 7" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);
@ -1080,7 +1093,7 @@ test "MLKEM1024-P384 test vector 8" {
const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed);
try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes());
const enc_result = try kp.public_key.encaps(&randomness);
const enc_result = try kp.public_key.encapsDeterministic(&randomness);
try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext);
try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret);

View file

@ -2019,12 +2019,9 @@ fn MLDSAImpl(comptime p: Params) type {
secret_key: SecretKey,
/// Generate a new random key pair.
/// This uses the system's cryptographically secure random number generator.
///
/// `crypto.random.bytes` must be supported by the target.
pub fn generate() KeyPair {
pub fn generate(io: std.Io) KeyPair {
var seed: [Self.seed_length]u8 = undefined;
crypto.random.bytes(&seed);
io.random(&seed);
return generateDeterministic(seed) catch unreachable;
}
@ -3198,8 +3195,9 @@ test "ML-DSA-87 KAT test vector 0" {
}
test "KeyPair API - generate and sign" {
const io = std.testing.io;
// Test the new KeyPair API with random generation
const kp = MLDSA44.KeyPair.generate();
const kp = MLDSA44.KeyPair.generate(io);
const msg = "Test message for KeyPair API";
// Sign with deterministic mode (no noise)
@ -3222,8 +3220,9 @@ test "KeyPair API - generateDeterministic" {
}
test "KeyPair API - fromSecretKey" {
const io = std.testing.io;
// Generate a key pair
const kp1 = MLDSA44.KeyPair.generate();
const kp1 = MLDSA44.KeyPair.generate(io);
// Derive public key from secret key
const kp2 = try MLDSA44.KeyPair.fromSecretKey(kp1.secret_key);
@ -3235,8 +3234,9 @@ test "KeyPair API - fromSecretKey" {
}
test "Signature verification with noise" {
const io = std.testing.io;
// Test signing with randomness (hedged signatures)
const kp = MLDSA65.KeyPair.generate();
const kp = MLDSA65.KeyPair.generate(io);
const msg = "Message to be signed with randomness";
// Create some noise
@ -3250,8 +3250,9 @@ test "Signature verification with noise" {
}
test "Signature verification failure" {
const io = std.testing.io;
// Test that invalid signatures are rejected
const kp = MLDSA44.KeyPair.generate();
const kp = MLDSA44.KeyPair.generate(io);
const msg = "Original message";
const sig = try kp.sign(msg, null);

View file

@ -244,32 +244,41 @@ fn Kyber(comptime p: Params) type {
/// Size of a serialized representation of the key, in bytes.
pub const encoded_length = InnerPk.encoded_length;
/// Generates a shared secret, and encapsulates it for the public key.
/// If `seed` is `null`, a random seed is used. This is recommended.
/// If `seed` is set, encapsulation is deterministic.
pub fn encaps(pk: PublicKey, seed_: ?[encaps_seed_length]u8) EncapsulatedSecret {
/// Generates a shared secret, encapsulated for the public key,
/// using random bytes.
///
/// This is recommended over `encapsDeterministic`.
pub fn encaps(pk: PublicKey, io: std.Io) EncapsulatedSecret {
var m: [inner_plaintext_length]u8 = undefined;
io.random(&m);
return encapsInner(pk, &m);
}
if (seed_) |seed| {
if (p.ml_kem) {
@memcpy(&m, &seed);
} else {
// m = H(seed)
sha3.Sha3_256.hash(&seed, &m, .{});
}
/// Generates a shared secret, encapsulated for the public key,
/// using the provided seed.
///
/// Calling `encaps` instead is recommended.
pub fn encapsDeterministic(pk: PublicKey, seed: *const [encaps_seed_length]u8) EncapsulatedSecret {
var m: [inner_plaintext_length]u8 = undefined;
if (p.ml_kem) {
@memcpy(&m, seed);
} else {
crypto.random.bytes(&m);
// m = H(seed)
sha3.Sha3_256.hash(seed, &m, .{});
}
return encapsInner(pk, &m);
}
fn encapsInner(pk: PublicKey, m: *[inner_plaintext_length]u8) EncapsulatedSecret {
// (K', r) = G(m H(pk))
var kr: [inner_plaintext_length + h_length]u8 = undefined;
var g = sha3.Sha3_512.init(.{});
g.update(&m);
g.update(m);
g.update(&pk.hpk);
g.final(&kr);
// c = innerEncrypt(pk, m, r)
const ct = pk.pk.encrypt(&m, kr[32..64]);
const ct = pk.pk.encrypt(m, kr[32..64]);
if (p.ml_kem) {
return EncapsulatedSecret{
@ -398,10 +407,10 @@ fn Kyber(comptime p: Params) type {
}
/// Generate a new, random key pair.
pub fn generate() KeyPair {
pub fn generate(io: std.Io) KeyPair {
var random_seed: [seed_length]u8 = undefined;
while (true) {
crypto.random.bytes(&random_seed);
io.random(&random_seed);
return generateDeterministic(random_seed) catch {
@branchHint(.unlikely);
continue;
@ -1634,15 +1643,15 @@ test "Test happy flow" {
}
inline for (modes) |mode| {
for (0..10) |i| {
seed[0] = @as(u8, @intCast(i));
seed[0] = @intCast(i);
const kp = try mode.KeyPair.generateDeterministic(seed);
const sk = try mode.SecretKey.fromBytes(&kp.secret_key.toBytes());
try testing.expectEqual(sk, kp.secret_key);
const pk = try mode.PublicKey.fromBytes(&kp.public_key.toBytes());
try testing.expectEqual(pk, kp.public_key);
for (0..10) |j| {
seed[1] = @as(u8, @intCast(j));
const e = pk.encaps(seed[0..32].*);
seed[1] = @intCast(j);
const e = pk.encapsDeterministic(seed[0..32]);
try testing.expectEqual(e.shared_secret, try sk.decaps(&e.ciphertext));
}
}
@ -1695,7 +1704,7 @@ fn testNistKat(mode: type, hash: []const u8) !void {
g2.fill(kseed[32..64]);
g2.fill(&eseed);
const kp = try mode.KeyPair.generateDeterministic(kseed);
const e = kp.public_key.encaps(eseed);
const e = kp.public_key.encapsDeterministic(&eseed);
const ss2 = try kp.secret_key.decaps(&e.ciphertext);
try testing.expectEqual(ss2, e.shared_secret);
try fw.writer.print("pk = {X}\n", .{&kp.public_key.toBytes()});

View file

@ -122,8 +122,8 @@ pub const P256 = struct {
}
/// Return a random point.
pub fn random() P256 {
const n = scalar.random(.little);
pub fn random(io: std.Io) P256 {
const n = scalar.random(io, .little);
return basePoint.mul(n, .little) catch unreachable;
}

View file

@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
}
/// Return a random scalar
pub fn random(endian: std.builtin.Endian) CompressedScalar {
return Scalar.random().toBytes(endian);
pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
return Scalar.random(io).toBytes(endian);
}
/// A scalar in unpacked representation.
@ -170,10 +170,10 @@ pub const Scalar = struct {
}
/// Return a random scalar < L.
pub fn random() Scalar {
pub fn random(io: std.Io) Scalar {
var s: [48]u8 = undefined;
while (true) {
crypto.random.bytes(&s);
io.random(&s);
const n = Scalar.fromBytes48(s, .little);
if (!n.isZero()) {
return n;

View file

@ -122,8 +122,8 @@ pub const P384 = struct {
}
/// Return a random point.
pub fn random() P384 {
const n = scalar.random(.little);
pub fn random(io: std.Io) P384 {
const n = scalar.random(io, .little);
return basePoint.mul(n, .little) catch unreachable;
}

View file

@ -63,8 +63,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
}
/// Return a random scalar
pub fn random(endian: std.builtin.Endian) CompressedScalar {
return Scalar.random().toBytes(endian);
pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
return Scalar.random(io).toBytes(endian);
}
/// A scalar in unpacked representation.
@ -159,10 +159,10 @@ pub const Scalar = struct {
}
/// Return a random scalar < L.
pub fn random() Scalar {
pub fn random(io: std.Io) Scalar {
var s: [64]u8 = undefined;
while (true) {
crypto.random.bytes(&s);
io.random(&s);
const n = Scalar.fromBytes64(s, .little);
if (!n.isZero()) {
return n;

View file

@ -175,8 +175,8 @@ pub const Secp256k1 = struct {
}
/// Return a random point.
pub fn random() Secp256k1 {
const n = scalar.random(.little);
pub fn random(io: std.Io) Secp256k1 {
const n = scalar.random(io, .little);
return basePoint.mul(n, .little) catch unreachable;
}

View file

@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian)
}
/// Return a random scalar
pub fn random(endian: std.builtin.Endian) CompressedScalar {
return Scalar.random().toBytes(endian);
pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar {
return Scalar.random(io).toBytes(endian);
}
/// A scalar in unpacked representation.
@ -170,10 +170,10 @@ pub const Scalar = struct {
}
/// Return a random scalar < L.
pub fn random() Scalar {
pub fn random(io: std.Io) Scalar {
var s: [48]u8 = undefined;
while (true) {
crypto.random.bytes(&s);
io.random(&s);
const n = Scalar.fromBytes48(s, .little);
if (!n.isZero()) {
return n;

View file

@ -5,8 +5,9 @@ const testing = std.testing;
const P256 = @import("../p256.zig").P256;
test "p256 ECDH key exchange" {
const dha = P256.scalar.random(.little);
const dhb = P256.scalar.random(.little);
const io = testing.io;
const dha = P256.scalar.random(io, .little);
const dhb = P256.scalar.random(io, .little);
const dhA = try P256.basePoint.mul(dha, .little);
const dhB = try P256.basePoint.mul(dhb, .little);
const shareda = try dhA.mul(dhb, .little);
@ -66,28 +67,32 @@ test "p256 test vectors - doubling" {
}
test "p256 compressed sec1 encoding/decoding" {
const p = P256.random();
const io = testing.io;
const p = P256.random(io);
const s = p.toCompressedSec1();
const q = try P256.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "p256 uncompressed sec1 encoding/decoding" {
const p = P256.random();
const io = testing.io;
const p = P256.random(io);
const s = p.toUncompressedSec1();
const q = try P256.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "p256 public key is the neutral element" {
const io = testing.io;
const n = P256.scalar.Scalar.zero.toBytes(.little);
const p = P256.random();
const p = P256.random(io);
try testing.expectError(error.IdentityElement, p.mul(n, .little));
}
test "p256 public key is the neutral element (public verification)" {
const io = testing.io;
const n = P256.scalar.Scalar.zero.toBytes(.little);
const p = P256.random();
const p = P256.random(io);
try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
}

View file

@ -5,8 +5,9 @@ const testing = std.testing;
const P384 = @import("../p384.zig").P384;
test "p384 ECDH key exchange" {
const dha = P384.scalar.random(.little);
const dhb = P384.scalar.random(.little);
const io = testing.io;
const dha = P384.scalar.random(io, .little);
const dhb = P384.scalar.random(io, .little);
const dhA = try P384.basePoint.mul(dha, .little);
const dhB = try P384.basePoint.mul(dhb, .little);
const shareda = try dhA.mul(dhb, .little);
@ -67,7 +68,8 @@ test "p384 test vectors - doubling" {
}
test "p384 compressed sec1 encoding/decoding" {
const p = P384.random();
const io = testing.io;
const p = P384.random(io);
const s0 = p.toUncompressedSec1();
const s = p.toCompressedSec1();
try testing.expectEqualSlices(u8, s0[1..49], s[1..49]);
@ -76,21 +78,24 @@ test "p384 compressed sec1 encoding/decoding" {
}
test "p384 uncompressed sec1 encoding/decoding" {
const p = P384.random();
const io = testing.io;
const p = P384.random(io);
const s = p.toUncompressedSec1();
const q = try P384.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "p384 public key is the neutral element" {
const io = testing.io;
const n = P384.scalar.Scalar.zero.toBytes(.little);
const p = P384.random();
const p = P384.random(io);
try testing.expectError(error.IdentityElement, p.mul(n, .little));
}
test "p384 public key is the neutral element (public verification)" {
const io = testing.io;
const n = P384.scalar.Scalar.zero.toBytes(.little);
const p = P384.random();
const p = P384.random(io);
try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
}

View file

@ -5,8 +5,9 @@ const testing = std.testing;
const Secp256k1 = @import("../secp256k1.zig").Secp256k1;
test "secp256k1 ECDH key exchange" {
const dha = Secp256k1.scalar.random(.little);
const dhb = Secp256k1.scalar.random(.little);
const io = testing.io;
const dha = Secp256k1.scalar.random(io, .little);
const dhb = Secp256k1.scalar.random(io, .little);
const dhA = try Secp256k1.basePoint.mul(dha, .little);
const dhB = try Secp256k1.basePoint.mul(dhb, .little);
const shareda = try dhA.mul(dhb, .little);
@ -15,8 +16,9 @@ test "secp256k1 ECDH key exchange" {
}
test "secp256k1 ECDH key exchange including public multiplication" {
const dha = Secp256k1.scalar.random(.little);
const dhb = Secp256k1.scalar.random(.little);
const io = testing.io;
const dha = Secp256k1.scalar.random(io, .little);
const dhb = Secp256k1.scalar.random(io, .little);
const dhA = try Secp256k1.basePoint.mul(dha, .little);
const dhB = try Secp256k1.basePoint.mulPublic(dhb, .little);
const shareda = try dhA.mul(dhb, .little);
@ -77,28 +79,32 @@ test "secp256k1 test vectors - doubling" {
}
test "secp256k1 compressed sec1 encoding/decoding" {
const p = Secp256k1.random();
const io = testing.io;
const p = Secp256k1.random(io);
const s = p.toCompressedSec1();
const q = try Secp256k1.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "secp256k1 uncompressed sec1 encoding/decoding" {
const p = Secp256k1.random();
const io = testing.io;
const p = Secp256k1.random(io);
const s = p.toUncompressedSec1();
const q = try Secp256k1.fromSec1(&s);
try testing.expect(p.equivalent(q));
}
test "secp256k1 public key is the neutral element" {
const io = testing.io;
const n = Secp256k1.scalar.Scalar.zero.toBytes(.little);
const p = Secp256k1.random();
const p = Secp256k1.random(io);
try testing.expectError(error.IdentityElement, p.mul(n, .little));
}
test "secp256k1 public key is the neutral element (public verification)" {
const io = testing.io;
const n = Secp256k1.scalar.Scalar.zero.toBytes(.little);
const p = Secp256k1.random();
const p = Secp256k1.random(io);
try testing.expectError(error.IdentityElement, p.mulPublic(n, .little));
}

View file

@ -533,9 +533,9 @@ pub const SealedBox = struct {
/// Encrypt a message `m` for a recipient whose public key is `public_key`.
/// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
pub fn seal(io: std.Io, c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
debug.assert(c.len == m.len + seal_length);
var ekp = KeyPair.generate();
var ekp = KeyPair.generate(io);
const nonce = createNonce(ekp.public_key, public_key);
c[0..public_length].* = ekp.public_key;
try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);
@ -573,29 +573,31 @@ test "(x)salsa20" {
}
test "xsalsa20poly1305" {
const io = std.testing.io;
var msg: [100]u8 = undefined;
var msg2: [msg.len]u8 = undefined;
var c: [msg.len]u8 = undefined;
var key: [XSalsa20Poly1305.key_length]u8 = undefined;
var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
crypto.random.bytes(&msg);
crypto.random.bytes(&key);
crypto.random.bytes(&nonce);
io.random(&msg);
io.random(&key);
io.random(&nonce);
XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
}
test "xsalsa20poly1305 secretbox" {
const io = std.testing.io;
var msg: [100]u8 = undefined;
var msg2: [msg.len]u8 = undefined;
var key: [XSalsa20Poly1305.key_length]u8 = undefined;
var nonce: [Box.nonce_length]u8 = undefined;
var boxed: [msg.len + Box.tag_length]u8 = undefined;
crypto.random.bytes(&msg);
crypto.random.bytes(&key);
crypto.random.bytes(&nonce);
io.random(&msg);
io.random(&key);
io.random(&nonce);
SecretBox.seal(boxed[0..], msg[0..], nonce, key);
try SecretBox.open(msg2[0..], boxed[0..], nonce, key);
@ -604,15 +606,16 @@ test "xsalsa20poly1305 secretbox" {
test "xsalsa20poly1305 box" {
if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
const io = std.testing.io;
var msg: [100]u8 = undefined;
var msg2: [msg.len]u8 = undefined;
var nonce: [Box.nonce_length]u8 = undefined;
var boxed: [msg.len + Box.tag_length]u8 = undefined;
crypto.random.bytes(&msg);
crypto.random.bytes(&nonce);
io.random(&msg);
io.random(&nonce);
const kp1 = Box.KeyPair.generate();
const kp2 = Box.KeyPair.generate();
const kp1 = Box.KeyPair.generate(io);
const kp2 = Box.KeyPair.generate(io);
try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
}
@ -620,13 +623,14 @@ test "xsalsa20poly1305 box" {
test "xsalsa20poly1305 sealedbox" {
if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
const io = std.testing.io;
var msg: [100]u8 = undefined;
var msg2: [msg.len]u8 = undefined;
var boxed: [msg.len + SealedBox.seal_length]u8 = undefined;
crypto.random.bytes(&msg);
io.random(&msg);
const kp = Box.KeyPair.generate();
try SealedBox.seal(boxed[0..], msg[0..], kp.public_key);
const kp = Box.KeyPair.generate(io);
try SealedBox.seal(io, boxed[0..], msg[0..], kp.public_key);
try SealedBox.open(msg2[0..], boxed[0..], kp);
}

View file

@ -20,7 +20,7 @@ const Error = pwhash.Error;
const max_size = math.maxInt(usize);
const max_int = max_size >> 1;
const default_salt_len = 32;
pub const default_salt_len = 32;
const default_hash_len = 32;
const max_salt_len = 64;
const max_hash_len = 64;
@ -417,10 +417,9 @@ const PhcFormatHasher = struct {
password: []const u8,
params: Params,
buf: []u8,
/// Filled with cryptographically secure entropy.
salt: []const u8,
) HasherError![]const u8 {
var salt: [default_salt_len]u8 = undefined;
crypto.random.bytes(&salt);
var hash: [default_hash_len]u8 = undefined;
try kdf(allocator, &hash, password, &salt, params);
@ -466,9 +465,9 @@ const CryptFormatHasher = struct {
password: []const u8,
params: Params,
buf: []u8,
/// Filled with cryptographically secure entropy.
salt_bin: []const u8,
) HasherError![]const u8 {
var salt_bin: [default_salt_len]u8 = undefined;
crypto.random.bytes(&salt_bin);
const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin);
var hash: [default_hash_len]u8 = undefined;

View file

@ -180,24 +180,24 @@ pub fn declassify(ptr: anytype) void {
}
test eql {
const random = std.crypto.random;
const io = std.testing.io;
const expect = std.testing.expect;
var a: [100]u8 = undefined;
var b: [100]u8 = undefined;
random.bytes(a[0..]);
random.bytes(b[0..]);
io.random(&a);
io.random(&b);
try expect(!eql([100]u8, a, b));
a = b;
try expect(eql([100]u8, a, b));
}
test "eql (vectors)" {
const random = std.crypto.random;
const io = std.testing.io;
const expect = std.testing.expect;
var a: [100]u8 = undefined;
var b: [100]u8 = undefined;
random.bytes(a[0..]);
random.bytes(b[0..]);
io.random(&a);
io.random(&b);
const v1: @Vector(100, u8) = a;
const v2: @Vector(100, u8) = b;
try expect(!eql(@Vector(100, u8), v1, v2));
@ -220,9 +220,10 @@ test compare {
}
test "add and sub" {
const io = std.testing.io;
const expectEqual = std.testing.expectEqual;
const expectEqualSlices = std.testing.expectEqualSlices;
const random = std.crypto.random;
const len = 32;
var a: [len]u8 = undefined;
var b: [len]u8 = undefined;
@ -230,8 +231,8 @@ test "add and sub" {
const zero = [_]u8{0} ** len;
var iterations: usize = 100;
while (iterations != 0) : (iterations -= 1) {
random.bytes(&a);
random.bytes(&b);
io.random(&a);
io.random(&b);
const endian = if (iterations % 2 == 0) Endian.big else Endian.little;
_ = sub(u8, &a, &b, &c, endian); // a-b
_ = add(u8, &c, &b, &c, endian); // (a-b)+b
@ -243,11 +244,11 @@ test "add and sub" {
}
test classify {
const random = std.crypto.random;
const io = std.testing.io;
const expect = std.testing.expect;
var secret: [32]u8 = undefined;
random.bytes(&secret);
io.random(&secret);
// Input of the hash function is marked as secret
classify(&secret);

View file

@ -1,169 +0,0 @@
//! Thread-local cryptographically secure pseudo-random number generator.
//! This file has public declarations that are intended to be used internally
//! by the standard library; this namespace is not intended to be exposed
//! directly to standard library users.
const std = @import("std");
const builtin = @import("builtin");
const mem = std.mem;
const native_os = builtin.os.tag;
const posix = std.posix;
/// We use this as a layer of indirection because global const pointers cannot
/// point to thread-local variables.
pub const interface: std.Random = .{
.ptr = undefined,
.fillFn = tlsCsprngFill,
};
const os_has_fork = @TypeOf(posix.fork) != void;
const os_has_arc4random = builtin.link_libc and (@TypeOf(std.c.arc4random_buf) != void);
const want_fork_safety = os_has_fork and !os_has_arc4random and std.options.crypto_fork_safety;
const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{
.major = 4,
.minor = 14,
.patch = 0,
}) orelse true;
const Rng = std.Random.DefaultCsprng;
const Context = struct {
init_state: enum(u8) { uninitialized = 0, initialized, failed },
rng: Rng,
};
var install_atfork_handler = std.once(struct {
// Install the global handler only once.
// The same handler is shared among threads and is inherinted by fork()-ed
// processes.
fn do() void {
const r = std.c.pthread_atfork(null, null, childAtForkHandler);
std.debug.assert(r == 0);
}
}.do);
threadlocal var wipe_mem: []align(std.heap.page_size_min) u8 = &[_]u8{};
fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
if (os_has_arc4random) {
// arc4random is already a thread-local CSPRNG.
return std.c.arc4random_buf(buffer.ptr, buffer.len);
}
// Allow applications to decide they would prefer to have every call to
// std.crypto.random always make an OS syscall, rather than rely on an
// application implementation of a CSPRNG.
if (std.options.crypto_always_getrandom) {
return std.options.cryptoRandomSeed(buffer);
}
if (wipe_mem.len == 0) {
// Not initialized yet.
if (want_fork_safety and maybe_have_wipe_on_fork) {
// Allocate a per-process page, madvise operates with page
// granularity.
wipe_mem = posix.mmap(
null,
@sizeOf(Context),
posix.PROT.READ | posix.PROT.WRITE,
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
-1,
0,
) catch {
// Could not allocate memory for the local state, fall back to
// the OS syscall.
return std.options.cryptoRandomSeed(buffer);
};
// The memory is already zero-initialized.
} else {
// Use a static thread-local buffer.
const S = struct {
threadlocal var buf: Context align(std.heap.page_size_min) = .{
.init_state = .uninitialized,
.rng = undefined,
};
};
wipe_mem = mem.asBytes(&S.buf);
}
}
const ctx: *Context = @ptrCast(wipe_mem.ptr);
switch (ctx.init_state) {
.uninitialized => {
if (!want_fork_safety) {
return initAndFill(buffer);
}
if (maybe_have_wipe_on_fork) wof: {
// Qemu user-mode emulation ignores any valid/invalid madvise
// hint and returns success. Check if this is the case by
// passing bogus parameters, we expect EINVAL as result.
if (posix.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| {
break :wof;
} else |_| {}
if (posix.madvise(wipe_mem.ptr, wipe_mem.len, posix.MADV.WIPEONFORK)) |_| {
return initAndFill(buffer);
} else |_| {}
}
if (std.Thread.use_pthreads) {
return setupPthreadAtforkAndFill(buffer);
}
// Since we failed to set up fork safety, we fall back to always
// calling getrandom every time.
ctx.init_state = .failed;
return std.options.cryptoRandomSeed(buffer);
},
.initialized => {
return fillWithCsprng(buffer);
},
.failed => {
if (want_fork_safety) {
return std.options.cryptoRandomSeed(buffer);
} else {
unreachable;
}
},
}
}
fn setupPthreadAtforkAndFill(buffer: []u8) void {
install_atfork_handler.call();
return initAndFill(buffer);
}
fn childAtForkHandler() callconv(.c) void {
// The atfork handler is global, this function may be called after
// fork()-ing threads that never initialized the CSPRNG context.
if (wipe_mem.len == 0) return;
std.crypto.secureZero(u8, wipe_mem);
}
fn fillWithCsprng(buffer: []u8) void {
const ctx: *Context = @ptrCast(wipe_mem.ptr);
return ctx.rng.fill(buffer);
}
pub fn defaultRandomSeed(buffer: []u8) void {
posix.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
}
fn initAndFill(buffer: []u8) void {
var seed: [Rng.secret_seed_length]u8 = undefined;
// Because we panic on getrandom() failing, we provide the opportunity
// to override the default seed function. This also makes
// `std.crypto.random` available on freestanding targets, provided that
// the `std.options.cryptoRandomSeed` function is provided.
std.options.cryptoRandomSeed(&seed);
const ctx: *Context = @ptrCast(wipe_mem.ptr);
ctx.rng = Rng.init(seed);
std.crypto.secureZero(u8, &seed);
// This is at the end so that accidental recursive dependencies result
// in stack overflows instead of invalid random data.
ctx.init_state = .initialized;
return fillWithCsprng(buffer);
}

View file

@ -109,7 +109,7 @@ pub const Options = struct {
read_buffer: []u8,
/// Cryptographically secure random bytes. The pointer is not captured; data is only
/// read during `init`.
entropy: *const [176]u8,
entropy: *const [entropy_len]u8,
/// Current time according to the wall clock / calendar, in seconds.
realtime_now_seconds: i64,
@ -130,6 +130,8 @@ pub const Options = struct {
allow_truncation_attacks: bool = false,
/// Populated when `error.TlsAlert` is returned from `init`.
alert: ?*tls.Alert = null,
pub const entropy_len = 240;
};
const InitError = error{
@ -200,7 +202,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
var server_hello_rand: [32]u8 = undefined;
const legacy_session_id = options.entropy[32..64].*;
var key_share = KeyShare.init(options.entropy[64..176].*) catch |err| switch (err) {
var key_share = KeyShare.init(options.entropy[64..240]) catch |err| switch (err) {
// Only possible to happen if the seed is all zeroes.
error.IdentityElement => return error.InsufficientEntropy,
};
@ -1330,12 +1332,12 @@ const KeyShare = struct {
crypto.dh.X25519.shared_length,
);
fn init(seed: [112]u8) error{IdentityElement}!KeyShare {
fn init(seed: *const [176]u8) error{IdentityElement}!KeyShare {
return .{
.ml_kem768_kp = .generate(),
.secp256r1_kp = try .generateDeterministic(seed[0..32].*),
.secp384r1_kp = try .generateDeterministic(seed[32..80].*),
.x25519_kp = try .generateDeterministic(seed[80..112].*),
.ml_kem768_kp = try .generateDeterministic(seed[0..64].*),
.secp256r1_kp = try .generateDeterministic(seed[64..96].*),
.secp384r1_kp = try .generateDeterministic(seed[96..144].*),
.x25519_kp = try .generateDeterministic(seed[144..176].*),
.sk_buf = undefined,
.sk_len = 0,
};

View file

@ -1022,8 +1022,7 @@ test "Dir.rename directory onto non-empty dir" {
file.close(io);
target_dir.close(io);
// Rename should fail with PathAlreadyExists if target_dir is non-empty
try expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
try expectError(error.DirNotEmpty, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io));
// Ensure the directory was not renamed
var dir = try ctx.dir.openDir(io, test_dir_path, .{});
@ -1651,6 +1650,21 @@ test "AtomicFile" {
\\ this is a test file
;
// link() succeeds with no file already present
{
var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = false });
defer af.deinit(io);
try af.file.writeStreamingAll(io, test_content);
try af.link(io);
}
// link() returns error.PathAlreadyExists if file already present
{
var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = false });
defer af.deinit(io);
try af.file.writeStreamingAll(io, test_content);
try expectError(error.PathAlreadyExists, af.link(io));
}
// replace() succeeds if file already present
{
var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true });
defer af.deinit(io);
@ -1761,7 +1775,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" {
const io = testing.io;
var random_bytes: [12]u8 = undefined;
std.crypto.random.bytes(&random_bytes);
io.random(&random_bytes);
var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
_ = std.fs.base64_encoder.encode(&random_b64, &random_bytes);

View file

@ -321,8 +321,8 @@ pub const Connection = struct {
assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len);
@memcpy(host_buffer, remote_host.bytes);
const tls: *Tls = @ptrCast(base);
var random_buffer: [176]u8 = undefined;
std.crypto.random.bytes(&random_buffer);
var random_buffer: [std.crypto.tls.Client.Options.entropy_len]u8 = undefined;
io.random(&random_buffer);
tls.* = .{
.connection = .{
.client = client,

View file

@ -501,6 +501,15 @@ pub const O = switch (native_arch) {
else => @compileError("missing std.os.linux.O constants for this architecture"),
};
pub const RENAME = packed struct(u32) {
/// Cannot be set together with `EXCHANGE`.
NOREPLACE: bool = false,
/// Cannot be set together with `NOREPLACE`.
EXCHANGE: bool = false,
WHITEOUT: bool = false,
_: u29 = 0,
};
/// Set by startup code, used by `getauxval`.
pub var elf_aux_maybe: ?[*]std.elf.Auxv = null;
@ -1346,9 +1355,22 @@ pub fn rename(old: [*:0]const u8, new: [*:0]const u8) usize {
if (@hasField(SYS, "rename")) {
return syscall2(.rename, @intFromPtr(old), @intFromPtr(new));
} else if (@hasField(SYS, "renameat")) {
return syscall4(.renameat, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new));
return syscall4(
.renameat,
@as(usize, @bitCast(@as(isize, AT.FDCWD))),
@intFromPtr(old),
@as(usize, @bitCast(@as(isize, AT.FDCWD))),
@intFromPtr(new),
);
} else {
return syscall5(.renameat2, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new), 0);
return syscall5(
.renameat2,
@as(usize, @bitCast(@as(isize, AT.FDCWD))),
@intFromPtr(old),
@as(usize, @bitCast(@as(isize, AT.FDCWD))),
@intFromPtr(new),
0,
);
}
}
@ -1373,14 +1395,14 @@ pub fn renameat(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]co
}
}
pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: u32) usize {
pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: RENAME) usize {
return syscall5(
.renameat2,
@as(usize, @bitCast(@as(isize, oldfd))),
@intFromPtr(oldpath),
@as(usize, @bitCast(@as(isize, newfd))),
@intFromPtr(newpath),
flags,
@as(u32, @bitCast(flags)),
);
}

View file

@ -531,13 +531,11 @@ pub fn prepareArea(area: []u8) usize {
};
}
/// The main motivation for the size chosen here is that this is how much ends up being requested for
/// the thread-local variables of the `std.crypto.random` implementation. I'm not sure why it ends up
/// being so much; the struct itself is only 64 bytes. I think it has to do with being page-aligned
/// and LLVM or LLD is not smart enough to lay out the TLS data in a space-conserving way. Anyway, I
/// think it's fine because it's less than 3 pages of memory, and putting it in the ELF like this is
/// equivalent to moving the `mmap` call below into the kernel, avoiding syscall overhead.
var main_thread_area_buffer: [0x2100]u8 align(page_size_min) = undefined;
/// The main motivation for the size chosen here is to be larger than total
/// amount of thread-local variables for most programs. Putting this allocation
/// in the ELF like this is equivalent to moving the `mmap` call below into the
/// kernel, avoiding syscall overhead.
var main_thread_area_buffer: [0x1000]u8 align(page_size_min) = undefined;
/// Computes the layout of the static TLS area, allocates the area, initializes all of its fields,
/// and assigns the architecture-specific value to the TP register.

View file

@ -2647,62 +2647,6 @@ pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInfor
}
}
/// An alternate implementation of ProcessPrng from bcryptprimitives.dll
/// This one has the following differences:
/// * does not heap allocate `buffer`
/// * does not introduce a dependency on bcryptprimitives.dll, which apparently
/// runs a test suite every time it is loaded
/// * reads buffer.len bytes from "\\Device\\CNG" rather than seeding a per-CPU
/// AES csprng with 48 bytes.
pub fn ProcessPrng(buffer: []u8) error{Unexpected}!void {
const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
var nt_name: UNICODE_STRING = .{
.Length = device_path.len * 2,
.MaximumLength = 0,
.Buffer = @constCast(&device_path),
};
var cng_device: HANDLE = undefined;
var io_status_block: IO_STATUS_BLOCK = undefined;
switch (ntdll.NtOpenFile(
&cng_device,
.{
.STANDARD = .{ .SYNCHRONIZE = true },
.SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
},
&.{
.Length = @sizeOf(OBJECT_ATTRIBUTES),
.RootDirectory = null,
.ObjectName = &nt_name,
.Attributes = .{},
.SecurityDescriptor = null,
.SecurityQualityOfService = null,
},
&io_status_block,
.VALID_FLAGS,
.{ .IO = .SYNCHRONOUS_NONALERT },
)) {
.SUCCESS => {},
.OBJECT_NAME_NOT_FOUND => return error.Unexpected, // Observed on wine 10.0
else => |status| return unexpectedStatus(status),
}
defer _ = ntdll.NtClose(cng_device);
switch (ntdll.NtDeviceIoControlFile(
cng_device,
null,
null,
null,
&io_status_block,
IOCTL.KSEC.GEN_RANDOM,
null,
0,
buffer.ptr,
@intCast(buffer.len),
)) {
.SUCCESS => {},
else => |status| return unexpectedStatus(status),
}
}
pub const WaitForSingleObjectError = error{
WaitAbandoned,
WaitTimeOut,
@ -3250,7 +3194,7 @@ pub const RenameError = error{
NetworkNotFound,
AntivirusInterference,
BadPathName,
RenameAcrossMountPoints,
CrossDevice,
} || UnexpectedError;
pub fn RenameFile(
@ -3351,7 +3295,7 @@ pub fn RenameFile(
.ACCESS_DENIED => return error.AccessDenied,
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
.OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
.NOT_SAME_DEVICE => return error.RenameAcrossMountPoints,
.NOT_SAME_DEVICE => return error.CrossDevice,
.OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
.DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists,
.FILE_IS_A_DIRECTORY => return error.IsDir,

View file

@ -361,107 +361,6 @@ pub fn reboot(cmd: RebootCommand) RebootError!void {
}
}
pub const GetRandomError = OpenError;
/// Obtain a series of random bytes. These bytes can be used to seed user-space
/// random number generators or for cryptographic purposes.
/// When linking against libc, this calls the
/// appropriate OS-specific library call. Otherwise it uses the zig standard
/// library implementation.
pub fn getrandom(buffer: []u8) GetRandomError!void {
if (native_os == .windows) {
return windows.ProcessPrng(buffer);
}
if (builtin.link_libc and @TypeOf(system.arc4random_buf) != void) {
system.arc4random_buf(buffer.ptr, buffer.len);
return;
}
if (native_os == .wasi) switch (wasi.random_get(buffer.ptr, buffer.len)) {
.SUCCESS => return,
else => |err| return unexpectedErrno(err),
};
if (@TypeOf(system.getrandom) != void) {
var buf = buffer;
const use_c = native_os != .linux or
std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 28, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 25, .patch = 0 });
while (buf.len != 0) {
const num_read: usize, const err = if (use_c) res: {
const rc = std.c.getrandom(buf.ptr, buf.len, 0);
break :res .{ @bitCast(rc), errno(rc) };
} else res: {
const rc = linux.getrandom(buf.ptr, buf.len, 0);
break :res .{ rc, linux.errno(rc) };
};
switch (err) {
.SUCCESS => buf = buf[num_read..],
.INVAL => unreachable,
.FAULT => unreachable,
.INTR => continue,
else => return unexpectedErrno(err),
}
}
return;
}
if (native_os == .emscripten) {
const err = errno(std.c.getentropy(buffer.ptr, buffer.len));
switch (err) {
.SUCCESS => return,
else => return unexpectedErrno(err),
}
}
return getRandomBytesDevURandom(buffer);
}
fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {
const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
defer close(fd);
switch (native_os) {
.linux => {
var stx = std.mem.zeroes(linux.Statx);
const rc = linux.statx(
fd,
"",
linux.AT.EMPTY_PATH,
.{ .TYPE = true },
&stx,
);
switch (errno(rc)) {
.SUCCESS => {},
.ACCES => unreachable,
.BADF => unreachable,
.FAULT => unreachable,
.INVAL => unreachable,
.LOOP => unreachable,
.NAMETOOLONG => unreachable,
.NOENT => unreachable,
.NOMEM => return error.SystemResources,
.NOTDIR => unreachable,
else => |err| return unexpectedErrno(err),
}
if (!S.ISCHR(stx.mode)) {
return error.NoDevice;
}
},
else => {
const st = fstat(fd) catch |err| switch (err) {
error.Streaming => return error.NoDevice,
else => |e| return e,
};
if (!S.ISCHR(st.mode)) {
return error.NoDevice;
}
},
}
var i: usize = 0;
while (i < buf.len) {
i += read(fd, buf[i..]) catch return error.Unexpected;
}
}
pub const RaiseError = UnexpectedError;
pub fn raise(sig: SIG) RaiseError!void {
@ -1695,7 +1594,7 @@ pub const FanotifyMarkError = error{
NotDir,
OperationUnsupported,
PermissionDenied,
NotSameFileSystem,
CrossDevice,
NameTooLong,
} || UnexpectedError;
@ -1735,7 +1634,7 @@ pub fn fanotify_markZ(
.NOTDIR => return error.NotDir,
.OPNOTSUPP => return error.OperationUnsupported,
.PERM => return error.PermissionDenied,
.XDEV => return error.NotSameFileSystem,
.XDEV => return error.CrossDevice,
else => |err| return unexpectedErrno(err),
}
}

View file

@ -33,16 +33,6 @@ test "check WASI CWD" {
}
}
test "getrandom" {
var buf_a: [50]u8 = undefined;
var buf_b: [50]u8 = undefined;
try posix.getrandom(&buf_a);
try posix.getrandom(&buf_b);
// If this test fails the chance is significantly higher that there is a bug than
// that two sets of 50 bytes were equal.
try expect(!mem.eql(u8, &buf_a, &buf_b));
}
test "getuid" {
if (native_os == .windows or native_os == .wasi) return error.SkipZigTest;
_ = posix.getuid();

View file

@ -137,12 +137,6 @@ pub const Options = struct {
fmt_max_depth: usize = fmt.default_max_depth,
cryptoRandomSeed: fn (buffer: []u8) void = @import("crypto/tlcsprng.zig").defaultRandomSeed,
crypto_always_getrandom: bool = false,
crypto_fork_safety: bool = true,
/// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
/// disable TLS support.
///

View file

@ -631,7 +631,7 @@ pub const TmpDir = struct {
pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir {
comptime assert(builtin.is_test);
var random_bytes: [TmpDir.random_bytes_count]u8 = undefined;
std.crypto.random.bytes(&random_bytes);
io.random(&random_bytes);
var sub_path: [TmpDir.sub_path_len]u8 = undefined;
_ = std.fs.base64_encoder.encode(&sub_path, &random_bytes);

View file

@ -2942,7 +2942,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
.none => |none| {
assert(none.tmp_artifact_directory == null);
none.tmp_artifact_directory = d: {
tmp_dir_rand_int = std.crypto.random.int(u64);
io.random(@ptrCast(&tmp_dir_rand_int));
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| {
@ -3023,7 +3023,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
// Compile the artifacts to a temporary directory.
whole.tmp_artifact_directory = d: {
tmp_dir_rand_int = std.crypto.random.int(u64);
io.random(@ptrCast(&tmp_dir_rand_int));
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| {
@ -3460,7 +3460,7 @@ fn renameTmpIntoCache(
},
else => return error.AccessDenied,
},
error.PathAlreadyExists => {
error.DirNotEmpty => {
try cache_directory.handle.deleteTree(io, o_sub_path);
continue;
},
@ -5759,7 +5759,11 @@ pub fn translateC(
const gpa = comp.gpa;
const io = comp.io;
const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
const tmp_basename = r: {
var x: u64 = undefined;
io.random(@ptrCast(&x));
break :r std.fmt.hex(x);
};
const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
const cache_dir = comp.dirs.local_cache.handle;
var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{});
@ -6889,8 +6893,13 @@ fn spawnZigRc(
}
pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
const io = comp.io;
const rand_int = r: {
var x: u64 = undefined;
io.random(@ptrCast(&x));
break :r x;
};
const s = fs.path.sep_str;
const rand_int = std.crypto.random.int(u64);
if (comp.dirs.local_cache.path) |p| {
return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix });
} else {

View file

@ -14,9 +14,9 @@ pub const Fingerprint = packed struct(u64) {
id: u32,
checksum: u32,
pub fn generate(name: []const u8) Fingerprint {
pub fn generate(rng: std.Random, name: []const u8) Fingerprint {
return .{
.id = std.crypto.random.intRangeLessThan(u32, 1, 0xffffffff),
.id = rng.intRangeLessThan(u32, 1, 0xffffffff),
.checksum = std.hash.Crc32.hash(name),
};
}

View file

@ -494,7 +494,11 @@ fn runResource(
const eb = &f.error_bundle;
const s = fs.path.sep_str;
const cache_root = f.job_queue.global_cache;
const rand_int = std.crypto.random.int(u64);
const rand_int = r: {
var x: u64 = undefined;
io.random(@ptrCast(&x));
break :r x;
};
const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(rand_int);
const package_sub_path = blk: {
@ -690,7 +694,9 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void {
return error.FetchFailed;
}
f.manifest = try Manifest.parse(arena, ast.*, .{
const rng: std.Random.IoSource = .{ .io = io };
f.manifest = try Manifest.parse(arena, ast.*, rng.interface(), .{
.allow_missing_paths_field = f.allow_missing_paths_field,
.allow_missing_fingerprint = f.allow_missing_fingerprint,
.allow_name_string = f.allow_name_string,
@ -1305,7 +1311,11 @@ fn unzip(
zip_path[prefix.len + random_len ..].* = suffix.*;
var zip_file = while (true) {
const random_integer = std.crypto.random.int(u64);
const random_integer = r: {
var x: u64 = undefined;
io.random(@ptrCast(&x));
break :r x;
};
zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer);
break cache_root.handle.createFile(io, &zip_path, .{
@ -1466,7 +1476,7 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u
};
continue;
},
error.PathAlreadyExists, error.AccessDenied => {
error.DirNotEmpty, error.AccessDenied => {
// Package has been already downloaded and may already be in use on the system.
cache_dir.deleteTree(io, tmp_dir_sub_path) catch {
// Garbage files leftover in zig-cache/tmp/ is, as they say

View file

@ -57,7 +57,7 @@ pub const ParseOptions = struct {
pub const Error = Allocator.Error;
pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest {
pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) Error!Manifest {
const main_node_index = ast.nodeData(.root).node;
var arena_instance = std.heap.ArenaAllocator.init(gpa);
@ -87,7 +87,7 @@ pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest {
defer p.dependencies.deinit(gpa);
defer p.paths.deinit(gpa);
p.parseRoot(main_node_index) catch |err| switch (err) {
p.parseRoot(main_node_index, rng) catch |err| switch (err) {
error.ParseFailure => assert(p.errors.items.len > 0),
else => |e| return e,
};
@ -157,7 +157,7 @@ const Parse = struct {
const InnerError = error{ ParseFailure, OutOfMemory };
fn parseRoot(p: *Parse, node: Ast.Node.Index) !void {
fn parseRoot(p: *Parse, node: Ast.Node.Index, rng: std.Random) !void {
const ast = p.ast;
const main_token = ast.nodeMainToken(node);
@ -217,13 +217,13 @@ const Parse = struct {
if (fingerprint) |n| {
if (!n.validate(p.name)) {
return fail(p, main_token, "invalid fingerprint: 0x{x}; if this is a new or forked package, use this value: 0x{x}", .{
n.int(), Package.Fingerprint.generate(p.name).int(),
n.int(), Package.Fingerprint.generate(rng, p.name).int(),
});
}
p.id = n.id;
} else if (!p.allow_missing_fingerprint) {
try appendError(p, main_token, "missing top-level 'fingerprint' field; suggested value: 0x{x}", .{
Package.Fingerprint.generate(p.name).int(),
Package.Fingerprint.generate(rng, p.name).int(),
});
} else {
p.id = 0;
@ -623,7 +623,9 @@ test "basic" {
try testing.expect(ast.errors.len == 0);
var manifest = try Manifest.parse(gpa, ast, .{});
var rng = std.Random.DefaultPrng.init(0);
var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
defer manifest.deinit(gpa);
try testing.expect(manifest.errors.len == 0);
@ -666,7 +668,9 @@ test "minimum_zig_version" {
try testing.expect(ast.errors.len == 0);
var manifest = try Manifest.parse(gpa, ast, .{});
var rng = std.Random.DefaultPrng.init(0);
var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
defer manifest.deinit(gpa);
try testing.expect(manifest.errors.len == 0);
@ -698,7 +702,9 @@ test "minimum_zig_version - invalid version" {
try testing.expect(ast.errors.len == 0);
var manifest = try Manifest.parse(gpa, ast, .{});
var rng = std.Random.DefaultPrng.init(0);
var manifest = try Manifest.parse(gpa, ast, rng.random(), .{});
defer manifest.deinit(gpa);
try testing.expect(manifest.errors.len == 1);

View file

@ -616,8 +616,13 @@ pub const File = struct {
// it will return ETXTBSY. So instead, we copy the file, atomically rename it
// over top of the exe path, and then proceed normally. This changes the inode,
// avoiding the error.
const random_integer = r: {
var x: u32 = undefined;
io.random(@ptrCast(&x));
break :r x;
};
const tmp_sub_path = try std.fmt.allocPrint(gpa, "{s}-{x}", .{
emit.sub_path, std.crypto.random.int(u32),
emit.sub_path, random_integer,
});
defer gpa.free(tmp_sub_path);
try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{});

View file

@ -1636,7 +1636,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
const err = switch (first_err) {
error.NameTooLong => err: {
const s = fs.path.sep_str;
const rand_int = std.crypto.random.int(u64);
const rand_int = r: {
var x: u64 = undefined;
io.random(@ptrCast(&x));
break :r x;
};
const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";
const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{});

View file

@ -3395,7 +3395,7 @@ fn buildOutputType(
// "-" is stdin. Dump it to a real file.
const sep = fs.path.sep_str;
const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{
std.crypto.random.int(u64), ext.canonicalName(target),
randInt(io, u64), ext.canonicalName(target),
});
try dirs.local_cache.handle.createDirPath(io, "tmp");
@ -4433,7 +4433,7 @@ fn runOrTest(
try argv.append(exe_path);
if (arg_mode == .zig_test) {
try argv.append(
try std.fmt.allocPrint(arena, "--seed=0x{x}", .{std.crypto.random.int(u32)}),
try std.fmt.allocPrint(arena, "--seed=0x{x}", .{randInt(io, u32)}),
);
}
} else {
@ -4763,7 +4763,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
const cwd_basename = fs.path.basename(cwd_path);
const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename);
const fingerprint: Package.Fingerprint = .generate(sanitized_root_name);
const rng: std.Random.IoSource = .{ .io = io };
const fingerprint: Package.Fingerprint = .generate(rng.interface(), sanitized_root_name);
switch (template) {
.example => {
@ -4919,7 +4920,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
try child_argv.appendSlice(&.{
"--seed",
try std.fmt.allocPrint(arena, "0x{x}", .{std.crypto.random.int(u32)}),
try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}),
});
const argv_index_seed = child_argv.items.len - 1;
@ -4937,7 +4938,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
// the strategy is to choose a temporary file name ahead of time, and then
// read this file in the parent to obtain the results, in the case the child
// exits with code 3.
const results_tmp_file_nonce = std.fmt.hex(std.crypto.random.int(u64));
const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64));
try child_argv.append("-Z" ++ results_tmp_file_nonce);
var color: Color = .auto;
@ -7223,7 +7224,7 @@ fn createDependenciesModule(
) !*Package.Module {
// Atomically create the file in a directory named after the hash of its contents.
const basename = "dependencies.zig";
const rand_int = std.crypto.random.int(u64);
const rand_int = randInt(io, u64);
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
{
var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{});
@ -7339,6 +7340,8 @@ fn loadManifest(
io: Io,
options: LoadManifestOptions,
) !struct { Package.Manifest, Ast } {
const rng: std.Random.IoSource = .{ .io = io };
const manifest_bytes = while (true) {
break options.dir.readFileAllocOptions(
io,
@ -7360,15 +7363,13 @@ fn loadManifest(
, .{
options.root_name,
build_options.version,
Package.Fingerprint.generate(options.root_name).int(),
Package.Fingerprint.generate(rng.interface(), options.root_name).int(),
}) catch |e| {
fatal("unable to write {s}: {s}", .{ Package.Manifest.basename, @errorName(e) });
fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e });
};
continue;
},
else => |e| fatal("unable to load {s}: {s}", .{
Package.Manifest.basename, @errorName(e),
}),
else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }),
};
};
var ast = try Ast.parse(gpa, manifest_bytes, .zon);
@ -7379,7 +7380,7 @@ fn loadManifest(
process.exit(2);
}
var manifest = try Package.Manifest.parse(gpa, ast, .{});
var manifest = try Package.Manifest.parse(gpa, ast, rng.interface(), .{});
errdefer manifest.deinit(gpa);
if (manifest.errors.len > 0) {
@ -7632,3 +7633,9 @@ fn setThreadLimit(n: usize) void {
threaded_impl_ptr.setAsyncLimit(limit);
threaded_impl_ptr.concurrent_limit = limit;
}
fn randInt(io: Io, comptime T: type) T {
var x: T = undefined;
io.random(@ptrCast(&x));
return x;
}

View file

@ -10,7 +10,8 @@ pub fn main(init: std.process.Init) !void {
try out.writeAll("Welcome to the Guess Number Game in Zig.\n");
const answer = std.crypto.random.intRangeLessThan(u8, 0, 100) + 1;
var rng: std.Random.IoSource = .{ .io = init.io };
const answer = rng.interface().intRangeLessThan(u8, 0, 100) + 1;
while (true) {
try out.writeAll("\nGuess a number between 1 and 100: ");

View file

@ -52,7 +52,7 @@ pub fn build(b: *std.Build) !void {
const fuzz_seed = b.option(u64, "seed", "Seed to use for the PRNG (default: random)") orelse seed: {
var buf: [8]u8 = undefined;
try std.posix.getrandom(&buf);
b.graph.io.random(&buf);
break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
};
const fuzz_seed_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_seed}) catch @panic("oom");

View file

@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const io = init.io;
const args = try init.minimal.args.toSlice(init.arena.allocator());
if (args.len < 2) return error.MissingArgs;
@ -23,7 +24,7 @@ pub fn main(init: std.process.Init) !void {
if (args.len < 4) {
rand_seed = true;
var buf: [8]u8 = undefined;
try std.posix.getrandom(&buf);
io.random(&buf);
break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
}
break :seed try std.fmt.parseUnsigned(u64, args[3], 10);

View file

@ -65,7 +65,7 @@ pub fn build(b: *std.Build) !void {
const fuzz_seed = b.option(u64, "seed", "Seed to use for the PRNG (default: random)") orelse seed: {
var buf: [8]u8 = undefined;
try std.posix.getrandom(&buf);
b.graph.io.random(&buf);
break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
};
const fuzz_seed_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_seed}) catch @panic("oom");

View file

@ -22,7 +22,7 @@ pub fn main(init: std.process.Init) !void {
const seed_arg = it.next() orelse {
rand_seed = true;
var buf: [8]u8 = undefined;
try std.posix.getrandom(&buf);
io.random(&buf);
break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
};
break :seed try std.fmt.parseUnsigned(u64, seed_arg, 10);

View file

@ -78,9 +78,10 @@ pub fn main(init: std.process.Init) !void {
const code = try parseManifest(arena, source_bytes);
const source = stripManifest(source_bytes);
const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{
cache_root, std.crypto.random.int(u64),
});
var random_integer: u64 = undefined;
io.random(@ptrCast(&random_integer));
const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ cache_root, random_integer });
Dir.cwd().createDirPath(io, tmp_dir_path) catch |err|
fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err });
defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{

View file

@ -100,7 +100,7 @@ pub fn main(init: std.process.Init) !void {
const prog_node = std.Progress.start(io, .{});
defer prog_node.end();
const rand_int = std.crypto.random.int(u64);
const rand_int = rand64(io);
const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{});
defer {
@ -452,20 +452,19 @@ const Eval = struct {
std.debug.assert(eval.target.backend == .sema);
return;
};
const io = eval.io;
const binary_path = switch (eval.target.backend) {
.sema => unreachable,
.selfhosted, .llvm => emitted_path,
.cbe => bin: {
const rand_int = std.crypto.random.int(u64);
const rand_int = rand64(io);
const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);
try eval.buildCOutput(emitted_path, out_bin_name, prog_node);
break :bin out_bin_name;
},
};
const io = eval.io;
var argv_buf: [2][]const u8 = undefined;
const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor(
io,
@ -957,3 +956,9 @@ fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError {
.msg = message,
};
}
fn rand64(io: Io) u64 {
var x: u64 = undefined;
io.random(@ptrCast(&x));
return x;
}