Skip to content

VerifyInline does not revert canonical line endings for text signatures #363

Description

@jaideeppyne

VerifyInline and VerifyingReader never revert canonical line endings for a text-mode signature, so the plaintext they return keeps CRLF. The other two paths do revert it, and GnuPG reverts it as well.

In crypto/verify_handle.go the inline path wraps in a sanitize reader only when the caller asked for UTF-8:

// inline, line 68
if err == nil && vh.IsUTF8 {

while the detached path also detects a text signature on its own:

// detached, line 263
md.SignatureCandidates[0].SigType == packet.SigTypeText {

decryption_core.go does the same detection in two places.

Three things follow from that.

It does not round-trip with itself. Signing "a\nb\n" with Sign().Utf8() and reading it back with VerifyInline() gives "a\r\nb\r\n". The same message through Encryption().Utf8() and Decryption() gives "a\nb\n".

DisableAutomaticTextSanitize is a no-op there. Its doc comment says "If not disabled, the output will be sanitized if a text signature is present", but on this path the output is byte-identical whether the flag is set or not, because there is no automatic sanitization to disable.

GnuPG disagrees. A message from gpg --textmode --sign read back through VerifyInline gives "a\r\nb\r\n", while gpg --decrypt on the same bytes gives "a\nb\n". RFC 9580 §5.9 says text is stored with CRLF in the literal data packet and converted to native line endings by the receiving software.

I found this with a differential harness against GnuPG 2.5.19, all local, no key servers. Armor round-trips, cleartext, detached, and both encrypt/decrypt directions matched across 1988 cases. The only divergence was inline sign in gpg then verify in gopenpgp, 26 cases, where the lf and crlf inputs differ.

Signature validity is not affected, only the returned plaintext. TestSignVerifyUtf8 misses it because it sets .Utf8() on both sides and signs a string with no newlines.

Looking at the history it seems like an omission rather than a decision: ffbfa49 added the UTF-8 wrap to every path, cb7fc0c added the automatic text-signature detection to the decrypt and detached paths, and the inline path was not given it.

I have a fix and tests ready locally, but the contribution policy assigns copyright to Proton AG and certifies the contribution was created in whole by the submitter, and I used AI assistance for the harness and the patch, so I would rather report it than submit something that misrepresents that. Happy to hand over the reproducer or the patch if that is useful to whoever picks it up.

Worth noting separately: the two existing sanitize call sites disagree about which signature they inspect, SignatureCandidates[0] for detached and [len-1] for decrypt. That only matters for multi-signature messages and I did not chase it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions