successful decryption with session key reports failure if public key is unknown
Testing, NormalPublic

Description

Consider an encrypted message $M encrypted with session key $K, which is wrapped in a PKESK encrypted to public key $X.

This command will always produce the cleartext:

gpg --override-session-key=$K --decrypt  < $M

but it will return a non-zero error code if the current GNUPGHOME doesn't know anything about $X.

I think this return code also causes gpgme (gpgme_op_decrypt) to think that the message was not successfully decrypted, even though it was successfully decrypted. This results in a cascade of unncessary failure further up the stack, as the tools that use gpgme try to report the "error" to the user.

I think if the decryption succeeds this should result in no error, just like it wouldn't be an error if there is another PKESK that you don't recognize.

dkg created this task.Oct 24 2017, 8:38 AM
dkg added a comment.Oct 24 2017, 8:43 AM

Hm, perhaps this non-zero return code is due to not being able to write to the GNUPGHOME directory, actually. It goes away when GNUPGHOME is writable. That doesn't make sense either -- this operation doesn't actually depend on being able to write to GNUPGHOME, so it shouldn't return a different error code if GNUPGHOME is unwritable.

werner triaged this task as Normal priority.Oct 24 2017, 8:48 AM
werner added a subscriber: werner.

gpgme does not known about return codes because it uses a double fork approach. However, certain staus lines could have the same effect.

Just tried this but can't replicate it:

$ ../g10/gpg -dv <1.msg --override-session-key 7:D6E1027D58A0CB047C41EA881A137197 --status-fd 2 
gpg: public key is 7F3B7ED4319BCCA8
[GNUPG:] ENC_TO 7F3B7ED4319BCCA8 18 0
gpg: encrypted with ECDH key, ID 7F3B7ED4319BCCA8
[GNUPG:] BEGIN_DECRYPTION
gpg: AES encrypted data
[GNUPG:] DECRYPTION_INFO 2 7
gpg: original file name=''
[GNUPG:] PLAINTEXT 62 1508859245 
[GNUPG:] PLAINTEXT_LENGTH 68
"Well hello there Charlie Brown, you blockhead."
                -- Lucy Van Pelt
[GNUPG:] DECRYPTION_OKAY
[GNUPG:] GOODMDC
[GNUPG:] END_DECRYPTION
$ echo $?
0
$ gpg -k 7F3B7ED4319BCCA8
gpg: error reading key: No public key

I created the message in my standard environment using

$ fortune | gpg -ear wk@gnupg.org

and c+p the message to the file 1.msg in the test environment.

dkg added a comment.Oct 27 2017, 8:28 AM

can you try it with --homedir /does/not/exist

$ gpg --homedir /notexistent -dv <1.msg --override-session-key 7:D6E1027D58A0CB047C41EA881A137197 --status-fd 2 
gpg: keyblock resource '/notexistent/pubring.kbx': No such file or directory
[GNUPG:] ERROR add_keyblock_resource 33587281
gpg: public key is 7F3B7ED4319BCCA8
[GNUPG:] ENC_TO 7F3B7ED4319BCCA8 18 0
[GNUPG:] ERROR keydb_search 33554445
gpg: encrypted with ECDH key, ID 7F3B7ED4319BCCA8

Indeed, this makes gpg return 2. The reason is that the first error message uses log_error which sets a flag to have gpg return 2. Now, changing this to log_info may produce problems for applications which expect that gpg errors out for a bad homedir.

dkg added a comment.Oct 28 2017, 2:11 PM

agreed, generically changing this check to log_info doesn't make sense. However, in *this circumstance*, gpg actually has no error.

Similarly, gpg --homedir /nonexistent --version does not return an error -- it also doesn't need the homedir :)

When trying to use gpg in such a way that ensures no private key access happens (e.g. either session key or symmetric passphrase is known), and where there is no interest in signature verification, (e.g. T3277) the safest and quickest way to do this is to point to a non-existent directory, rather than to set up and tear down a homedir.

Consider a suitably-restricted user account that is unable to write *anywhere* in the local filesystem, but has access to session keys or passphrases. It should still be able to do thse decryption operations without failure, right?

werner added a comment.Nov 1 2017, 6:56 PM

What do you think about a special case for the homedir "/dev/null" ? We use this device as a specila value at other places too. I have often seen "/nonexistent" in /etc/passwd but there is no standard for this. However, /dev/null is well defined.

dkg added a comment.Nov 13 2017, 5:18 AM

I'm not sure why a special case should be needed -- failure to create
the .kbx should not be a failure for a decryption operation in general.

a special case just seems like one more rough edge for users to catch
themselves on -- something works with --homedir=/dev/null, but fails
with --homedir=/nonexistent seems likely to be surprising for people not
aware of the development history of the project.

