| ... | ... | @@ -2128,7 +2128,10 @@ pub const Commit = struct { |
| 2128 | 2128 | } |
| 2129 | 2129 | |
| 2130 | 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 | 2331 | valid: ?bool, |
| 2329 | 2332 | }; |
| 2330 | 2333 | |
| 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 { |
| 2337 | 2335 | if (pem_sig.len == 0) { |
| 2338 | 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 | 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 | 2370 | const pembody = pem_sig[33..std.mem.indexOf(u8, pem_sig, "\n -----END").?]; |
| 2342 | 2371 | const sigcontent = pembody[0..std.mem.lastIndexOf(u8, pembody, "\n ").?]; |
| 2343 | 2372 | var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent); |
| 2344 | 2373 | var skip = nio.SkipReader(void).from(&fixed, "\n "); |
| 2345 | 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); |
| 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 | } |
| 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 | 2396 | var signed_data: nio.AllocatingWriter = .init(allocator); |
| 2378 | 2397 | defer signed_data.deinit(); |
| 2379 | | try signed_data.writeAll(message.list.items); |
| 2398 | try signed_data.writeAll(message); |
| 2380 | 2399 | |
| 2381 | 2400 | const packet_type: packed struct { id: u6, format: u1, reserved: u1 } = @bitCast(try b64r.readByte()); |
| 2382 | 2401 | if (packet_type.reserved != 1) return .unrecognized; |
| 2383 | 2402 | if (packet_type.format != 1) return .unrecognized; // legacy non-OpenPGP format |
| 2384 | 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 | 2407 | const sig_version = try b64r.readByte(); |
| 2389 | 2408 | try signed_data.writeAll(&.{sig_version}); |
| ... | ... | @@ -2415,7 +2434,7 @@ pub const Signature = union(enum) { |
| 2415 | 2434 | try signed_data.writeAll(subpacket_bytes); |
| 2416 | 2435 | try signed_data.writeAll(&.{0x04}); |
| 2417 | 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 | 2439 | var lr = nio.FixedBufferStream([]u8).init(subpacket_bytes); |
| 2421 | 2440 | while (lr.rest().len > 0) { |
| ... | ... | @@ -2453,7 +2472,7 @@ pub const Signature = union(enum) { |
| 2453 | 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 | 2477 | var valid: ?bool = null; |
| 2459 | 2478 | _ = &valid; |
| ... | ... | @@ -2493,11 +2512,8 @@ pub const Signature = union(enum) { |
| 2493 | 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-----")) { |
| 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) { |
| 2501 | 2517 | if (!std.mem.eql(u8, &try b64r.readArray(6), "SSHSIG")) return .unrecognized; |
| 2502 | 2518 | const sigversion = try b64r.readInt(u32, .big); |
| 2503 | 2519 | if (sigversion != 1) return .unrecognized; |
| ... | ... | @@ -2514,36 +2530,6 @@ pub const Signature = union(enum) { |
| 2514 | 2530 | if (!std.mem.eql(u8, namespace, "git")) return .unrecognized; |
| 2515 | 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 | 2533 | var signed_data: nio.AllocatingWriter = .init(allocator); |
| 2548 | 2534 | defer signed_data.deinit(); |
| 2549 | 2535 | try signed_data.writeAll("SSHSIG"); |
| ... | ... | @@ -2556,12 +2542,12 @@ pub const Signature = union(enum) { |
| 2556 | 2542 | if (std.mem.eql(u8, hash_algorithm, "sha256")) { |
| 2557 | 2543 | const H = std.crypto.hash.sha2.Sha256; |
| 2558 | 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 | 2547 | if (std.mem.eql(u8, hash_algorithm, "sha512")) { |
| 2562 | 2548 | const H = std.crypto.hash.sha2.Sha512; |
| 2563 | 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 | 2553 | var valid: ?bool = null; |
| ... | ... | @@ -2603,6 +2589,11 @@ pub const Signature = union(enum) { |
| 2603 | 2589 | .valid = valid, |
| 2604 | 2590 | } }; |
| 2605 | 2591 | } |
| 2592 | |
| 2593 | switch (kind) { |
| 2594 | .pgp => {}, //above |
| 2595 | .ssh => {}, //above |
| 2596 | } |
| 2606 | 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 | 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 | } |