Subject: Re: bug#67937: 30.0.50; auth-source-pass relies on epa-file being enabled

From: Arsen Arsenović <[email-masked]>
Date: Thu, 21 Nov 2024 19:54:30 +0100

Hi Michael,

Please keep the bug tracker in CC.

Michael Albinus <[email-masked]> writes:

>>> A .gpg file could be taken from a remote location. In that case, you
>>> have two file name handlers, which must cooperate: epa-file-handler, and
>>> tramp-file-name-handler.
>>
>> No, just one: tramp-file-name-handler.  epa-file-handler has nothing to
>> do with remote file access.
>
> So you don't knmow the principle of file name handlers.

I do not appreciate this dismissal.  I am very aware that
epa-file-handler is called in that scenario, that it processes the file,
and that it temporarily inhibits itself in order to cause TRAMP to
access a remote file.  I was trying to imply that epa-file-handler has
nothing to do with remote access there, it simply recurses as normal.

> The idea is to provide an alternative implementation for several basic
> primitive functions in Emacs. You can still use those functions, like
> insert-file-contents, but the file name handler uses its own
> implementation if it detects, that the file is special (on a remote
> host, compressed, whatever). And the file name handlers are combined
> if the file is special in different ways.
>
> Make a test: There is a file, let's say file.gpg. You open it via 'M-x
> find-file RET /path/to/file.gpg'. In the corresponding buffer you'll see
> the contents of decrypted file.gpg, thanks to the file name handler
> epa-file-handler.
>
>>> Furthermore, a .gpg file could be compressed, like file.gpg.gz.
>>
>> No, it cannot, not in a pass store.  Here's an example:
>>
>>   ~/.password-store$ touch thing.gpg.gz
>>   ~/.password-store$ pass show thing
>>   Error: thing is not in the password store.
>
> Now compress the file on the shell to, let's say, foo.gpg.gz. Open this
> file like 'M-x find-file /path/to/file.gpg.gz'. You should see now the
> contents of foo.gpg, again, due to file name handlers jka-compr-handler
> and epa-file-handler. Actually, this doesn't work ATM, due to an error
> in jka-compr-handler (the temporary file should keep the suffix of the
> original file), I'll check next days.

I know, this is how I keep journals (well, not compressed, but I have
encrypted files in my Org setup).  This isn't relevant here because...

> Of course, it doesn't work in your example on *shell* level. However, on
> Emacs level it should work, because jka-compr-handler should return an
> uncompressed temp file /tmp/xyz.gpg, and epa-file-handler should know
> how to handle this gpg file.

... the reference implementation does not do this, and it has no reason
to do this, because the 'storage format' in question does not support
compression, and so, auth-source-pass has no reason to implement this.

> And when calling 'pass show', that temp file /tmp/xyz.gpg should be used.

Nothing here calls 'pass show', mind you.  'pass show' was called in my
example not to demonstrate something Emacs does, but what the reference
implementation for the job auth-source-pass implements does.

Please read through auth-source-pass.el: it is a reimplementation.  This
is fine, of course.  It is short, so it should be pretty easy to read
through.

>> In general, a pass file is _specifically_ a gpg-encrypted file (and it
>> says so in the manual), and, indeed, pass assumes so, a lot:
>
> Of course. jka-compr-handler is responsible to convert the compressed
> file.gpg.gz to the uncompressed temp file xyz.gpg.

Indeed.  However, this does not matter, as a pass file is specifically
only a gpg-encrypted file, without any compression.