What about a new flag --ephemeral, but with the same semantics as your
proposed --homedir=/dev/null ? That would be less-surprising than
finding /dev/null treated separately, and would make it explicit that
this is a supported (and encouraged) use case.

I was already implementing a --no-homedir when I figured that we have --no-keyring. Using that with any homedir fulfills the requested purpose.

@dkg does --no-keyring solves the problem for you?

yes, it looks like using --no-keyring does change the return code from 2 to 0 for me.

Okay. So for GPGME should we add --no-keyring if --override-session-key is also enabled? I think this would be better than relying on the fact that gpgme ignores the returned error code.

dkg added a comment.EditedSep 12 2018, 4:12 PM

if gpgme doesn't rely on the return value, but instead on parsing the --status-fd for errors, then there will still be an ERROR printed:

Create an encrypted file with a public key that you know of (you don't need its secret key), and learn the encrypted file's session key:

PGPID=0EE5BE979282D80B9F7540F1CCD2ED94D21739E9
gpg --always-trust --show-session-key --pinentry-mode loopback --passphrase-fd 4 4<<<bananas --encrypt --symmetric -a -r $PGPID <<<testing > test.pgp --status-fd 2
gpg --pinentry-mode loopback --passphrase-fd 4 4<<<bananas --show-session-key --status-file session.key --decrypt < test.pgp
sessionkey=$(awk '/^\[GNUPG:\] SESSION_KEY /{ print $3 }' < session.key )

then try a decryption with a non-existent homedir:

gpg --override-session-key-fd 4 4<<<"$sessionkey" --homedir /does/not/exist --no-keyring --batch --no-tty --status-fd 2 --decrypt <test.pgp

while the return code is 0, the status-fd still shows an ERROR when looking for the key:

[GNUPG:] ENC_TO 25D5152727E61757 18 0
[GNUPG:] ERROR keydb_search 33554445
[GNUPG:] BEGIN_DECRYPTION
[GNUPG:] DECRYPTION_INFO 2 9
[GNUPG:] PLAINTEXT 62 1536760986 
[GNUPG:] PLAINTEXT_LENGTH 8
[GNUPG:] DECRYPTION_OKAY
[GNUPG:] GOODMDC
[GNUPG:] END_DECRYPTION

in your earlier comment you mentioned that certain status lines could cause gpgme_op_decrypt to report a problem. perhaps this needs to be added to the gpgme test suite?

Using an ephemeral or non-existent home directory -- regardless of --no-keyring -- is a good, safe practice for users of gpgme that want to be sure some temporary work doesn't interact at all with their current settings.

The background of my earlier comment was that I didn't tested GPGME in this regard.

Having an ephemeral home directory has the disadvantage that there will be no configuration at all. S no log files for example.

dkg added a comment.Sep 12 2018, 4:53 PM

sorry, i haven't had time to test gpgme with those changes myself. i hope someone can do so.

given that there are many different ways that commands might interact with or modify a home directory, or to start daemons, etc, there are legitimate circumstances where an ephemeral home directory is simply the safest and most robust choice. alternately, we need a way for the user to express that they expect their use of gpg to completely avoid changing anything about the state of the homedir. I don't think we have that, do we? i've seen "use an ephemeral homedir" as guidance on the various mailing lists for people who want a stateless invocation more than once. If there's different guidance, we should be clear about what it is.

werner changed the task status from Open to Testing.Nov 5 2018, 10:33 AM

Looking at the GPGME code the ERROR stati don't matter because they are only used to return a better error code in case an operation failed. The specific ones are not even recognized.

I add --no-keyring to GPGME's gpg invocation.

dkg added a comment.Nov 8 2018, 6:44 AM

I'm fine with this change, but i do note that some people expect --decrypt to mean "decrypt and verify, if possible". In particular, gpg(1) says about --decrypt:

If the decrypted file is  signed,  the  signature  is also  verified.

I can't tell how this change interacts with GPGME_DECRYPT_VERIFY or gpgme_op_decrypt_verify. can you comment on this? or is it covered by the test suite?

werner added a comment.Nov 8 2018, 8:41 AM

gpgme_op_decrypt_verify can always be used instead of gpgme_op_decrypt. This is an obvious requirement because the signature and the fact that there is a signature is only known after the decryption step. The newer GPGME_DECRYPT_VERIFY of the gpgme_op_decrypt_ext function is basically an alias for gpgme_op_decrypt_verify.
For both functions gpgme employs "gpg --decrypt".

We don't have a regression test for --override-session-key in gpgme. It can be tested only manually using tests/run-decrypt.

dkg added a comment.Nov 8 2018, 8:59 AM

I don't think this answered my question -- i'm asking how adding --no-keyring affects gpgme_op_decrypt_verify -- it seems like verification would fail if no keyring is used, no?