Permalink
358 lines (254 sloc) 13.7 KB

Table of Contents generated with DocToc

Using GPG

This is an introduction to setting up and using GPG keys with Leiningen to sign artifacts for publication to Clojars and to encrypt repository credentials.

There are two versions of GPG available: v1.x and v2.x. For our purposes, they are functionally equivalent. Package managers generally install v2.x as gpg2, and v1.x as gpg, except for Homebrew which installs v2.x as gpg, and v1.x as gpg1. By default, Leiningen expects the GPG command to be gpg. You're welcome to use any version you like, but this primer will only cover installing v1.x (except under macOS), and has only been tested under v1.x.

What is it?

GPG (or Gnu Privacy Guard) is a set of tools for cryptographic key creation/management and encryption/signing of data. If you are unfamiliar with the concepts of public key cryptography, this Wikipedia entry serves as a good introduction.

An important concept to understand in public key cryptography is that you are really dealing with two keys (a keypair): one public, the other private (or secret). The public key can be freely shared, and is used by yourself and others to encrypt data that only you, as the holder of the private key, can decrypt. It can also be used to verify the signature of a file, confirming that the file was signed by the holder of the private key, and the contents of the file have not been altered since it was signed. You should guard your private key and passphrase closely, and share them with no one.

Installing GPG

Linux

Debian based distributions

Apt uses GPG v1, so it should already be installed. If not:

apt-get install gnupg

Fedora based distributions

Fedora and friends may have GPG v2 installed by default, but GPG v1 is available via:

yum install gnupg

Mac

There are several options here, depending on which package manager you have installed (if any):

  1. via homebrew: brew install gnupg2
  2. via macports: port install gnupg2
  3. via a binary installer (this installs gpg2 as gpg)

Windows

GPG4Win provides a binary installer that provides some possibly useful GUI tools in addition to providing the gpg command.

Creating a keypair

Create a keypair with:

gpg --gen-key

This will prompt you for details about the keypair to be generated, and store the resulting keypair in ~/.gnupg/.

The default key type (RSA and RSA) is fine for our purposes, as is the default keysize (2048). We recommend a validity period of 2 years.

You'll be prompted for a passphrase to protect your private key - it's important to use a strong one to help protect the integrity of your key.

Listing keys

GPG stores keys in a keystore located in ~/.gnupg/. This keystore holds your keypair(s), along with any other public keys you have used.

To list all of the public keys in your keystore:

gpg --list-keys

