diff --git a/build.zig b/build.zig index 4cdbd47..638f4c0 100644 --- a/build.zig +++ b/build.zig @@ -40,7 +40,14 @@ pub fn build(b: *std.Build) !void { "-fno-sanitize=undefined", }; + const openssl = b.option(bool, "enable-openssl", "Use OpenSSL instead of MbedTLS") orelse false; + if (target.result.os.tag == .windows) { + if (openssl) { + std.log.err("OpenSSL option unsupported on Windows", .{}); + return; + } + lib.linkSystemLibrary("winhttp"); lib.linkSystemLibrary("rpcrt4"); lib.linkSystemLibrary("crypt32"); @@ -61,20 +68,37 @@ pub fn build(b: *std.Build) !void { lib.addWin32ResourceFile(.{ .file = libgit_src.path("src/libgit2/git2.rc") }); lib.addCSourceFiles(.{ .root = libgit_root, .files = &util_win32_sources, .flags = &flags }); } else { - // mbedTLS https and SHA backend - lib.linkSystemLibrary("mbedtls"); - lib.linkSystemLibrary("mbedcrypto"); - lib.linkSystemLibrary("mbedx509"); - features.addValues(.{ - .GIT_HTTPS = 1, - .GIT_MBEDTLS = 1, - .GIT_SHA1_MBEDTLS = 1, - .GIT_SHA256_MBEDTLS = 1, - - .GIT_USE_FUTIMENS = 1, - .GIT_IO_POLL = 1, - .GIT_IO_SELECT = 1, - }); + if (openssl) { + // OpenSSL backend + const openssl_dep = b.dependency("openssl", .{}); + const openssl_lib = openssl_dep.artifact("openssl"); + lib.linkLibrary(openssl_lib); + features.addValues(.{ + .GIT_HTTPS = 1, + .GIT_OPENSSL = 1, + .GIT_SHA1_OPENSSL = 1, + .GIT_SHA256_OPENSSL = 1, + + .GIT_USE_FUTIMENS = 1, + .GIT_IO_POLL = 1, + .GIT_IO_SELECT = 1, + }); + } else { + // mbedTLS https and SHA backend + lib.linkSystemLibrary("mbedtls"); + lib.linkSystemLibrary("mbedcrypto"); + lib.linkSystemLibrary("mbedx509"); + features.addValues(.{ + .GIT_HTTPS = 1, + .GIT_MBEDTLS = 1, + .GIT_SHA1_MBEDTLS = 1, + .GIT_SHA256_MBEDTLS = 1, + + .GIT_USE_FUTIMENS = 1, + .GIT_IO_POLL = 1, + .GIT_IO_SELECT = 1, + }); + } // ntlmclient { @@ -85,15 +109,30 @@ pub fn build(b: *std.Build) !void { .link_libc = true, }); ntlm.addIncludePath(libgit_src.path("deps/ntlmclient")); + if (openssl) addOpenSSLHeaders(ntlm); + + const ntlm_cflags = .{ + "-Wno-implicit-fallthrough", + "-DNTLM_STATIC=1", + "-DUNICODE_BUILTIN=1", + if (openssl) + "-DCRYPT_OPENSSL" + else + "-DCRYPT_MBEDTLS", + }; ntlm.addCSourceFiles(.{ .root = libgit_root, .files = &ntlm_sources, - .flags = &.{ - "-Wno-implicit-fallthrough", - "-DNTLM_STATIC=1", - "-DUNICODE_BUILTIN=1", - "-DCRYPT_MBEDTLS", + .flags = &ntlm_cflags, + }); + ntlm.addCSourceFiles(.{ + .root = libgit_root, + .files = if (openssl) &.{ + "deps/ntlmclient/crypt_openssl.c", + } else &.{ + "deps/ntlmclient/crypt_mbedtls.c", }, + .flags = &ntlm_cflags, }); lib.linkLibrary(ntlm); @@ -106,6 +145,15 @@ pub fn build(b: *std.Build) !void { .files = &util_unix_sources, .flags = &flags, }); + lib.addCSourceFiles(.{ + .root = libgit_root, + .files = if (openssl) &.{ + "src/util/hash/openssl.c", + } else &.{ + "src/util/hash/mbedtls.c", + }, + .flags = &flags, + }); } if (b.option(bool, "enable-ssh", "Enable SSH support") orelse false) { @@ -249,6 +297,7 @@ pub fn build(b: *std.Build) !void { cli.addIncludePath(libgit_src.path("include")); cli.addIncludePath(libgit_src.path("src/util")); cli.addIncludePath(libgit_src.path("src/cli")); + if (openssl) addOpenSSLHeaders(cli); if (target.result.os.tag == .windows) cli.addCSourceFiles(.{ .root = libgit_root, .files = &cli_win32_sources }) @@ -293,6 +342,7 @@ pub fn build(b: *std.Build) !void { }); exe.addIncludePath(libgit_src.path("include")); + if (openssl) addOpenSSLHeaders(exe); exe.linkLibrary(lib); // independent install step so you can easily access the binary @@ -304,6 +354,38 @@ pub fn build(b: *std.Build) !void { example_run.step.dependOn(&examples_install.step); examples_step.dependOn(&example_run.step); } + + const tests_step = b.step("run-tests", "Tests"); + { + const tests = b.addTest(.{ + .root_source_file = b.path("tests/main.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }); + + const fixture = b.addOptions(); + fixture.addOptionPath("resources", libgit_src.path("tests/resources")); + + tests.root_module.addOptions("fixture", fixture); + + tests.addConfigHeader(features); + tests.addIncludePath(libgit_src.path("include")); + tests.addIncludePath(libgit_src.path("src/util")); + if (openssl) addOpenSSLHeaders(tests); + + tests.linkLibrary(lib); + + const tests_run = b.addRunArtifact(tests); + tests_step.dependOn(&tests_run.step); + } +} + +fn addOpenSSLHeaders(compile: *std.Build.Step.Compile) void { + const b = compile.step.owner; + const openssl_dep = b.dependency("openssl", .{}); + const openssl_lib = openssl_dep.artifact("openssl"); + compile.addIncludePath(openssl_lib.getEmittedIncludeTree()); } const libgit_sources = [_][]const u8{ @@ -478,8 +560,6 @@ const util_unix_sources = [_][]const u8{ "src/util/unix/map.c", "src/util/unix/process.c", "src/util/unix/realpath.c", - - "src/util/hash/mbedtls.c", }; const util_win32_sources = [_][]const u8{ @@ -555,7 +635,6 @@ const xdiff_sources = [_][]const u8{ const ntlm_sources = [_][]const u8{ "deps/ntlmclient/crypt_builtin_md4.c", - "deps/ntlmclient/crypt_mbedtls.c", "deps/ntlmclient/ntlm.c", "deps/ntlmclient/unicode_builtin.c", "deps/ntlmclient/util.c", diff --git a/build.zig.zon b/build.zig.zon index 41a2db8..4701602 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,7 +6,11 @@ .libgit2 = .{ .url = "https://github.com/libgit2/libgit2/archive/refs/tags/v1.8.1.tar.gz", .hash = "12208db692747f305b79ecfb3ac69e691d0b9584383ec4bcb0eb07b62b73de77b1cf", - } + }, + .openssl = .{ + .url = "https://github.com/allyourcodebase/openssl/archive/refs/tags/3.3.1-1.tar.gz", + .hash = "12207c40cefa38fe90e4230dfba2e5c76b37e1ee36602512cad8ff0501f892002a65", + }, }, .paths = .{ "build.zig", diff --git a/tests/main.zig b/tests/main.zig new file mode 100644 index 0000000..7997590 --- /dev/null +++ b/tests/main.zig @@ -0,0 +1,3 @@ +test { + _ = @import("sha.zig"); +} diff --git a/tests/sha.zig b/tests/sha.zig new file mode 100644 index 0000000..1fbd959 --- /dev/null +++ b/tests/sha.zig @@ -0,0 +1,114 @@ +const std = @import("std"); +const testing = std.testing; + +const c = @cImport({ + @cInclude("hash.h"); +}); + +const fixture = @import("fixture"); + +fn calcHashFile( + allocator: std.mem.Allocator, + filename: []const u8, + comptime algorithm: c.git_hash_algorithm_t, +) ![]u8 { + const file = try std.fs.openFileAbsolute( + filename, + .{}, + ); + defer file.close(); + + const size: usize = switch (algorithm) { + c.GIT_HASH_ALGORITHM_SHA1 => c.GIT_HASH_SHA1_SIZE, + c.GIT_HASH_ALGORITHM_SHA256 => c.GIT_HASH_SHA256_SIZE, + else => unreachable, + }; + const actual = try allocator.alloc(u8, size); + + var r: c_int = undefined; + var ctx: c.git_hash_ctx = undefined; + r = c.git_hash_ctx_init(&ctx, algorithm); + if (r != 0) return error.Unexpected; + defer c.git_hash_ctx_cleanup(&ctx); + + const reader = file.reader(); + while (true) { + var buf: [2048]u8 = undefined; + const len = try reader.read(&buf); + if (len == 0) break; + r = c.git_hash_update(&ctx, &buf, len); + if (r != 0) return error.Unexpected; + } + + r = c.git_hash_final(actual.ptr, &ctx); + if (r != 0) return error.Unexpected; + + return actual; +} + +test "sha1" { + const expect = [c.GIT_HASH_SHA1_SIZE]u8{ + 0x4e, 0x72, 0x67, 0x9e, 0x3e, 0xa4, 0xd0, 0x4e, + 0x0c, 0x64, 0x2f, 0x02, 0x9e, 0x61, 0xeb, 0x80, + 0x56, 0xc7, 0xed, 0x94, + }; + const actual = try calcHashFile( + testing.allocator, + fixture.resources ++ "/sha1/hello_c", + c.GIT_HASH_ALGORITHM_SHA1, + ); + defer testing.allocator.free(actual); + + try testing.expectEqualSlices(u8, &expect, actual); +} + +test "sha256 empty" { + const expect = [c.GIT_HASH_SHA256_SIZE]u8{ + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, + }; + const actual = try calcHashFile( + testing.allocator, + fixture.resources ++ "/sha1/empty", + c.GIT_HASH_ALGORITHM_SHA256, + ); + defer testing.allocator.free(actual); + + try testing.expectEqualSlices(u8, &expect, actual); +} + +test "sha256 hello" { + const expect = [c.GIT_HASH_SHA256_SIZE]u8{ + 0xaa, 0x32, 0x7f, 0xae, 0x5c, 0x91, 0x58, 0x3a, + 0x4f, 0xb6, 0x54, 0xcc, 0xb6, 0xc2, 0xb1, 0x0c, + 0x77, 0xd7, 0x49, 0xc9, 0x91, 0x2a, 0x8d, 0x6b, + 0x47, 0x26, 0x13, 0xc0, 0xa0, 0x4b, 0x4d, 0xad, + }; + const actual = try calcHashFile( + testing.allocator, + fixture.resources ++ "/sha1/hello_c", + c.GIT_HASH_ALGORITHM_SHA256, + ); + defer testing.allocator.free(actual); + + try testing.expectEqualSlices(u8, &expect, actual); +} + +test "sha256 pdf" { + const expect = [c.GIT_HASH_SHA256_SIZE]u8{ + 0x2b, 0xb7, 0x87, 0xa7, 0x3e, 0x37, 0x35, 0x2f, + 0x92, 0x38, 0x3a, 0xbe, 0x7e, 0x29, 0x02, 0x93, + 0x6d, 0x10, 0x59, 0xad, 0x9f, 0x1b, 0xa6, 0xda, + 0xaa, 0x9c, 0x1e, 0x58, 0xee, 0x69, 0x70, 0xd0, + }; + const actual = try calcHashFile( + testing.allocator, + fixture.resources ++ "/sha1/shattered-1.pdf", + c.GIT_HASH_ALGORITHM_SHA256, + ); + defer testing.allocator.free(actual); + + try testing.expectEqualSlices(u8, &expect, actual); +}