authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-07-15 01:26:44 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-07-15 01:26:44 -07:00
log1d3fefe41c151f8e7f1bb2f33dde2861cc809ca6
treea5347c88cc73bb57ec95b8fbd92ce17708bd5340
parent02e70c5de8f1002a6193fbe47ef6733546db8c75
signaturebadge-check Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

Signature: tidy up and also make it so malformed sigs return unrecognized


1 files changed, 81 insertions(+), 77 deletions(-)

git.zig+81-77
......@@ -2128,7 +2128,10 @@ pub const Commit = struct {
21282128 }
21292129
21302130 pub fn signature(t: *const Commit, allocator: std.mem.Allocator) !Signature {
2131 return .from(allocator, t.gpgsig, t.raw);
2131 return Signature.fromHeader(allocator, t.gpgsig, t.raw) catch |err| switch (err) {
2132 error.OutOfMemory => |e| e,
2133 error.EndOfStream, error.InvalidCharacter => .unrecognized,
2134 };
21322135 }
21332136};
21342137
......@@ -2328,62 +2331,78 @@ pub const Signature = union(enum) {
23282331 valid: ?bool,
23292332 };
23302333
2331 // https://www.ietf.org/archive/id/draft-josefsson-sshsig-format-03.html
2332 // https://datatracker.ietf.org/doc/html/rfc4251#section-5
2333 // https://pkg.go.dev/golang.org/x/crypto/ssh#pkg-constants
2334 // https://datatracker.ietf.org/doc/html/rfc9580#section-4
2335 // https://datatracker.ietf.org/doc/html/rfc9580#signature-packet
2336 pub fn from(allocator: std.mem.Allocator, pem_sig: []const u8, content_plus_gpgsig: []const u8) !Signature {
2334 pub fn fromHeader(allocator: std.mem.Allocator, pem_sig: []const u8, content_plus_gpgsig: []const u8) !Signature {
23372335 if (pem_sig.len == 0) {
23382336 return .none;
23392337 }
2338
2339 var message = extras.ManyArrayList(u8).init(allocator);
2340 defer message.deinit();
2341 try message.appendSlice(try message.add(), content_plus_gpgsig);
2342 message.lengths.items.len = 0;
2343 {
2344 var iter = std.mem.splitScalar(u8, content_plus_gpgsig, '\n');
2345 while (iter.next()) |line| {
2346 try message.lengths.append(allocator, line.len + 1);
2347 }
2348 var skipping = false;
2349 var i: usize = 0;
2350 while (i < message.lengths.items.len) : (i += 1) {
2351 if (!skipping and std.mem.startsWith(u8, message.items(i), "gpgsig ")) {
2352 skipping = true;
2353 message.remove(i);
2354 i -= 1;
2355 continue;
2356 }
2357 if (!skipping) {
2358 continue;
2359 }
2360 if (skipping and std.mem.startsWith(u8, message.items(i), " ")) {
2361 message.remove(i);
2362 i -= 1;
2363 continue;
2364 }
2365 break;
2366 }
2367 }
2368
23402369 if (std.mem.startsWith(u8, pem_sig, "-----BEGIN PGP SIGNATURE-----\n \n ") and (std.mem.endsWith(u8, pem_sig, "\n -----END PGP SIGNATURE-----\n ") or std.mem.endsWith(u8, pem_sig, "\n -----END PGP SIGNATURE-----"))) {
23412370 const pembody = pem_sig[33..std.mem.indexOf(u8, pem_sig, "\n -----END").?];
23422371 const sigcontent = pembody[0..std.mem.lastIndexOf(u8, pembody, "\n ").?];
23432372 var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent);
23442373 var skip = nio.SkipReader(void).from(&fixed, "\n ");
23452374 var b64r = nio.Base64Reader(void).from(&skip);
2375 return fromReader(.pgp, allocator, &b64r, message.list.items);
2376 }
23462377
2347 var message = extras.ManyArrayList(u8).init(allocator);
2348 defer message.deinit();
2349 try message.appendSlice(try message.add(), content_plus_gpgsig);
2350 message.lengths.items.len = 0;
2351 {
2352 var iter = std.mem.splitScalar(u8, content_plus_gpgsig, '\n');
2353 while (iter.next()) |line| {
2354 try message.lengths.append(allocator, line.len + 1);
2355 }
2356 var skipping = false;
2357 var i: usize = 0;
2358 while (i < message.lengths.items.len) : (i += 1) {
2359 if (!skipping and std.mem.startsWith(u8, message.items(i), "gpgsig ")) {
2360 skipping = true;
2361 message.remove(i);
2362 i -= 1;
2363 continue;
2364 }
2365 if (!skipping) {
2366 continue;
2367 }
2368 if (skipping and std.mem.startsWith(u8, message.items(i), " ")) {
2369 message.remove(i);
2370 i -= 1;
2371 continue;
2372 }
2373 break;
2374 }
2375 }
2378 if (std.mem.startsWith(u8, pem_sig, "-----BEGIN SSH SIGNATURE-----\n") and std.mem.endsWith(u8, pem_sig, "\n -----END SSH SIGNATURE-----")) {
2379 const sigcontent = pem_sig[30 .. pem_sig.len - 29];
2380 var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent);
2381 var skip = nio.SkipReader(void).from(&fixed, "\n ");
2382 var b64r = nio.Base64Reader(void).from(&skip);
2383 return fromReader(.ssh, allocator, &b64r, message.list.items);
2384 }
23762385
2386 return .unrecognized;
2387 }
2388
2389 // https://www.ietf.org/archive/id/draft-josefsson-sshsig-format-03.html
2390 // https://datatracker.ietf.org/doc/html/rfc4251#section-5
2391 // https://pkg.go.dev/golang.org/x/crypto/ssh#pkg-constants
2392 // https://datatracker.ietf.org/doc/html/rfc9580#section-4
2393 // https://datatracker.ietf.org/doc/html/rfc9580#signature-packet
2394 pub fn fromReader(kind: NonVoidUnionFieldEnum(Signature), allocator: std.mem.Allocator, b64r: anytype, message: []const u8) !Signature {
2395 if (kind == .pgp) {
23772396 var signed_data: nio.AllocatingWriter = .init(allocator);
23782397 defer signed_data.deinit();
2379 try signed_data.writeAll(message.list.items);
2398 try signed_data.writeAll(message);
23802399
23812400 const packet_type: packed struct { id: u6, format: u1, reserved: u1 } = @bitCast(try b64r.readByte());
23822401 if (packet_type.reserved != 1) return .unrecognized;
23832402 if (packet_type.format != 1) return .unrecognized; // legacy non-OpenPGP format
23842403 if (packet_type.id != 2) return .unrecognized; // not a signature
23852404
2386 _, const packet_len = pgp_read_packet_len(&b64r) catch return .unrecognized;
2405 _, const packet_len = pgp_read_packet_len(b64r) catch return .unrecognized;
23872406
23882407 const sig_version = try b64r.readByte();
23892408 try signed_data.writeAll(&.{sig_version});
......@@ -2415,7 +2434,7 @@ pub const Signature = union(enum) {
24152434 try signed_data.writeAll(subpacket_bytes);
24162435 try signed_data.writeAll(&.{0x04});
24172436 try signed_data.writeAll(&.{0xFF});
2418 try signed_data.writeInt(u32, @truncate(signed_data.items.len - 2 - message.list.items.len), .big);
2437 try signed_data.writeInt(u32, @truncate(signed_data.items.len - 2 - message.len), .big);
24192438 {
24202439 var lr = nio.FixedBufferStream([]u8).init(subpacket_bytes);
24212440 while (lr.rest().len > 0) {
......@@ -2453,7 +2472,7 @@ pub const Signature = union(enum) {
24532472 signed_hash_value_prefix = try b64r.readArray(2);
24542473 }
24552474
2456 const material = try pgp_read_signature_material(&b64r, allocator, pk_algo);
2475 const material = try pgp_read_signature_material(b64r, allocator, pk_algo);
24572476
24582477 var valid: ?bool = null;
24592478 _ = &valid;
......@@ -2493,11 +2512,8 @@ pub const Signature = union(enum) {
24932512 .valid = valid,
24942513 } };
24952514 }
2496 if (std.mem.startsWith(u8, pem_sig, "-----BEGIN SSH SIGNATURE-----\n") and std.mem.endsWith(u8, pem_sig, "\n -----END SSH SIGNATURE-----")) {
2497 const sigcontent = pem_sig[30 .. pem_sig.len - 29];
2498 var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent);
2499 var skip = nio.SkipReader(void).from(&fixed, "\n ");
2500 var b64r = nio.Base64Reader(void).from(&skip);
2515
2516 if (kind == .ssh) {
25012517 if (!std.mem.eql(u8, &try b64r.readArray(6), "SSHSIG")) return .unrecognized;
25022518 const sigversion = try b64r.readInt(u32, .big);
25032519 if (sigversion != 1) return .unrecognized;
......@@ -2514,36 +2530,6 @@ pub const Signature = union(enum) {
25142530 if (!std.mem.eql(u8, namespace, "git")) return .unrecognized;
25152531 if (reserved.len > 0) return .unrecognized;
25162532
2517 var message = extras.ManyArrayList(u8).init(allocator);
2518 defer message.deinit();
2519 try message.appendSlice(try message.add(), content_plus_gpgsig);
2520 message.lengths.items.len = 0;
2521 {
2522 var iter = std.mem.splitScalar(u8, content_plus_gpgsig, '\n');
2523 while (iter.next()) |line| {
2524 try message.lengths.append(allocator, line.len + 1);
2525 }
2526 var skipping = false;
2527 var i: usize = 0;
2528 while (i < message.lengths.items.len) : (i += 1) {
2529 if (!skipping and std.mem.startsWith(u8, message.items(i), "gpgsig ")) {
2530 skipping = true;
2531 message.remove(i);
2532 i -= 1;
2533 continue;
2534 }
2535 if (!skipping) {
2536 continue;
2537 }
2538 if (skipping and std.mem.startsWith(u8, message.items(i), " ")) {
2539 message.remove(i);
2540 i -= 1;
2541 continue;
2542 }
2543 break;
2544 }
2545 }
2546
25472533 var signed_data: nio.AllocatingWriter = .init(allocator);
25482534 defer signed_data.deinit();
25492535 try signed_data.writeAll("SSHSIG");
......@@ -2556,12 +2542,12 @@ pub const Signature = union(enum) {
25562542 if (std.mem.eql(u8, hash_algorithm, "sha256")) {
25572543 const H = std.crypto.hash.sha2.Sha256;
25582544 try signed_data.writeInt(u32, H.digest_length, .big);
2559 try signed_data.writeAll(&extras.hashBytes(H, message.list.items));
2545 try signed_data.writeAll(&extras.hashBytes(H, message));
25602546 }
25612547 if (std.mem.eql(u8, hash_algorithm, "sha512")) {
25622548 const H = std.crypto.hash.sha2.Sha512;
25632549 try signed_data.writeInt(u32, H.digest_length, .big);
2564 try signed_data.writeAll(&extras.hashBytes(H, message.list.items));
2550 try signed_data.writeAll(&extras.hashBytes(H, message));
25652551 }
25662552
25672553 var valid: ?bool = null;
......@@ -2603,6 +2589,11 @@ pub const Signature = union(enum) {
26032589 .valid = valid,
26042590 } };
26052591 }
2592
2593 switch (kind) {
2594 .pgp => {}, //above
2595 .ssh => {}, //above
2596 }
26062597 return .unrecognized;
26072598 }
26082599
......@@ -2781,3 +2772,16 @@ pub fn idFor(r: *Repository, tree: *Tree, sub_path: []const u8) !?struct { *cons
27812772 },
27822773 }
27832774}
2775
2776fn NonVoidUnionFieldEnum(U: type) type {
2777 const info = @typeInfo(U).@"union";
2778 var names: [info.fields.len][:0]const u8 = @splat("");
2779 var count: usize = 0;
2780 for (info.fields) |f| {
2781 if (f.type == void) continue;
2782 names[count] = f.name;
2783 count += 1;
2784 }
2785 const T = std.math.IntFittingRange(0, count - 1);
2786 return @Enum(T, .exhaustive, names[0..count], &std.simd.iota(T, count));
2787}