diff options
| author | Sadeep Madurange <sadeep@asciimx.com> | 2026-08-17 17:29:28 +0800 |
|---|---|---|
| committer | Sadeep Madurange <sadeep@asciimx.com> | 2026-08-17 17:29:28 +0800 |
| commit | ae06cd0db80d688ab2c82cd2b03b8f1d6cf9f595 (patch) | |
| tree | 10fc8fb102cb94047ca91efd92ff4e460a13c1b5 | |
| parent | bdb2060407df9c798fd8460b8aaa1c395d7a16b4 (diff) | |
| download | lex-ae06cd0db80d688ab2c82cd2b03b8f1d6cf9f595.tar.gz | |
Improve PGP error logs, get things working with K-mail.
| -rwxr-xr-x | lex.cgi | 49 |
1 files changed, 43 insertions, 6 deletions
@@ -75,11 +75,23 @@ sub respond_ok { exit 0; } +my $pgp_error; + +sub set_pgp_error { + my ($msg) = @_; + $msg //= 'unknown error'; + $msg =~ s/\s+/ /g; # collapse any newlines/whitespace runs to single spaces + $msg =~ s/^\s+|\s+$//g; # trim leading/trailing space + $pgp_error = $msg; +} + sub verify_pgp_signature { my ($signed_data, $signature) = @_; + $pgp_error = undef; + unless (-f $keyring_file) { - log_msg("ERROR: No keyring at $keyring_file"); + set_pgp_error("No keyring at $keyring_file"); return 0; } @@ -91,7 +103,7 @@ sub verify_pgp_signature { ); unless ($pgp) { - log_msg("ERROR: Failed to initialize PGP"); + set_pgp_error(Crypt::OpenPGP->errstr // 'unknown PGP init error'); return 0; } @@ -100,6 +112,10 @@ sub verify_pgp_signature { Signature => $signature, ); + unless ($verified) { + set_pgp_error($pgp->errstr // 'unknown error'); + } + return $verified ? 1 : 0; } @@ -188,8 +204,29 @@ unless (@parts >= 2) { respond_ok(); } -# Extract canonical signed part directly (Part 0 string representation) -my $signed_part = $parts[0]->as_string; +# Extract the boundary string from the outer Content-Type header so the +# signed part can be sliced out of the raw message verbatim. PGP/MIME +# signature verification (RFC 3156) requires the exact original octets of +# the signed entity; re-serializing it via Email::MIME's ->as_string() +# is not safe, since header folding, whitespace, and field order can be +# normalized differently on reserialization than the sender produced, +# which breaks the signature hash even though the content is unchanged. +my ($boundary) = $parsed->content_type =~ /boundary="?([^";]+)"?/; + +unless ($boundary) { + log_msg("WARN: No MIME boundary found in Content-Type header"); + respond_ok(); +} + +# Slice the literal bytes between the first boundary delimiter and the +# CRLF immediately preceding the second boundary delimiter. That trailing +# CRLF belongs to the boundary line itself, not the signed content, per +# the MIME multipart spec. +my $signed_part; +if ($mime_raw =~ /--\Q$boundary\E\r?\n(.*?)\r?\n--\Q$boundary\E/s) { + $signed_part = $1; + $signed_part =~ s/\r?\n/\r\n/g; # canonical OpenPGP line endings +} # Extract PGP signature block my $sig_part_raw = $parts[1]->body_raw // ''; @@ -200,13 +237,13 @@ unless ($pgp_sig) { ($pgp_sig) = $sig_part_str =~ /(-----BEGIN PGP SIGNATURE-----[\s\S]*?-----END PGP SIGNATURE-----)/; } -unless (length($signed_part) > 0 && $pgp_sig) { +unless (defined $signed_part && length($signed_part) > 0 && $pgp_sig) { log_msg("WARN: Missing PGP signature block"); respond_ok(); } unless (verify_pgp_signature($signed_part, $pgp_sig)) { - log_msg("WARN: PGP signature verification failed"); + log_msg("WARN: PGP signature verification failed: " . ($pgp_error // 'unknown error')); respond_ok(); } |