>> ... as does auth-source-pass:
>>
>> (defun auth-source-pass--read-entry (entry)
>>   "Return a string with the file content of ENTRY."
>>   (with-temp-buffer
>>     (insert-file-contents (expand-file-name
>>                            (format "%s.gpg" entry)
>>                            auth-source-pass-filename))
>>     (buffer-substring-no-properties (point-min) (point-max))))
>>
>> ;; TODO: add tests for that when `assess-with-filesystem' is included
>> ;; in Emacs
>> (defun auth-source-pass-entries ()
>>   "Return a list of all password store entries."
>>   (let ((store-dir (expand-file-name auth-source-pass-filename)))
>>     (mapcar
>>      (lambda (file) (file-name-sans-extension (file-relative-name file store-dir)))
>>      (directory-files-recursively store-dir "\\.gpg\\'"))))
>>
>> This is fine, of course, not making this assumption would be
>> unreasonable because of what the format of pass stores is.
>
> This must be enhanced then. A compressed (or remote) gpg file shall be
> acceptable as well.

No, there's no reason to "enhance" this - in fact, doing so could only
complicate this logic, for no reason.  Pass stores do not contain
compressed files (besides, I am not sure there is even a general
solution for this "enhancement").

The only possible result of this "enhancement" is a difference of
behavior between auth-source-pass and the reference implementation of
pass.  I don't know about you, but I don't feel comfortable with a
noncompatible implementation handling my passwords.

>> I do understand that pass also does not cover TRAMP the same way it does
>> not cover compressed files, but I don't believe this is relevant here:
>> when we discuss a filesystem hierarchy, the TRAMP handler serves to
>> remap it to a remote location, while the EPA file handler serves to
>> _alter contents_.  This is quite different.
>
> The same scenario: Tramp shall provide a local temp file xyz.gpg of the
> remote file /ssh:remotehost:/path/to/file.gpg.gz.

I'm not sure I follow.  How is this related to the paragraph above it?

I was trying to show a distinction between the jobs of epa-file-handler
and tramp-file-name-handler: the former "remaps" the contents of a file,
while the latter "remaps" the filesystem layout, which are very distinct
jobs, and to justify why the reference implementation not supporting
either is irrelevant.

We don't benefit from customizing of the former in auth-source-pass (it
would be akin to being able to change what cons does, IMO).

>> Emacs recognizes this: '-literally' file operations support TRAMP, but
>> not the content-altering handlers.  This is neat, I think.
>
> I don't know whether the other handlers need to support
> insert-file-contents-literally. To be investigated.

All the ones relevant to retrieving file contents unchanged ought to, or
ought to be altered to.

In the paragraph above, I said "'-literally' file operations support
TRAMP".  I meant this vice-versa, of course: TRAMP implements the
'-literally' file operations.  epa-file intentionally omits that
implementation, as far as I can see.

> But likely, it is sufficient, that they support file-local-copy. See:
>
> --8<---------------cut here---------------start------------->8---
> (file-local-copy "~/foo.gpg") = nil
> (file-local-copy "~/foo.gpg.gz") => "/tmp/jka-comeyhZmp"
> (file-local-copy "/ssh::~/foo.gpg") => "/home/albinus/.cache/emacs/tramp.fxF6zy.gpg"
> --8<---------------cut here---------------end--------------->8---
>
> Yes, there is the error, that jka-compr-handler does not keep the
> suffix, but this will be fixed. And with this, you will always ensure,
> that you have a local gpg file, a copy of the compressed or remote file.
>
>>> No, it doesn't make sense to bypass the file name handler machinery.
>>
>> Indeed - I have not implied otherwise.  There are useful handlers.
>> epa-file is not one of them for this use-case.
>
> My experience over some decades is, that whenever we don't use the
> file name handler mechanism where we should, we will run into trouble
> midterm.

And I argue we should not _specifically_ for the file contents.  For
reaching pass entries, of course, we should.

To reiterate my point, pass files are not just some files on the disk,
they are specifically .gpg encrypted files, and as such, have only one
reasonable thing to do with their contents: decrypt them.  There is
nothing else reasonable to do, so getting the file contents literally
and decrypting them seems like the most robust and correct approach.

The current approach does precisely this (it is not possible for
anything else to happen, because all files in a pass store are .gpg
files), but it relies on file-name-handler-alist being set correctly,
which is demonstrably unreliable.

It is only the contents of a pass file that are special, not how the
file is found, 'insert-file-contents-literally' provides the right
abstraction to get the file contents as-is and allow the user to treat
it specially.

Alternatively, if you insist, we could have auth-source-pass always add
the epa-file-handler in a let, because the only reasonable thing to do
with pass entries is decrypt them.  I recall that this option was not
favored.  I think it's still preferable to relying on epa-file being
enabled without ensuring so, though I'd still prefer if that dependency
wasn't present.

I do not think that checking for the presence of a file handler is
effective (because, firstly, it won't fix this issue in the general
case, and secondly, it is not useful), nor that checking for a specific
file handler being present is effective (because that'd be a roundabout
way to simply force the epa-file handler or not rely on it).

I'd also love to hear from the authors of auth-source-pass.

Have a lovely day.
-- 
Arsen Arsenović