mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 02:24:33 +01:00
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:
commit
62d6bbc7dc
66 changed files with 1301 additions and 803 deletions
10
build.zig
10
build.zig
|
|
@ -593,15 +593,7 @@ pub fn build(b: *std.Build) !void {
|
|||
.x86_64 => 3_756_422_348,
|
||||
else => 3_800_000_000,
|
||||
},
|
||||
.linux => switch (b.graph.host.result.cpu.arch) {
|
||||
.aarch64 => 6_732_817_203,
|
||||
.loongarch64 => 3_216_349_593,
|
||||
.powerpc64le => 3_090_179_276,
|
||||
.riscv64 => 4_052_670_054,
|
||||
.s390x => 3_652_514_201,
|
||||
.x86_64 => 3_249_546_854,
|
||||
else => 6_800_000_000,
|
||||
},
|
||||
.linux => 6_800_000_000,
|
||||
.macos => switch (b.graph.host.result.cpu.arch) {
|
||||
.aarch64 => 8_273_795_481,
|
||||
else => 8_300_000_000,
|
||||
|
|
|
|||
3
lib/compiler/aro/aro/Driver.zig
vendored
3
lib/compiler/aro/aro/Driver.zig
vendored
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}", .{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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()});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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, .{});
|
||||
|
|
|
|||
|
|
@ -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, .{});
|
||||
|
|
|
|||
31
src/main.zig
31
src/main.zig
|
|
@ -3395,7 +3395,7 @@ fn buildOutputType(
|
|||
// "-" is stdin. Dump it to a real file.
|
||||
const sep = fs.path.sep_str;
|
||||
const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{
|
||||
std.crypto.random.int(u64), ext.canonicalName(target),
|
||||
randInt(io, u64), ext.canonicalName(target),
|
||||
});
|
||||
try dirs.local_cache.handle.createDirPath(io, "tmp");
|
||||
|
||||
|
|
@ -4433,7 +4433,7 @@ fn runOrTest(
|
|||
try argv.append(exe_path);
|
||||
if (arg_mode == .zig_test) {
|
||||
try argv.append(
|
||||
try std.fmt.allocPrint(arena, "--seed=0x{x}", .{std.crypto.random.int(u32)}),
|
||||
try std.fmt.allocPrint(arena, "--seed=0x{x}", .{randInt(io, u32)}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -4763,7 +4763,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
|
|||
const cwd_basename = fs.path.basename(cwd_path);
|
||||
const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename);
|
||||
|
||||
const fingerprint: Package.Fingerprint = .generate(sanitized_root_name);
|
||||
const rng: std.Random.IoSource = .{ .io = io };
|
||||
const fingerprint: Package.Fingerprint = .generate(rng.interface(), sanitized_root_name);
|
||||
|
||||
switch (template) {
|
||||
.example => {
|
||||
|
|
@ -4919,7 +4920,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
|
|||
|
||||
try child_argv.appendSlice(&.{
|
||||
"--seed",
|
||||
try std.fmt.allocPrint(arena, "0x{x}", .{std.crypto.random.int(u32)}),
|
||||
try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}),
|
||||
});
|
||||
const argv_index_seed = child_argv.items.len - 1;
|
||||
|
||||
|
|
@ -4937,7 +4938,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8,
|
|||
// the strategy is to choose a temporary file name ahead of time, and then
|
||||
// read this file in the parent to obtain the results, in the case the child
|
||||
// exits with code 3.
|
||||
const results_tmp_file_nonce = std.fmt.hex(std.crypto.random.int(u64));
|
||||
const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64));
|
||||
try child_argv.append("-Z" ++ results_tmp_file_nonce);
|
||||
|
||||
var color: Color = .auto;
|
||||
|
|
@ -7223,7 +7224,7 @@ fn createDependenciesModule(
|
|||
) !*Package.Module {
|
||||
// Atomically create the file in a directory named after the hash of its contents.
|
||||
const basename = "dependencies.zig";
|
||||
const rand_int = std.crypto.random.int(u64);
|
||||
const rand_int = randInt(io, u64);
|
||||
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
|
||||
{
|
||||
var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{});
|
||||
|
|
@ -7339,6 +7340,8 @@ fn loadManifest(
|
|||
io: Io,
|
||||
options: LoadManifestOptions,
|
||||
) !struct { Package.Manifest, Ast } {
|
||||
const rng: std.Random.IoSource = .{ .io = io };
|
||||
|
||||
const manifest_bytes = while (true) {
|
||||
break options.dir.readFileAllocOptions(
|
||||
io,
|
||||
|
|
@ -7360,15 +7363,13 @@ fn loadManifest(
|
|||
, .{
|
||||
options.root_name,
|
||||
build_options.version,
|
||||
Package.Fingerprint.generate(options.root_name).int(),
|
||||
Package.Fingerprint.generate(rng.interface(), options.root_name).int(),
|
||||
}) catch |e| {
|
||||
fatal("unable to write {s}: {s}", .{ Package.Manifest.basename, @errorName(e) });
|
||||
fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e });
|
||||
};
|
||||
continue;
|
||||
},
|
||||
else => |e| fatal("unable to load {s}: {s}", .{
|
||||
Package.Manifest.basename, @errorName(e),
|
||||
}),
|
||||
else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }),
|
||||
};
|
||||
};
|
||||
var ast = try Ast.parse(gpa, manifest_bytes, .zon);
|
||||
|
|
@ -7379,7 +7380,7 @@ fn loadManifest(
|
|||
process.exit(2);
|
||||
}
|
||||
|
||||
var manifest = try Package.Manifest.parse(gpa, ast, .{});
|
||||
var manifest = try Package.Manifest.parse(gpa, ast, rng.interface(), .{});
|
||||
errdefer manifest.deinit(gpa);
|
||||
|
||||
if (manifest.errors.len > 0) {
|
||||
|
|
@ -7632,3 +7633,9 @@ fn setThreadLimit(n: usize) void {
|
|||
threaded_impl_ptr.setAsyncLimit(limit);
|
||||
threaded_impl_ptr.concurrent_limit = limit;
|
||||
}
|
||||
|
||||
fn randInt(io: Io, comptime T: type) T {
|
||||
var x: T = undefined;
|
||||
io.random(@ptrCast(&x));
|
||||
return x;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: ");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}", .{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue