Skip to content

Commit

Permalink
wip: add minisign
Browse files Browse the repository at this point in the history
  • Loading branch information
hendriknielaender committed Nov 17, 2024
1 parent 4354f07 commit 87ab1b3
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 15 deletions.
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
67 changes: 52 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,7 @@ 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();
var allocator = util_data.get_allocator();

const platform_str = try util_arch.platform_str(.{
.os = builtin.os.tag,
Expand All @@ -41,17 +42,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 +73,58 @@ 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();

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

// Derive signature URI by appending ".minisign" to the tarball URL
var signature_uri_buffer: [1024]u8 = undefined;
const signature_uri_buf = try std.fmt.bufPrint(
&signature_uri_buffer,
"{s}.minisign",
.{version_data.tarball},
);
const signature_uri = try std.Uri.parse(signature_uri_buffer[0..signature_uri_buf.len]);

// Define signature file name
const signature_file_name = try std.mem.concat(
arena_allocator,
u8,
&.{ file_name, ".minisign" },
);

// Download the signature file using the corrected signature_uri
const signature_data = try util_http.http_get(arena_allocator, signature_uri);
defer allocator.free(signature_data);

// Save the signature file to disk
const store_path = try std.fs.path.join(arena_allocator, &.{ "store", signature_file_name });
const signature_file = try std.fs.cwd().createFile(store_path, .{ .truncate = true });

defer signature_file.close();
try signature_file.writeAll(signature_data);

// Perform Minisign Verification
try util_minisign.verify(
&allocator,
config.ZIG_MINISIGN_PUBLIC_KEY,
file_name,
store_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
210 changes: 210 additions & 0 deletions src/util/minisign.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
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 legacy_alg = "Ed";
const prehash_alg = "ED";

if (self.signature_algorithm == prehash_alg) {
return .prehash;
} else if (self.signature_algorithm == legacy_alg) {
return .legacy;
} else {
return Error.unsupported_algorithm;
}
}

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();
return Verifier{
.public_key = public_key,
.signature = signature,
.hasher = switch (algorithm) {
.prehash => Blake2b512.init(.{}),
.legacy => try Ed25519.Verifier.fromBytes(signature.signature),
},
};
}

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);
const signature = try Ed25519.Signature.fromBytes(self.signature.signature);
try signature.verify(&digest, public_key);
},
.legacy => |legacy| {
try legacy.verify();
},
}

// Verify Global Signature
var global_data: [128]u8 = undefined;
mem.copy(u8, global_data[0..64], self.signature.signature);
mem.copy(u8, global_data[64..128], self.signature.trusted_comment);
const global_signature = try Ed25519.Signature.fromBytes(self.signature.global_signature);
try 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
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();
}

0 comments on commit 87ab1b3

Please sign in to comment.