mirror of
https://github.com/jedisct1/libsodium.git
synced 2026-08-25 08:37:13 +09:00
Add test vectors from Rooterberg and Wycheproof test suites
Closes #1517
This commit is contained in:
@@ -71,6 +71,15 @@ jobs:
|
||||
zig build -Doptimize=ReleaseFast
|
||||
rm -fr zig-cache zig-out
|
||||
|
||||
- name: Tests with zig
|
||||
run: |
|
||||
zig build test
|
||||
zig build test-vectors
|
||||
zig build test -Doptimize=ReleaseSafe
|
||||
zig build test-vectors -Doptimize=ReleaseSafe
|
||||
zig build test -Doptimize=ReleaseFast
|
||||
zig build test-vectors -Doptimize=ReleaseFast
|
||||
|
||||
regular:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -193,6 +193,7 @@ test/js.done
|
||||
test/symbols/all-host-symbols.txt
|
||||
test/symbols/internal-host-symbols.txt
|
||||
test/symbols/internal-symbols.txt
|
||||
test/vectors/cache/
|
||||
testing
|
||||
Vagrantfile
|
||||
zig-cache
|
||||
|
||||
@@ -355,4 +355,30 @@ pub fn build(b: *std.Build) !void {
|
||||
test_step.dependOn(&run_test.step);
|
||||
}
|
||||
}
|
||||
|
||||
if (build_tests) {
|
||||
const offline = b.option(bool, "offline", "Skip downloading test vectors; use cached files only") orelse false;
|
||||
const tv_options = b.addOptions();
|
||||
tv_options.addOption(bool, "offline", offline);
|
||||
tv_options.addOption([]const u8, "cache_dir", "test/vectors/cache");
|
||||
|
||||
const tv_mod = b.createModule(.{
|
||||
.root_source_file = b.path("test/vectors/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
});
|
||||
tv_mod.linkLibrary(static_lib);
|
||||
tv_mod.addIncludePath(b.path("src/libsodium/include"));
|
||||
tv_mod.addOptions("build_options", tv_options);
|
||||
|
||||
const tv_exe = b.addExecutable(.{
|
||||
.name = "test-vectors",
|
||||
.root_module = tv_mod,
|
||||
});
|
||||
b.installArtifact(tv_exe);
|
||||
const run_tv = b.addRunArtifact(tv_exe);
|
||||
const tv_step = b.step("test-vectors", "Run external test vectors (Rooterberg)");
|
||||
tv_step.dependOn(&run_tv.step);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,6 +1028,7 @@ AC_CONFIG_FILES([Makefile
|
||||
src/libsodium/include/Makefile
|
||||
src/libsodium/include/sodium/version.h
|
||||
test/default/Makefile
|
||||
test/vectors/Makefile
|
||||
test/Makefile
|
||||
])
|
||||
AC_OUTPUT
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
SUBDIRS = \
|
||||
default
|
||||
default \
|
||||
vectors
|
||||
|
||||
EXTRA_DIST = \
|
||||
quirks/quirks.h
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
EXTRA_DIST = \
|
||||
fetch.zig \
|
||||
main.zig \
|
||||
parse.zig \
|
||||
suites/aead.zig \
|
||||
suites/box.zig \
|
||||
suites/eddsa.zig \
|
||||
suites/hash.zig \
|
||||
suites/kdf.zig \
|
||||
suites/kem.zig \
|
||||
suites/mac.zig \
|
||||
suites/pwhash.zig \
|
||||
suites/secretbox.zig \
|
||||
suites/stream.zig \
|
||||
suites/xdh.zig \
|
||||
suites/xof.zig
|
||||
@@ -0,0 +1,153 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Io = std.Io;
|
||||
const Dir = Io.Dir;
|
||||
|
||||
pub const Source = struct {
|
||||
base_url: []const u8,
|
||||
category: []const u8,
|
||||
filename: []const u8,
|
||||
suite_tag: SuiteTag,
|
||||
};
|
||||
|
||||
pub const SuiteTag = enum {
|
||||
aead_chacha20poly1305_ietf,
|
||||
aead_xchacha20poly1305,
|
||||
aead_aes256gcm,
|
||||
aead_aegis128l,
|
||||
aead_aegis256,
|
||||
hash_sha256,
|
||||
hash_sha512,
|
||||
hash_blake2b,
|
||||
hash_sha3_256,
|
||||
hash_sha3_512,
|
||||
mac_hmacsha256,
|
||||
mac_hmacsha512,
|
||||
mac_siphash24,
|
||||
mac_siphashx24,
|
||||
onetimeauth_poly1305,
|
||||
xdh_x25519,
|
||||
eddsa_ed25519_verify,
|
||||
eddsa_ed25519_cofactored_verify,
|
||||
eddsa_ed25519_sign,
|
||||
secretbox_xsalsa20,
|
||||
secretbox_xchacha20,
|
||||
box_xsalsa20,
|
||||
box_xchacha20,
|
||||
stream_salsa20,
|
||||
stream_xsalsa20,
|
||||
kdf_hkdf_sha256,
|
||||
kdf_hkdf_sha512,
|
||||
pwhash_scrypt,
|
||||
xof_shake128,
|
||||
xof_shake256,
|
||||
kem_mlkem768,
|
||||
};
|
||||
|
||||
const rooterberg_base = "https://raw.githubusercontent.com/bleichenbacher-daniel/Rooterberg/master/test_vectors";
|
||||
const wycheproof_base = "https://raw.githubusercontent.com/C2SP/wycheproof/master/testvectors_v1";
|
||||
|
||||
pub const rooterberg_sources = [_]Source{
|
||||
.{ .base_url = rooterberg_base, .category = "aead", .filename = "chacha20_poly1305.json", .suite_tag = .aead_chacha20poly1305_ietf },
|
||||
.{ .base_url = rooterberg_base, .category = "aead", .filename = "xchacha20_poly1305.json", .suite_tag = .aead_xchacha20poly1305 },
|
||||
.{ .base_url = rooterberg_base, .category = "aead", .filename = "aes_gcm_256_96_128.json", .suite_tag = .aead_aes256gcm },
|
||||
.{ .base_url = rooterberg_base, .category = "aead", .filename = "aegis128_l_256.json", .suite_tag = .aead_aegis128l },
|
||||
.{ .base_url = rooterberg_base, .category = "aead", .filename = "aegis256_256.json", .suite_tag = .aead_aegis256 },
|
||||
.{ .base_url = rooterberg_base, .category = "message_digest", .filename = "sha_256.json", .suite_tag = .hash_sha256 },
|
||||
.{ .base_url = rooterberg_base, .category = "message_digest", .filename = "sha_512.json", .suite_tag = .hash_sha512 },
|
||||
.{ .base_url = rooterberg_base, .category = "message_digest", .filename = "blake2b.json", .suite_tag = .hash_blake2b },
|
||||
.{ .base_url = rooterberg_base, .category = "message_digest", .filename = "sha3_256.json", .suite_tag = .hash_sha3_256 },
|
||||
.{ .base_url = rooterberg_base, .category = "message_digest", .filename = "sha3_512.json", .suite_tag = .hash_sha3_512 },
|
||||
.{ .base_url = rooterberg_base, .category = "mac", .filename = "hmac_sha256_256.json", .suite_tag = .mac_hmacsha256 },
|
||||
.{ .base_url = rooterberg_base, .category = "mac", .filename = "hmac_sha512_512.json", .suite_tag = .mac_hmacsha512 },
|
||||
.{ .base_url = rooterberg_base, .category = "mac", .filename = "sip_hash_2_4.json", .suite_tag = .mac_siphash24 },
|
||||
.{ .base_url = rooterberg_base, .category = "mac", .filename = "sip_hash128_2_4.json", .suite_tag = .mac_siphashx24 },
|
||||
.{ .base_url = rooterberg_base, .category = "one_time_mac", .filename = "poly1305.json", .suite_tag = .onetimeauth_poly1305 },
|
||||
.{ .base_url = rooterberg_base, .category = "xdh", .filename = "x25519.json", .suite_tag = .xdh_x25519 },
|
||||
.{ .base_url = rooterberg_base, .category = "eddsa", .filename = "ed25519.json", .suite_tag = .eddsa_ed25519_verify },
|
||||
.{ .base_url = rooterberg_base, .category = "eddsa", .filename = "ed25519_cofactored.json", .suite_tag = .eddsa_ed25519_cofactored_verify },
|
||||
.{ .base_url = rooterberg_base, .category = "eddsa", .filename = "ed25519_sign.json", .suite_tag = .eddsa_ed25519_sign },
|
||||
.{ .base_url = rooterberg_base, .category = "auth_enc", .filename = "nacl_xsalsa20_poly1305.json", .suite_tag = .secretbox_xsalsa20 },
|
||||
.{ .base_url = rooterberg_base, .category = "auth_enc", .filename = "nacl_xchacha20_poly1305.json", .suite_tag = .secretbox_xchacha20 },
|
||||
.{ .base_url = rooterberg_base, .category = "nacl_crypto_box", .filename = "nacl_crypto_box_curve25519_xsalsa20_poly1305.json", .suite_tag = .box_xsalsa20 },
|
||||
.{ .base_url = rooterberg_base, .category = "nacl_crypto_box", .filename = "nacl_crypto_box_curve25519_xchacha20_poly1305.json", .suite_tag = .box_xchacha20 },
|
||||
.{ .base_url = rooterberg_base, .category = "ind_cpa", .filename = "salsa20_256_64.json", .suite_tag = .stream_salsa20 },
|
||||
.{ .base_url = rooterberg_base, .category = "ind_cpa", .filename = "xsalsa20_256_192.json", .suite_tag = .stream_xsalsa20 },
|
||||
.{ .base_url = rooterberg_base, .category = "kdf", .filename = "hkdf_sha256.json", .suite_tag = .kdf_hkdf_sha256 },
|
||||
.{ .base_url = rooterberg_base, .category = "kdf", .filename = "hkdf_sha512.json", .suite_tag = .kdf_hkdf_sha512 },
|
||||
.{ .base_url = rooterberg_base, .category = "kdf", .filename = "scrypt.json", .suite_tag = .pwhash_scrypt },
|
||||
.{ .base_url = rooterberg_base, .category = "xof", .filename = "shake128.json", .suite_tag = .xof_shake128 },
|
||||
.{ .base_url = rooterberg_base, .category = "xof", .filename = "shake256.json", .suite_tag = .xof_shake256 },
|
||||
.{ .base_url = wycheproof_base, .category = "", .filename = "mlkem_768_test.json", .suite_tag = .kem_mlkem768 },
|
||||
};
|
||||
|
||||
pub fn cacheName(buf: []u8, category: []const u8, filename: []const u8) []const u8 {
|
||||
if (category.len == 0)
|
||||
return std.fmt.bufPrint(buf, "{s}", .{filename}) catch buf[0..0];
|
||||
return std.fmt.bufPrint(buf, "{s}_{s}", .{ category, filename }) catch buf[0..0];
|
||||
}
|
||||
|
||||
pub fn ensureCached(
|
||||
allocator: Allocator,
|
||||
io: Io,
|
||||
cache_dir: Dir,
|
||||
sources: []const Source,
|
||||
offline: bool,
|
||||
) !void {
|
||||
if (offline) return;
|
||||
|
||||
var http_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
defer http_arena.deinit();
|
||||
var client: std.http.Client = .{ .allocator = http_arena.allocator(), .io = io };
|
||||
defer client.deinit();
|
||||
|
||||
for (sources) |src| {
|
||||
var name_buf: [256]u8 = undefined;
|
||||
const name = cacheName(&name_buf, src.category, src.filename);
|
||||
if (cache_dir.openFile(io, name, .{})) |file| {
|
||||
file.close(io);
|
||||
} else |err| {
|
||||
if (err != error.FileNotFound) return err;
|
||||
try download(allocator, &client, io, cache_dir, src, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn download(
|
||||
allocator: Allocator,
|
||||
client: *std.http.Client,
|
||||
io: Io,
|
||||
cache_dir: Dir,
|
||||
src: Source,
|
||||
dest_name: []const u8,
|
||||
) !void {
|
||||
var url_buf: [512]u8 = undefined;
|
||||
const url = (if (src.category.len == 0)
|
||||
std.fmt.bufPrint(&url_buf, "{s}/{s}", .{ src.base_url, src.filename })
|
||||
else
|
||||
std.fmt.bufPrint(&url_buf, "{s}/{s}/{s}", .{ src.base_url, src.category, src.filename })) catch return error.UrlTooLong;
|
||||
|
||||
var body = std.Io.Writer.Allocating.init(allocator);
|
||||
defer body.deinit();
|
||||
|
||||
const result = try client.fetch(.{
|
||||
.location = .{ .url = url },
|
||||
.response_writer = &body.writer,
|
||||
});
|
||||
|
||||
if (result.status != .ok) {
|
||||
std.debug.print("HTTP {d} fetching {s}\n", .{ @intFromEnum(result.status), url });
|
||||
return error.HttpFetchFailed;
|
||||
}
|
||||
|
||||
try cache_dir.writeFile(io, .{ .sub_path = dest_name, .data = body.written() });
|
||||
}
|
||||
|
||||
pub fn loadCached(allocator: Allocator, io: Io, cache_dir: Dir, src: Source) !?[]const u8 {
|
||||
var name_buf: [256]u8 = undefined;
|
||||
const name = cacheName(&name_buf, src.category, src.filename);
|
||||
return cache_dir.readFileAlloc(io, name, allocator, .unlimited) catch |err| {
|
||||
if (err == error.FileNotFound) return null;
|
||||
return err;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Dir = Io.Dir;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const options = @import("build_options");
|
||||
const fetch = @import("fetch.zig");
|
||||
const parse = @import("parse.zig");
|
||||
const aead = @import("suites/aead.zig");
|
||||
const hash = @import("suites/hash.zig");
|
||||
const mac = @import("suites/mac.zig");
|
||||
const xdh = @import("suites/xdh.zig");
|
||||
const eddsa = @import("suites/eddsa.zig");
|
||||
const secretbox = @import("suites/secretbox.zig");
|
||||
const box = @import("suites/box.zig");
|
||||
const stream = @import("suites/stream.zig");
|
||||
const kdf = @import("suites/kdf.zig");
|
||||
const pwhash = @import("suites/pwhash.zig");
|
||||
const xof = @import("suites/xof.zig");
|
||||
const kem = @import("suites/kem.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
fn runSuite(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
return switch (tag) {
|
||||
.aead_chacha20poly1305_ietf,
|
||||
.aead_xchacha20poly1305,
|
||||
.aead_aes256gcm,
|
||||
.aead_aegis128l,
|
||||
.aead_aegis256,
|
||||
=> aead.run(allocator, data, tag),
|
||||
|
||||
.hash_sha256,
|
||||
.hash_sha512,
|
||||
.hash_blake2b,
|
||||
.hash_sha3_256,
|
||||
.hash_sha3_512,
|
||||
=> hash.run(allocator, data, tag),
|
||||
|
||||
.mac_hmacsha256,
|
||||
.mac_hmacsha512,
|
||||
.mac_siphash24,
|
||||
.mac_siphashx24,
|
||||
.onetimeauth_poly1305,
|
||||
=> mac.run(allocator, data, tag),
|
||||
|
||||
.xdh_x25519 => xdh.run(allocator, data),
|
||||
|
||||
.eddsa_ed25519_verify => eddsa.runVerify(allocator, data, false),
|
||||
.eddsa_ed25519_cofactored_verify => eddsa.runVerify(allocator, data, true),
|
||||
.eddsa_ed25519_sign => eddsa.runSign(allocator, data),
|
||||
|
||||
.secretbox_xsalsa20,
|
||||
.secretbox_xchacha20,
|
||||
=> secretbox.run(allocator, data, tag),
|
||||
|
||||
.box_xsalsa20,
|
||||
.box_xchacha20,
|
||||
=> box.run(allocator, data, tag),
|
||||
|
||||
.stream_salsa20,
|
||||
.stream_xsalsa20,
|
||||
=> stream.run(allocator, data, tag),
|
||||
|
||||
.kdf_hkdf_sha256,
|
||||
.kdf_hkdf_sha512,
|
||||
=> kdf.run(allocator, data, tag),
|
||||
|
||||
.pwhash_scrypt => pwhash.run(allocator, data),
|
||||
|
||||
.xof_shake128,
|
||||
.xof_shake256,
|
||||
=> xof.run(allocator, data, tag),
|
||||
|
||||
.kem_mlkem768 => kem.run(allocator, data),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn main(init: std.process.Init) !void {
|
||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||
defer if (debug_allocator.deinit() == .leak) @panic("memory leak detected");
|
||||
const allocator = debug_allocator.allocator();
|
||||
const io = init.io;
|
||||
|
||||
if (c.sodium_init() < 0) return error.SodiumInitFailed;
|
||||
|
||||
const cache_dir = Dir.cwd().createDirPathOpen(io, options.cache_dir, .{}) catch |err| {
|
||||
std.debug.print("Cannot open cache directory '{s}': {}\n", .{ options.cache_dir, err });
|
||||
return err;
|
||||
};
|
||||
|
||||
fetch.ensureCached(allocator, io, cache_dir, &fetch.rooterberg_sources, options.offline) catch |err| {
|
||||
std.debug.print("Fetch failed: {}\n", .{err});
|
||||
if (!options.offline) return err;
|
||||
};
|
||||
|
||||
var total = Result{};
|
||||
var suite_count: u32 = 0;
|
||||
var suite_fail: u32 = 0;
|
||||
|
||||
for (&fetch.rooterberg_sources) |*src| {
|
||||
const data = fetch.loadCached(allocator, io, cache_dir, src.*) catch |err| {
|
||||
std.debug.print("Cannot read {s}/{s}: {}\n", .{ src.category, src.filename, err });
|
||||
total.skipped += 1;
|
||||
continue;
|
||||
} orelse {
|
||||
if (!options.offline) {
|
||||
std.debug.print("SKIP {s}/{s}: not cached\n", .{ src.category, src.filename });
|
||||
}
|
||||
total.skipped += 1;
|
||||
continue;
|
||||
};
|
||||
defer allocator.free(data);
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const result = runSuite(arena.allocator(), data, src.suite_tag) catch |err| {
|
||||
std.debug.print("ERROR {s}/{s}: {}\n", .{ src.category, src.filename, err });
|
||||
suite_fail += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
suite_count += 1;
|
||||
total.passed += result.passed;
|
||||
total.failed += result.failed;
|
||||
total.skipped += result.skipped;
|
||||
|
||||
const status: []const u8 = if (result.failed > 0) "FAIL" else "ok";
|
||||
std.debug.print("{s}: {s}/{s} -- {d} passed, {d} failed, {d} skipped\n", .{
|
||||
status,
|
||||
src.category,
|
||||
src.filename,
|
||||
result.passed,
|
||||
result.failed,
|
||||
result.skipped,
|
||||
});
|
||||
}
|
||||
|
||||
std.debug.print("\n{d} suites, {d} passed, {d} failed, {d} skipped\n", .{
|
||||
suite_count,
|
||||
total.passed,
|
||||
total.failed,
|
||||
total.skipped,
|
||||
});
|
||||
|
||||
if (total.failed > 0 or suite_fail > 0) {
|
||||
return error.TestsFailed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
const std = @import("std");
|
||||
const json = std.json;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const Validity = enum { valid, invalid, acceptable };
|
||||
|
||||
pub const Result = struct {
|
||||
passed: u32 = 0,
|
||||
failed: u32 = 0,
|
||||
skipped: u32 = 0,
|
||||
};
|
||||
|
||||
pub const AeadVector = struct {
|
||||
tc_id: u32,
|
||||
key: []const u8,
|
||||
nonce: []const u8,
|
||||
aad: []const u8,
|
||||
msg: []const u8,
|
||||
ct: []const u8,
|
||||
tag: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const HashVector = struct {
|
||||
tc_id: u32,
|
||||
msg: []const u8,
|
||||
digest: []const u8,
|
||||
out_len: usize,
|
||||
key: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const MacVector = struct {
|
||||
tc_id: u32,
|
||||
key: []const u8,
|
||||
msg: []const u8,
|
||||
mac: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const XdhVector = struct {
|
||||
tc_id: u32,
|
||||
public_key: []const u8,
|
||||
private_key: []const u8,
|
||||
shared: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const EddsaVerifyVector = struct {
|
||||
tc_id: u32,
|
||||
public_key: []const u8,
|
||||
msg: []const u8,
|
||||
sig: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const EddsaSignVector = struct {
|
||||
tc_id: u32,
|
||||
private_key: []const u8,
|
||||
public_key: []const u8,
|
||||
msg: []const u8,
|
||||
sig: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const AuthEncVector = struct {
|
||||
tc_id: u32,
|
||||
key: []const u8,
|
||||
nonce: []const u8,
|
||||
msg: []const u8,
|
||||
ct: []const u8,
|
||||
tag: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const BoxVector = struct {
|
||||
tc_id: u32,
|
||||
public_key: []const u8,
|
||||
private_key: []const u8,
|
||||
nonce: []const u8,
|
||||
msg: []const u8,
|
||||
ct: []const u8,
|
||||
validity: Validity,
|
||||
shared_zero: bool,
|
||||
};
|
||||
|
||||
pub const StreamVector = struct {
|
||||
tc_id: u32,
|
||||
key: []const u8,
|
||||
nonce: []const u8,
|
||||
msg: []const u8,
|
||||
ct: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const HkdfVector = struct {
|
||||
tc_id: u32,
|
||||
ikm: []const u8,
|
||||
salt: []const u8,
|
||||
info: []const u8,
|
||||
out_len: usize,
|
||||
okm: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const ScryptVector = struct {
|
||||
tc_id: u32,
|
||||
password: []const u8,
|
||||
salt: []const u8,
|
||||
n: u64,
|
||||
r: u32,
|
||||
p: u32,
|
||||
dk_len: usize,
|
||||
dk: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const XofVector = struct {
|
||||
tc_id: u32,
|
||||
msg: []const u8,
|
||||
out_len: usize,
|
||||
output: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
pub const MlkemVector = struct {
|
||||
tc_id: u32,
|
||||
seed: []const u8,
|
||||
ek: []const u8,
|
||||
ct: []const u8,
|
||||
ss: []const u8,
|
||||
validity: Validity,
|
||||
};
|
||||
|
||||
const TestFile = struct {
|
||||
parsed: json.Parsed(json.Value),
|
||||
root: json.ObjectMap,
|
||||
tests: []const json.Value,
|
||||
};
|
||||
|
||||
fn openTestFile(allocator: Allocator, data: []const u8) !TestFile {
|
||||
const parsed = try json.parseFromSlice(json.Value, allocator, data, .{
|
||||
.ignore_unknown_fields = true,
|
||||
});
|
||||
const root = getObj(parsed.value) orelse return error.InvalidVector;
|
||||
const tests = getArr(root.get("tests") orelse return error.InvalidVector) orelse return error.InvalidVector;
|
||||
return .{ .parsed = parsed, .root = root, .tests = tests };
|
||||
}
|
||||
|
||||
fn getObj(val: json.Value) ?json.ObjectMap {
|
||||
return switch (val) {
|
||||
.object => |o| o,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn getArr(val: json.Value) ?[]const json.Value {
|
||||
return switch (val) {
|
||||
.array => |a| a.items,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn getStr(obj: json.ObjectMap, key: []const u8) ?[]const u8 {
|
||||
const v = obj.get(key) orelse return null;
|
||||
return switch (v) {
|
||||
.string => |s| s,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn getInt(obj: json.ObjectMap, key: []const u8) ?i64 {
|
||||
const v = obj.get(key) orelse return null;
|
||||
return switch (v) {
|
||||
.integer => |i| i,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn getBool(obj: json.ObjectMap, key: []const u8) ?bool {
|
||||
const v = obj.get(key) orelse return null;
|
||||
return switch (v) {
|
||||
.bool => |b| b,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn hasFlag(obj: json.ObjectMap, flag: []const u8) bool {
|
||||
const flags = switch (obj.get("flags") orelse return false) {
|
||||
.array => |a| a.items,
|
||||
else => return false,
|
||||
};
|
||||
for (flags) |f| {
|
||||
const s = switch (f) {
|
||||
.string => |s| s,
|
||||
else => continue,
|
||||
};
|
||||
if (std.mem.eql(u8, s, flag)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn hexField(allocator: Allocator, obj: json.ObjectMap, key: []const u8) ![]const u8 {
|
||||
const s = getStr(obj, key) orelse return &.{};
|
||||
const out = try allocator.alloc(u8, s.len / 2);
|
||||
return std.fmt.hexToBytes(out, s) catch |err| {
|
||||
allocator.free(out);
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
fn rooterbergValidity(obj: json.ObjectMap) Validity {
|
||||
const v = getBool(obj, "valid") orelse return .valid;
|
||||
return if (v) .valid else .invalid;
|
||||
}
|
||||
|
||||
pub fn loadAeadVectors(allocator: Allocator, data: []const u8) ![]AeadVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(AeadVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.key = try hexField(allocator, obj, "key"),
|
||||
.nonce = try hexField(allocator, obj, "iv"),
|
||||
.aad = try hexField(allocator, obj, "aad"),
|
||||
.msg = try hexField(allocator, obj, "msg"),
|
||||
.ct = try hexField(allocator, obj, "ct"),
|
||||
.tag = try hexField(allocator, obj, "tag"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadHashVectors(allocator: Allocator, data: []const u8) ![]HashVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var out_len: usize = 0;
|
||||
if (getObj(tf.root.get("algorithm") orelse return error.InvalidVector)) |algo| {
|
||||
if (getInt(algo, "digestSize")) |ds| out_len = @intCast(@divExact(ds, 8));
|
||||
}
|
||||
|
||||
var list = try std.ArrayList(HashVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.msg = try hexField(allocator, obj, "msg"),
|
||||
.digest = try hexField(allocator, obj, "digest"),
|
||||
.out_len = out_len,
|
||||
.key = try hexField(allocator, obj, "key"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadMacVectors(allocator: Allocator, data: []const u8) ![]MacVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(MacVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.key = try hexField(allocator, obj, "key"),
|
||||
.msg = try hexField(allocator, obj, "msg"),
|
||||
.mac = try hexField(allocator, obj, "mac"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadXdhVectors(allocator: Allocator, data: []const u8) ![]XdhVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(XdhVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.public_key = try hexField(allocator, obj, "publicKey"),
|
||||
.private_key = try hexField(allocator, obj, "privateKey"),
|
||||
.shared = try hexField(allocator, obj, "shared"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadEddsaVerifyVectors(allocator: Allocator, data: []const u8) ![]EddsaVerifyVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(EddsaVerifyVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.public_key = try hexField(allocator, obj, "publicKey"),
|
||||
.msg = try hexField(allocator, obj, "msg"),
|
||||
.sig = try hexField(allocator, obj, "sig"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadEddsaSignVectors(allocator: Allocator, data: []const u8) ![]EddsaSignVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(EddsaSignVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.private_key = try hexField(allocator, obj, "privateKey"),
|
||||
.public_key = try hexField(allocator, obj, "publicKey"),
|
||||
.msg = try hexField(allocator, obj, "msg"),
|
||||
.sig = try hexField(allocator, obj, "sig"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadAuthEncVectors(allocator: Allocator, data: []const u8) ![]AuthEncVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(AuthEncVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.key = try hexField(allocator, obj, "key"),
|
||||
.nonce = try hexField(allocator, obj, "iv"),
|
||||
.msg = try hexField(allocator, obj, "msg"),
|
||||
.ct = try hexField(allocator, obj, "ct"),
|
||||
.tag = try hexField(allocator, obj, "tag"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadBoxVectors(allocator: Allocator, data: []const u8) ![]BoxVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(BoxVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.public_key = try hexField(allocator, obj, "publicKey"),
|
||||
.private_key = try hexField(allocator, obj, "privateKey"),
|
||||
.nonce = try hexField(allocator, obj, "iv"),
|
||||
.msg = try hexField(allocator, obj, "msg"),
|
||||
.ct = try hexField(allocator, obj, "ct"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
.shared_zero = hasFlag(obj, "SharedZero"),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadStreamVectors(allocator: Allocator, data: []const u8) ![]StreamVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(StreamVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.key = try hexField(allocator, obj, "key"),
|
||||
.nonce = try hexField(allocator, obj, "iv"),
|
||||
.msg = try hexField(allocator, obj, "msg"),
|
||||
.ct = try hexField(allocator, obj, "ct"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadHkdfVectors(allocator: Allocator, data: []const u8) ![]HkdfVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(HkdfVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
const out_len: usize = @intCast(getInt(obj, "outLen") orelse continue);
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.ikm = try hexField(allocator, obj, "ikm"),
|
||||
.salt = try hexField(allocator, obj, "salt"),
|
||||
.info = try hexField(allocator, obj, "info"),
|
||||
.out_len = out_len,
|
||||
.okm = try hexField(allocator, obj, "okm"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadScryptVectors(allocator: Allocator, data: []const u8) ![]ScryptVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(ScryptVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.password = try hexField(allocator, obj, "password"),
|
||||
.salt = try hexField(allocator, obj, "salt"),
|
||||
.n = @intCast(getInt(obj, "n") orelse continue),
|
||||
.r = @intCast(getInt(obj, "r") orelse continue),
|
||||
.p = @intCast(getInt(obj, "p") orelse continue),
|
||||
.dk_len = @intCast(getInt(obj, "dkLen") orelse continue),
|
||||
.dk = try hexField(allocator, obj, "dk"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
pub fn loadXofVectors(allocator: Allocator, data: []const u8) ![]XofVector {
|
||||
var tf = try openTestFile(allocator, data);
|
||||
defer tf.parsed.deinit();
|
||||
|
||||
var list = try std.ArrayList(XofVector).initCapacity(allocator, tf.tests.len);
|
||||
for (tf.tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
const out_len: usize = @intCast(getInt(obj, "size") orelse
|
||||
(getInt(obj, "outLen") orelse continue));
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.msg = try hexField(allocator, obj, "ikm"),
|
||||
.out_len = out_len,
|
||||
.output = try hexField(allocator, obj, "okm"),
|
||||
.validity = rooterbergValidity(obj),
|
||||
});
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
|
||||
fn wycheproofValidity(obj: json.ObjectMap) Validity {
|
||||
const s = getStr(obj, "result") orelse return .valid;
|
||||
if (std.mem.eql(u8, s, "valid")) return .valid;
|
||||
if (std.mem.eql(u8, s, "acceptable")) return .acceptable;
|
||||
return .invalid;
|
||||
}
|
||||
|
||||
pub fn loadMlkemVectors(allocator: Allocator, data: []const u8) ![]MlkemVector {
|
||||
const parsed = try json.parseFromSlice(json.Value, allocator, data, .{
|
||||
.ignore_unknown_fields = true,
|
||||
});
|
||||
defer parsed.deinit();
|
||||
const root = getObj(parsed.value) orelse return error.InvalidVector;
|
||||
const groups = getArr(root.get("testGroups") orelse return error.InvalidVector) orelse return error.InvalidVector;
|
||||
|
||||
var list: std.ArrayList(MlkemVector) = .empty;
|
||||
for (groups) |g| {
|
||||
const group = getObj(g) orelse continue;
|
||||
const tests = getArr(group.get("tests") orelse continue) orelse continue;
|
||||
for (tests) |t| {
|
||||
const obj = getObj(t) orelse continue;
|
||||
try list.append(allocator, .{
|
||||
.tc_id = @intCast(getInt(obj, "tcId") orelse continue),
|
||||
.seed = try hexField(allocator, obj, "seed"),
|
||||
.ek = try hexField(allocator, obj, "ek"),
|
||||
.ct = try hexField(allocator, obj, "c"),
|
||||
.ss = try hexField(allocator, obj, "K"),
|
||||
.validity = wycheproofValidity(obj),
|
||||
});
|
||||
}
|
||||
}
|
||||
return list.items;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const fetch = @import("../fetch.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
const EncryptDetachedFn = *const fn (
|
||||
[*c]u8,
|
||||
[*c]u8,
|
||||
[*c]c_ulonglong,
|
||||
[*c]const u8,
|
||||
c_ulonglong,
|
||||
[*c]const u8,
|
||||
c_ulonglong,
|
||||
[*c]const u8,
|
||||
[*c]const u8,
|
||||
[*c]const u8,
|
||||
) callconv(.c) c_int;
|
||||
|
||||
const DecryptDetachedFn = *const fn (
|
||||
[*c]u8,
|
||||
[*c]u8,
|
||||
[*c]const u8,
|
||||
c_ulonglong,
|
||||
[*c]const u8,
|
||||
[*c]const u8,
|
||||
c_ulonglong,
|
||||
[*c]const u8,
|
||||
[*c]const u8,
|
||||
) callconv(.c) c_int;
|
||||
|
||||
fn runAead(
|
||||
allocator: Allocator,
|
||||
vectors: []const parse.AeadVector,
|
||||
encrypt_fn: EncryptDetachedFn,
|
||||
decrypt_fn: DecryptDetachedFn,
|
||||
comptime tag_len: usize,
|
||||
) !Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.tag.len != tag_len) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
if (v.validity == .valid) {
|
||||
const ct_buf = try allocator.alloc(u8, v.msg.len);
|
||||
var mac: [tag_len]u8 = undefined;
|
||||
_ = encrypt_fn(
|
||||
ct_buf.ptr,
|
||||
&mac,
|
||||
null,
|
||||
v.msg.ptr,
|
||||
@intCast(v.msg.len),
|
||||
v.aad.ptr,
|
||||
@intCast(v.aad.len),
|
||||
null,
|
||||
v.nonce.ptr,
|
||||
v.key.ptr,
|
||||
);
|
||||
if (!std.mem.eql(u8, ct_buf, v.ct) or !std.mem.eql(u8, &mac, v.tag)) {
|
||||
std.debug.print("FAIL tc={d}: encrypt mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
const pt_buf = try allocator.alloc(u8, v.ct.len);
|
||||
const ret = decrypt_fn(
|
||||
pt_buf.ptr,
|
||||
null,
|
||||
v.ct.ptr,
|
||||
@intCast(v.ct.len),
|
||||
v.tag.ptr,
|
||||
v.aad.ptr,
|
||||
@intCast(v.aad.len),
|
||||
v.nonce.ptr,
|
||||
v.key.ptr,
|
||||
);
|
||||
if (ret != 0 or !std.mem.eql(u8, pt_buf, v.msg)) {
|
||||
std.debug.print("FAIL tc={d}: decrypt mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
} else {
|
||||
const pt_buf = try allocator.alloc(u8, if (v.ct.len > 0) v.ct.len else 1);
|
||||
const ret = decrypt_fn(
|
||||
pt_buf.ptr,
|
||||
null,
|
||||
v.ct.ptr,
|
||||
@intCast(v.ct.len),
|
||||
v.tag.ptr,
|
||||
v.aad.ptr,
|
||||
@intCast(v.aad.len),
|
||||
v.nonce.ptr,
|
||||
v.key.ptr,
|
||||
);
|
||||
if (ret == 0) {
|
||||
std.debug.print("FAIL tc={d}: decrypt should have failed\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
const vectors = try parse.loadAeadVectors(allocator, data);
|
||||
return switch (tag) {
|
||||
.aead_chacha20poly1305_ietf => try runAead(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_aead_chacha20poly1305_ietf_encrypt_detached,
|
||||
&c.crypto_aead_chacha20poly1305_ietf_decrypt_detached,
|
||||
c.crypto_aead_chacha20poly1305_ietf_ABYTES,
|
||||
),
|
||||
.aead_xchacha20poly1305 => try runAead(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_aead_xchacha20poly1305_ietf_encrypt_detached,
|
||||
&c.crypto_aead_xchacha20poly1305_ietf_decrypt_detached,
|
||||
c.crypto_aead_xchacha20poly1305_ietf_ABYTES,
|
||||
),
|
||||
.aead_aes256gcm => {
|
||||
if (c.crypto_aead_aes256gcm_is_available() == 0) {
|
||||
return Result{ .skipped = @intCast(vectors.len) };
|
||||
}
|
||||
return try runAead(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_aead_aes256gcm_encrypt_detached,
|
||||
&c.crypto_aead_aes256gcm_decrypt_detached,
|
||||
c.crypto_aead_aes256gcm_ABYTES,
|
||||
);
|
||||
},
|
||||
.aead_aegis128l => try runAead(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_aead_aegis128l_encrypt_detached,
|
||||
&c.crypto_aead_aegis128l_decrypt_detached,
|
||||
c.crypto_aead_aegis128l_ABYTES,
|
||||
),
|
||||
.aead_aegis256 => try runAead(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_aead_aegis256_encrypt_detached,
|
||||
&c.crypto_aead_aegis256_decrypt_detached,
|
||||
c.crypto_aead_aegis256_ABYTES,
|
||||
),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const fetch = @import("../fetch.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
const OpenEasyFn = *const fn ([*c]u8, [*c]const u8, c_ulonglong, [*c]const u8, [*c]const u8, [*c]const u8) callconv(.c) c_int;
|
||||
|
||||
fn runBox(
|
||||
allocator: Allocator,
|
||||
vectors: []const parse.BoxVector,
|
||||
dec_fn: OpenEasyFn,
|
||||
comptime mac_bytes: usize,
|
||||
comptime nonce_bytes: usize,
|
||||
) !Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.nonce.len != nonce_bytes) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
if (v.ct.len < nonce_bytes + mac_bytes) {
|
||||
if (v.validity != .valid) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
result.failed += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const box_ct = v.ct[nonce_bytes..];
|
||||
const ct_len = box_ct.len;
|
||||
|
||||
if (v.validity == .valid) {
|
||||
const pt_buf = try allocator.alloc(u8, if (ct_len > mac_bytes) ct_len - mac_bytes else 0);
|
||||
const ret = dec_fn(
|
||||
pt_buf.ptr,
|
||||
box_ct.ptr,
|
||||
@intCast(ct_len),
|
||||
v.nonce.ptr,
|
||||
v.public_key.ptr,
|
||||
v.private_key.ptr,
|
||||
);
|
||||
if (ret != 0) {
|
||||
if (v.shared_zero) {
|
||||
std.debug.print("INFO tc={d}: low-order public key rejected\n", .{v.tc_id});
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
std.debug.print("FAIL tc={d}: box_open failed\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else if (!std.mem.eql(u8, pt_buf, v.msg)) {
|
||||
std.debug.print("FAIL tc={d}: plaintext mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
} else {
|
||||
const pt_buf = try allocator.alloc(u8, if (ct_len > mac_bytes) ct_len - mac_bytes else 1);
|
||||
const ret = dec_fn(
|
||||
pt_buf.ptr,
|
||||
box_ct.ptr,
|
||||
@intCast(ct_len),
|
||||
v.nonce.ptr,
|
||||
v.public_key.ptr,
|
||||
v.private_key.ptr,
|
||||
);
|
||||
if (ret == 0) {
|
||||
std.debug.print("FAIL tc={d}: box_open should have failed\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
const vectors = try parse.loadBoxVectors(allocator, data);
|
||||
return switch (tag) {
|
||||
.box_xsalsa20 => runBox(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_box_open_easy,
|
||||
c.crypto_box_MACBYTES,
|
||||
c.crypto_box_NONCEBYTES,
|
||||
),
|
||||
.box_xchacha20 => runBox(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_box_curve25519xchacha20poly1305_open_easy,
|
||||
c.crypto_box_curve25519xchacha20poly1305_MACBYTES,
|
||||
c.crypto_box_curve25519xchacha20poly1305_NONCEBYTES,
|
||||
),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
pub fn runVerify(allocator: Allocator, data: []const u8, is_cofactored: bool) !Result {
|
||||
const vectors = try parse.loadEddsaVerifyVectors(allocator, data);
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.sig.len != c.crypto_sign_ed25519_BYTES or
|
||||
v.public_key.len != c.crypto_sign_ed25519_PUBLICKEYBYTES)
|
||||
{
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const ret = c.crypto_sign_ed25519_verify_detached(
|
||||
v.sig.ptr,
|
||||
v.msg.ptr,
|
||||
@intCast(v.msg.len),
|
||||
v.public_key.ptr,
|
||||
);
|
||||
const accepted = ret == 0;
|
||||
|
||||
if (v.validity == .valid) {
|
||||
if (!accepted) {
|
||||
if (is_cofactored) {
|
||||
std.debug.print("INFO tc={d}: cofactored-valid sig rejected (small-order)\n", .{v.tc_id});
|
||||
result.skipped += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: verify rejected valid sig\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
} else {
|
||||
if (accepted) {
|
||||
if (!is_cofactored) {
|
||||
std.debug.print("INFO tc={d}: non-cofactored-invalid sig accepted (cofactored)\n", .{v.tc_id});
|
||||
result.skipped += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: verify accepted invalid sig\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn runSign(allocator: Allocator, data: []const u8) !Result {
|
||||
const vectors = try parse.loadEddsaSignVectors(allocator, data);
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.private_key.len != c.crypto_sign_ed25519_SEEDBYTES) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
if (v.validity != .valid) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
var pk: [c.crypto_sign_ed25519_PUBLICKEYBYTES]u8 = undefined;
|
||||
var sk: [c.crypto_sign_ed25519_SECRETKEYBYTES]u8 = undefined;
|
||||
_ = c.crypto_sign_ed25519_seed_keypair(&pk, &sk, v.private_key.ptr);
|
||||
|
||||
if (v.public_key.len == c.crypto_sign_ed25519_PUBLICKEYBYTES and
|
||||
!std.mem.eql(u8, &pk, v.public_key))
|
||||
{
|
||||
std.debug.print("FAIL tc={d}: derived pk mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
var sig: [c.crypto_sign_ed25519_BYTES]u8 = undefined;
|
||||
_ = c.crypto_sign_ed25519_detached(&sig, null, v.msg.ptr, @intCast(v.msg.len), &sk);
|
||||
|
||||
if (std.mem.eql(u8, &sig, v.sig)) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: signature mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const fetch = @import("../fetch.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
const HashFn = *const fn ([*c]u8, [*c]const u8, c_ulonglong) callconv(.c) c_int;
|
||||
|
||||
const GenericHashFn = *const fn ([*c]u8, usize, [*c]const u8, c_ulonglong, [*c]const u8, usize) callconv(.c) c_int;
|
||||
|
||||
fn runFixedHash(vectors: []const parse.HashVector, hash_fn: HashFn, out_len: usize) Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.validity == .invalid) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
var out_buf: [64]u8 = undefined;
|
||||
const out = out_buf[0..out_len];
|
||||
const ret = hash_fn(out.ptr, v.msg.ptr, @intCast(v.msg.len));
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: hash returned {d}\n", .{ v.tc_id, ret });
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
if (!std.mem.eql(u8, out, v.digest)) {
|
||||
std.debug.print("FAIL tc={d}: digest mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn runBlake2b(vectors: []const parse.HashVector, hash_fn: GenericHashFn) Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.validity == .invalid) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const out_len = if (v.out_len > 0) v.out_len else v.digest.len;
|
||||
var out_buf: [64]u8 = undefined;
|
||||
const out = out_buf[0..out_len];
|
||||
const key_ptr: [*c]const u8 = if (v.key.len > 0) v.key.ptr else null;
|
||||
const ret = hash_fn(out.ptr, out_len, v.msg.ptr, @intCast(v.msg.len), key_ptr, v.key.len);
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: blake2b returned {d}\n", .{ v.tc_id, ret });
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
if (!std.mem.eql(u8, out, v.digest)) {
|
||||
std.debug.print("FAIL tc={d}: digest mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
const vectors = try parse.loadHashVectors(allocator, data);
|
||||
return switch (tag) {
|
||||
.hash_sha256 => runFixedHash(vectors, &c.crypto_hash_sha256, c.crypto_hash_sha256_BYTES),
|
||||
.hash_sha512 => runFixedHash(vectors, &c.crypto_hash_sha512, c.crypto_hash_sha512_BYTES),
|
||||
.hash_sha3_256 => runFixedHash(vectors, &c.crypto_hash_sha3256, c.crypto_hash_sha3256_BYTES),
|
||||
.hash_sha3_512 => runFixedHash(vectors, &c.crypto_hash_sha3512, c.crypto_hash_sha3512_BYTES),
|
||||
.hash_blake2b => runBlake2b(vectors, &c.crypto_generichash_blake2b),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const fetch = @import("../fetch.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
fn runHkdf(
|
||||
allocator: Allocator,
|
||||
vectors: []const parse.HkdfVector,
|
||||
comptime prk_len: usize,
|
||||
extract_fn: *const fn ([*c]u8, [*c]const u8, usize, [*c]const u8, usize) callconv(.c) c_int,
|
||||
expand_fn: *const fn ([*c]u8, usize, [*c]const u8, usize, [*c]const u8) callconv(.c) c_int,
|
||||
) !Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.validity != .valid) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
var prk: [prk_len]u8 = undefined;
|
||||
const salt_ptr: [*c]const u8 = if (v.salt.len > 0) v.salt.ptr else null;
|
||||
_ = extract_fn(&prk, salt_ptr, v.salt.len, v.ikm.ptr, v.ikm.len);
|
||||
|
||||
const out = try allocator.alloc(u8, v.out_len);
|
||||
const info_ptr: [*c]const u8 = if (v.info.len > 0) v.info.ptr else null;
|
||||
const ret = expand_fn(out.ptr, v.out_len, info_ptr, v.info.len, &prk);
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: expand returned {d}\n", .{ v.tc_id, ret });
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
if (std.mem.eql(u8, out, v.okm)) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: okm mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
const vectors = try parse.loadHkdfVectors(allocator, data);
|
||||
return switch (tag) {
|
||||
.kdf_hkdf_sha256 => runHkdf(
|
||||
allocator,
|
||||
vectors,
|
||||
c.crypto_kdf_hkdf_sha256_KEYBYTES,
|
||||
&c.crypto_kdf_hkdf_sha256_extract,
|
||||
@ptrCast(&c.crypto_kdf_hkdf_sha256_expand),
|
||||
),
|
||||
.kdf_hkdf_sha512 => runHkdf(
|
||||
allocator,
|
||||
vectors,
|
||||
c.crypto_kdf_hkdf_sha512_KEYBYTES,
|
||||
&c.crypto_kdf_hkdf_sha512_extract,
|
||||
@ptrCast(&c.crypto_kdf_hkdf_sha512_expand),
|
||||
),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8) !Result {
|
||||
const vectors = try parse.loadMlkemVectors(allocator, data);
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.seed.len != c.crypto_kem_mlkem768_SEEDBYTES or
|
||||
v.ct.len != c.crypto_kem_mlkem768_CIPHERTEXTBYTES)
|
||||
{
|
||||
if (v.validity == .invalid) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
result.skipped += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var pk: [c.crypto_kem_mlkem768_PUBLICKEYBYTES]u8 = undefined;
|
||||
var sk: [c.crypto_kem_mlkem768_SECRETKEYBYTES]u8 = undefined;
|
||||
_ = c.crypto_kem_mlkem768_seed_keypair(&pk, &sk, v.seed.ptr);
|
||||
|
||||
if (v.ek.len == c.crypto_kem_mlkem768_PUBLICKEYBYTES and
|
||||
!std.mem.eql(u8, &pk, v.ek))
|
||||
{
|
||||
std.debug.print("FAIL tc={d}: derived pk mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
var ss: [c.crypto_kem_mlkem768_SHAREDSECRETBYTES]u8 = undefined;
|
||||
const ret = c.crypto_kem_mlkem768_dec(&ss, v.ct.ptr, &sk);
|
||||
|
||||
if (v.validity == .valid) {
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: dec failed\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else if (!std.mem.eql(u8, &ss, v.ss)) {
|
||||
std.debug.print("FAIL tc={d}: shared secret mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
} else if (v.validity == .acceptable) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
// ML-KEM implicit rejection: dec always returns 0 but produces
|
||||
// a pseudorandom shared secret that won't match the expected one.
|
||||
if (ret == 0 and std.mem.eql(u8, &ss, v.ss)) {
|
||||
std.debug.print("FAIL tc={d}: invalid vector produced matching ss\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const fetch = @import("../fetch.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
const MacFn = *const fn ([*c]u8, [*c]const u8, c_ulonglong, [*c]const u8) callconv(.c) c_int;
|
||||
|
||||
fn checkMac(result: *Result, v: parse.MacVector, computed: []const u8) void {
|
||||
const matches = std.mem.eql(u8, computed, v.mac);
|
||||
if (v.validity == .valid) {
|
||||
if (matches) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: mac mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
} else {
|
||||
if (!matches) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: invalid mac should not match\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn runHmac256(vectors: []const parse.MacVector) Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
var state: c.crypto_auth_hmacsha256_state = undefined;
|
||||
_ = c.crypto_auth_hmacsha256_init(&state, v.key.ptr, v.key.len);
|
||||
_ = c.crypto_auth_hmacsha256_update(&state, v.msg.ptr, @intCast(v.msg.len));
|
||||
var out: [c.crypto_auth_hmacsha256_BYTES]u8 = undefined;
|
||||
_ = c.crypto_auth_hmacsha256_final(&state, &out);
|
||||
checkMac(&result, v, &out);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn runHmac512(vectors: []const parse.MacVector) Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
var state: c.crypto_auth_hmacsha512_state = undefined;
|
||||
_ = c.crypto_auth_hmacsha512_init(&state, v.key.ptr, v.key.len);
|
||||
_ = c.crypto_auth_hmacsha512_update(&state, v.msg.ptr, @intCast(v.msg.len));
|
||||
var out: [c.crypto_auth_hmacsha512_BYTES]u8 = undefined;
|
||||
_ = c.crypto_auth_hmacsha512_final(&state, &out);
|
||||
checkMac(&result, v, &out);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fn runOneShot(vectors: []const parse.MacVector, mac_fn: MacFn, comptime mac_len: usize) Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
var out: [mac_len]u8 = undefined;
|
||||
const ret = mac_fn(&out, v.msg.ptr, @intCast(v.msg.len), v.key.ptr);
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: mac returned {d}\n", .{ v.tc_id, ret });
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
checkMac(&result, v, &out);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
const vectors = try parse.loadMacVectors(allocator, data);
|
||||
return switch (tag) {
|
||||
.mac_hmacsha256 => runHmac256(vectors),
|
||||
.mac_hmacsha512 => runHmac512(vectors),
|
||||
.mac_siphash24 => runOneShot(vectors, &c.crypto_shorthash_siphash24, c.crypto_shorthash_siphash24_BYTES),
|
||||
.mac_siphashx24 => runOneShot(vectors, &c.crypto_shorthash_siphashx24, c.crypto_shorthash_siphashx24_BYTES),
|
||||
.onetimeauth_poly1305 => runOneShot(vectors, &c.crypto_onetimeauth_poly1305, c.crypto_onetimeauth_poly1305_BYTES),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8) !Result {
|
||||
const vectors = try parse.loadScryptVectors(allocator, data);
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.validity != .valid) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
if (v.n > 1 << 20 or v.r > 8 or v.p > 1) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const out = try allocator.alloc(u8, v.dk_len);
|
||||
const ret = c.crypto_pwhash_scryptsalsa208sha256_ll(
|
||||
v.password.ptr,
|
||||
v.password.len,
|
||||
v.salt.ptr,
|
||||
v.salt.len,
|
||||
v.n,
|
||||
v.r,
|
||||
v.p,
|
||||
out.ptr,
|
||||
v.dk_len,
|
||||
);
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: scrypt returned {d}\n", .{ v.tc_id, ret });
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
if (std.mem.eql(u8, out, v.dk)) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: derived key mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const fetch = @import("../fetch.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
const DetachedEncFn = *const fn ([*c]u8, [*c]u8, [*c]const u8, c_ulonglong, [*c]const u8, [*c]const u8) callconv(.c) c_int;
|
||||
const DetachedDecFn = *const fn ([*c]u8, [*c]const u8, [*c]const u8, c_ulonglong, [*c]const u8, [*c]const u8) callconv(.c) c_int;
|
||||
|
||||
fn runSecretbox(
|
||||
allocator: Allocator,
|
||||
vectors: []const parse.AuthEncVector,
|
||||
enc_fn: DetachedEncFn,
|
||||
dec_fn: DetachedDecFn,
|
||||
) !Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.validity == .valid) {
|
||||
const ct_buf = try allocator.alloc(u8, v.msg.len);
|
||||
var mac: [c.crypto_secretbox_MACBYTES]u8 = undefined;
|
||||
_ = enc_fn(ct_buf.ptr, &mac, v.msg.ptr, @intCast(v.msg.len), v.nonce.ptr, v.key.ptr);
|
||||
|
||||
if (!std.mem.eql(u8, ct_buf, v.ct) or !std.mem.eql(u8, &mac, v.tag)) {
|
||||
std.debug.print("FAIL tc={d}: encrypt mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
const pt_buf = try allocator.alloc(u8, v.ct.len);
|
||||
const ret = dec_fn(
|
||||
pt_buf.ptr,
|
||||
v.ct.ptr,
|
||||
v.tag.ptr,
|
||||
@intCast(v.ct.len),
|
||||
v.nonce.ptr,
|
||||
v.key.ptr,
|
||||
);
|
||||
if (ret != 0 or !std.mem.eql(u8, pt_buf, v.msg)) {
|
||||
std.debug.print("FAIL tc={d}: decrypt mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
} else {
|
||||
const pt_buf = try allocator.alloc(u8, if (v.ct.len > 0) v.ct.len else 1);
|
||||
const ret = dec_fn(
|
||||
pt_buf.ptr,
|
||||
v.ct.ptr,
|
||||
v.tag.ptr,
|
||||
@intCast(v.ct.len),
|
||||
v.nonce.ptr,
|
||||
v.key.ptr,
|
||||
);
|
||||
if (ret == 0) {
|
||||
std.debug.print("FAIL tc={d}: decrypt should have failed\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
const vectors = try parse.loadAuthEncVectors(allocator, data);
|
||||
return switch (tag) {
|
||||
.secretbox_xsalsa20 => runSecretbox(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_secretbox_detached,
|
||||
&c.crypto_secretbox_open_detached,
|
||||
),
|
||||
.secretbox_xchacha20 => runSecretbox(
|
||||
allocator,
|
||||
vectors,
|
||||
&c.crypto_secretbox_xchacha20poly1305_detached,
|
||||
&c.crypto_secretbox_xchacha20poly1305_open_detached,
|
||||
),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const fetch = @import("../fetch.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
const XorFn = *const fn ([*c]u8, [*c]const u8, c_ulonglong, [*c]const u8, [*c]const u8) callconv(.c) c_int;
|
||||
|
||||
fn runStream(allocator: Allocator, vectors: []const parse.StreamVector, xor_fn: XorFn) !Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.validity != .valid) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const out = try allocator.alloc(u8, v.msg.len);
|
||||
const ret = xor_fn(out.ptr, v.msg.ptr, @intCast(v.msg.len), v.nonce.ptr, v.key.ptr);
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: stream_xor returned {d}\n", .{ v.tc_id, ret });
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
if (std.mem.eql(u8, out, v.ct)) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: ciphertext mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
const vectors = try parse.loadStreamVectors(allocator, data);
|
||||
return switch (tag) {
|
||||
.stream_salsa20 => runStream(allocator, vectors, &c.crypto_stream_salsa20_xor),
|
||||
.stream_xsalsa20 => runStream(allocator, vectors, &c.crypto_stream_xsalsa20_xor),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8) !Result {
|
||||
const vectors = try parse.loadXdhVectors(allocator, data);
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.private_key.len != c.crypto_scalarmult_curve25519_SCALARBYTES or
|
||||
v.public_key.len != c.crypto_scalarmult_curve25519_BYTES)
|
||||
{
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
var shared: [c.crypto_scalarmult_curve25519_BYTES]u8 = undefined;
|
||||
const ret = c.crypto_scalarmult_curve25519(&shared, v.private_key.ptr, v.public_key.ptr);
|
||||
|
||||
if (v.validity == .valid) {
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: scalarmult returned {d}\n", .{ v.tc_id, ret });
|
||||
result.failed += 1;
|
||||
} else if (!std.mem.eql(u8, &shared, v.shared)) {
|
||||
std.debug.print("FAIL tc={d}: shared secret mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
} else {
|
||||
if (ret != 0) {
|
||||
result.passed += 1;
|
||||
} else if (std.mem.eql(u8, &shared, v.shared)) {
|
||||
std.debug.print("FAIL tc={d}: invalid vector produced matching output\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
} else {
|
||||
result.passed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const parse = @import("../parse.zig");
|
||||
const fetch = @import("../fetch.zig");
|
||||
const c = @cImport(@cInclude("sodium.h"));
|
||||
|
||||
const Result = parse.Result;
|
||||
|
||||
const XofFn = *const fn ([*c]u8, usize, [*c]const u8, c_ulonglong) callconv(.c) c_int;
|
||||
|
||||
fn runXof(allocator: Allocator, vectors: []const parse.XofVector, xof_fn: XofFn) !Result {
|
||||
var result = Result{};
|
||||
for (vectors) |v| {
|
||||
if (v.validity == .invalid) {
|
||||
result.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const out = try allocator.alloc(u8, v.out_len);
|
||||
const ret = xof_fn(out.ptr, v.out_len, v.msg.ptr, @intCast(v.msg.len));
|
||||
if (ret != 0) {
|
||||
std.debug.print("FAIL tc={d}: xof returned {d}\n", .{ v.tc_id, ret });
|
||||
result.failed += 1;
|
||||
continue;
|
||||
}
|
||||
if (std.mem.eql(u8, out, v.output)) {
|
||||
result.passed += 1;
|
||||
} else {
|
||||
std.debug.print("FAIL tc={d}: output mismatch\n", .{v.tc_id});
|
||||
result.failed += 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn run(allocator: Allocator, data: []const u8, tag: fetch.SuiteTag) !Result {
|
||||
const vectors = try parse.loadXofVectors(allocator, data);
|
||||
return switch (tag) {
|
||||
.xof_shake128 => runXof(allocator, vectors, &c.crypto_xof_shake128),
|
||||
.xof_shake256 => runXof(allocator, vectors, &c.crypto_xof_shake256),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user