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 {...@@ -2128,7 +2128,10 @@ pub const Commit = struct {
2128 }2128 }
21292129
2130 pub fn signature(t: *const Commit, allocator: std.mem.Allocator) !Signature {2130 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 };
2132 }2135 }
2133};2136};
21342137
...@@ -2328,62 +2331,78 @@ pub const Signature = union(enum) {...@@ -2328,62 +2331,78 @@ pub const Signature = union(enum) {
2328 valid: ?bool,2331 valid: ?bool,
2329 };2332 };
23302333
2331 // https://www.ietf.org/archive/id/draft-josefsson-sshsig-format-03.html2334 pub fn fromHeader(allocator: std.mem.Allocator, pem_sig: []const u8, content_plus_gpgsig: []const u8) !Signature {
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 {
2337 if (pem_sig.len == 0) {2335 if (pem_sig.len == 0) {
2338 return .none;2336 return .none;
2339 }2337 }
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
2340 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-----"))) {2369 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-----"))) {
2341 const pembody = pem_sig[33..std.mem.indexOf(u8, pem_sig, "\n -----END").?];2370 const pembody = pem_sig[33..std.mem.indexOf(u8, pem_sig, "\n -----END").?];
2342 const sigcontent = pembody[0..std.mem.lastIndexOf(u8, pembody, "\n ").?];2371 const sigcontent = pembody[0..std.mem.lastIndexOf(u8, pembody, "\n ").?];
2343 var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent);2372 var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent);
2344 var skip = nio.SkipReader(void).from(&fixed, "\n ");2373 var skip = nio.SkipReader(void).from(&fixed, "\n ");
2345 var b64r = nio.Base64Reader(void).from(&skip);2374 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);2378 if (std.mem.startsWith(u8, pem_sig, "-----BEGIN SSH SIGNATURE-----\n") and std.mem.endsWith(u8, pem_sig, "\n -----END SSH SIGNATURE-----")) {
2348 defer message.deinit();2379 const sigcontent = pem_sig[30 .. pem_sig.len - 29];
2349 try message.appendSlice(try message.add(), content_plus_gpgsig);2380 var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent);
2350 message.lengths.items.len = 0;2381 var skip = nio.SkipReader(void).from(&fixed, "\n ");
2351 {2382 var b64r = nio.Base64Reader(void).from(&skip);
2352 var iter = std.mem.splitScalar(u8, content_plus_gpgsig, '\n');2383 return fromReader(.ssh, allocator, &b64r, message.list.items);
2353 while (iter.next()) |line| {2384 }
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 }
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) {
2377 var signed_data: nio.AllocatingWriter = .init(allocator);2396 var signed_data: nio.AllocatingWriter = .init(allocator);
2378 defer signed_data.deinit();2397 defer signed_data.deinit();
2379 try signed_data.writeAll(message.list.items);2398 try signed_data.writeAll(message);
23802399
2381 const packet_type: packed struct { id: u6, format: u1, reserved: u1 } = @bitCast(try b64r.readByte());2400 const packet_type: packed struct { id: u6, format: u1, reserved: u1 } = @bitCast(try b64r.readByte());
2382 if (packet_type.reserved != 1) return .unrecognized;2401 if (packet_type.reserved != 1) return .unrecognized;
2383 if (packet_type.format != 1) return .unrecognized; // legacy non-OpenPGP format2402 if (packet_type.format != 1) return .unrecognized; // legacy non-OpenPGP format
2384 if (packet_type.id != 2) return .unrecognized; // not a signature2403 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
2388 const sig_version = try b64r.readByte();2407 const sig_version = try b64r.readByte();
2389 try signed_data.writeAll(&.{sig_version});2408 try signed_data.writeAll(&.{sig_version});
...@@ -2415,7 +2434,7 @@ pub const Signature = union(enum) {...@@ -2415,7 +2434,7 @@ pub const Signature = union(enum) {
2415 try signed_data.writeAll(subpacket_bytes);2434 try signed_data.writeAll(subpacket_bytes);
2416 try signed_data.writeAll(&.{0x04});2435 try signed_data.writeAll(&.{0x04});
2417 try signed_data.writeAll(&.{0xFF});2436 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);
2419 {2438 {
2420 var lr = nio.FixedBufferStream([]u8).init(subpacket_bytes);2439 var lr = nio.FixedBufferStream([]u8).init(subpacket_bytes);
2421 while (lr.rest().len > 0) {2440 while (lr.rest().len > 0) {
...@@ -2453,7 +2472,7 @@ pub const Signature = union(enum) {...@@ -2453,7 +2472,7 @@ pub const Signature = union(enum) {
2453 signed_hash_value_prefix = try b64r.readArray(2);2472 signed_hash_value_prefix = try b64r.readArray(2);
2454 }2473 }
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
2458 var valid: ?bool = null;2477 var valid: ?bool = null;
2459 _ = &valid;2478 _ = &valid;
...@@ -2493,11 +2512,8 @@ pub const Signature = union(enum) {...@@ -2493,11 +2512,8 @@ pub const Signature = union(enum) {
2493 .valid = valid,2512 .valid = valid,
2494 } };2513 } };
2495 }2514 }
2496 if (std.mem.startsWith(u8, pem_sig, "-----BEGIN SSH SIGNATURE-----\n") and std.mem.endsWith(u8, pem_sig, "\n -----END SSH SIGNATURE-----")) {2515
2497 const sigcontent = pem_sig[30 .. pem_sig.len - 29];2516 if (kind == .ssh) {
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);
2501 if (!std.mem.eql(u8, &try b64r.readArray(6), "SSHSIG")) return .unrecognized;2517 if (!std.mem.eql(u8, &try b64r.readArray(6), "SSHSIG")) return .unrecognized;
2502 const sigversion = try b64r.readInt(u32, .big);2518 const sigversion = try b64r.readInt(u32, .big);
2503 if (sigversion != 1) return .unrecognized;2519 if (sigversion != 1) return .unrecognized;
...@@ -2514,36 +2530,6 @@ pub const Signature = union(enum) {...@@ -2514,36 +2530,6 @@ pub const Signature = union(enum) {
2514 if (!std.mem.eql(u8, namespace, "git")) return .unrecognized;2530 if (!std.mem.eql(u8, namespace, "git")) return .unrecognized;
2515 if (reserved.len > 0) return .unrecognized;2531 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
2547 var signed_data: nio.AllocatingWriter = .init(allocator);2533 var signed_data: nio.AllocatingWriter = .init(allocator);
2548 defer signed_data.deinit();2534 defer signed_data.deinit();
2549 try signed_data.writeAll("SSHSIG");2535 try signed_data.writeAll("SSHSIG");
...@@ -2556,12 +2542,12 @@ pub const Signature = union(enum) {...@@ -2556,12 +2542,12 @@ pub const Signature = union(enum) {
2556 if (std.mem.eql(u8, hash_algorithm, "sha256")) {2542 if (std.mem.eql(u8, hash_algorithm, "sha256")) {
2557 const H = std.crypto.hash.sha2.Sha256;2543 const H = std.crypto.hash.sha2.Sha256;
2558 try signed_data.writeInt(u32, H.digest_length, .big);2544 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));
2560 }2546 }
2561 if (std.mem.eql(u8, hash_algorithm, "sha512")) {2547 if (std.mem.eql(u8, hash_algorithm, "sha512")) {
2562 const H = std.crypto.hash.sha2.Sha512;2548 const H = std.crypto.hash.sha2.Sha512;
2563 try signed_data.writeInt(u32, H.digest_length, .big);2549 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));
2565 }2551 }
25662552
2567 var valid: ?bool = null;2553 var valid: ?bool = null;
...@@ -2603,6 +2589,11 @@ pub const Signature = union(enum) {...@@ -2603,6 +2589,11 @@ pub const Signature = union(enum) {
2603 .valid = valid,2589 .valid = valid,
2604 } };2590 } };
2605 }2591 }
2592
2593 switch (kind) {
2594 .pgp => {}, //above
2595 .ssh => {}, //above
2596 }
2606 return .unrecognized;2597 return .unrecognized;
2607 }2598 }
26082599
...@@ -2781,3 +2772,16 @@ pub fn idFor(r: *Repository, tree: *Tree, sub_path: []const u8) !?struct { *cons...@@ -2781,3 +2772,16 @@ pub fn idFor(r: *Repository, tree: *Tree, sub_path: []const u8) !?struct { *cons
2781 },2772 },
2782 }2773 }
2783}2774}
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}