GPGMeh: GPG Made Even HARDER!

Build Status

Why?

GPG can be complicated: this gem is just a high level wrapper around gpg. GPGME also provides a nice API on top of GPG, but it has two drawbacks: it is not thread safe and it holds the GIL when shelling out to gpg. This holds up the entire ruby process for the duration of the GPG call, which can be relatively slow.

Installation

Add this line to your application's Gemfile:

gem 'gpgmeh'

And then execute:

$ bundle

Or install it yourself as:

$ gem install gpgmeh

Usage

Default Configuration

GPGMeh.default_cmd = "gpg" # first gpg found in your $PATH
GPGMeh.default_args = ["--armor", "--trust-model", "always"]` # --no-tty` and `--quiet` are always added to the argument list
GPGMeh.timeout_sec = 0.2 # wait up to 200ms for gpg to finish

Troubleshooting your Configuration

Make sure GPGMeh.default_cmd uses gpg, not gpg2. If you get any of the following errors, check your gpg version.

gpg2 has a slightly different format for --list-keys --with-colons:

lib/gpgmeh/key.rb:74:in `rescue in creation_date=': invalid date="1454695279" (GPGMeh::Key::ParseError)

gpg2 may have trouble starting the agent:

command get_passphrase failed: Inappropriate ioctl for device

Public Key Encryption: Rick wants to encrypt and sign something for Spiff

# 7CAAAB91 is Spaceman Spiff's public key id; multiple recipients can be specified
GPGMeh.encrypt("boom", ["7CAAAB91"]) do |key_id|
  # This is the passphrase callback. The argument is Rick's secret key id.
  # Return value: the secret keyring passphrase
  "rick's-secret-keyring-passphrase"
end

Public Key Decryption: Spiff wants to decrypt something from Rick

encrypted_message = <<EOM
-----BEGIN PGP MESSAGE-----
Version: GnuPG v1

hQEMA5IgSfURq0FaAQf/TxrcB0EeC5XpEwVyjaKoMNR7d2PZFBLQL9wX81jEIPIN
0tsq7/OSj/bZF1p9gkQ9YN+wzvS+1pLlPo1T/GNGrt6ay+ml4mOjezACfrQ+EBB+
ay5XrDbwemAW/tqLMkJMrx28dt8fkNlXv+uzPKpQI5cubBcDyoD/E53rqyybjt+D
pqA9bZ3OORqWHPBZy50eaTs/tyVgBpfXsgcTfbwSedSNnLXxdB0p2pKgPjeAYlCm
DGzxIRSSZjHSBieDm6ZUv/tcplXqrzQxZT/0rhneoG5FK+0g5sayEPQKozdVdFM3
B4a2jzcDbhkNEZ2HV2VVmRp2HHaFRFftuPeoECQGjckoxTo5u9K6cnOymDbf2lN0
/0Jec1LUDWLYUtzNonBpPdlUIxlllT6Q0Q==
=ANji
-----END PGP MESSAGE-----
EOM
GPGMeh.decrypt(encrypted_message) do |key_id|
  # This is the passphrase callback. The argument is Spiff's secret sub key id
  # that Rick used to encypt the message.
  # Return value: the secret keyring passphrase
  "spiff's-secret-keyring-passphrase"
end

Symmetric Encryption: Rick wants to symmetrically encrypt and sign a message

GPGMeh.encrypt_symmetric("boom") do |key_id|
  # This is the passphrase callback. The argument is Rick's secret key id OR :symmetric
  if key_id == :symmetric
    "the-symmetric-passphrase"
  else
    "rick's-secret-keyring-passphrase"
  end
end

Override default configuration

# 7CAAAB91 is Spaceman Spiff's public key id; multiple recipients can be specified
GPGMeh.encrypt(
  "boom",
  ["7CAAAB91"],
  gpg_options: {
    cmd: "/usr/local/bin/gpg",
    homedir: "/tmp/.gnupg",
    timeout_sec: 10
  }
) do |key_id|
  # This is the passphrase callback. The argument is Rick's secret key id.
  # Return value: the secret keyring passphrase
  "rick's-secret-keyring-passphrase"
end

GPG documentation

The gpg 1.4 docs describe the key types and status-fd output format.

GPG setup (this was done to setup the tests, here for posterity)

Generate key for Rick Hardslab

gpg --homedir spec/support/rickhardslab --gen-key

Generate key for Spaceman Spiff

gpg --homedir spec/support/spacemanspiff --gen-key

Rick Hardslab imports Spaceman Spiff's public key

gpg --homedir spec/support/rickhardslab --import spacemanspiff/pubring.gpg

Rick Hardslab trusts Spaceman Spiff's public key

gpg --homedir spec/support/spacemanspiff --export-ownertrust | gpg --homedir spec/support/rickhardslab --import-ownertrust

Spaceman Spiff imports Rick Hardslab's public key

gpg --homedir spec/support/spacemanspiff --import rickhardslab/pubring.gpg

Spaceman Spiff trusts Rick Hardslab's public key

gpg --homedir spec/support/rickhardslab --export-ownertrust | gpg --homedir spec/support/spacemanspiff --import-ownertrust

Edit Rick's Key so a "uid" record exists for the tests to ignore ;)

gpg --homedir spec/support/rickhardslab/ --edit-key 7A9910E0243D6FEB
# Edit the fields and "save" to exit

Rick Hardslab's keys

% gpg --homedir spec/support/rickhardslab/ -k
rickhardslab/pubring.gpg
------------------------
pub   2048R/243D6FEB 2016-01-18
uid                  Richard Hardslab (The Real Rick) <[email protected]>
uid                  Rick Hardslab <[email protected]>
sub   2048R/7FCAE6B3 2016-01-18

pub   2048R/7CAAAB91 2016-01-18
uid                  Spaceman Spiff <[email protected]>
sub   2048R/11AB415A 2016-01-18

% gpg --homedir spec/support/rickhardslab -K
rickhardslab/secring.gpg
------------------------
sec   2048R/243D6FEB 2016-01-18
uid                  Rick Hardslab <[email protected]>
uid                  Richard Hardslab (The Real Rick) <[email protected]>
ssb   2048R/7FCAE6B3 2016-01-18

Spaceman Spiff's keys

% gpg --homedir spec/support/spacemanspiff -K
spacemanspiff/secring.gpg
-------------------------
sec   2048R/7CAAAB91 2016-01-18
uid                  Spaceman Spiff <[email protected]>
ssb   2048R/11AB415A 2016-01-18

% gpg --homedir spec/support/spacemanspiff -k
spacemanspiff/pubring.gpg
-------------------------
pub   2048R/7CAAAB91 2016-01-18
uid                  Spaceman Spiff <[email protected]>
sub   2048R/11AB415A 2016-01-18

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rake to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment. If lots of tests fail, check you are using the correct version of gpg. You can specify the gpg binary with: GPG=gpg1 bundle exec rake.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

We always welcome bug reports, on GitHub's issue tracker.

If you would like to contribute code to GPGMeh, thank you! You can do so through GitHub by forking the repository and sending a pull request. However, before your code can be accepted into the project we need you to sign Square's (super simple) Individual Contributor License Agreement (CLA)

License

Copyright 2016 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.