From 5e3d540b6216bfdd7088e4959c2c58f45c49df56 Mon Sep 17 00:00:00 2001 From: Daniel Berger <78529+djberg96@users.noreply.github.com> Date: Sat, 14 Feb 2026 05:22:44 -0500 Subject: [PATCH 1/3] Initial commit. --- README.md | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..83e45ec --- /dev/null +++ b/README.md @@ -0,0 +1,217 @@ +# GPGME Ruby Bindings + +[![Build Status](https://github.com/ueno/ruby-gpgme/actions/workflows/test.yml/badge.svg)](https://github.com/ueno/ruby-gpgme/actions/workflows/test.yml) +[![Coverage Status](https://coveralls.io/repos/ueno/ruby-gpgme/badge.png)](https://coveralls.io/r/ueno/ruby-gpgme) + + +> **Documentation:** +> - [API docs (GitHub, latest)](https://www.rubydoc.info/github/ueno/ruby-gpgme) +> - [API docs (Gem release)](https://www.rubydoc.info/gems/gpgme) + +> **Badges:** +> - Build: [![Build Status](https://github.com/ueno/ruby-gpgme/actions/workflows/test.yml/badge.svg)](https://github.com/ueno/ruby-gpgme/actions/workflows/test.yml) +> - Coverage: [![Coverage Status](https://coveralls.io/repos/ueno/ruby-gpgme/badge.png)](https://coveralls.io/r/ueno/ruby-gpgme) +> (If badges do not render, check the respective service for status.) + + +## Requirements + +- Ruby 2.5 or later (Ruby 2.0–2.4 are no longer supported) +- GPGME 2.0.0 or later (older versions are deprecated) +- `gpg-agent` (optional, but recommended) + +> **Tested with:** +> - Ruby 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 +> - GPGME 2.0.0, 1.21.0 + +## Installation + +```sh +$ gem install gpgme +``` + + +## API Overview + +GPGME provides three levels of API: +- **High-level API:** Easiest to use for common operations. +- **Mid-level API:** More control, less user-friendly. +- **Low-level API:** Closest to the C interface, for advanced use. + +## Recent Features + +- Support for GPGME 2.0.0 and later +- Deletion of secret keys without confirmation +- Improved thread safety and encryption flags +- `ignore_mdc_error` flag setter/getter +- Minimal key exports (`GPGME::Key.valid?`) +- Support for Ruby 3.2 and later +- More efficient key sign lookups +- Updated dependency versions (libgpg-error, libassuan) + +See [NEWS](NEWS) for full changelog. + +### High-level API Example + +```ruby +crypto = GPGME::Crypto.new +crypto.clearsign $stdin, output: $stdout +``` + +### Mid-level API Example + +```ruby +plain = GPGME::Data.new($stdin) +sig = GPGME::Data.new($stdout) +GPGME::Ctx.new do |ctx| + ctx.sign(plain, sig, GPGME::SIG_MODE_CLEAR) +end +``` + +### Low-level API Example + +```ruby +ret = [] +GPGME::gpgme_new(ret) +ctx = ret.shift +GPGME::gpgme_data_new_from_fd(ret, 0) +plain = ret.shift +GPGME::gpgme_data_new_from_fd(ret, 1) +sig = ret.shift +GPGME::gpgme_op_sign(ctx, plain, sig, GPGME::SIG_MODE_CLEAR) +``` + +The high-level API is recommended for most users. The low-level API is only needed for advanced use cases or when porting C code. + +## Usage + +Most high-level methods use the mid-level `GPGME::Ctx` API. Input/output is handled via `GPGME::Data` objects, which can wrap strings, files, or other IO objects. See the documentation for `GPGME::Ctx` and `GPGME::Data` for details. + +### Crypto Operations + +The `GPGME::Crypto` class provides methods for encryption, decryption, signing, and verification. Examples: + +- **Encrypt for a recipient:** + ```ruby + crypto = GPGME::Crypto.new + crypto.encrypt "Hello world!", recipients: "someone@example.com" + ``` +- **Symmetric encryption:** + ```ruby + crypto = GPGME::Crypto.new(password: "gpgme") + crypto.encrypt "Hello world!", symmetric: true + ``` +- **Decrypt (with signature verification):** + ```ruby + crypto.decrypt File.open("text.gpg") + ``` +- **Sign data:** + ```ruby + crypto.sign "I hereby proclaim Github the beneficiary of all my money when I die" + ``` +- **Verify signature:** + ```ruby + sign = crypto.sign "Some text" + data = crypto.verify(sign) { |signature| signature.valid? } + ``` + + +### Key Management + +The `GPGME::Key` object represents a key and provides methods to find, export, import, delete, and create keys. + +- **List keys:** + ```ruby + GPGME::Key.find(:secret, "someone@example.com") + # => Array of secret keys matching the email + ``` +- **Export a key:** + ```ruby + GPGME::Key.export("someone@example.com") + # => GPGME::Data object with the exported key + key = GPGME::Key.find(:secret, "someone@example.com").first + key.export + # => GPGME::Data object + ``` +- **Import a key:** + ```ruby + GPGME::Key.import(File.open("my.key")) + ``` +- **Validate a key:** + ```ruby + GPGME::Key.valid?(public_key) + # => true/false + ``` +- **Key generation:** _(See API docs; new flags available in GPGME 2.0.0+)_ + +### Engine Information + +GPGME provides methods to obtain information about the GPG engine in use: + +- **Get engine info:** + ```ruby + GPGME::Engine.info.first + # => # + ``` +- **Change home directory:** + ```ruby + GPGME::Engine.home_dir = '/tmp' + ``` + +### Round Trip Example Using Keychain Keys + +Rather than importing keys, you can specify the recipient directly. Example (for IRB/console): + +```ruby +# Stop IRB echoing everything, which errors with binary data (not needed in production) +conf.echo = false + +class PassphraseCallback + def initialize(passphrase) + @passphrase = passphrase + end + + def call(*args) + fd = args.last + io = IO.for_fd(fd, 'w') + io.puts(@passphrase) + io.flush + end +end + +# Find recipients using: $ gpg --list-keys --homedir ./keychain_location +# pub 2048R/A1B2C3D4 2014-01-17 +# Use the key ID (e.g., 'A1B2C3D4') + +# To use a non-default keychain: +# home_dir = Rails.root.join('keychain_location').to_s +# GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, '/usr/local/bin/gpg', home_dir) + +crypto = GPGME::Crypto.new +options = { recipients: 'A1B2C3D4' } +plaintext = GPGME::Data.new(File.open(Rails.root.join('Gemfile'))) +data = crypto.encrypt plaintext, options + +File.open(Rails.root.join('Gemfile.gpg'), 'wb') { |f| f.write(data) } + +# Decrypt +crypto = GPGME::Crypto.new +options = { recipients: 'A1B2C3D4', passphrase_callback: PassphraseCallback.new('my_passphrase') } +ciphertext = GPGME::Data.new(File.open(Rails.root.join('Gemfile.gpg'))) +data = crypto.decrypt ciphertext, options +puts data +``` + +## Contributing + +To run the local test suite you need Bundler and GPG: + +```sh +bundle +rake compile # Compile the extension +rake # Run the test suite +``` + +## License + +This library is licensed under LGPLv2.1+. See the file `COPYING.LESSER` and each source file for copyright and warranty information. From ed2695df0e76b5b86376818c18bb7da89d092c56 Mon Sep 17 00:00:00 2001 From: Daniel Berger <78529+djberg96@users.noreply.github.com> Date: Sat, 14 Feb 2026 05:24:33 -0500 Subject: [PATCH 2/3] Remove old rdoc file. --- README.rdoc | 211 ---------------------------------------------------- 1 file changed, 211 deletions(-) delete mode 100644 README.rdoc diff --git a/README.rdoc b/README.rdoc deleted file mode 100644 index 006ea55..0000000 --- a/README.rdoc +++ /dev/null @@ -1,211 +0,0 @@ -= GPGME - -This README is better viewed through the YARD formatted documentation: -https://www.rubydoc.info/github/ueno/ruby-gpgme for latest github version, -or https://www.rubydoc.info/gems/gpgme for latest gem release. - -{Build Status}[https://github.com/ueno/ruby-gpgme/actions/workflows/test.yml] -{Coverage Status}[https://coveralls.io/r/ueno/ruby-gpgme] - -== Requirements - -* Ruby 1.8 or later -* GPGME 1.1.2 or later -* gpg-agent (optional, but recommended) - -== Installation - - $ gem install gpgme - -== API - -GPGME provides three levels of API. The highest level API is as simple as it -gets, the mid level API provides more functionality but might be less -user-friendly, and the lowest level API is close to the C interface of GPGME. - -=== The highest level API - -For example, to create a cleartext signature of the plaintext from -stdin and write the result to stdout can be written as follows. - - crypto = GPGME::Crypto.new - crypto.clearsign $stdin, :output => $stdout - -=== The mid level API - -The same example can be rewritten in the mid level API as follows. - - plain = GPGME::Data.new($stdin) - sig = GPGME::Data.new($stdout) - GPGME::Ctx.new do |ctx| - ctx.sign(plain, sig, GPGME::SIG_MODE_CLEAR) - end - -=== The lowest level API - -The same example can be rewritten in the lowest level API as follows. - - ret = [] - GPGME::gpgme_new(ret) - ctx = ret.shift - GPGME::gpgme_data_new_from_fd(ret, 0) - plain = ret.shift - GPGME::gpgme_data_new_from_fd(ret, 1) - sig = ret.shift - GPGME::gpgme_op_sign(ctx, plain, sig, GPGME::SIG_MODE_CLEAR) - -As you see, it's much harder to write a program in this API than the -highest level API. However, if you are already familiar with the C -interface of GPGME and want to control detailed behavior of GPGME, it -might be useful. - -== Usage - -All the high level methods attack the mid level GPGME::Ctx API. It is -recommended to read through the GPGME::Ctx.new methods for common options. - -Also, most of the input/output is done via GPGME::Data objects that create a -common interface for reading/writing to normal strings, or other common -objects like files. Read the GPGME::Data documentation to understand -how it works. Every time the lib needs a GPGME::Data object, it will be -automatically converted to it. - -=== Crypto - -The GPGME::Crypto class has the high level convenience methods to encrypt, -decrypt, sign and verify signatures. Here are some examples, but it is -recommended to read through the GPGME::Crypto class to see all the options. - -* Document encryption via GPGME::Crypto#encrypt: - crypto = GPGME::Crypto.new - crypto.encrypt "Hello world!", :recipients => "someone@example.com" - -* Symmetric encryption: - crypto = GPGME::Crypto.new :password => "gpgme" - crypto.encrypt "Hello world!", :symmetric => true - - -* Document decryption via GPGME::Crypto#decrypt (including signature verification): - crypto.decrypt File.open("text.gpg") - -* Document signing via GPGME::Crypto#sign. Also the clearsigning and detached signing. - crypto.sign "I hereby proclaim Github the beneficiary of all my money when I die" - -* Sign verification via GPGME::Crypto#verify - sign = crypto.sign "Some text" - data = crypto.verify(sign) { |signature| signature.valid? } - -=== Key - -The GPGME::Key object represents a key, and has the high level related -methods to work with them and find them, export, import, deletetion and -creation. - -* Key listing - GPGME::Key.find(:secret, "someone@example.com") - # => Returns an array with all the secret keys available in the keychain. - # that match "someone@example.com" - -* Key exporting - GPGME::Key.export("someone@example.com") - # => Returns a GPGME::Data object with the exported key. - - key = GPGME::Key.find(:secret, "someone@example.com").first - key.export - # => Returns a GPGME::Data object with the exported key. - -* Key importing - GPGME::Key.import(File.open("my.key")) - -* Key validation - GPGME::Key.valid?(public_key) - # => Returns wheter this key is valid or not - - -* TODO: Key generation - -=== Engine - -Provides three convenience methods to obtain information about the gpg engine -one is currently using. For example: - -* Getting current information - GPGME::Engine.info.first - # => # - -* Changing home directory to work with different settings: - GPGME::Engine.home_dir = '/tmp' - -=== Round trip example using keychain keys - -Rather than importing the keys it's possible to specify the recipient -when performing crypto functions. Here's a roundtrip example, -and note that as this is for a console, the conf.echo = false -line is to stop IRB complaining when echoing binary data - - # Stop IRB echoing everything, which errors with binary data. - # Not required for production code - conf.echo = false - - class PassphraseCallback - def initialize(passphrase) - @passphrase = passphrase - end - - def call(*args) - fd = args.last - io = IO.for_fd(fd, 'w') - io.puts(@passphrase) - io.flush - end - end - - # recipients can be found using $ gpg --list-keys --homedir ./keychain_location - # pub 2048R/A1B2C3D4 2014-01-17 - # Use that line to substitute your own. 2048R is the key length and type (RSA in this case) - - # If you want to substitute a non-default keychain into the engine do this: - # home_dir = Rails.root.join('keychain_location').to_s - # GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, '/usr/local/bin/gpg', home_dir) - # Note GPG executable location will change across platforms - - - crypto = GPGME::Crypto.new - options = {:recipients => 'A1B2C3D4'} - - plaintext = GPGME::Data.new(File.open(Rails.root.join('Gemfile'))) - - data = crypto.encrypt plaintext, options - - f = File.open(Rails.root.join('Gemfile.gpg'), 'wb') - bytes_written = f.write(data) - f.close - - puts bytes_written - - - crypto = GPGME::Crypto.new - options = {:recipients => 'A1B2C3D4', :passphrase_callback => PassphraseCallback.new('my_passphrase')} - - cipthertext = GPGME::Data.new(File.open(Rails.root.join('Gemfile.gpg'))) - - data = crypto.decrypt cipthertext, options - puts data - - -== Contributing - -To run the local test suite you need bundler and gpg: - - bundle - rake compile # simple rake task to compile the extension - rake # runs the test suite - -== License - -The library itself is licensed under LGPLv2.1+. See the file -COPYING.LESSER and each file for copyright and warranty information. From 6835e94c6cc1ffe1231cbf2d79fe2620bc8ffe2d Mon Sep 17 00:00:00 2001 From: Daniel Berger <78529+djberg96@users.noreply.github.com> Date: Sat, 14 Feb 2026 05:27:46 -0500 Subject: [PATCH 3/3] Don't run matrix on changes to markdown files. --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 16af25c..1d7f011 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,12 @@ name: Matrix Testing on: push: branches: [ master ] + paths-ignore: + - '**/*.md' pull_request: branches: [ master ] + paths-ignore: + - '**/*.md' jobs: test: