1

In GPG, there is no proper documentation how to check a valid passphrase via bash code so, this is a hack. Based on the following example code is use to check whether GPG password that is cached in gpg-agent is valid or not:

#!/bin/bash
KEY_ID=YOUR_KEY_ID
echo "1234" | gpg -q --batch --status-fd 1 --sign --local-user $KEY_ID --passphrase-fd 0 --output /dev/null
return_code=$?
if [ "$return_code" = 0 ]; then
 echo "Valid passphrase has been set in gpg-agent"
   else
 echo "Invalid passphrase or no passphrase is set in gpg-agent"
fi

If a valid passphrase is set, and when I run this bash script, the return value is 0. This is correct

But

If no passphrase or invalid passphrase is set, I can see that the command is waiting for some unknown input or processes and it does not exit (blinking cursor until I terminate with CTRL+C). But this is a good signal to show that invalid passphrase is supplied.

My question is, if invalid passphrase is supplied, how do I force the command to exit and get return value of 1 so I can use the if else conditional correctly ?

NOTE AND INFO TO REPRODUCE THE PROBLEM FROM THE CODE (THIS IS NOT A QUESTION):

to set gpg password there are 2 ways:

  1. gpg --export-secret-keys -a <KEY_ID> (this can validate the passphrase) or
  2. without prompt in bash: /usr/libexec/gpg-preset-passphrase -c $KEY_GRIP <<< $PASSPHRASE (this does not validate the passphrase). I need to use command for cron. Why? Read below.

to clear the password I do this: echo RELOADAGENT | gpg-connect-agent

KEY_ID - you got when you first create the cert

KEY_GRIP - can be obtain with this command: gpg --with-keygrip --list-secret-keys $KEY_ID.

PASSPHRASE - is your passphrase / password for your cert to cache in gpg-agent.

Note that, If you are using this method to cache password in gpg-agent: /usr/libexec/gpg-preset-passphrase -c $KEY_GRIP <<< $PASSPHRASE, it won't validate the passphrase because wrong passphrase can also be cached in gpg-agent. This method is the only way to skip interactive input when run as cron to prevent script error because no input from user. This is the reason I use the hacked code.

