Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: add minisign #60

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ pub var progress_root: std.Progress.Node = undefined;

/// zig meta data url
pub const zig_meta_url: []const u8 = "https://ziglang.org/download/index.json";

/// zig minisign public key
pub const ZIG_MINISIGN_PUBLIC_KEY = "RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U";

/// zls meta data url
pub const zls_meta_url: []const u8 = "https://api.github.com/repos/zigtools/zls/releases";

Expand Down
95 changes: 80 additions & 15 deletions src/install.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const util_data = @import("util/data.zig");
const util_extract = @import("util/extract.zig");
const util_tool = @import("util/tool.zig");
const util_http = @import("util/http.zig");
const util_minisign = @import("util/minisign.zig");

const Version = struct {
name: []const u8,
Expand All @@ -28,7 +29,9 @@ pub fn install(version: []const u8, is_zls: bool) !void {

/// Try to install the specified version of zig
fn install_zig(version: []const u8) !void {
const allocator = util_data.get_allocator();
std.debug.print("install start", .{});

var allocator = util_data.get_allocator();

const platform_str = try util_arch.platform_str(.{
.os = builtin.os.tag,
Expand All @@ -41,17 +44,17 @@ fn install_zig(version: []const u8) !void {

const arena_allocator = arena.allocator();

// get version path
// Get version path
const version_path = try util_data.get_zvm_zig_version(arena_allocator);
// get extract path
// Get extract path
const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version });

if (util_tool.does_path_exist(extract_path)) {
try alias.set_version(version, false);
return;
}

// get version data
// Get version data
const version_data: meta.Zig.VersionData = blk: {
const res = try util_http.http_get(arena_allocator, config.zig_url);
var zig_meta = try meta.Zig.init(res, arena_allocator);
Expand All @@ -72,22 +75,63 @@ fn install_zig(version: []const u8) !void {
);

const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable;
const new_file = try util_http.download(parsed_uri, file_name, version_data.shasum, version_data.size);
defer new_file.close();

std.debug.print("parsed url: {any}", .{parsed_uri});

// Download the tarball
const tarball_file = try util_http.download(parsed_uri, file_name, version_data.shasum, version_data.size);
defer tarball_file.close();

std.debug.print("download done", .{});

// Derive signature URI by appending ".minisig" to the tarball URL
// https://ziglang.org/builds/zig-0.14.0-dev.2261+fbcb00fbb.tar.xz.minisig
const minisig_url = try remove_arch_from_url(arena_allocator, version_data.tarball);
var signature_uri_buffer: [1024]u8 = undefined;
const signature_uri_buf = try std.fmt.bufPrint(
&signature_uri_buffer,
"{s}.minisig",
.{minisig_url},
);

const signature_uri = try std.Uri.parse(signature_uri_buffer[0..signature_uri_buf.len]);

std.debug.print("signature url {any}", .{signature_uri});
// Define signature file name
const signature_file_name = try std.mem.concat(
arena_allocator,
u8,
&.{ file_name, ".minisig" },
);

// Download the signature file using the corrected signature_uri
const minisig_file = try util_http.download(signature_uri, signature_file_name, null, null);
defer minisig_file.close();

const zvm_path = try util_data.get_zvm_path_segment(allocator, "store");
defer allocator.free(zvm_path);
const sig_path = try std.fs.path.join(arena_allocator, &.{ zvm_path, signature_file_name });

// Perform Minisign Verification
try util_minisign.verify(
&allocator,
sig_path,
config.ZIG_MINISIGN_PUBLIC_KEY,
extract_path,
);

// Proceed with extraction after successful verification
try util_tool.try_create_path(extract_path);
const extract_dir = try std.fs.openDirAbsolute(extract_path, .{});

try util_extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, false);
try util_extract.extract(extract_dir, tarball_file, if (builtin.os.tag == .windows) .zip else .tarxz, false);

// mv
const sub_path = try std.fs.path.join(arena_allocator, &.{
extract_path, try std.mem.concat(
arena_allocator,
u8,
&.{ "zig-", reverse_platform_str, "-", version },
),
});
// Move extracted files if necessary
const sub_path = try std.fs.path.join(arena_allocator, &.{ extract_path, try std.mem.concat(
arena_allocator,
u8,
&.{ "zig-", reverse_platform_str, "-", version },
) });
defer std.fs.deleteTreeAbsolute(sub_path) catch unreachable;

try util_tool.copy_dir(sub_path, extract_path);
Expand Down Expand Up @@ -162,3 +206,24 @@ fn install_zls(version: []const u8) !void {
}

pub fn build_zls() !void {}

fn remove_arch_from_url(allocator: std.mem.Allocator, url: []const u8) ![]const u8 {
const prefix = "zig-";
const version_marker = "-0."; // A reliable marker for version information

const prefix_index = std.mem.indexOf(u8, url, prefix) orelse return url;
const version_start = std.mem.indexOf(u8, url, version_marker) orelse return url;

// Ensure proper removal of redundant dashes
const prefix_end = prefix_index + prefix.len;

// Rebuild the URL without platform/architecture
return try std.mem.concat(
allocator,
u8,
&.{
url[0..prefix_end], // Keep everything up to and including `zig-`
url[version_start + 1 ..],
},
);
}
203 changes: 203 additions & 0 deletions src/util/minisign.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
const std = @import("std");
const base64 = std.base64;
const crypto = std.crypto;
const fs = std.fs;
const mem = std.mem;
const fmt = std.fmt;
const heap = std.heap;
const io = std.io;
const Endian = std.builtin.Endian;
const Ed25519 = crypto.sign.Ed25519;
const Blake2b512 = crypto.hash.blake2.Blake2b512;

// Error Definitions
const Error = error{
invalid_encoding,
unsupported_algorithm,
key_id_mismatch,
signature_verification_failed,
file_read_error,
public_key_format_error,
};

// Algorithm Enumeration
pub const Algorithm = enum {
Prehash,
Legacy,
};

// Signature Structure
pub const Signature = struct {
signature_algorithm: [2]u8,
key_id: [8]u8,
signature: [64]u8,
trusted_comment: []const u8,
global_signature: [64]u8,

pub fn get_algorithm(self: Signature) !Algorithm {
const signature_algorithm = self.signature_algorithm;
const prehashed = if (signature_algorithm[0] == 0x45 and signature_algorithm[1] == 0x64) false else if (signature_algorithm[0] == 0x45 and signature_algorithm[1] == 0x44) true else return error.UnsupportedAlgorithm;
return if (prehashed) .Prehash else .Legacy;
}

pub fn decode(_: *std.mem.Allocator, lines: []const u8) !Signature {
var tokenizer = mem.tokenizeScalar(u8, lines, '\n');

// Decode first line: signature_algorithm + key_id + signature
const sig_line = tokenizer.next() orelse return Error.invalid_encoding;
var sig_bin: [74]u8 = undefined;
try base64.standard.Decoder.decode(&sig_bin, sig_line);

// Decode trusted comment
const trusted_comment_prefix = "trusted comment: ";
const comment_line = tokenizer.next() orelse return Error.invalid_encoding;
if (!mem.startsWith(u8, comment_line, trusted_comment_prefix)) {
return error.invalid_encoding;
}
const trusted_comment = comment_line[trusted_comment_prefix.len..];

// Decode global signature
const global_sig_line = tokenizer.next() orelse return Error.invalid_encoding;
var global_sig_bin: [64]u8 = undefined;
try base64.standard.Decoder.decode(&global_sig_bin, global_sig_line);

return Signature{
.signature_algorithm = sig_bin[0..2].*,
.key_id = sig_bin[2..10].*,
.signature = sig_bin[10..74].*,
.trusted_comment = trusted_comment,
.global_signature = global_sig_bin,
};
}

pub fn from_file(allocator: *std.mem.Allocator, path: []const u8) !Signature {
const file = try fs.cwd().openFile(path, .{ .mode = .read_only });
defer file.close();

const sig_str = try file.readToEndAlloc(allocator.*, 4096);
defer allocator.free(sig_str);

return decode(allocator, sig_str);
}
};

// PublicKey Structure
pub const PublicKey = struct {
signature_algorithm: [2]u8 = "Ed".*,
key_id: [8]u8,
key: [32]u8,

pub fn decode(str: []const u8) !PublicKey {
if (str.len != 44) { // Base64 for 32-byte key
return error.public_key_format_error;
}

var bin: [42]u8 = undefined;
try base64.standard.Decoder.decode(&bin, str);
const signature_algorithm = bin[0..2];
if (bin[0] != 0x45 or (bin[1] != 0x64 and bin[1] != 0x44)) {
return error.unsupported_algorithm;
}

return PublicKey{
.signature_algorithm = signature_algorithm.*,
.key_id = bin[2..10].*,
.key = bin[10..42].*,
};
}

pub fn from_file(allocator: *std.mem.Allocator, path: []const u8) !PublicKey {
const file = try fs.cwd().openFile(path, .{ .mode = .read_only });
defer file.close();

const pk_str = try file.readToEndAlloc(allocator.*, 4096);
defer allocator.free(pk_str);

const trimmed_pk = mem.trim(u8, pk_str, " \t\r\n");
return decode(trimmed_pk);
}
};

// Verifier Structure
pub const Verifier = struct {
public_key: PublicKey,
signature: Signature,
hasher: union(Algorithm) {
Prehash: Blake2b512,
Legacy: Ed25519.Verifier,
},

pub fn init(public_key: PublicKey, signature: Signature) !Verifier {
const algorithm = try signature.get_algorithm();
const ed25519_pk = try Ed25519.PublicKey.fromBytes(public_key.key);
return Verifier{
.public_key = public_key,
.signature = signature,
.hasher = switch (algorithm) {
.Prehash => .{ .Prehash = Blake2b512.init(.{}) },
.Legacy => .{ .Legacy = try Ed25519.Signature.fromBytes(signature.signature).verifier(ed25519_pk) },
},
};
}

pub fn update(self: *Verifier, data: []const u8) void {
switch (self.hasher) {
.Prehash => |*prehash| prehash.update(data),
.Legacy => |*legacy| legacy.update(data),
}
}

pub fn finalize(self: *Verifier) !void {
const public_key = try Ed25519.PublicKey.fromBytes(self.public_key.key);
switch (self.hasher) {
.Prehash => |*prehash| {
var digest: [64]u8 = undefined;
prehash.final(&digest);
try Ed25519.Signature.fromBytes(self.signature.signature).verify(&digest, public_key);
},
.Legacy => |*legacy| {
try legacy.verify();
},
}

// Verify Global Signature
var global_data: [128]u8 = undefined;
mem.copyForwards(u8, global_data[0..64], &self.signature.signature);
mem.copyForwards(u8, global_data[64..128], self.signature.trusted_comment);
try Ed25519.Signature.fromBytes(self.signature.global_signature).verify(&global_data, public_key);
}
};

// Verification Function
pub fn verify(
allocator: *std.mem.Allocator,
signature_path: []const u8,
public_key_path: []const u8,
file_path: []const u8,
) !void {
// Load Signature
std.debug.print("signature_path: {s}", .{signature_path});
const signature = try Signature.from_file(allocator, signature_path);
defer allocator.free(signature.trusted_comment);

// Load Public Key
const public_key = try PublicKey.from_file(allocator, public_key_path);

// Initialize Verifier
var verifier = try Verifier.init(public_key, signature);

// Open File to Verify
const file = try fs.cwd().openFile(file_path, .{ .mode = .read_only });
defer file.close();

// Read and Update Verifier with File Data
var buffer: [4096]u8 = undefined;
while (true) {
const bytes_read = try file.read(&buffer);
if (bytes_read == 0) break;
verifier.update(buffer[0..bytes_read]);
}

// Finalize Verification
try verifier.finalize();
}
Loading