Scripting gpg with Go

Written by Jiří Keresteš on 2020-01-23

pgp

Sometimes, you just need to use gpg from a script. There's a couple of ways to achieve your goals, and a lot of dead ends. Read on to learn more!


If you ever looked for a gpg library, you probably know that there are many different projects out there. Unfortunately, most of these do not support all the features you need. Did you say: "card support"? If yes, you are going to have a particularly bad time, because I've yet to see a library other than GPGME (GnuPG Made Easy) with card support. Why don't we use GPGME then? Well, it's a lot of things, but I certainly wouldn't call it "easy". Documentation is often rather cryptic, and using it in a cross-platform build process is something I'd rather avoid for the rest of my life.

Ok, so let's just execute gpg command with right parameters, right? Yes, but you have to keep in mind a few caveats. You can generate your usual on-disk gpg keys using something like this [1]:


$ cat &gt;foo << EOF
     %echo Generating a basic OpenPGP key
     Key-Type: DSA
     Key-Length: 1024
     Subkey-Type: ELG-E
     Subkey-Length: 1024
     Name-Real: Joe Tester
     Name-Comment: with stupid passphrase
     Name-Email: joe@foo.bar
     Expire-Date: 0
     Passphrase: abc
     # Do a commit here, so that we can later print "done" :-)
     %commit
     %echo done
EOF
$ gpg --batch --generate-key foo
 [...]
$ gpg --list-secret-keys
/tmp/tmp.0NQxB74PEf/pubring.kbx
-------------------------------
sec   dsa1024 2016-12-16 [SCA]
      768E895903FC1C44045C8CB95EEBDB71E9E849D0
uid           [ultimate] Joe Tester (with stupid passphrase) < joe@foo.bar &gt;
ssb   elg1024 2016-12-16 [E]

However, there's no --batch parameter for gpg --card-edit! GnuPG has notoriously bad user interface and this is an example. GnuPG version 2.3 should include a new tool gpg-card[2], which aims to provide a better interface, but as of January 2020 it still hasn't been released.

You can use some Expect-like[3] library with gpg --card-edit, but it usually depends on PTY interfaces, and PTY interfaces are difficult (i.e. often not supported) on Windows. Or you can dive deep into GnuPG documentation and discover something called "Esoteric options"[4] (yeah).

Most portable way to script card operations with GnuPG is to to launch gpg with --command-fd 0 and --status-fd 2 parameters. This makes gpg receive commands from stdin and print status output to stdout. I've promised some Go, so here, have a PoC!


package main

import (
        "bytes"
        "fmt"
        "os/exec"
        "log"
)

func main() {
        var out bytes.Buffer
        proc := exec.Command("gpg", "--card-edit", "--command-fd=0", "--status-fd=2")
        name := "Fred Tangerine"
        email := "fred.tangerine@trustica.cz"
        comment := "scripted key"
        expires := "0"
        proc.Stdout = &out
        proc.Stdin = bytes.NewBufferString("admin\ngenerate\ny\n" + expires + "\n" + name + "\n" + email + "\n" + comment + "\n")

        if err := proc.Start(); err != nil {
                log.Fatal(err)
        }
        proc.Wait()
        // debugging
        fmt.Print(out.String())
}

Of course, you should read status output messages and react to errors and whatnots. But this is just a PoC.

Thanks for reading this rather technical post. If you "just wanted to use Assuan", stay tuned. There'll be another post about Assuan struggles soon. See you next Thursday!

References

  1. https://www.gnupg.org/documentation/manuals/gnupg-devel/Unattended-GPG-key-generation.html
  2. https://lists.gnupg.org/pipermail/gnupg-devel/2019-September/034447.html
  3. Wikipedia contributors. (2019, December 9). Expect. In Wikipedia, The Free Encyclopedia. Retrieved 13:05, January 23, 2020, from https://en.wikipedia.org/w/index.php?title=Expect&oldid=929914083
  4. https://www.gnupg.org/documentation/manuals/gnupg/GPG-Esoteric-Options.html