GNOME Keyring Daemon Breaks My GPG Encrypted Backups

8 minute read

Background

Somewhere along the way my backups stopped working when I upgraded to Ubuntu 13.04. I don’t understand what exactly changed as the backup script is the same and I’m pretty sure I was using gnome-keyring-daemon as my gpg-agent.

I use obnam from remote back-ups and it works great. Every night it copies the delta from the last back-up to Dreamhost Personal Backup and encrypts my data on the fly. In the event my apartment burns down I’ll have my most important data. The rest of my data is backed-up using rsync to another local harddrive which is considerably cheaper and significantly faster.

Troubleshooting

My cron scripts started complaining, so I began my investigation with a simple obnam command. This command is supposed to list all my backups on the remote server after it decrypts the remote metadata locally. However it failed after I took a while guess that an empty or unset $DISPLAY environmental variable would trip it up:

$ DISPLAY="" obnam generations
Host key fingerprint is 0e:c2:f6:f4:d9:86:9d:4b:c4:3d:77:e7:a4:bb:59:14
+--[ RSA 2048]----+
|                 |
|                 |
|               E |
|   .     . .    .|
|    + o S o o . =|
|   . + + * . o *.|
|      . = *   . o|
|         o .   + |
|          .   +. |
+-----------------+

ERROR: gpg: problem with the agent - disabling agent use
gpg: can't query passphrase in batch mode
gpg: Invalid passphrase; please try again ...
gpg: can't query passphrase in batch mode
gpg: Invalid passphrase; please try again ...
gpg: can't query passphrase in batch mode
gpg: decryption failed: secret key not available

Next step was to try gpg standalone in a terminal with DISPLAY set and then unset.

$ echo "test" | gpg -ase --batch --default-key 0x4437655D -r 0x4437655D | gpg                                                                          

You need a passphrase to unlock the secret key for
user: "Obnam <[email protected]>"
2048-bit RSA key, ID 6DEBA469, created 2012-07-08 (main key ID 4437655D)

gpg: encrypted with 2048-bit RSA key, ID 6DEBA469, created 2012-07-08
	  "Obnam <[email protected]>"
test
gpg: Signature made Tue 07 May 2013 12:20:01 AM PDT using RSA key ID 4437655D
gpg: Good signature from "Obnam <[email protected]>"

$ echo "test" | DISPLAY="" gpg -ase --batch --default-key 0x4437655D -r 0x4437655D | gpg
gpg: problem with the agent - disabling agent use
gpg: can't query passphrase in batch mode
gpg: Invalid passphrase; please try again ...
gpg: can't query passphrase in batch mode
gpg: Invalid passphrase; please try again ...
gpg: can't query passphrase in batch mode
gpg: no default secret key: bad passphrase
gpg: [stdin]: sign+encrypt failed: bad passphrase
gpg: processing message failed: eof

Okay, so it’s not an obnam invocation problem or something from cron other then DISPLAY seems to break everything. Lets see what strace tells us:

$ echo "test" | DISPLAY="" strace -vo strace.log gpg -ase --batch --default-key 0x4437655D -r 0x4437655D
gpg: problem with the agent - disabling agent use
gpg: can't query passphrase in batch mode
gpg: Invalid passphrase; please try again ...
gpg: can't query passphrase in batch mode
gpg: Invalid passphrase; please try again ...
gpg: can't query passphrase in batch mode
gpg: no default secret key: bad passphrase
gpg: [stdin]: sign+encrypt failed: bad passphrase

And the strace output is in strace.log:

...

socket(PF_FILE, SOCK_STREAM, 0)         = 7
connect(7, {sa_family=AF_FILE, path="/run/user/nitro/keyring-FUvSXm/gpg"}, 36) = 0
read(7, "OK your orders please\n", 1002) = 22
 | 00000  4f 4b 20 79 6f 75 72 20  6f 72 64 65 72 73 20 70  OK your  orders p |
 | 00010  6c 65 61 73 65 0a                                 lease.            |
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff53fd8900) = -1 ENOTTY (Inappropriate ioctl for device)
write(7, "OPTION ttyname=/dev/tty", 23) = 23
 | 00000  4f 50 54 49 4f 4e 20 74  74 79 6e 61 6d 65 3d 2f  OPTION t tyname=/ |
 | 00010  64 65 76 2f 74 74 79                              dev/tty           |
write(7, "\n", 1)                       = 1
 | 00000  0a                                                .                 |
read(7, "OK \n", 1002)                  = 4
 | 00000  4f 4b 20 0a                                       OK .              |
write(7, "OPTION ttytype=screen-256color", 30) = 30
 | 00000  4f 50 54 49 4f 4e 20 74  74 79 74 79 70 65 3d 73  OPTION t tytype=s |
 | 00010  63 72 65 65 6e 2d 32 35  36 63 6f 6c 6f 72        creen-25 6color   |
write(7, "\n", 1)                       = 1
 | 00000  0a                                                .                 |
read(7, "OK \n", 1002)                  = 4
 | 00000  4f 4b 20 0a                                       OK .              |
write(7, "OPTION lc-ctype=en_US.UTF-8", 27) = 27
 | 00000  4f 50 54 49 4f 4e 20 6c  63 2d 63 74 79 70 65 3d  OPTION l c-ctype= |
 | 00010  65 6e 5f 55 53 2e 55 54  46 2d 38                 en_US.UT F-8      |
write(7, "\n", 1)                       = 1
 | 00000  0a                                                .                 |
read(7, "OK \n", 1002)                  = 4
 | 00000  4f 4b 20 0a                                       OK .              |