This will include any public keys you have used, including keys from others (if you've never used GPG before and just created your first keypair, you should just see your own key).

The output of the --list-keys option will include the id of your public key in the 'pub' line in the key listing (you'll need that id for other commands described here):

$ gpg --list-keys

            ↓↓↓↓↓↓↓↓
pub   2048R/2ADFB13E 2013-03-16 [expires: 2014-03-16]
uid                  Bob Bobson <bob@bobsons.net>
sub   2048R/8D2344D0 2013-03-16 [expires: 2014-03-16]

The --fingerprint option will act just like --list-keys, but will include the fingerprint of each certificate in the output. You can filter the output of the --fingerprint option by providing a key id or any substring from the uid (this trick also works for the --list-keys option):

$ gpg --fingerprint 2ADFB13E

pub   2048R/2ADFB13E 2013-03-16 [expires: 2014-03-16]
      Key fingerprint = 3367 5FD0 D67B 3218 5815  51A3 97D4 06D0 2ADF B13E
uid                  Bob Bobson <bob@bobsons.net>
sub   2048R/8D2344D0 2013-03-16 [expires: 2014-03-16]

Publishing your public key

To make it easier for others that need your public key to find it, you can publish it to a key server with:

gpg --send-keys 2ADFB13E # use your id instead

This pushes a copy of your public key to one of a cluster of free key servers, and the key is propagated to all of the other servers in the cluster in short order.

If your keypair is compromised, you can publish a revocation certificate to the key server to let others know that your key can no longer be trusted for any future signing or encryption. It's a good idea to generate a revocation certificate whenever you create a new keypair, and store it in a safe place. As long as you have that revocation certificate, you can revoke a keypair even if you no longer have the private key. You can generate a revocation certificate with:

$ gpg --output 2ADFB13E-revoke.asc --gen-revoke 2ADFB13E

Be sure to protect your revocation certificate carefully - anyone who gains access to it can use it to revoke your keypair. The GPG maintainers recommend printing it out and storing the hardcopy in a safe place.

To revoke your certificate when the time comes (not now!), do the following:

$ gpg --import 2ADFB13E-revoke.asc  # ONLY WHEN YOU NEED TO REVOKE
$ gpg --send-keys 2ADFB13E          # ONLY WHEN YOU NEED TO REVOKE

How Leiningen uses GPG

Leiningen uses GPG for three things: decrypting credential files, signing release artifacts, and signing tags. We'll focus on artifact signing here; for information on credentials encryption/decryption, see the deploy guide. Once you are configured to sign releases, signing tags should be straightforward.

On some systems you will be prompted for your GPG passphrase when it is needed if you haven't entered it. If yours does not, you can install Keychain, which provides this functionality portably.

Signing a file

When you deploy a non-SNAPSHOT artifact via the deploy task, Leiningen will attempt to create GPG signatures of the jar and pom files. It does so by shelling out to gpg and using your default private key to sign each artifact. This will create a signature file for each artifact named by appending .asc to the artifact name.

Both signatures are then uploaded along with the artifacts. If you're deploying to Clojars, you'll want to provide it with your public key (see below) in order that the signatures can be verified.

To disable signing of releases, set :sign-releases to false in the :repositories entry you are targeting. If you do this, everyone who is depending on your library will not be able to confirm that the copy they get has not been tampered with, so this is not recommended.

Overriding the gpg defaults

By default, Leiningen will try to call GPG as gpg, which assumes that gpg is in your path, and your GPG binary is actually called gpg. If either of those are false, you can override the command Leiningen uses for GPG by setting the LEIN_GPG environment variable.

GPG by default will select the first private key it finds (which will be the first key listed by gpg --list-secret-keys). If you have multiple keys and want to sign with one other than first, or want to use specific keys for a particular release repository, you can specify which key to use either globally, per-project, or per-deploy-repository. All three places use the same configuration syntax, it's all about where you put it. You can specify the key by id or by the uid.

To set a key globally, add it to your user profile in ~/.lein/profiles.clj:

{:user {...
        :signing {:gpg-key "2ADFB13E"}}} ;; using the key id

To set a key for a particular project, add it to the project definition:

(defproject ham-biscuit "0.1.0"
   ...
   :signing {:gpg-key "bob@bobsons.net"} ;; using the key uid
   ...)

To set a key for a particular deploy repository, add it to the repository specification in your project definition:

(defproject ham-biscuit "0.1.0"
   ...
   :deploy-repositories 
     [["releases" {:url "https://blueant.com/archiva/internal/releases"
                   :signing {:gpg-key "2ADFB13E"}}]
     ["snapshots" "https://blueant.com/archiva/internal/snapshots"]]
   ...)

Setting the gpg passphrase for unattended deploys

It's also possible to provide the passphrase required to unlock your keyring. This is meant only for unattended deploys, for example in a continuous integration system like Travis CI or CircleCI or Jenkins.

Passphrase can be configured in the environment:

(defproject ham-biscuit "0.1.0"
   ...
   :signing {:gpg-key "bob@bobsons.net"
             :gpg-passphrase :env/gpgpass} ;; looks up GPGPASS from env
   ...)

In your CI service your gpg keyring will need to be encrypted and injected into the build, and the passphrase likewise encrypted such that the environment variable is visible only to the build.

For testing purposes the pasphrase can also be set as a string literal but this is strongly discouraged in any production usage.

(defproject ham-biscuit "0.1.0"
   ...
   :signing {:gpg-key "bob@bobsons.net"
             :gpg-passphrase "my-passphrase-in-the-clear"}
   ...)

Troubleshooting

Debian based distributions

gpg: can't query passphrase in batch mode

Could not decrypt credentials from /home/xxx/.lein/credentials.clj.gpg
gpg: can't query passphrase in batch mode
gpg: decryption failed: secret key not available

This error happens with gpg 1.4.12. Make sure that you have use-agent option explicitly enabled in ~/.gnupg/gpg.conf. See gpg option list.

You can test whether this solution works with

gpg --quiet --batch --decrypt ~/.lein/credentials.clj.gpg

If the system asked your passphrase then problem solved.

Mac OSX

Unable to get GPG installed via Homebrew and OSX Keychain to work

Installing GPG from here instead: https://www.gpgtools.org/installer/index.html

GPG doesn't ask for a passphrase

Make sure that you have "use-agent" option explicitly enabled in ~/.gnupg/gpg.conf. See gpg option list.

You can test the config with

gpg --quiet --batch --decrypt ~/.lein/credentials.clj.gpg

Leiningen should pick it up automatically when the command above works correctly.

gpg: decryption failed: secret key not available

When you run

gpg --quiet --batch --decrypt ~/.lein/credentials.clj.gpg

you get the error message

gpg: decryption failed: secret key not available

try running it without --quiet

gpg  --use-agent --decrypt ~/.lein/credentials.clj.gpg

If you get

gpg: encrypted with RSA key, ID DEAD8F70
gpg: decryption failed: secret key not available

run gpg -k and check that DEAD8F70 is in the known keys list. If it isn't, ~/.lein/credentials.clj.gpg may have been encrypted with a different key.

GPG prompts for passphrase but does not work with Leiningen

gpg --quiet --batch --decrypt ~/.lein/credentials.clj.gpg

It's hanging for a while after executing lein repl and then prints out these messages:

$ lein repl
Could not decrypt credentials from /Users/xxx/.lein/credentials.clj.gpg
pinentry-curses: no LC_CTYPE known - assuming UTF-8
pinentry-curses: no LC_CTYPE known - assuming UTF-8
pinentry-curses: no LC_CTYPE known - assuming UTF-8
pinentry-curses: no LC_CTYPE known - assuming UTF-8
pinentry-curses: no LC_CTYPE known - assuming UTF-8
gpg-agent[1009]: command get_passphrase failed: Invalid IPC response
gpg: problem with the agent: Invalid IPC response
gpg: decryption failed: No secret key

Leiningen can't present enter-passphrase page of gpg. It's an issue with Leiningen or gpg2 or gpg-agent or pinentry.

Use the GUI version of gpg, GPG Suite. Now when executing 'lein repl', it prompts for passphrase on the GUI instead.