| ... | @@ -2128,7 +2128,10 @@ pub const Commit = struct { | ... | @@ -2128,7 +2128,10 @@ pub const Commit = struct { |
| 2128 | } | 2128 | } |
| 2129 | | 2129 | |
| 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 | }; |
| 2134 | | 2137 | |
| ... | @@ -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 | }; |
| 2330 | | 2333 | |
| 2331 | // https://www.ietf.org/archive/id/draft-josefsson-sshsig-format-03.html | 2334 | 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 | } |
| 2346 | | 2377 | |
| 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 | } | | |
| 2376 | | 2385 | |
| | 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); |
| 2380 | | 2399 | |
| 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 format | 2402 | if (packet_type.format != 1) return .unrecognized; // legacy non-OpenPGP format |
| 2384 | if (packet_type.id != 2) return .unrecognized; // not a signature | 2403 | if (packet_type.id != 2) return .unrecognized; // not a signature |
| 2385 | | 2404 | |
| 2386 | _, const packet_len = pgp_read_packet_len(&b64r) catch return .unrecognized; | 2405 | _, const packet_len = pgp_read_packet_len(b64r) catch return .unrecognized; |
| 2387 | | 2406 | |
| 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 | } |
| 2455 | | 2474 | |
| 2456 | const material = try pgp_read_signature_material(&b64r, allocator, pk_algo); | 2475 | const material = try pgp_read_signature_material(b64r, allocator, pk_algo); |
| 2457 | | 2476 | |
| 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; |
| 2516 | | 2532 | |
| 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 | } |
| 2566 | | 2552 | |
| 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 | } |
| 2608 | | 2599 | |
| ... | @@ -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 | |
| | 2776 | fn 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 | } |