authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-07-13 01:59:00 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-07-13 01:59:00 -07:00
logc79ce4fada829f3afeebd2a3b55e46a2f8c98592
treeceac41204ade9be8a920e84b72ec9095a86dece0
parent4bd071c4aac6f607786424bf9d07e7764708a0f7
signaturebadge-check Signed by SSH key SHA256:4hHJbtBRU58AYXwjL7fkz2fnQHdiye8x1QpTCQ0sHNw

Signature: more pgp detection


2 files changed, 126 insertions(+), 1 deletions(-)

README.md+1
...@@ -13,3 +13,4 @@ Inspect into the depths of your .git folder purely from Zig....@@ -13,3 +13,4 @@ Inspect into the depths of your .git folder purely from Zig.
13- https://git-scm.com/book/en/v2/Git-Internals-Git-Objects13- https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
14- https://git-scm.com/docs/gitformat-pack14- https://git-scm.com/docs/gitformat-pack
15- https://awasu.com/weblog/git-guts/delta-objects/15- https://awasu.com/weblog/git-guts/delta-objects/
16- https://git-scm.com/docs/gitformat-signature
git.zig+125-1
...@@ -2164,7 +2164,87 @@ pub const Signature = union(enum) {...@@ -2164,7 +2164,87 @@ pub const Signature = union(enum) {
2164 ssh: Ssh,2164 ssh: Ssh,
21652165
2166 pub const Pgp = struct {2166 pub const Pgp = struct {
2167 packet_length: u32,
2168 version: u8,
2169 type_id: Pgp.Type,
2170 pk_algorithm: Pgp.PublicKeyAlgorithm,
2171 hash_algorithm: Pgp.HashAlgorithm,
2172 signed_hash_value_prefix: [4]u8,
2167 valid: ?bool,2173 valid: ?bool,
2174
2175 /// https://datatracker.ietf.org/doc/html/rfc9580#name-signature-types
2176 pub const Type = enum(u8) {
2177 binary = 0x00,
2178 text = 0x01,
2179 standalone = 0x02,
2180 generic_certification = 0x10,
2181 persona_certification = 0x11,
2182 casual_certification = 0x12,
2183 positive_certification = 0x13,
2184 subkey_binding = 0x18,
2185 primary_key_binding = 0x19,
2186 direct_key = 0x1F,
2187 key_revocation = 0x20,
2188 subkey_revocation = 0x28,
2189 certification_revocation = 0x30,
2190 timestamp = 0x40,
2191 third_party_confirmation = 0x50,
2192 reserved = 0xFF,
2193 _,
2194
2195 pub fn stringifyJson(e: Type, writer: anytype, options: std.json.Stringify.Options, json: type) !void {
2196 return switch (e) {
2197 _ => json.stringify(writer, @intFromEnum(e), options),
2198 else => json.stringify(writer, @tagName(e), options),
2199 };
2200 }
2201 };
2202
2203 /// https://datatracker.ietf.org/doc/html/rfc9580#name-public-key-algorithms
2204 pub const PublicKeyAlgorithm = enum(u8) {
2205 reserved = 0,
2206 rsa_encrypt_or_sign = 1,
2207 rsa_encrypt = 2,
2208 rsa_sign = 3,
2209 elgamal_encrypt = 16,
2210 dsa = 17,
2211 ecdh = 18,
2212 ecdsa = 19,
2213 x25519 = 25,
2214 x448 = 26,
2215 ed25519 = 27,
2216 ed448 = 28,
2217 _,
2218
2219 pub fn stringifyJson(e: PublicKeyAlgorithm, writer: anytype, options: std.json.Stringify.Options, json: type) !void {
2220 return switch (e) {
2221 _ => json.stringify(writer, @intFromEnum(e), options),
2222 else => json.stringify(writer, @tagName(e), options),
2223 };
2224 }
2225 };
2226
2227 /// https://datatracker.ietf.org/doc/html/rfc9580#name-hash-algorithms
2228 pub const HashAlgorithm = enum(u8) {
2229 reserved = 0,
2230 md5 = 1,
2231 sha1 = 2,
2232 ripemd160 = 3,
2233 sha2_256 = 8,
2234 sha2_384 = 9,
2235 sha2_512 = 10,
2236 sha2_224 = 11,
2237 sha3_256 = 12,
2238 sha3_512 = 14,
2239 _,
2240
2241 pub fn stringifyJson(e: HashAlgorithm, writer: anytype, options: std.json.Stringify.Options, json: type) !void {
2242 return switch (e) {
2243 _ => json.stringify(writer, @intFromEnum(e), options),
2244 else => json.stringify(writer, @tagName(e), options),
2245 };
2246 }
2247 };
2168 };2248 };
21692249
2170 pub const Ssh = struct {2250 pub const Ssh = struct {
...@@ -2177,6 +2257,8 @@ pub const Signature = union(enum) {...@@ -2177,6 +2257,8 @@ pub const Signature = union(enum) {
2177 // https://www.ietf.org/archive/id/draft-josefsson-sshsig-format-03.html2257 // https://www.ietf.org/archive/id/draft-josefsson-sshsig-format-03.html
2178 // https://datatracker.ietf.org/doc/html/rfc4251#section-52258 // https://datatracker.ietf.org/doc/html/rfc4251#section-5
2179 // https://pkg.go.dev/golang.org/x/crypto/ssh#pkg-constants2259 // https://pkg.go.dev/golang.org/x/crypto/ssh#pkg-constants
2260 // https://datatracker.ietf.org/doc/html/rfc9580#section-4
2261 // https://datatracker.ietf.org/doc/html/rfc9580#signature-packet
2180 pub fn from(allocator: std.mem.Allocator, pem_sig: []const u8, content_plus_gpgsig: []const u8) !Signature {2262 pub fn from(allocator: std.mem.Allocator, pem_sig: []const u8, content_plus_gpgsig: []const u8) !Signature {
2181 if (pem_sig.len == 0) {2263 if (pem_sig.len == 0) {
2182 return .none;2264 return .none;
...@@ -2187,9 +2269,51 @@ pub const Signature = union(enum) {...@@ -2187,9 +2269,51 @@ pub const Signature = union(enum) {
2187 var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent);2269 var fixed: nio.FixedBufferStream([]const u8) = .init(sigcontent);
2188 var skip = nio.SkipReader(void).from(&fixed, "\n ");2270 var skip = nio.SkipReader(void).from(&fixed, "\n ");
2189 var b64r = nio.Base64Reader(void).from(&skip);2271 var b64r = nio.Base64Reader(void).from(&skip);
2190 _ = &b64r;2272
2273 const packet_type: packed struct { id: u6, format: u1, reserved: u1 } = @bitCast(try b64r.readByte());
2274 if (packet_type.reserved != 1) return .unrecognized;
2275 if (packet_type.format != 1) return .unrecognized; // legacy non-OpenPGP format
2276 if (packet_type.id != 2) return .unrecognized; // not a signature
2277
2278 var packet_len_partial = false;
2279 const packet_len: u32 = blk: {
2280 const oct1: u32 = try b64r.readByte();
2281 if (oct1 <= 191) break :blk oct1;
2282 if (oct1 >= 224 and oct1 < 255) packet_len_partial = true;
2283 if (oct1 >= 224 and oct1 < 255) break :blk @as(u32, 1) << @intCast(oct1 & 0x1F);
2284 const oct2: u32 = try b64r.readByte();
2285 if (oct1 <= 223) break :blk ((oct1 - 192) << 8) + (oct2) + 192;
2286 const oct3: u32 = try b64r.readByte();
2287 const oct4: u32 = try b64r.readByte();
2288 const oct5: u32 = try b64r.readByte();
2289 std.debug.assert(oct1 == 255);
2290 break :blk (oct2 << 24) | (oct3 << 16) | (oct4 << 8) | oct5;
2291 };
2292
2293 const sig_version = try b64r.readByte();
2294 var type_id: Pgp.Type = .reserved;
2295 var pk_algo: Pgp.PublicKeyAlgorithm = .reserved;
2296 var hash_algo: Pgp.HashAlgorithm = .reserved;
2297 var signed_hash_value_prefix: [2]u8 = @splat(0);
2298
2299 if (sig_version == 4) {
2300 type_id = @enumFromInt(try b64r.readByte());
2301 pk_algo = @enumFromInt(try b64r.readByte());
2302 hash_algo = @enumFromInt(try b64r.readByte());
2303 const subpacket_len_hashed = try b64r.readInt(u16, .big);
2304 try b64r.skipBytes(subpacket_len_hashed, .{}); // TODO read this
2305 const subpacket_len_unhashed = try b64r.readInt(u16, .big);
2306 try b64r.skipBytes(subpacket_len_unhashed, .{}); // TODO read this
2307 signed_hash_value_prefix = try b64r.readArray(2);
2308 }
21912309
2192 return .{ .pgp = .{2310 return .{ .pgp = .{
2311 .packet_length = packet_len,
2312 .version = sig_version,
2313 .type_id = type_id,
2314 .pk_algorithm = pk_algo,
2315 .hash_algorithm = hash_algo,
2316 .signed_hash_value_prefix = extras.to_hex(signed_hash_value_prefix),
2193 .valid = null,2317 .valid = null,
2194 } };2318 } };
2195 }2319 }