write(7, "OPTION lc-messages=en_US.UTF-8", 30) = 30
 | 00000  4f 50 54 49 4f 4e 20 6c  63 2d 6d 65 73 73 61 67  OPTION l c-messag |
 | 00010  65 73 3d 65 6e 5f 55 53  2e 55 54 46 2d 38        es=en_US .UTF-8   |
write(7, "\n", 1)                       = 1
 | 00000  0a                                                .                 |
read(7, "OK \n", 1002)                  = 4
 | 00000  4f 4b 20 0a                                       OK .              |

...

write(7, "GET_PASSPHRASE 0C7DFA3DEF7B1A269"..., 202) = 202
 | 00000  47 45 54 5f 50 41 53 53  50 48 52 41 53 45 20 30  GET_PASS PHRASE 0 |
 | 00010  43 37 44 46 41 33 44 45  46 37 42 31 41 32 36 39  C7DFA3DE F7B1A269 |
 | 00020  34 45 41 38 39 34 31 30  46 38 34 45 31 30 41 34  4EA89410 F84E10A4 |
 | 00030  34 33 37 36 35 35 44 20  58 20 58 20 59 6f 75 2b  437655D  X X You+ |
 | 00040  6e 65 65 64 2b 61 2b 70  61 73 73 70 68 72 61 73  need+a+p assphras |
 | 00050  65 2b 74 6f 2b 75 6e 6c  6f 63 6b 2b 74 68 65 2b  e+to+unl ock+the+ |
 | 00060  73 65 63 72 65 74 2b 6b  65 79 2b 66 6f 72 2b 75  secret+k ey+for+u |
 | 00070  73 65 72 3a 25 30 41 22  4f 62 6e 61 6d 2b 3c 6b  ser:%0A" Obnam+<k |
 | 00080  79 6c 65 40 6b 79 6c 65  6d 61 6e 6e 61 2e 63 6f  yle@kyle manna.co |
 | 00090  6d 3e 22 25 30 41 32 30  34 38 2d 62 69 74 2b 52  m>"%0A20 48-bit+R |
 | 000a0  53 41 2b 6b 65 79 2c 2b  49 44 2b 34 34 33 37 36  SA+key,+ ID+44376 |
 | 000b0  35 35 44 2c 2b 63 72 65  61 74 65 64 2b 32 30 31  55D,+cre ated+201 |
 | 000c0  32 2d 30 37 2d 30 38 25  30 41                    2-07-08% 0A       |
write(7, "\n", 1)                       = 1
 | 00000  0a                                                .                 |
read(7, "ERR 113 Server Resource Problem\n", 1002) = 32
 | 00000  45 52 52 20 31 31 33 20  53 65 72 76 65 72 20 52  ERR 113  Server R |
 | 00010  65 73 6f 75 72 63 65 20  50 72 6f 62 6c 65 6d 0a  esource  Problem. |
write(2, "gpg: ", 5)                    = 5
write(2, "problem with the agent - disabli"..., 45) = 45
write(7, "BYE", 3)                      = 3
 | 00000  42 59 45                                          BYE               |
write(7, "\n", 1)                       = 1
 | 00000  0a                                                .                 |
close(7)                                = 0

...

Note the “ERR 113 Server Resource Problem.” response from the gpg-agent. That seems odd doesn’t it?

Next lets compare a working strace (with DISPLAY set) and one without, diff and we see the following:

 socket(PF_FILE, SOCK_STREAM, 0)         = 7
 connect(7, {sa_family=AF_FILE, path="/run/user/nitro/keyring-FUvSXm/gpg"}, 36) = 0
 read(7, "OK your orders please\n", 1002) = 22
  | 00000  4f 4b 20 79 6f 75 72 20  6f 72 64 65 72 73 20 70  OK your  orders p |
  | 00010  6c 65 61 73 65 0a                                 lease.            |
-ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffb7a69230) = -1 ENOTTY (Inappropriate ioctl for device)
+write(7, "OPTION display=:0.0", 19)     = 19
+ | 00000  4f 50 54 49 4f 4e 20 64  69 73 70 6c 61 79 3d 3a  OPTION d isplay=: |
+ | 00010  30 2e 30                                          0.0               |
+write(7, "\n", 1)                       = 1
+ | 00000  0a                                                .                 |
+read(7, "OK \n", 1002)                  = 4
+ | 00000  4f 4b 20 0a                                       OK .              |
+ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff847e57e0) = -1 ENOTTY (Inappropriate ioctl for device)
 write(7, "OPTION ttyname=/dev/tty", 23) = 23
  | 00000  4f 50 54 49 4f 4e 20 74  74 79 6e 61 6d 65 3d 2f  OPTION t tyname=/ |
  | 00010  64 65 76 2f 74 74 79                              dev/tty           |

Clearly the working one passes a “OPTION display=:0.0” to the server. Why should the server care what the display is if the key is already unlocked and doesn’t need to pop-up a passphrase entry dialog?

Since I’m running Ubuntu 13.04 and that uses gnupg v3.6.3, lets look at the source code. In gkd-gpg-agent-ops.c we can see why it doesn’t work. We need to set $DISPLAY correctly in-order for call->terminal_ok to be set. Otherwise, if call->terminal_ok isn’t set, it breaks like I’ve observed.

Solutions

This leaves me with a few options:

  1. Forge export DISPLAY=:0.0 in my cron script to dodge this pointless “security” check
  2. Patch the code so it isn’t silly
  3. Use gnupg-agent which doesn’t behave silly like this

In the interest of simplicity, I picked 1 and added one line to my backup script. And life goes on.

Part of me still wonders why this broke when I upgrade to Ubuntu 13.04, this should have affected earlier versions of Ubuntu looking at the change log for gkd-gpg-agent-ops.c.

Comments