MaXi32
  • 632
  • 9
  • 26
  • 1
    Err. `1` is what you're supposed to get for an error, `0` is what you're supposed to get for success; just as a matter of how standard UNIX exit status values are defined. – Charles Duffy Aug 24 '20 at 19:06
  • 3
    Bigger picture, though... if `gpg` doesn't treat an EOF from the passphrase FD as an exit condition, I'd suggest bringing that as a bug to its authors -- or doing as I've done, and switching away to a better-written OpenPGP implementation (I use the [golang x/crypto/openpgp](https://godoc.org/golang.org/x/crypto/openpgp) one). – Charles Duffy Aug 24 '20 at 19:07
  • Gotta love that the `--batch` flag is only actually batch mode if everything works smoothly o_0 – JNevill Aug 24 '20 at 19:08
  • Sorry typo. It should be 1 @CharlesDuffy – MaXi32 Aug 24 '20 at 19:09
  • ...btw, as an aside that doesn't really have anything to do with your problem, it's more reliable to use `=` rather than `==` in `[`; support for `==` is a bash extension, whereas the POSIX standard for `test`/`[` only guarantees `=` as a supported string comparison operator, and some baseline POSIX implementations fail when they see `==`. – Charles Duffy Aug 24 '20 at 19:09
  • 1
    Speaking to the title alone, though: A command that doesn't exit doesn't _have_ a return value. If you want to _force_ it to exit, consider running it under `timeout` or some equivalent. [timeout a command in bash without unnecessary delay](https://stackoverflow.com/questions/687948/timeout-a-command-in-bash-without-unnecessary-delay) provides one way to get there. – Charles Duffy Aug 24 '20 at 19:11
  • Honestly I never use timeout. I will look into this. Thank you for suggestion. – MaXi32 Aug 24 '20 at 19:22
  • What would an [MCVE] be? I should create a new key with a password, and then use that key id in `--local-user` and that command? – KamilCuk Aug 24 '20 at 20:12
  • @KamilCuk I updated some info and code. I'm using a timeout command suggested from Charles and it works well, but still looking for alternative answer if there is a better answer without dealing with timestamp. – MaXi32 Aug 24 '20 at 20:40
  • 1
    Just coming back to this, and revisiting what you're trying to do... you're trying to determine whether the agent already has a key loaded? You do realize you can query the agent directly, right? `gpg-connect-agent` is a thing. – Charles Duffy Aug 24 '20 at 20:45
  • There is a command to determine whether a password cache / loaded in gpg-agent or not (but it does not validate the password. Even the wrong password can be cached). This can be achieve using `gpg-connect agent`. I use this command: `RES=$(echo "KEYINFO --no-ask $KEY_GRIP Err Pmt Des" | gpg-connect-agent | awk '{ print $7 }');RETVAL=$?` . The 7th column if it returns 1, then the password for the key is cached. Actually I'm trying to determine if the password is correct or not when it is loaded in gpg-agent. – MaXi32 Aug 24 '20 at 20:59
  • The wrong password can be cached because I use this method to cache password: `/usr/libexec/gpg-preset-passphrase -c $KEY_GRIP <<< $PASSPHRASE` which is the only way to work with bash without interactive input because this code will run on cronjob. – MaXi32 Aug 24 '20 at 21:04
  • 2
    Have you dug into the codepaths to determine what gpg is waiting for? If it's waiting for the agent to query the user for a new passphrase, for example, then one ugly-yet-workable hack might be to temporarily reconfigure the executable the agent uses to request a passphrase from the user to instead be something that immediately returns an error. A stacktrace from wherever it's sitting while it hangs would be useful for root-cause analysis as to the _why_ of that hang, and thus to determining what to do about it. – Charles Duffy Aug 24 '20 at 21:11
  • 1
    Even if you don't want to do something so invasive and ugly as temporarily reconfiguring the agent, if you know what it's doing is invoking an `askpass` executable, that gives you a definitive way to determine that it's determined the password it has to be bad by looking for the presence of that child process without needing to play with timing-based heuristics. – Charles Duffy Aug 24 '20 at 21:13
  • Seems like more work need to be done to achieve this. I think I will stick with the timeout command at this moment. :D. Thank you for your help. – MaXi32 Aug 24 '20 at 21:14
  • 1
    A quick note about the close vote -- since this is still titled as being about "getting a return value for a command that does not exit", and you've indicated that you're actually using `timeout` as your answer, it probably makes more sense to close this as a duplicate of a "how do I efficiently make bash commands time out?" question that already has answers describing `timeout`. – Charles Duffy Aug 24 '20 at 22:27
  • 1
    There's certainly a good question that's specifically about scripting GnuPG here too, though. Whether you want to ask that separately, or edit the title to be asking that question explicitly (in which case I'll reverse the close vote) is up to you. – Charles Duffy Aug 24 '20 at 22:28
  • I need answer specifically on gpg because timeout is just a workaround not the real solution. Maybe one day they fixed the code and someone found this thread and provide a good solution. Also this topic about validating gpg password is not widely discussed on the internet. So, it's better not to close this question yet. Sometimes I feel like putting a generic title for a question would have more view :D. Changed the title. – MaXi32 Aug 25 '20 at 05:54
  • i found an answer for this, would you re-open this question so that I can write the exact answer to help others ? @CharlesDuffy – MaXi32 Oct 12 '20 at 12:07
  • @MaXi32, reopened. – Charles Duffy Oct 12 '20 at 14:06

3 Answers3

1

I have found the answer for this problem. The return code did not output correctly because of bug. I just need to modify the code so that it won't use default pin-entry passphrase prompt. Using the pin-entry passphrase can cause return code stuck at random time based on this related bug (https://dev.gnupg.org/T5076). So in brief, the return code was stuck at random time but this can be solved using this option:

--pinentry-mode=loopback

So, the above option is to disable pin-entry prompt for passphrase and we can use gpg-preset-passphrase utility instead to set the passphrase in terminal. So, using this method I can get the correct return code.

I found this thread talking about how to disable pin entry in gpg-agent such as this: here

So this is the working code with the correct return code when validating passphrase:

#!/bin/bash
gpg-agent --daemon
KEY_ID=ABC12321SS
GPG_PRESET_PASS="/usr/libexec/gpg-preset-passphrase" # This utility came with gpg
KEY_GRIP=$(gpg --with-keygrip --list-secret-keys "$KEY_ID" | grep -Pom1 '^ *Keygrip += +\K.*') # Get they key grip
# Ask passphrase
read -s -p "Enter a passphrase to cache into a running gpg-agent: " PASSPHRASE; echo
# Cache RAW passphrase here. RAW passphrase means un-validated passphrase. Passphrase can be valid or invalid cached in gpg-agent.
$GPG_PRESET_PASS -c $KEY_GRIP <<< $PASSPHRASE 
# AND NOW WE check if a RAW passphrase is cached:
RET_VAL=$?
if [ $RET_VAL = 0 ]; then
        echo "Un-validated (RAW) passphrase is cached"
        # This is the part that I must use --pinentry-mode=loopback, so I will get correct return code
        echo "Now validating a RAW cached passphrase from gpg-agent ..."
        test=$(echo "1234" | gpg -q --pinentry-mode=loopback --status-fd 1 --sign --local-user "$KEY_ID" --passphrase-fd 0 > /dev/null)
        RET_VAL=$?
        if [ $RET_VAL = 0 ]; then
                echo "Passphrase is valid"
        else
                echo "Passphrase is invalid"
                # Can clear the passphrase and ask again for RAW passphrase
        fi

else
        echo "Error when trying to cache RAW passphrase"
        exit 1
        # Run again the script to cache RAW passphrase
fi
MaXi32
  • 632
  • 9
  • 26
0

If I may, you can start another script that could time this script's execution time. If the actual script exits then correct password, else, if the set time reaches, you kill the script's PID and the script ends.

Muhammad Hasan
  • 140
  • 1
  • 9
  • Is this similar to using timeout built in feature ? – MaXi32 Aug 25 '20 at 04:11
  • because this is what I did to achieve the result (timeout in 1 second): `echo "1234" | timeout 1 gpg -q --batch --status-fd 1 --sign --local-user $KEY_ID --passphrase-fd 0 --output /dev/null` . I'm not sure if this workaround would run without error in the long run. My question in my mind, what if the gpg command has sleep function in between like 4 seconds and it needs to run next command. Then this is not a good solution using 1 second timeout. 4 seconds is just an example. Not really sure how timeout can handle this. I would have to read or ask more. – MaXi32 Aug 25 '20 at 04:17
  • agreed. i think your answer lies somewhere within the gpg code itself. and that would require much in-depth knowledge. – Muhammad Hasan Aug 25 '20 at 14:14
-1

you can try something like:

echo "1234" | gpg -q --batch --status-fd 1 --sign --local-user <KEY_ID> --passphrase-fd 0 --output /dev/null ||
{
    echo "Invalid passphrase or no passphrase is set in gpg-agent"
    exit
}
echo "Valid passphrase has been set in gpg-agent"

I am not sure whether this can deal with what looks like waiting for input.

Snorik
  • 201
  • 3
  • 15
  • This doesn't work. It still stuck when invalid passphrase is supplied. The first statement by gpg command is the cause of stuck, so it won't exit. – MaXi32 Aug 24 '20 at 19:31