pax_global_header00006660000000000000000000000064135343365660014530gustar00rootroot0000000000000052 comment=481d4096c67bac6a4248604dba0d43753f5b2404 mail-gpg-0.4.2/000077500000000000000000000000001353433656600132305ustar00rootroot00000000000000mail-gpg-0.4.2/.gitignore000066400000000000000000000003151353433656600152170ustar00rootroot00000000000000.ruby-version .ruby-gemset *.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp test/gpghome tmp *.swp tags mail-gpg-0.4.2/.travis.yml000066400000000000000000000005741353433656600153470ustar00rootroot00000000000000language: ruby rvm: - 2.6.3 - 2.5.5 - 2.4.6 env: - RAILS=4.2.11.1 GPG_BIN=/usr/bin/gpg2 - RAILS=4.2.11.1 GPG_BIN=/usr/bin/gpg - RAILS=5.2.3 GPG_BIN=/usr/bin/gpg2 - RAILS=5.2.3 GPG_BIN=/usr/bin/gpg matrix: exclude: before_install: - gem install bundler -v "~> 2.0" - sudo apt install -y gnupg gnupg2 cache: bundler dist: xenial addons: apt: update: true mail-gpg-0.4.2/Gemfile000066400000000000000000000003011353433656600145150ustar00rootroot00000000000000source 'https://rubygems.org' # Specify your gem's dependencies in mail-gpg.gemspec gemspec if ENV['RAILS'] gem 'actionmailer', "~> #{ENV['RAILS']}" gem 'shoulda-context', '~> 1.1.5' end mail-gpg-0.4.2/History.txt000066400000000000000000000100121353433656600154240ustar00rootroot00000000000000== 0.4.2 2019-09-02 * do not die on invalid content-transfer encodings when checking if a message is inline-signed or encrypted == 0.4.1 2019-07-08 * do not modify argument hash #61 * fix tests on travis and run them with both gpg < 2.0 and >= 2.1 * gpg 2.0.x apparently has no way of preseeding passphrases and thus will only ever work with passphraseless keys. == 0.4.0 2018-05-19 * [MIGHT BREAK THINGS] changes to the way keys are looked up #55 Previously, keys that were not explicitly mentioned but already present in the key chain for one of the recipient addresses would have been used silently. This is no longer the case, if the :keys option is given, all necessary keys have to be specified as either key data, key id, fingerprint or GPGME::Key object. * fix error when calling encrypt with actual key objects #60 == 0.3.3 2018-04-01 * fix broken GpgmeHelper#keys_for_data #59 == 0.3.2 2018-03-30 * do not attempt to decrypt inline-encrypted HTML parts #52 * fixes possible double decoding of quoted printable bodies #57 * fixes a bug which occured in some environments that led to encryption with all available public keys (#31, #55) == 0.3.1 2017-04-13 * fixes a bug with signature verification that only surfaced in environments where ActiveSupport isn't loaded. Thanks @mashedcode for pointing this out. == 0.3.0 2016-12-27 * [MIGHT BREAK THINGS] All mail headers will preserved now, if you want to suppress headers you'll have to remove them yourself from now on. * Strip "headers" when stripping inline signature (patch by @duckdalbe) * support hkps URI scheme * bugfix for verifying the "encapsulated" variant of pgp/mime (patch by @duckdalbe) == 0.2.9 2016-11-15 * add missing require to test case (patch by @ge-fa) == 0.2.8 2016-10-26 * fix tests with gnupg2 (patch by @duckdalbe) == 0.2.7 2016-09-28 * fix replying to message objects #35 * remove usage of deprecated alias_method_chain * fix tests with Rails >= 4 * run tests with Rails 5, bump versions in travis config == 0.2.6 2015-10-20 * fix handling of inline encrypted messages == 0.2.5 2015-09-30 * allow overriding of encrypted attachment name (patch by @chrislewis60) == 0.2.4 2015-04-20 * preserve List-Id and List-Unsubscribe headers * run tests on travis under Ruby 2.2.2 == 0.2.3 2015-02-18 * inline pgp detection now ignores signature.asc attachments == 0.2.2 2014-09-03 * add option to import missing keys for signature verification * better Hkp error handling == 0.2.1 2014-07-23 * keep original message id if set == 0.2.0 2014-07-16 * implement signature verification for inline signed messages * make signature verification more consistent with decryption by providing Mail::Message#verify which strips raw signature data and initializes verify_result member. * add Mail::Message#signatures as an easy way to retrieve all signatures on a Message == 0.1.7 2014-06-03 * preserve References: header == 0.1.6 2014-05-30 * preserve names in mail headers (i.e. Jane Doe ) == 0.1.5 2014-05-28 * signing refactoring so the signed part only contains the necessary headers * preserve Auto-Submitted header == 0.1.4 2014-05-26 * preserve return path headers and message id * fix signature verification == 0.1.3 2014-02-17 * Signature checking implemented, thanks to Morten Andersen == 0.1.2 2013-11-19 * bugfix release == 0.1.1 2013-11-14 * bugfix release == 0.1.0 2013-11-06 * decryption support (thanks to Morten Andersen) * sign-only operation (thanks to FewKinG) * keyserver url lookup (thanks to FewKinG) == 0.0.6 2013-08-28 * bugfix: only encrypt to specified keys if :keys option is present == 0.0.5 2013-08-28 * add some docs to the Hkp client * also add convenience method to fetch and import keys == 0.0.4 2013-08-27 * better ActionMailer integration == 0.0.3 2013-08-20 * better ActionMailer integration * 'gpg encrypt: true' instead of 'gpg true' * test coverage == 0.0.2 2013-08-19 * ActionMailer integration * add gpg method to Mail::Message * support importing of public keys == 0.0.1 2013-08-18 * Initial release mail-gpg-0.4.2/LICENSE.txt000066400000000000000000000020551353433656600150550ustar00rootroot00000000000000Copyright (c) 2013 Jens Kraemer MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mail-gpg-0.4.2/README.md000066400000000000000000000234111353433656600145100ustar00rootroot00000000000000# Mail::Gpg [![Build Status](https://travis-ci.org/jkraemer/mail-gpg.png?branch=master)](https://travis-ci.org/jkraemer/mail-gpg) This gem adds GPG/MIME encryption capabilities to the [Ruby Mail Library](https://github.com/mikel/mail) For maximum interoperability the gem also supports *decryption* of messages using the non-standard 'PGP-Inline' method as for example supported in the Mozilla Enigmail OpenPGP plugin. There may still be GPG encrypted messages that can not be handled by the library, as there are some legacy formats used in the wild as described in this [Know your PGP implementation](http://binblog.info/2008/03/12/know-your-pgp-implementation/) blog. ## Installation Add this line to your application's Gemfile: ```ruby gem 'mail-gpg' ``` And then execute: $ bundle Or install it yourself as: $ gem install mail-gpg ## Usage ### Encrypting / Signing Construct your Mail object as usual and specify you want it to be encrypted with the gpg method: ```ruby Mail.new do to 'jane@doe.net' from 'john@doe.net' subject 'gpg test' body "encrypt me!" add_file "some_attachment.zip" # encrypt message, no signing gpg encrypt: true # encrypt and sign message with sender's private key, using the given # passphrase to decrypt the key gpg encrypt: true, sign: true, password: 'secret' # encrypt and sign message using a different key gpg encrypt: true, sign_as: 'joe@otherdomain.com', password: 'secret' # encrypt and sign message and use a callback function to provide the # passphrase. gpg encrypt: true, sign_as: 'joe@otherdomain.com', passphrase_callback: ->(obj, uid_hint, passphrase_info, prev_was_bad, fd){puts "Enter passphrase for #{passphrase_info}: "; (IO.for_fd(fd, 'w') << readline.chomp).flush } end.deliver ``` Make sure all recipients' public keys are present in your local gpg keychain. You will get errors in case encryption is not possible due to missing keys. If you collect public key data from your users, you can specify the ascii armored key data for recipients using the `:keys` option like this: ```ruby johns_key = <<-END -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1.4.12 (GNU/Linux) mQGiBEk39msRBADw1ExmrLD1OUMdfvA7cnVVYTC7CyqfNvHUVuuBDhV7azs .... END Mail.new do to 'john@foo.bar' gpg encrypt: true, keys: { 'john@foo.bar' => johns_key } end ``` The key will then be imported before actually trying to encrypt/send the mail. In theory you only need to specify the key once like that, however doing it every time does not hurt as gpg is clever enough to recognize known keys, only updating it's db when necessary. Note: Mail-Gpg in version 0.4 and up is more strict regarding the keys option: if it is present, only key material from there (either given as key data like above, or as key id, key fingerprint or `GPGMe::Key` object if they have been imported before) will be used. Keys already present in the local keychain for any of the recipients that are not explicitly mentioned in the `keys` hash will be ignored. You may also want to have a look at the [GPGME](https://github.com/ueno/ruby-gpgme) docs and code base for more info on the various options, especially regarding the `passphrase_callback` arguments. ### Decrypting Receive the mail as usual. Check if it is encrypted using the `encrypted?` method. Get a decrypted version of the mail with the `decrypt` method: ```ruby mail = Mail.first mail.subject # subject is never encrypted if mail.encrypted? # decrypt using your private key, protected by the given passphrase plaintext_mail = mail.decrypt(:password => 'abc') # the plaintext_mail, is a full Mail::Message object, just decrypted end ``` Set the `:verify` option to `true` when calling `decrypt` to decrypt *and* verify signatures. A `GPGME::Error::BadPassphrase` will be raised if the password for the private key is incorrect. A `EncodingError` will be raised if the encrypted mails is not encoded correctly as a [RFC 3156](http://www.ietf.org/rfc/rfc3156.txt) message. Please note that in case of a multipart/alternative-message where both parts are inline-encrypted, the HTML-part will be dropped during decryption. Handling the HTML-part would require parsing HTML and guessing about the decrypted contents, which is brittle and out of the scope of this library. If you need the HTML-part of multipart/alternative-messages, use pgp/mime-encryption. ### Signing only Just leave the `:encrypt` option out or pass `encrypt: false`, i.e. ```ruby Mail.new do to 'jane@doe.net' gpg sign: true end.deliver ``` ### Verify signature(s) Receive the mail as usual. Check if it is signed using the `signed?` method. Check the signature of the mail with the `signature_valid?` method: ```ruby mail = Mail.first if !mail.encrypted? && mail.signed? verified = mail.verify puts "signature(s) valid: #{verified.signature_valid?}" puts "message signed by: #{verified.signatures.map{|sig|sig.from}.join("\n")}" end ``` Note that for encrypted mails the signatures can not be checked using these methods. For encrypted mails the `:verify` option for the `decrypt` operation must be used instead: ```ruby if mail.encrypted? decrypted = mail.decrypt(verify: true, password: 's3cr3t') puts "signature(s) valid: #{decrypted.signature_valid?}" puts "message signed by: #{decrypted.signatures.map{|sig|sig.from}.join("\n")}" end ``` It's important to actually use the information contained in the `signatures` array to check if the message really has been signed by the person that you (or your users) think is the sender - usually by comparing the key id of the signature with the key id of the expected sender. ### Key import from public key servers The Hkp class can be used to lookup and import public keys from public key servers. You can specify the keyserver url when initializing the class: ```ruby hkp = Hkp.new("hkp://my-key-server.de") ``` Or, if you want to override how ssl certificates should be treated in case of TLS-secured keyservers (the default is `VERIFY_PEER`): ```ruby hkp = Hkp.new(keyserver: "hkps://another.key-server.com", ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) ``` If no port is specified in hkp or hkps URIs (as in the examples above), port 11371 will be used for hkp and port 443 for hkps URIs. Standard `http` or `https` URIs with or without explicitly set ports work as well. If no url is given, this gem will try to determine the default keyserver url from the system's gpg config (using `gpgconf` if available or by parsing the `gpg.conf` file). As a last resort, the server-pool at `http://pool.sks-keyservers.net:11371` will be used. Lookup key ids by searching the keyserver for an email address ```ruby hkp.search('jane@doe.net') ``` You can lookup (and import) a specific key by its id: ```ruby key = hkp.fetch(id) GPGME::Key.import(key) # or do both in one step hkp.fetch_and_import(id) ``` ## Rails / ActionMailer integration ```ruby class MyMailer < ActionMailer::Base default from: 'baz@bar.com' def some_mail mail to: 'foo@bar.com', subject: 'subject!', gpg: { encrypt: true } end end ``` The gpg option takes the same arguments as outlined above for the Mail::Message#gpg method. ## Passwords and GnuPG versions >= 2.x GnuPG versions >= 2.x require the use of gpg-agent for key-handling. That's a problem for using password-protected keys non-interactively, because gpg-agent doesn't read from file-descriptors (which is the usual way to non-interactively provide passwords with GnuPG 1.x). With GnuPG 2.x you have two options to provide passwords to gpg-agent: 1. Implement a pinentry-kind-of program that speaks the assuan-protocol and configure gpg-agent to use it. 2. Run gpg-preset-passphrase and allow gpg-agent to read preset passwords. The second options is somewhat easier and is described below. Note: You *don't* need this if your key is *not* protected with a password. To feed a password into gpg-agent run this code early in your program: ```ruby # The next two lines need adaption, obviously. fpr = fingerprint_of_key_to_unlock passphrase = gpg_passphrase_for_key # You may copy&paste the rest of this block unchanged. Maybe you want to change the error-handling, though. ENV['GPG_AGENT_INFO'] = `eval $(gpg-agent --allow-preset-passphrase --daemon) && echo $GPG_AGENT_INFO` `gpgconf --list-dir`.match(/libexecdir:(.*)/) gppbin = File.join($1, 'gpg-preset-passphrase') Open3.popen3(gppbin, '--preset', fpr) do |stdin, stdout, stderr| stdin.puts passphrase err = stderr.readlines $stderr.puts err if ! err.to_s.empty? end # Hook to kill our gpg-agent when script finishes. Signal.trap(0, proc { Process.kill('TERM', ENV['GPG_AGENT_INFO'].split(':')[1]) }) ``` ## Running the tests bundle exec rake Test cases use a mock gpghome located in `test/gpghome` in order to not mess around with your personal gpg keychain. Password for the test PGP private keys is `abc` ## Todo * signature verification for received mails with inline PGP * on the fly import of recipients' keys from public key servers based on email address or key id * handle encryption errors due to missing keys - maybe return a list of failed recipients * add some setup code to help initialize a basic keychain directory with public/private keypair. ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## Credits Thanks to: * [Planio GmbH](https://plan.io) for sponsoring the ongoing maintenance and development of this library * [morten-andersen](https://github.com/morten-andersen) for implementing decryption support for PGP/MIME and inline encrypted messages * [FewKinG](https://github.com/FewKinG) for implementing the sign only feature and keyserver url lookup * [Fup Duck](https://github.com/duckdalbe) for various tweaks and fixes mail-gpg-0.4.2/Rakefile000066400000000000000000000004161353433656600146760ustar00rootroot00000000000000require "bundler/gem_tasks" require 'rake/testtask' require 'gpgme' require 'byebug' task :default => [:test] Rake::TestTask.new(:test) do |test| test.libs << 'test' test.test_files = FileList['test/**/*_test.rb'] test.verbose = true test.warning = false end mail-gpg-0.4.2/lib/000077500000000000000000000000001353433656600137765ustar00rootroot00000000000000mail-gpg-0.4.2/lib/hkp.rb000066400000000000000000000100341353433656600151030ustar00rootroot00000000000000require 'gpgme' require 'openssl' require 'net/http' # simple HKP client for public key search and retrieval class Hkp class TooManyRedirects < StandardError; end class InvalidResponse < StandardError; end class Client MAX_REDIRECTS = 3 def initialize(server, ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER) uri = URI server @host = uri.host @port = uri.port @use_ssl = false @ssl_verify_mode = ssl_verify_mode # set port and ssl flag according to URI scheme case uri.scheme.downcase when 'hkp' # use the HKP default port unless another port has been given @port ||= 11371 when /\A(hkp|http)s\z/ # hkps goes through 443 by default @port ||= 443 @use_ssl = true end @port ||= 80 end def get(path, redirect_depth = 0) Net::HTTP.start @host, @port, use_ssl: @use_ssl, verify_mode: @ssl_verify_mode do |http| request = Net::HTTP::Get.new path response = http.request request case response.code.to_i when 200 return response.body when 301, 302 if redirect_depth >= MAX_REDIRECTS raise TooManyRedirects else http_get response['location'], redirect_depth + 1 end else raise InvalidResponse, response.code end end end end def initialize(options = {}) if String === options options = { keyserver: options } end @keyserver = options.delete(:keyserver) || lookup_keyserver || 'http://pool.sks-keyservers.net:11371' @options = { raise_errors: true }.merge options end def raise_errors? !!@options[:raise_errors] end # # hkp.search 'user@host.com' # will return an array of arrays, one for each matching key found, containing # the key id as the first elment and any further info returned by the key # server in the following elements. # see http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-5.2 for # what that *might* be. unfortunately key servers seem to differ in how much # and what info they return besides the key id def search(name) [].tap do |results| result = hkp_client.get "/pks/lookup?options=mr&search=#{URI.escape name}" result.each_line do |l| components = l.strip.split(':') if components.shift == 'pub' results << components end end if result end rescue raise $! if raise_errors? nil end # returns the key data as returned from the server as a string def fetch(id) result = hkp_client.get "/pks/lookup?options=mr&op=get&search=0x#{URI.escape id}" return clean_key(result) if result rescue Exception raise $! if raise_errors? nil end # fetches key data by id and imports the found key(s) into GPG, returning the full hex fingerprints of the # imported key(s) as an array. Given there are no collisions with the id given / the server has returned # exactly one key this will be a one element array. def fetch_and_import(id) if key = fetch(id) GPGME::Key.import(key).imports.map(&:fpr) end rescue Exception raise $! if raise_errors? end private def hkp_client @hkp_client ||= Client.new @keyserver, ssl_verify_mode: @options[:ssl_verify_mode] end def clean_key(key) if key =~ /(-----BEGIN PGP PUBLIC KEY BLOCK-----.*-----END PGP PUBLIC KEY BLOCK-----)/m return $1 end end def exec_cmd(cmd) res = `#{cmd}` return nil if $?.exitstatus != 0 res end def lookup_keyserver url = nil if res = exec_cmd("gpgconf --list-options gpgs 2>&1 | grep keyserver 2>&1") url = URI.decode(res.split(":").last.split("\"").last.strip) elsif res = exec_cmd("gpg --gpgconf-list 2>&1 | grep gpgconf-gpg.conf 2>&1") conf_file = res.split(":").last.split("\"").last.strip if res = exec_cmd("cat #{conf_file} 2>&1 | grep ^keyserver 2>&1") url = res.split(" ").last.strip end end url =~ /^(http|hkp)/ ? url : nil end end mail-gpg-0.4.2/lib/mail-gpg.rb000066400000000000000000000000231353433656600160130ustar00rootroot00000000000000require 'mail/gpg' mail-gpg-0.4.2/lib/mail/000077500000000000000000000000001353433656600147205ustar00rootroot00000000000000mail-gpg-0.4.2/lib/mail/gpg.rb000066400000000000000000000212421353433656600160230ustar00rootroot00000000000000require 'mail' require 'mail/message' require 'gpgme' require 'mail/gpg/version' require 'mail/gpg/missing_keys_error' require 'mail/gpg/version_part' require 'mail/gpg/decrypted_part' require 'mail/gpg/encrypted_part' require 'mail/gpg/inline_decrypted_message' require 'mail/gpg/gpgme_helper' require 'mail/gpg/message_patch' require 'mail/gpg/rails' require 'mail/gpg/signed_part' require 'mail/gpg/mime_signed_message' require 'mail/gpg/inline_signed_message' module Mail module Gpg BEGIN_PGP_MESSAGE_MARKER = /^-----BEGIN PGP MESSAGE-----/ BEGIN_PGP_SIGNED_MESSAGE_MARKER = /^-----BEGIN PGP SIGNED MESSAGE-----/ # options are: # :sign: sign message using the sender's private key # :sign_as: sign using this key (give the corresponding email address or key fingerprint) # :password: passphrase for the signing key # :keys: A hash mapping recipient email addresses to public keys or public # key ids. Imports any keys given here that are not already part of the # local keychain before sending the mail. # :always_trust: send encrypted mail to untrusted receivers, true by default def self.encrypt(cleartext_mail, options = {}) construct_mail(cleartext_mail, options) do receivers = [] receivers += cleartext_mail.to if cleartext_mail.to receivers += cleartext_mail.cc if cleartext_mail.cc receivers += cleartext_mail.bcc if cleartext_mail.bcc if options[:sign_as] options[:sign] = true options[:signers] = options.delete(:sign_as) elsif options[:sign] options[:signers] = cleartext_mail.from end add_part VersionPart.new add_part EncryptedPart.new(cleartext_mail, options.merge({recipients: receivers})) content_type "multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=#{boundary}" body.preamble = options[:preamble] || "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)" end end def self.sign(cleartext_mail, options = {}) options[:sign_as] ||= cleartext_mail.from construct_mail(cleartext_mail, options) do to_be_signed = SignedPart.build(cleartext_mail) add_part to_be_signed add_part to_be_signed.sign(options) content_type "multipart/signed; micalg=pgp-sha1; protocol=\"application/pgp-signature\"; boundary=#{boundary}" body.preamble = options[:preamble] || "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)" end end # options are: # :verify: decrypt and verify def self.decrypt(encrypted_mail, options = {}) if encrypted_mime?(encrypted_mail) decrypt_pgp_mime(encrypted_mail, options) elsif encrypted_inline?(encrypted_mail) decrypt_pgp_inline(encrypted_mail, options) else raise EncodingError, "Unsupported encryption format '#{encrypted_mail.content_type}'" end end def self.signature_valid?(signed_mail, options = {}) if signed_mime?(signed_mail) signature_valid_pgp_mime?(signed_mail, options) elsif signed_inline?(signed_mail) signature_valid_inline?(signed_mail, options) else raise EncodingError, "Unsupported signature format '#{signed_mail.content_type}'" end end # true if a mail is encrypted def self.encrypted?(mail) return true if encrypted_mime?(mail) return true if encrypted_inline?(mail) false end # true if a mail is signed. # # throws EncodingError if called on an encrypted mail (so only call this method if encrypted? is false) def self.signed?(mail) return true if signed_mime?(mail) return true if signed_inline?(mail) if encrypted?(mail) raise EncodingError, 'Unable to determine signature on an encrypted mail, use :verify option on decrypt()' end false end private def self.construct_mail(cleartext_mail, options, &block) Mail.new do self.perform_deliveries = cleartext_mail.perform_deliveries Mail::Gpg.copy_headers cleartext_mail, self # necessary? if cleartext_mail.message_id header['Message-ID'] = cleartext_mail['Message-ID'].value end instance_eval &block end end # decrypts PGP/MIME (RFC 3156, section 4) encrypted mail def self.decrypt_pgp_mime(encrypted_mail, options) if encrypted_mail.parts.length < 2 raise EncodingError, "RFC 3156 mandates exactly two body parts, found '#{encrypted_mail.parts.length}'" end if !VersionPart.isVersionPart? encrypted_mail.parts[0] raise EncodingError, "RFC 3156 first part not a valid version part '#{encrypted_mail.parts[0]}'" end decrypted = DecryptedPart.new(encrypted_mail.parts[1], options) Mail.new(decrypted.raw_source) do # headers from the encrypted part (set by the initializer above) take # precedence over those from the outer mail. Mail::Gpg.copy_headers encrypted_mail, self, overwrite: false verify_result decrypted.verify_result if options[:verify] end end # decrypts inline PGP encrypted mail def self.decrypt_pgp_inline(encrypted_mail, options) InlineDecryptedMessage.setup(encrypted_mail, options) end def self.verify(signed_mail, options = {}) if signed_mime?(signed_mail) Mail::Gpg::MimeSignedMessage.setup signed_mail, options elsif signed_inline?(signed_mail) Mail::Gpg::InlineSignedMessage.setup signed_mail, options else signed_mail end end # check signature for PGP/MIME (RFC 3156, section 5) signed mail def self.signature_valid_pgp_mime?(signed_mail, options) # MUST contain exactly two body parts if signed_mail.parts.length != 2 raise EncodingError, "RFC 3156 mandates exactly two body parts, found '#{signed_mail.parts.length}'" end result, verify_result = SignPart.verify_signature(signed_mail.parts[0], signed_mail.parts[1], options) signed_mail.verify_result = verify_result return result end # check signature for inline signed mail def self.signature_valid_inline?(signed_mail, options) result = nil if signed_mail.multipart? signed_mail.parts.each do |part| if signed_inline?(part) if result.nil? result = true signed_mail.verify_result = [] end result &= signature_valid_inline?(part, options) signed_mail.verify_result << part.verify_result end end else result, verify_result = GpgmeHelper.inline_verify(signed_mail.body.to_s, options) signed_mail.verify_result = verify_result end return result end # copies all header fields from mail in first argument to that given last def self.copy_headers(from, to, overwrite: true) from.header.fields.each do |field| if overwrite || to.header[field.name].nil? to.header[field.name] = field.value end end end # check if PGP/MIME encrypted (RFC 3156) def self.encrypted_mime?(mail) mail.has_content_type? && 'multipart/encrypted' == mail.mime_type && 'application/pgp-encrypted' == mail.content_type_parameters[:protocol] end # check if inline PGP (i.e. if any parts of the mail includes # the PGP MESSAGE marker) def self.encrypted_inline?(mail) return true if mail.body.to_s =~ BEGIN_PGP_MESSAGE_MARKER rescue nil if mail.multipart? mail.parts.each do |part| return true if part.body.to_s =~ BEGIN_PGP_MESSAGE_MARKER rescue nil return true if part.has_content_type? && /application\/(?:octet-stream|pgp-encrypted)/ =~ part.mime_type && /.*\.(?:pgp|gpg|asc)$/ =~ part.content_type_parameters[:name] && 'signature.asc' != part.content_type_parameters[:name] # that last condition above prevents false positives in case e.g. # someone forwards a mime signed mail including signature. end end false end # check if PGP/MIME signed (RFC 3156) def self.signed_mime?(mail) mail.has_content_type? && 'multipart/signed' == mail.mime_type && 'application/pgp-signature' == mail.content_type_parameters[:protocol] end # check if inline PGP (i.e. if any parts of the mail includes # the PGP SIGNED marker) def self.signed_inline?(mail) return true if mail.body.to_s =~ BEGIN_PGP_SIGNED_MESSAGE_MARKER rescue nil if mail.multipart? mail.parts.each do |part| return true if part.body.to_s =~ BEGIN_PGP_SIGNED_MESSAGE_MARKER rescue nil end end false end end end mail-gpg-0.4.2/lib/mail/gpg/000077500000000000000000000000001353433656600154755ustar00rootroot00000000000000mail-gpg-0.4.2/lib/mail/gpg/decrypted_part.rb000066400000000000000000000011251353433656600210320ustar00rootroot00000000000000require 'mail/gpg/verified_part' module Mail module Gpg class DecryptedPart < VerifiedPart # options are: # # :verify: decrypt and verify def initialize(cipher_part, options = {}) if cipher_part.mime_type != EncryptedPart::CONTENT_TYPE raise EncodingError, "RFC 3156 incorrect mime type for encrypted part '#{cipher_part.mime_type}'" end decrypted = GpgmeHelper.decrypt(cipher_part.body.decoded, options) self.verify_result = decrypted.verify_result if options[:verify] super(decrypted) end end end end mail-gpg-0.4.2/lib/mail/gpg/delivery_handler.rb000066400000000000000000000022001353433656600213340ustar00rootroot00000000000000module Mail module Gpg class DeliveryHandler def self.deliver_mail(mail) if mail.gpg encrypted_mail = nil begin options = TrueClass === mail.gpg ? { encrypt: true } : mail.gpg if options[:encrypt] encrypted_mail = Mail::Gpg.encrypt(mail, options) elsif options[:sign] || options[:sign_as] encrypted_mail = Mail::Gpg.sign(mail, options) else # encrypt and sign are off -> do not encrypt or sign yield end rescue Exception raise $! if mail.raise_encryption_errors end if encrypted_mail if dm = mail.delivery_method encrypted_mail.instance_variable_set :@delivery_method, dm end encrypted_mail.perform_deliveries = mail.perform_deliveries encrypted_mail.raise_delivery_errors = mail.raise_delivery_errors encrypted_mail.deliver end else yield end rescue Exception raise $! if mail.raise_delivery_errors end end end end mail-gpg-0.4.2/lib/mail/gpg/encrypted_part.rb000066400000000000000000000026071353433656600210520ustar00rootroot00000000000000module Mail module Gpg class EncryptedPart < Mail::Part CONTENT_TYPE = 'application/octet-stream' # options are: # # :signers : sign using this key (give the corresponding email address) # :password: passphrase for the signing key # :recipients : array of receiver addresses # :keys : A hash mapping recipient email addresses to public keys or public # key ids. Imports any keys given here that are not already part of the # local keychain before sending the mail. If this option is given, strictly # only the key material from this hash is used, ignoring any keys for # recipients that might have been added to the local key chain but are # not mentioned here. # :always_trust : send encrypted mail to untrusted receivers, true by default # :filename : define a custom name for the encrypted file attachment def initialize(cleartext_mail, options = {}) options = { always_trust: true }.merge options encrypted = GpgmeHelper.encrypt(cleartext_mail.encoded, options) super() do body encrypted.to_s filename = options[:filename] || 'encrypted.asc' content_type "#{CONTENT_TYPE}; name=\"#{filename}\"" content_disposition "inline; filename=\"#{filename}\"" content_description 'OpenPGP encrypted message' end end end end end mail-gpg-0.4.2/lib/mail/gpg/gpgme_ext.rb000066400000000000000000000003251353433656600200010ustar00rootroot00000000000000require 'gpgme' require 'mail/gpg/verify_result_attribute' # extend GPGME::Data with an attribute to hold the result of signature # verifications class GPGME::Data include Mail::Gpg::VerifyResultAttribute end mail-gpg-0.4.2/lib/mail/gpg/gpgme_helper.rb000066400000000000000000000123751353433656600204700ustar00rootroot00000000000000require 'mail/gpg/gpgme_ext' # GPGME methods for encryption/decryption/signing module Mail module Gpg class GpgmeHelper def self.encrypt(plain, options = {}) options = options.merge({armor: true}) plain_data = GPGME::Data.new(plain) cipher_data = GPGME::Data.new(options[:output]) recipient_keys = keys_for_data options[:recipients], options.delete(:keys) if recipient_keys.empty? raise MissingKeysError.new('No keys to encrypt to!') end flags = 0 flags |= GPGME::ENCRYPT_ALWAYS_TRUST if options[:always_trust] GPGME::Ctx.new(options) do |ctx| begin if options[:sign] if options[:signers] && options[:signers].size > 0 signers = GPGME::Key.find(:secret, options[:signers], :sign) ctx.add_signer(*signers) end ctx.encrypt_sign(recipient_keys, plain_data, cipher_data, flags) else ctx.encrypt(recipient_keys, plain_data, cipher_data, flags) end rescue GPGME::Error::UnusablePublicKey => exc exc.keys = ctx.encrypt_result.invalid_recipients raise exc rescue GPGME::Error::UnusableSecretKey => exc exc.keys = ctx.sign_result.invalid_signers raise exc end end cipher_data.seek(0) cipher_data end def self.decrypt(cipher, options = {}) cipher_data = GPGME::Data.new(cipher) plain_data = GPGME::Data.new(options[:output]) GPGME::Ctx.new(options) do |ctx| begin if options[:verify] ctx.decrypt_verify(cipher_data, plain_data) plain_data.verify_result = ctx.verify_result else ctx.decrypt(cipher_data, plain_data) end rescue GPGME::Error::UnsupportedAlgorithm => exc exc.algorithm = ctx.decrypt_result.unsupported_algorithm raise exc rescue GPGME::Error::WrongKeyUsage => exc exc.key_usage = ctx.decrypt_result.wrong_key_usage raise exc end end plain_data.seek(0) plain_data end def self.sign(plain, options = {}) options.merge!({ armor: true, signer: options.delete(:sign_as), mode: GPGME::SIG_MODE_DETACH }) crypto = GPGME::Crypto.new crypto.sign GPGME::Data.new(plain), options end # returns [success(bool), VerifyResult(from gpgme)] # success will be true when there is at least one sig and no invalid sig def self.sign_verify(plain, signature, options = {}) signed_data = GPGME::Data.new(plain) signature = GPGME::Data.new(signature) success = verify_result = nil GPGME::Ctx.new(options) do |ctx| ctx.verify signature, signed_data, nil verify_result = ctx.verify_result signatures = verify_result.signatures success = signatures && signatures.size > 0 && signatures.detect{|s| !s.valid? }.nil? end return [success, verify_result] end def self.inline_verify(signed_text, options = {}) signed_data = GPGME::Data.new(signed_text) success = verify_result = nil GPGME::Ctx.new(options) do |ctx| ctx.verify signed_data, nil verify_result = ctx.verify_result signatures = verify_result.signatures success = signatures && signatures.size > 0 && signatures.detect{|s| !s.valid? }.nil? end return [success, verify_result] end private # normalizes the list of recipients' emails, key ids and key data to a # list of Key objects # # if key_data is given, _only_ key material from there is used, # and eventually already imported keys in the keychain are ignored. def self.keys_for_data(emails_or_shas_or_keys, key_data = nil) if key_data # in this case, emails_or_shas_or_keys is supposed to be the list of # recipients, and key_data the key material to be used. # We now map these to whatever we find in key_data for each of these # addresses. [emails_or_shas_or_keys].flatten.map do |r| k = key_data[r] key_id = case k when GPGME::Key # assuming this is already imported k.fingerprint when nil, '' # nothing nil when /-----BEGIN PGP/ # ASCII key data GPGME::Key.import(k).imports.map(&:fpr) else # key id or fingerprint k end unless key_id.nil? || key_id.empty? GPGME::Key.find(:public, key_id, :encrypt) end end.flatten.compact elsif emails_or_shas_or_keys and emails_or_shas_or_keys.size > 0 # key lookup in keychain for all receivers GPGME::Key.find :public, emails_or_shas_or_keys, :encrypt else # empty array given [] end end end end end mail-gpg-0.4.2/lib/mail/gpg/inline_decrypted_message.rb000066400000000000000000000074541353433656600230610ustar00rootroot00000000000000require 'mail/gpg/verified_part' # decryption of the so called 'PGP-Inline' message types # this is not a standard, so the implementation is based on the notes # here http://binblog.info/2008/03/12/know-your-pgp-implementation/ # and on test messages generated with the Mozilla Enigmail OpenPGP # plugin https://www.enigmail.net module Mail module Gpg class InlineDecryptedMessage < Mail::Message # options are: # # :verify: decrypt and verify def self.setup(cipher_mail, options = {}) if cipher_mail.multipart? self.new do Mail::Gpg.copy_headers cipher_mail, self # Drop the HTML-part of a multipart/alternative-message if it is # inline-encrypted: that ciphertext is probably wrapped in HTML, # which GnuPG chokes upon, so we would have to parse the HTML to # handle the message-part properly. # Also it's not clear how to handle the resulting plain-text: is # it HTML or simple text? That depends on the sending MUA and # the original input. # In summary, that's too much complications. if cipher_mail.mime_type == 'multipart/alternative' && cipher_mail.html_part.present? && cipher_mail.html_part.body.decoded.include?('-----BEGIN PGP MESSAGE-----') cipher_mail.parts.delete_if do |part| part[:content_type].content_type == 'text/html' end # Set the content-type of the newly generated message to # something less confusing. content_type 'multipart/mixed' # Leave a marker for other code. header['X-MailGpg-Deleted-Html-Part'] = 'true' end cipher_mail.parts.each do |part| p = VerifiedPart.new do |p| if part.has_content_type? && /application\/(?:octet-stream|pgp-encrypted)/ =~ part.mime_type # encrypted attachment, we set the content_type to the generic 'application/octet-stream' # and remove the .pgp/gpg/asc from name/filename in header fields decrypted = GpgmeHelper.decrypt(part.decoded, options) p.verify_result decrypted.verify_result if options[:verify] p.content_type part.content_type.sub(/application\/(?:octet-stream|pgp-encrypted)/, 'application/octet-stream') .sub(/name=(?:"')?(.*)\.(?:pgp|gpg|asc)(?:"')?/, 'name="\1"') p.content_disposition part.content_disposition.sub(/filename=(?:"')?(.*)\.(?:pgp|gpg|asc)(?:"')?/, 'filename="\1"') p.content_transfer_encoding Mail::Encodings::Base64 p.body Mail::Encodings::Base64::encode(decrypted.to_s) else body = part.body.decoded if body.include?('-----BEGIN PGP MESSAGE-----') decrypted = GpgmeHelper.decrypt(body, options) p.verify_result decrypted.verify_result if options[:verify] p.body decrypted.to_s else p.content_type part.content_type p.content_transfer_encoding part.content_transfer_encoding p.body part.body.to_s end end end add_part p end end # of multipart else decrypted = cipher_mail.body.empty? ? '' : GpgmeHelper.decrypt(cipher_mail.body.decoded, options) self.new do cipher_mail.header.fields.each do |field| header[field.name] = field.value end body decrypted.to_s verify_result decrypted.verify_result if options[:verify] && '' != decrypted end end end end end end mail-gpg-0.4.2/lib/mail/gpg/inline_signed_message.rb000066400000000000000000000047441353433656600223460ustar00rootroot00000000000000require 'mail/gpg/verified_part' module Mail module Gpg class InlineSignedMessage < Mail::Message def self.setup(signed_mail, options = {}) if signed_mail.multipart? self.new do global_verify_result = [] signed_mail.header.fields.each do |field| header[field.name] = field.value end signed_mail.parts.each do |part| if Mail::Gpg.signed_inline?(part) signed_text = part.body.to_s success, vr = GpgmeHelper.inline_verify(signed_text, options) p = VerifiedPart.new(part) if success p.body self.class.strip_inline_signature signed_text end p.verify_result vr global_verify_result << vr add_part p else add_part part end end verify_result global_verify_result end # of multipart else self.new do signed_mail.header.fields.each do |field| header[field.name] = field.value end signed_text = signed_mail.body.to_s success, vr = GpgmeHelper.inline_verify(signed_text, options) if success body self.class.strip_inline_signature signed_text else body signed_text end verify_result vr end end end END_SIGNED_TEXT = '-----END PGP SIGNED MESSAGE-----' END_SIGNED_TEXT_RE = /^#{END_SIGNED_TEXT}\s*$/ INLINE_SIG_RE = Regexp.new('^-----BEGIN PGP SIGNATURE-----\s*$.*^-----END PGP SIGNATURE-----\s*$', Regexp::MULTILINE) BEGIN_SIG_RE = /^(-----BEGIN PGP SIGNATURE-----)\s*$/ # utility method to remove inline signature and related pgp markers def self.strip_inline_signature(signed_text) if signed_text =~ INLINE_SIG_RE signed_text = signed_text.dup if signed_text !~ END_SIGNED_TEXT_RE # insert the 'end of signed text' marker in case it is missing signed_text = signed_text.gsub BEGIN_SIG_RE, "-----END PGP SIGNED MESSAGE-----\n\\1" end signed_text.gsub! INLINE_SIG_RE, '' signed_text.strip! end # Strip possible inline-"headers" (e.g. "Hash: SHA256", or "Comment: something"). signed_text.gsub(/(.*^-----BEGIN PGP SIGNED MESSAGE-----\n)(.*?)^$(.+)/m, '\1\3') end end end end mail-gpg-0.4.2/lib/mail/gpg/message_patch.rb000066400000000000000000000066331353433656600206350ustar00rootroot00000000000000require 'hkp' require 'mail/gpg/delivery_handler' require 'mail/gpg/verify_result_attribute' module Mail module Gpg module MessagePatch def self.included(base) base.class_eval do attr_accessor :raise_encryption_errors include VerifyResultAttribute end end # turn on gpg encryption / set gpg options. # # options are: # # encrypt: encrypt the message. defaults to true # sign: also sign the message. false by default # sign_as: UIDs to sign the message with # # See Mail::Gpg methods encrypt and sign for more # possible options # # mail.gpg encrypt: true # mail.gpg encrypt: true, sign: true # mail.gpg encrypt: true, sign_as: "other_address@host.com" # # sign-only mode is also supported: # mail.gpg sign: true # mail.gpg sign_as: 'jane@doe.com' # # To turn off gpg encryption use: # mail.gpg false # def gpg(options = nil) case options when nil @gpg when false @gpg = nil if Mail::Gpg::DeliveryHandler == delivery_handler self.delivery_handler = nil end nil else self.raise_encryption_errors = true if raise_encryption_errors.nil? @gpg = options self.delivery_handler ||= Mail::Gpg::DeliveryHandler nil end end # true if this mail is encrypted def encrypted? Mail::Gpg.encrypted?(self) end # returns the decrypted mail object. # # pass verify: true to verify signatures as well. The gpgme verification # result will be available via decrypted_mail.verify_result def decrypt(options = {}) import_missing_keys = options[:verify] && options.delete(:import_missing_keys) Mail::Gpg.decrypt(self, options).tap do |decrypted| if import_missing_keys && !decrypted.signature_valid? import_keys_for_signatures! decrypted.signatures return Mail::Gpg.decrypt(self, options) end end end # true if this mail is signed (but not encrypted) def signed? Mail::Gpg.signed?(self) end # verify signatures. returns a new mail object with signatures removed and # populated verify_result. # # verified = signed_mail.verify() # verified.signature_valid? # signers = mail.signatures.map{|sig| sig.from} # # use import_missing_keys: true in order to try to fetch and import # unknown keys for signature validation def verify(options = {}) import_missing_keys = options.delete(:import_missing_keys) Mail::Gpg.verify(self, options).tap do |verified| if import_missing_keys && !verified.signature_valid? import_keys_for_signatures! verified.signatures return Mail::Gpg.verify(self, options) end end end def import_keys_for_signatures!(signatures = []) hkp = Hkp.new raise_errors: false signatures.each do |sig| begin sig.key rescue EOFError # gpgme throws this for unknown keys :( hkp.fetch_and_import sig.fingerprint end end end end end end unless Mail::Message.included_modules.include?(Mail::Gpg::MessagePatch) Mail::Message.send :include, Mail::Gpg::MessagePatch end mail-gpg-0.4.2/lib/mail/gpg/mime_signed_message.rb000066400000000000000000000013771353433656600220160ustar00rootroot00000000000000require 'mail/gpg/verified_part' module Mail module Gpg class MimeSignedMessage < Mail::Message def self.setup(signed_mail, options = {}) content_part, signature = signed_mail.parts success, vr = SignPart.verify_signature(content_part, signature, options) self.new do verify_result vr signed_mail.header.fields.each do |field| header[field.name] = field.value end content_part.header.fields.each do |field| header[field.name] = field.value end if content_part.multipart? content_part.parts.each{|part| add_part part} else body content_part.body.raw_source end end end end end end mail-gpg-0.4.2/lib/mail/gpg/missing_keys_error.rb000066400000000000000000000001261353433656600217360ustar00rootroot00000000000000module Mail module Gpg class MissingKeysError < StandardError end end end mail-gpg-0.4.2/lib/mail/gpg/rails.rb000066400000000000000000000003221353433656600171310ustar00rootroot00000000000000begin require 'action_mailer' require 'active_support' require 'mail/gpg/rails/action_mailer_base_patch' Mail::Gpg::Rails::ActionMailerPatch.apply rescue LoadError # no actionmailer, do nothing end mail-gpg-0.4.2/lib/mail/gpg/rails/000077500000000000000000000000001353433656600166075ustar00rootroot00000000000000mail-gpg-0.4.2/lib/mail/gpg/rails/action_mailer_base_patch.rb000066400000000000000000000015721353433656600241200ustar00rootroot00000000000000require 'action_mailer/base' module Mail module Gpg module Rails module ActionMailerPatch def self.apply unless ActionMailer::Base < InstanceMethods ActionMailer::Base.prepend InstanceMethods ActionMailer::Base.singleton_class.prepend ClassMethods end end module InstanceMethods def mail(headers = {}, &block) headers = headers.dup gpg_options = headers.delete :gpg super(headers, &block).tap do |m| if gpg_options m.gpg gpg_options end end end end module ClassMethods def deliver_mail(mail, &block) super(mail) do Mail::Gpg::DeliveryHandler.deliver_mail mail, &block end end end end end end end mail-gpg-0.4.2/lib/mail/gpg/sign_part.rb000066400000000000000000000027501353433656600200140ustar00rootroot00000000000000module Mail module Gpg class SignPart < Mail::Part def initialize(cleartext_mail, options = {}) signature = GpgmeHelper.sign(cleartext_mail.encoded, options) super() do body signature.to_s content_type "application/pgp-signature; name=\"signature.asc\"" content_disposition 'attachment; filename="signature.asc"' content_description 'OpenPGP digital signature' end end # true if all signatures are valid def self.signature_valid?(plain_part, signature_part, options = {}) verify_signature(plain_part, signature_part, options)[0] end # will return [success(boolean), verify_result(as returned by gpgme)] def self.verify_signature(plain_part, signature_part, options = {}) if !(signature_part.has_content_type? && ('application/pgp-signature' == signature_part.mime_type)) return false end # Work around the problem that plain_part.raw_source prefixes an # erroneous CRLF, . if ! plain_part.raw_source.empty? plaintext = [ plain_part.header.raw_source, "\r\n\r\n", plain_part.body.raw_source ].join else plaintext = plain_part.encoded end signature = signature_part.body.encoded GpgmeHelper.sign_verify(plaintext, signature, options) end end end end mail-gpg-0.4.2/lib/mail/gpg/signed_part.rb000066400000000000000000000014001353433656600203140ustar00rootroot00000000000000require 'mail/part' require 'mail/gpg/sign_part' module Mail module Gpg class SignedPart < Mail::Part def self.build(cleartext_mail) new do if cleartext_mail.body.multipart? if cleartext_mail.content_type =~ /^(multipart[^;]+)/ # preserve multipart/alternative etc content_type $1 else content_type 'multipart/mixed' end cleartext_mail.body.parts.each do |p| add_part p end else content_type cleartext_mail.content_type body cleartext_mail.body.raw_source end end end def sign(options) SignPart.new(self, options) end end end end mail-gpg-0.4.2/lib/mail/gpg/verified_part.rb000066400000000000000000000002401353433656600206410ustar00rootroot00000000000000require 'mail/gpg/verify_result_attribute' module Mail module Gpg class VerifiedPart < Mail::Part include VerifyResultAttribute end end end mail-gpg-0.4.2/lib/mail/gpg/verify_result_attribute.rb000066400000000000000000000013251353433656600230100ustar00rootroot00000000000000module Mail module Gpg module VerifyResultAttribute # the result of signature verification, as provided by GPGME def verify_result(result = nil) if result self.verify_result = result else @verify_result end end def verify_result=(result) @verify_result = result end # checks validity of signatures (true / false) def signature_valid? sigs = self.signatures sigs.any? && sigs.all?{|s|s.valid?} end # list of all signatures from verify_result def signatures [verify_result].flatten.compact.map do |vr| vr.signatures end.flatten.compact end end end end mail-gpg-0.4.2/lib/mail/gpg/version.rb000066400000000000000000000000711353433656600175050ustar00rootroot00000000000000module Mail module Gpg VERSION = "0.4.2" end end mail-gpg-0.4.2/lib/mail/gpg/version_part.rb000066400000000000000000000010011353433656600205250ustar00rootroot00000000000000require 'mail/part' module Mail module Gpg class VersionPart < Mail::Part VERSION_1 = 'Version: 1' CONTENT_TYPE = 'application/pgp-encrypted' CONTENT_DESC = 'PGP/MIME Versions Identification' def initialize(*args) super body VERSION_1 content_type CONTENT_TYPE content_description CONTENT_DESC end def self.isVersionPart?(part) part.mime_type == CONTENT_TYPE && part.body =~ /#{VERSION_1}/ end end end end mail-gpg-0.4.2/mail-gpg.gemspec000066400000000000000000000024661353433656600163020ustar00rootroot00000000000000# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'mail/gpg/version' Gem::Specification.new do |spec| spec.name = "mail-gpg" spec.version = Mail::Gpg::VERSION spec.authors = ["Jens Kraemer"] spec.email = ["jk@jkraemer.net"] spec.description = "GPG/MIME encryption plugin for the Ruby Mail Library\nThis tiny gem adds GPG capabilities to Mail::Message and ActionMailer::Base. Because privacy matters." spec.summary = %q{GPG/MIME encryption plugin for the Ruby Mail Library} spec.homepage = "https://github.com/jkraemer/mail-gpg" spec.license = "MIT" spec.files = `git ls-files`.split($/) spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_dependency "mail", "~> 2.5", ">= 2.5.3" spec.add_dependency "gpgme", "~> 2.0", ">= 2.0.2" spec.add_development_dependency "bundler", "~> 2.0" spec.add_development_dependency "test-unit", "~> 3.0" spec.add_development_dependency "rake" spec.add_development_dependency "actionmailer", ">= 3.2.0" spec.add_development_dependency "byebug" spec.add_development_dependency "shoulda-context", '~> 1.1' end mail-gpg-0.4.2/test/000077500000000000000000000000001353433656600142075ustar00rootroot00000000000000mail-gpg-0.4.2/test/action_mailer_test.rb000066400000000000000000000103451353433656600204040ustar00rootroot00000000000000require 'test_helper' class MyMailer < ActionMailer::Base default from: 'joe@foo.bar', to: 'jane@foo.bar' def unencrypted(bouncy=false) mail subject: 'unencrypted', body: 'unencrypted mail', return_path: (bouncy && bounce_address) end def encrypted(bouncy=false) mail subject: 'encrypted', body: 'encrypted mail', return_path: (bouncy && bounce_address), gpg: {encrypt: true} end def signed(bouncy=false) mail from: 'jane@foo.bar', to: 'joe@foo.bar', subject: 'signed', body: 'signed mail', return_path: (bouncy && bounce_address), gpg: { sign: true, password: 'abc' } end private def bounce_address SecureRandom.uuid + "@bounces.example.com" end end class ActionMailerTest < MailGpgTestCase context 'without return_path' do setup do set_passphrase('abc') (@emails = ActionMailer::Base.deliveries).clear end context "with action mailer" do should "send unencrypted mail" do MyMailer.unencrypted.deliver assert_equal 1, @emails.size assert m = @emails.first assert_equal 'unencrypted', m.subject end should "send encrypted mail" do assert m = MyMailer.encrypted assert true == m.gpg[:encrypt] m.deliver assert_equal 1, @emails.size assert m = @emails.first assert_equal 'encrypted', m.subject assert_equal 2, m.parts.size assert encrypted = m.parts.detect{|p| p.content_type =~ /encrypted\.asc/} assert clear = GPGME::Crypto.new.decrypt(encrypted.body.to_s, password: 'abc') m = Mail.new clear assert_equal 'encrypted mail', m.body.to_s end should "send signed mail" do assert m = MyMailer.signed assert true == m.gpg[:sign] m.deliver assert_equal 1, @emails.size assert delivered = @emails.first assert_equal 'signed', delivered.subject assert_equal 2, delivered.parts.size assert sign_part = delivered.parts.detect{|p| p.content_type =~ /signature\.asc/} assert signed_part = delivered.parts.detect{|p| p.content_type !~ /signature\.asc/} GPGME::Crypto.new.verify(sign_part.body.to_s, signed_text: signed_part.encoded) do |sig| assert sig.valid? end end end end context 'with return_path' do setup do set_passphrase('abc') (@emails = ActionMailer::Base.deliveries).clear end context "with action mailer" do should "send unencrypted mail" do MyMailer.unencrypted(true).deliver assert_equal 1, @emails.size assert m = @emails.first assert_match /@bounces\.example\.com\z/, m.return_path assert_equal 'unencrypted', m.subject end # For unknown reasons this test can't decrypt the test-message if # it's the first one that's running. Therefore we misspelled # its name a little. should "zend encrypted mail" do assert m = MyMailer.encrypted(true) assert true == m.gpg[:encrypt] m.deliver assert_equal 1, @emails.size assert m = @emails.first assert_match /@bounces\.example\.com\z/, m.return_path assert_equal 'encrypted', m.subject assert_equal 2, m.parts.size assert encrypted = m.parts.detect{|p| p.content_type =~ /encrypted\.asc/} assert clear = GPGME::Crypto.new.decrypt(encrypted.body.to_s, password: 'abc') m = Mail.new clear assert_equal 'encrypted mail', m.body.to_s end should "send signed mail" do assert m = MyMailer.signed(true) assert true == m.gpg[:sign] m.deliver assert_equal 1, @emails.size assert delivered = @emails.first assert_match /@bounces\.example\.com\z/, delivered.return_path assert_equal 'signed', delivered.subject assert_equal 2, delivered.parts.size assert sign_part = delivered.parts.detect{|p| p.content_type =~ /signature\.asc/} assert signed_part = delivered.parts.detect{|p| p.content_type !~ /signature\.asc/} GPGME::Crypto.new.verify(sign_part.body.to_s, signed_text: signed_part.encoded) do |sig| assert sig.valid? end end end end end mail-gpg-0.4.2/test/decrypted_part_test.rb000066400000000000000000000026031353433656600206050ustar00rootroot00000000000000require 'test_helper' require 'mail/gpg/decrypted_part' require 'mail/gpg/encrypted_part' class DecryptedPartTest < MailGpgTestCase context 'DecryptedPart' do setup do @mail = Mail.new do to 'jane@foo.bar' from 'joe@foo.bar' subject 'test' body 'i am unencrypted' end @part = Mail::Gpg::EncryptedPart.new(@mail, { recipients: ['jane@foo.bar'], :sign => true, :password => 'abc' }) end should 'decrypt' do assert mail = Mail::Gpg::DecryptedPart.new(@part, { :password => 'abc' }) assert mail == @mail assert mail.message_id == @mail.message_id assert mail.message_id != @part.message_id end should 'decrypt and verify' do assert mail = Mail::Gpg::DecryptedPart.new(@part, { :verify => true, :password => 'abc' }) assert mail == @mail assert mail.message_id == @mail.message_id assert mail.message_id != @part.message_id assert vr = mail.verify_result assert sig = vr.signatures.first assert sig.to_s=~ /Joe/ assert sig.valid? end should 'raise encoding error for non gpg mime type' do part = Mail::Part.new(@part) part.content_type = 'text/plain' assert_raise(EncodingError) { Mail::Gpg::DecryptedPart.new(part) } end end end mail-gpg-0.4.2/test/encrypted_part_test.rb000066400000000000000000000016511353433656600206210ustar00rootroot00000000000000require 'test_helper' require 'mail/gpg/encrypted_part' class EncryptedPartTest < MailGpgTestCase def check_key_list(keys) assert_equal 1, keys.size assert_equal GPGME::Key, keys.first.class assert_equal 'jane@foo.bar', keys.first.email end context 'EncryptedPart' do setup do mail = Mail.new do to 'jane@foo.bar' from 'joe@foo.bar' subject 'test' body 'i am unencrypted' end @part = Mail::Gpg::EncryptedPart.new(mail, recipients: ['jane@foo.bar']) end should 'have binary content type and name' do assert_equal 'application/octet-stream; name=encrypted.asc', @part.content_type end should 'have description' do assert_match(/openpgp/i, @part.content_description) end should 'have inline disposition and default filename' do assert_equal 'inline; filename=encrypted.asc', @part.content_disposition end end end mail-gpg-0.4.2/test/gpg_test.rb000066400000000000000000000262211353433656600163530ustar00rootroot00000000000000require 'test_helper' class GpgTest < MailGpgTestCase def check_headers(mail = @mail, encrypted = @encrypted) assert_equal mail.to, encrypted.to assert_equal mail.cc, encrypted.cc assert_equal mail.bcc, encrypted.bcc assert_equal mail.subject, encrypted.subject end def check_mime_structure(mail = @mail, encrypted = @encrypted) assert_equal 2, encrypted.parts.size v_part, enc_part = encrypted.parts assert_match /Version: 1/, v_part.to_s assert_match /application\/pgp-encrypted(?:; charset=UTF-8)?/, v_part.content_type assert_equal 'application/octet-stream; name=encrypted.asc', enc_part.content_type end def check_attachment_name(mail = @mail, encrypted = @encrypted) v_part, enc_part = encrypted.parts assert_equal 'application/octet-stream; name=custom_filename.asc', enc_part.content_type assert_equal 'inline; filename=custom_filename.asc', enc_part.content_disposition end def check_content(mail = @mail, encrypted = @encrypted) assert enc = encrypted.parts.last assert clear = GPGME::Crypto.new.decrypt(enc.to_s, password: 'abc').to_s assert_match /encrypt me/, clear assert_equal mail.to_s, clear end def check_signature(mail = @mail, signed = @signed) assert signed.signed? assert signature = signed.parts.detect{|p| p.content_type =~ /signature\.asc/}.body.to_s assert signed_part = signed.parts.detect{|p| p.content_type !~ /signature\.asc/} assert_equal mail.parts.size, signed_part.parts.size GPGME::Crypto.new.verify(signature, signed_text: signed_part.encoded) do |sig| assert sig.valid? end assert Mail::Gpg.signature_valid?(signed) assert verified = signed.verify assert verified.verify_result.present? assert verified.verify_result.signatures.any? assert verified.signatures.any? assert verified.signature_valid? end def check_mime_structure_signed(mail = @mail, signed = @signed) assert_match /multipart\/signed/, signed.content_type assert_equal 2, signed.parts.size orig_part, sign_part = signed.parts assert_equal 'application/pgp-signature; name=signature.asc', sign_part.content_type assert_equal mail.parts.size, orig_part.parts.size assert_nil orig_part.to assert_nil orig_part.from assert_nil orig_part.subject end def check_headers_signed(mail = @mail, signed = @signed) assert_equal mail.to, signed.to if mail.cc assert_equal mail.cc, signed.cc end if mail.bcc assert_equal mail.bcc, signed.bcc end assert_equal mail.subject, signed.subject assert_equal mail.return_path, signed.return_path end context "gpg installation" do should "have keys for jane and joe" do assert joe = GPGME::Key.find(:public, 'joe@foo.bar') assert_equal 1, joe.size joe = joe.first assert jane = GPGME::Key.find(:public, 'jane@foo.bar') assert_equal 1, jane.size jane = jane.first assert id = jane.fingerprint assert jane = GPGME::Key.find(:public, id).first assert_equal id, jane.fingerprint end end context "gpg signed" do setup do @mail = Mail.new do to 'joe@foo.bar' from ' jane@foo.bar' subject 'test test' body 'sign me!' content_type 'text/plain; charset=UTF-8' end end context 'simple mail' do setup do @signed = Mail::Gpg.sign(@mail, password: 'abc') end should 'preserve from name' do assert_equal ' jane@foo.bar', @signed.header['from'].value end should 'have same recipients and subject' do check_headers_signed end should 'have proper gpgmime structure' do check_mime_structure_signed end should 'have correct signature' do check_signature end end context 'mail with custom headers' do setup do @mail.header['X-Custom-Header'] = 'custom value' @mail.header['Return-Path'] = 'bounces@example.com' @mail.header['References'] = 'some-message-id' @signed = Mail::Gpg.sign(@mail, password: 'abc') end should 'have same recipients and subject' do check_headers_signed end should 'have proper gpgmime structure' do check_mime_structure_signed end should 'have correct signature' do check_signature end should 'preserve customer header values' do assert_equal 'custom value', @signed.header['X-Custom-Header'].to_s assert_equal 'bounces@example.com', @signed.return_path assert_equal 'some-message-id', @signed.header['References'].value end end context 'mail with multiple recipients' do setup do @mail.bcc 'jane@foo.bar' @signed = Mail::Gpg.sign(@mail, password: 'abc') end should 'have same recipients and subject' do check_headers_signed end should 'have proper gpgmime structure' do check_mime_structure_signed end should 'have correct signature' do check_signature end end context 'multipart alternative mail' do setup do @mail = Mail.new do to 'joe@foo.bar' from 'jane@foo.bar' subject 'test test' text_part do body 'sign me!' end html_part do body '

H1

' end end @signed = Mail::Gpg.sign(@mail, password: 'abc') end should 'have same recipients and subject' do check_headers_signed end should 'have proper gpgmime structure' do check_mime_structure_signed end should 'have correct signature' do check_signature end should 'have multiple parts in original content' do assert original_part = @signed.parts.first assert @mail.multipart? assert_match /alternative/, @mail.content_type assert_match /alternative/, original_part.content_type assert_equal original_part.parts.size, @mail.parts.size assert_match /sign me!/, original_part.parts.first.body.to_s assert_match /H1/, original_part.parts.last.body.to_s end end end context "gpg encrypted" do setup do @mail = Mail.new do to 'jane@foo.bar' from 'joe@foo.bar' subject 'test test' body 'encrypt me!' end end context 'simple mail' do setup do @encrypted = Mail::Gpg.encrypt(@mail) end should 'have same recipients and subject' do check_headers end should 'have proper gpgmime structure' do check_mime_structure end should 'have correctly encrypted content' do check_content end should 'decrypt' do assert mail = Mail::Gpg.decrypt(@encrypted, { :password => 'abc' }) assert mail == @mail end end context 'simple mail (custom filename)' do setup do @encrypted = Mail::Gpg.encrypt(@mail, {filename: 'custom_filename.asc'}) end should 'have same custom attachment filename' do check_attachment_name end end context 'simple mail (signed)' do setup do @encrypted = Mail::Gpg.encrypt(@mail, { :sign => true, :password => 'abc' }) end should 'have same recipients and subject' do check_headers end should 'have proper gpgmime structure' do check_mime_structure end should 'have correctly encrypted content' do check_content end should 'decrypt and verify' do assert mail = Mail::Gpg.decrypt(@encrypted, { :verify => true, :password => 'abc' }) assert mail == @mail assert mail.verify_result assert sig = mail.signatures.first assert sig.to_s =~ /Joe/ assert sig.valid? end end context 'mail with custom header' do setup do @mail.header['X-Custom-Header'] = 'custom value' @mail.header['Return-Path'] = 'bounces@example.com' @encrypted = Mail::Gpg.encrypt(@mail) @encrypted.header['X-Another-Header'] = 'another value' end should 'have same recipients and subject' do check_headers end should 'have proper gpgmime structure' do check_mime_structure end should 'have correctly encrypted content' do check_content end should 'preserve customer header values' do assert_equal 'custom value', @encrypted.header['X-Custom-Header'].to_s assert_equal 'bounces@example.com', @encrypted.return_path end context 'when decrypted' do setup do @decrypted_mail = Mail::Gpg.decrypt(@encrypted, { :password => 'abc' }) end should 'have same subject and body as the original' do assert_equal @mail.subject, @decrypted_mail.subject assert_equal @mail.body.to_s, @decrypted_mail.body.to_s end should 'preserve custom header from encrypted inner mail' do assert_equal 'custom value', @decrypted_mail.header['X-Custom-Header'].to_s end should 'preserve custom header from outer mail' do assert_equal 'another value', @decrypted_mail.header['X-Another-Header'].to_s end end end context 'mail with multiple recipients' do setup do @mail.bcc 'joe@foo.bar' @encrypted = Mail::Gpg.encrypt(@mail) end should 'have same recipients and subject' do check_headers end should 'have proper gpgmime structure' do check_mime_structure end should 'have correctly encrypted content' do check_content end should "encrypt for all recipients" do assert encrypted_body = @encrypted.parts.last.to_s end should 'decrypt' do assert mail = Mail::Gpg.decrypt(@encrypted, { :password => 'abc' }) assert mail == @mail end end context 'multipart mail' do setup do @mail.add_file 'Rakefile' @encrypted = Mail::Gpg.encrypt(@mail, sign: true, password: 'abc') end should 'have same recipients and subject' do check_headers end should 'have proper gpgmime structure' do check_mime_structure end should 'have correctly encrypted content' do check_content end should 'have multiple parts in encrypted content' do assert encrypted_body = @encrypted.parts.last.to_s assert clear = GPGME::Crypto.new.decrypt(encrypted_body.to_s, password: 'abc').to_s assert m = Mail::Message.new(clear.to_s) assert m.multipart? assert_equal 2, m.parts.size assert_match /encrypt me/, m.parts.first.body.to_s assert_match /Rakefile/, m.parts.last.content_disposition end should 'decrypt and verify' do assert mail = Mail::Gpg.decrypt(@encrypted, { :verify => true, :password => 'abc' }) assert mail == @mail assert mail.parts[1] == @mail.parts[1] assert mail.verify_result assert signatures = mail.signatures assert_equal 1, signatures.size assert sig = signatures[0] assert sig.to_s =~ /Joe/ assert sig.valid? end end end end mail-gpg-0.4.2/test/gpghome/000077500000000000000000000000001353433656600156355ustar00rootroot00000000000000mail-gpg-0.4.2/test/gpghome/gpg-agent.conf000066400000000000000000000000361353433656600203540ustar00rootroot00000000000000allow-preset-passphrase batch mail-gpg-0.4.2/test/gpghome/pubring.gpg000066400000000000000000000034751353433656600200130ustar00rootroot00000000000000R2&}?ª#CJpt*{bh {R+=b<7V46^1RAuE96F_=Dž`+-v6/Jx|mؒUֲ_xlϦ;徴q8%Eb^}&IWtvP0&|!yيAi` 90G,`Zx .'?Ҽ%KVˬib 2, HI1Joe Tester (with stupid passphrase) b"R2#   GHHw˕PyzoOe}h,+ R2 \O[.KA|~ǂVhE.A%Fzk d-N֎_6rl=fEg 2bzg%ƽe KtTټ$ߧeAݟ`@Iq5d5@u  ݳib)0N0Ҙ mo_l!||Xtix5g$T[6Cq]LM!PZR2d ܏.>_y4LW'8+[Fڿ$r8@ %ڕcZ~u Y[;B8[nZ Z#1m`qld#@ QLCI؎B&V?_+.SU#4E2^9 T{lpm9DTѧ~EH~(zT Y*'$4XRut H.1`ox&/o~s:3 gw~ T&;bU>x:HsY=ͳ~nw. 1Lm=`V|,3 <0炼 菡J4>8ÊB6@u!QX Vk<[G1ȫERjQ-y0Jane Doe (with stupid passphrase) b"R2d#   ee*E>;\5+ɩЛ$NtOz%-9vl+|2 R2dM~Ikik!6ʚ z<fX|'Tbi+$ U;%Tv4EJ0Arj,Wfb9#ʧGiY g+)EE\R!Б['NGaxg$r] lD,D@ߔg3QngoM#AR/PxNcyB -)$WTWE?%i"N8a15 CY}. 8I R2d ee*FD?ԶS _]]:H[+-v6/Jx|mؒUֲ_xlϦ;徴q8%Eb^}&IWtvP0&|!yيAi` 90G,`Zx .'?Ҽ%KVˬib 2, HI1Joe Tester (with stupid passphrase) b"R2#   GHHw˕PyzoOe}h,+ R2 \O[.KA|~ǂVhE.A%Fzk d-N֎_6rl=fEg 2bzg%ƽe KtTټ$ߧeAݟ`@Iq5d5@u  ݳib)0N0Ҙ mo_l!||Xtix5g$T[6Cq]LM!PZR2d ܏.>_y4LW'8+[Fڿ$r8@ %ڕcZ~u Y[;B8[nZ Z#1m`qld#@ QLCI؎B&V?_+.SU#4E2^9 T{lpm9DTѧ~EH~(zT Y*'$4XRut H.1`ox&/o~s:3 gw~ T&;bU>x:HsY=ͳ~nw. 1Lm=`V|,3 <0炼 菡J4>8ÊB6@u!QX Vk<[G1ȫERjQ-y0Jane Doe (with stupid passphrase) b"R2d#   ee*E>;\5+ɩЛ$NtOz%-9vl+|2 R2dM~Ikik!6ʚ z<fX|'Tbi+$ U;%Tv4EJ0Arj,Wfb9#ʧGiY g+)EE\R!Б['NGaxg$r] lD,D@ߔg3QngoM#AR/PxNcyB -)$WTWE?%i"N8a15 CY}. 8I R2d ee*FD?ԶS _]]:H[&'OT\,և\J1e.UAqny\AZBfI}s^~6 i 04qhh<& s$_ q/mPXc24š;;TUjxOyAP_fЭ0 ?! t,|l2ͻA`hffUɝn?DszׇⓝtX5<eutd -`? 0rw6 IQ?&kKKGȂA ѓnawsC±,nv%Tͺz0"M6ru@4݈dy^$'Y ArW@{֝J_Ҧt(8Vjz$p5 EG ӂ֦KsPi83TRBLms\#s⥼Vu!m~a)r_~n\L`jS?W\ -^jD@@AtXsl%1zEBÖwSJ)=VE XqE_ܞؼ#-jJX3w mail-gpg-0.4.2/test/gpghome/secring.gpg000066400000000000000000000041211353433656600177640ustar00rootroot00000000000000R2&}?ª#CJpt*{bh {R+=b<7V46^1RAuE96F_=Dž`+-v6/Jx|mؒUֲ_xlϦ;徴q8%Eb^}&IWtvP0&|!yيAi` 90G,`Zx .'?Ҽ%KVˬib 2, HIJqlnk*`VhLw2Bd!#)/+i67 4vtF^NqZ1Joe Tester (with stupid passphrase) b"R2#   GHHw˕PyzoOe}h,+XR2 \O[.KA|~ǂVhE.A%Fzk d-N֎_6rl=fEg 2bzg%ƽe KtTټ$ߧeAݟ`@Iq5d5@u  ݳib)0N0Ҙ mo^|=tG+RP%u>:rct9 j` D\R2d ܏.>_y4LW'8+[Fڿ$r8@ %ڕcZ~u Y[;B8[nZ Z#1m`qld#@ QLCI؎B&V?_+.SU#4E2^9 T{lpm9DTѧ~EH~(zT Y*'$4XRut H.1`ox&/o~s:3 gw~ T&;bU>x:HsY=ͳ~nw. 1Lm=`V|,3 <0炼 菡J4>8ÊB6@u!QX Vk<[G1ȫERjQ-y2CK`}=&5`B;? -x?OҤx{$\fIp$0Jane Doe (with stupid passphrase) b"R2d#   ee*E>;\5+ɩЛ$NtOz%-9vl+|2XR2dM~Ikik!6ʚ z<fX|'Tbi+$ U;%Tv4EJ0Arj,Wfb9#ʧGiY g+)EE\R!Б['NGaxg$r] lD,D@ߔg3QngoM#AR/PxNcyB -)$WTWE?%i"N8a15 CY}. 82CK`AUiX:p<>nyFx-BpK6BE(=}^! OrJ Aee* ? R;!iխqۇ;B #\p؜KΚmail-gpg-0.4.2/test/gpgme_helper_test.rb000066400000000000000000000120731353433656600202340ustar00rootroot00000000000000require 'test_helper' class GpgmeHelperTest < MailGpgTestCase def check_key_list(keys) assert_equal 1, keys.size assert_equal GPGME::Key, keys.first.class assert_equal 'jane@foo.bar', keys.first.email end context 'GpgmeHelper' do should 'handle empty email list' do assert_equal [], Mail::Gpg::GpgmeHelper.send(:keys_for_data, nil) assert_equal [], Mail::Gpg::GpgmeHelper.send(:keys_for_data, []) end # no keys given, assuming they are already in the keychain context 'with email address' do setup do @email = 'jane@foo.bar' end should 'resolve email to gpg keys' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @email) check_key_list keys end should 'resolve emails to gpg keys' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, [@email]) check_key_list keys end end # this is a use case we do not really need but it works due to the way # Gpgme looks up keys context 'with key id' do setup do @key_id = GPGME::Key.find(:public, 'jane@foo.bar').first.sha end should 'resolve single id gpg keys' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @key_id) check_key_list keys end should 'resolve id list to gpg keys' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, [@key_id]) check_key_list keys end end # this is a use case we do not really need but it works due to the way # Gpgme looks up keys context 'with key fingerprint' do setup do @key_fpr = GPGME::Key.find(:public, 'jane@foo.bar').first.fingerprint end should 'resolve single id gpg keys' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @key_fpr) check_key_list keys end should 'resolve id list to gpg keys' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, [@key_fpr]) check_key_list keys end end context 'with email addresses' do setup do @key = GPGME::Key.find(:public, 'jane@foo.bar').first @emails = ['jane@foo.bar'] end # probably the most common use case - one or more recipient addresses and a # hash mapping them to public key data that the user pasted into a text # field at some point context 'and key data' do setup do @key = @key.export(armor: true).to_s @key_data = { 'jane@foo.bar' => @key } end should 'resolve to gpg key for single address' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails.first, @key_data) check_key_list keys end should 'resolve to gpg keys' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, @key_data) check_key_list keys end should 'ignore unknown addresses' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, ['john@doe.com'], @key_data) assert keys.blank? end should 'ignore invalid key data and not use existing key' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, ['jane@foo.bar'], { 'jane@foo.bar' => "-----BEGIN PGP\ninvalid key data" }) assert keys.blank? end end context 'and key id or fpr' do setup do @key_id = @key.sha @key_fpr = @key.fingerprint @email = @emails.first end should 'resolve id to gpg key for single address' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails.first, { @email => @key_id }) check_key_list keys end should 'resolve id to gpg key' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, { @email => @key_id }) check_key_list keys end should 'resolve fpr to gpg key' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, { @email => @key_fpr }) check_key_list keys end should 'ignore unknown addresses' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, ['john@doe.com'], { @email => @key_fpr }) assert keys.blank? end should 'ignore invalid key id and not use existing key' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, { @email => "invalid key id" }) assert keys.blank? end end # mapping email addresses to already retrieved key objects or # key fingerprints is also possible. context 'and key object' do setup do @key_data = { 'jane@foo.bar' => @key } end should 'resolve to gpg keys for these addresses' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, @emails, @key_data) check_key_list keys end should 'ignore unknown addresses' do assert keys = Mail::Gpg::GpgmeHelper.send(:keys_for_data, ['john@doe.com'], @key_data) assert keys.blank? end end end end end mail-gpg-0.4.2/test/hkp_test.rb000066400000000000000000000044111353433656600163550ustar00rootroot00000000000000require 'test_helper' require 'byebug' require 'hkp' class HkpTest < MailGpgTestCase context "hpk client" do { "http://pool.sks-keyservers.net:11371" => { host: 'pool.sks-keyservers.net', ssl: false, port: 11371 }, "https://hkps.pool.sks-keyservers.net" => { host: 'hkps.pool.sks-keyservers.net', ssl: true, port: 443 }, "hkp://pool.sks-keyservers.net" => { host: 'pool.sks-keyservers.net', ssl: false, port: 11371 }, "hkps://hkps.pool.sks-keyservers.net" => { host: 'hkps.pool.sks-keyservers.net', ssl: true, port: 443 }, }.each do |url, data| context "with server #{url}" do context 'client setup' do setup do @client = Hkp::Client.new url end should "have correct port" do assert_equal data[:port], @client.instance_variable_get("@port") end should "have correct ssl setting" do assert_equal data[:ssl], @client.instance_variable_get("@use_ssl") end should "have correct host" do assert_equal data[:host], @client.instance_variable_get("@host") end end if ENV['ONLINE_TESTS'] context 'key search' do setup do @hkp = Hkp.new keyserver: url, ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE end should 'find key' do assert result = @hkp.search('jk@jkraemer.net') assert result.size > 0 end should 'fetch key' do assert result = @hkp.fetch('584C8BEE17CAC560') assert_match 'PGP PUBLIC KEY BLOCK', result end end end end end end context 'key search' do context "without keyserver url" do setup do @hkp = Hkp.new end should "have a non-empty keyserver" do assert url = @hkp.instance_variable_get("@keyserver") assert !url.blank? end if ENV['ONLINE_TESTS'] should 'find key' do assert result = @hkp.search('jk@jkraemer.net') assert result.size > 0 end end end end end mail-gpg-0.4.2/test/inline_decrypted_message_test.rb000066400000000000000000000141551353433656600226260ustar00rootroot00000000000000require 'test_helper' # test cases for PGP inline messages (i.e. non-mime) class InlineDecryptedMessageTest < MailGpgTestCase context "InlineDecryptedMessage" do setup do (@mails = Mail::TestMailer.deliveries).clear @mail = Mail.new do to 'jane@foo.bar' from 'joe@foo.bar' subject 'test' body 'i am unencrypted' gpg encrypt: false end end context "inline message" do should "decrypt and verify body" do mail = Mail.new(@mail) mail.body = InlineDecryptedMessageTest.encrypt(mail, mail.body.to_s) assert !mail.multipart? assert mail.encrypted? assert decrypted = mail.decrypt(:password => 'abc', verify: true) assert decrypted == @mail assert !decrypted.encrypted? assert vr = decrypted.verify_result assert sig = vr.signatures.first assert sig.to_s=~ /Joe/ assert sig.valid? end end context "multipart/alternative message" do should "have dropped HTML-part" do mail = Mail.new(@mail) mail.body = InlineDecryptedMessageTest.encrypt(mail, mail.body.to_s) mail.html_part do |p| p.body "
#{InlineDecryptedMessageTest.encrypt(mail, mail.body.to_s)}
" end assert mail.multipart? assert mail.encrypted? assert decrypted = mail.decrypt(:password => 'abc', verify: true) assert !decrypted.encrypted? assert decrypted.mime_type == 'multipart/mixed' assert decrypted.parts.size == 1 assert decrypted.parts.first.mime_type == 'text/plain' assert decrypted.header['X-MailGpg-Deleted-Html-Part'].value == 'true' end end context "attachment message" do should "decrypt attachment" do rakefile = File.open('Rakefile') { |file| file.read } mail = Mail.new(@mail) mail.content_type = 'multipart/mixed' mail.body = '' mail.part do |p| p.content_type 'application/octet-stream; name=Rakefile.pgp' p.content_transfer_encoding Mail::Encodings::Base64 p.content_disposition 'attachment; filename="Rakefile.pgp"' p.body Mail::Encodings::Base64::encode(InlineDecryptedMessageTest.encrypt(mail, rakefile, false)) end assert mail.multipart? assert mail.encrypted? assert decrypted = mail.decrypt(:password => 'abc') assert !decrypted.encrypted? check_headers(@mail, decrypted) assert_equal 1, decrypted.parts.length assert /application\/octet-stream; (?:charset=UTF-8; )?name=Rakefile/ =~ decrypted.parts[0].content_type assert_equal 'attachment; filename=Rakefile', decrypted.parts[0].content_disposition assert_equal rakefile, decrypted.parts[0].body.decoded end end context "cleartext body and encrypted attachment message" do should "decrypt and verify attachment" do rakefile = File.open('Rakefile') { |file| file.read } mail = Mail.new(@mail) mail.content_type = 'multipart/mixed' mail.part do |p| p.content_type 'application/octet-stream; name=Rakefile.pgp' p.content_transfer_encoding Mail::Encodings::Base64 p.content_disposition 'attachment; filename="Rakefile.pgp"' p.body Mail::Encodings::Base64::encode(InlineDecryptedMessageTest.encrypt(mail, rakefile, false)) end assert mail.multipart? assert mail.encrypted? assert decrypted = mail.decrypt(password: 'abc', verify: true) assert !decrypted.encrypted? check_headers(@mail, decrypted) assert_equal 2, decrypted.parts.length assert @mail.body.to_s == decrypted.parts[0].body.to_s assert /application\/octet-stream; (?:charset=UTF-8; )?name=Rakefile/ =~ decrypted.parts[1].content_type assert_equal 'attachment; filename=Rakefile', decrypted.parts[1].content_disposition assert_equal rakefile, decrypted.parts[1].body.decoded assert_nil decrypted.parts[0].verify_result assert vr = decrypted.parts[1].verify_result assert sig = vr.signatures.first assert sig.to_s=~ /Joe/ assert sig.valid? end end context "encrypted body and attachment message" do should "decrypt and verify" do rakefile = File.open('Rakefile') { |file| file.read } mail = Mail.new(@mail) mail.content_type = 'multipart/mixed' mail.body = InlineDecryptedMessageTest.encrypt(mail, mail.body.to_s) mail.part do |p| p.content_type 'application/octet-stream; name=Rakefile.pgp' p.content_transfer_encoding Mail::Encodings::Base64 p.content_disposition 'attachment; filename="Rakefile.pgp"' p.body Mail::Encodings::Base64::encode(InlineDecryptedMessageTest.encrypt(mail, rakefile, false)) end assert mail.multipart? assert mail.encrypted? assert decrypted = mail.decrypt(password: 'abc', verify: true) assert !decrypted.encrypted? check_headers(@mail, decrypted) assert_equal 2, decrypted.parts.length assert @mail.body.to_s == decrypted.parts[0].body.to_s assert /application\/octet-stream; (?:charset=UTF-8; )?name=Rakefile/ =~ decrypted.parts[1].content_type assert_equal 'attachment; filename=Rakefile', decrypted.parts[1].content_disposition assert_equal rakefile, decrypted.parts[1].body.decoded decrypted.parts.each do |part| assert vr = part.verify_result assert sig = vr.signatures.first assert sig.to_s=~ /Joe/ assert sig.valid? end end end end def self.encrypt(mail, plain, armor = true) GPGME::Crypto.new.encrypt(plain, password: 'abc', recipients: mail.to, sign: true, signers: mail.from, armor: armor).to_s end def check_headers(expected, actual) assert_equal expected.to, actual.to assert_equal expected.cc, actual.cc assert_equal expected.bcc, actual.bcc assert_equal expected.subject, actual.subject assert_equal expected.message_id, actual.message_id assert_equal expected.date, actual.date end end mail-gpg-0.4.2/test/inline_signed_message_test.rb000066400000000000000000000072151353433656600221130ustar00rootroot00000000000000require 'test_helper' # test cases for PGP inline signed messages (i.e. non-mime) class InlineSignedMessageTest < MailGpgTestCase context "InlineSignedMessage" do setup do (@mails = Mail::TestMailer.deliveries).clear @mail = Mail.new do to 'jane@foo.bar' from 'joe@foo.bar' subject 'test' body 'i am unencrypted' end end context 'strip_inline_signature' do should 'strip signature from signed text' do body = self.class.inline_sign(@mail, 'i am signed') assert stripped_body = Mail::Gpg::InlineSignedMessage.strip_inline_signature(body) assert_equal "-----BEGIN PGP SIGNED MESSAGE-----\n\ni am signed\n-----END PGP SIGNED MESSAGE-----", stripped_body end should 'not change unsigned text' do assert stripped_body = Mail::Gpg::InlineSignedMessage.strip_inline_signature("foo\nbar\n") assert_equal "foo\nbar\n", stripped_body end end context "signed message" do should "verify body" do mail = Mail.new(@mail) mail.body = self.class.inline_sign(mail, mail.body.to_s) assert !mail.multipart? assert mail.signed? assert verified = mail.verify assert verified.signature_valid? assert sig = verified.signatures.first assert sig.to_s=~ /Joe/ assert sig.valid? end should "detect invalid sig" do mail = Mail.new(@mail) mail.body = self.class.inline_sign(mail, mail.body.to_s).gsub /i am/, 'i was' assert !mail.multipart? assert mail.signed? assert verified = mail.verify assert !verified.signature_valid? assert vr = verified.verify_result assert sig = verified.signatures.first assert sig.to_s=~ /Joe/ assert !sig.valid? end end context "message with signed attachment" do should "check attachment signature" do mail = Mail.new(@mail) mail.body = 'foobar' mail.part do |p| p.body = self.class.inline_sign(mail, 'sign me!') end assert mail.multipart? assert mail.signed? assert verified = mail.verify assert verified.signature_valid? assert vr = verified.parts.last.verify_result assert !verified.parts.first.signed? assert verified.parts.last.signed? assert Mail::Gpg.signed_inline?(verified.parts.last) assert_equal [vr], verified.verify_result assert sig = verified.signatures.first assert sig.to_s=~ /Joe/ assert sig.valid? end should "detect invalid sig" do mail = Mail.new(@mail) mail.body = 'foobar' mail.part do |p| p.body = self.class.inline_sign(mail, 'i am signed!').gsub /i am/, 'i was' end mail.part do |p| p.body = self.class.inline_sign(mail, 'i am signed!') end assert mail.multipart? assert mail.signed? assert verified = mail.verify assert !verified.signature_valid? assert vr = verified.verify_result assert_equal 2, vr.size invalid = verified.parts[1] assert !invalid.signature_valid? assert sig = invalid.verify_result.signatures.first assert sig.to_s=~ /Joe/ assert !sig.valid? valid = verified.parts[2] assert valid.signature_valid? assert sig = valid.verify_result.signatures.first assert sig.to_s=~ /Joe/ assert sig.valid? end end end def self.inline_sign(mail, plain, armor = true) GPGME::Crypto.new.clearsign(plain, password: 'abc', signers: mail.from, armor: armor).to_s end end mail-gpg-0.4.2/test/message_test.rb000066400000000000000000000176371353433656600172350ustar00rootroot00000000000000require 'test_helper' class MessageTest < MailGpgTestCase context "Mail::Message" do setup do set_passphrase('abc') (@mails = Mail::TestMailer.deliveries).clear @mail = Mail.new do to 'jane@foo.bar' from 'joe@foo.bar' subject 'test' body 'i am unencrypted' end end context "with gpg turned off" do setup do @mail.deliver end should "deliver unencrypted mail as usual" do assert_equal 1, @mails.size assert m = @mails.first assert_equal 'test', m.subject assert !m.encrypted? assert_equal 'i am unencrypted', m.body.to_s end should "raise encoding error" do assert_equal 1, @mails.size assert m = @mails.first assert_equal 'test', m.subject assert_raises(EncodingError){ m.decrypt(:password => 'abc') } end end context "with gpg signing only" do setup do @mail.gpg sign: true, password: 'abc' end context 'with multiple parts' do setup do p = Mail::Part.new do body 'and another part' end @mail.add_part p p = Mail::Part.new do body 'and a third part' end @mail.add_part p @mail.deliver @signed = @mails.first @verified = @signed.verify end should 'verify signature' do assert @verified.signature_valid? end should 'have original three parts' do assert_equal 3, @mail.parts.size assert_equal 3, @verified.parts.size assert_equal 'i am unencrypted', @verified.parts[0].body.to_s assert_equal 'and another part', @verified.parts[1].body.to_s assert_equal 'and a third part', @verified.parts[2].body.to_s end end context "" do setup do @mail.header['Auto-Submitted'] = 'foo' @mail.header['List-Help'] = 'https://lists.example.org/help/' @mail.header['List-Id'] = 'test.lists.example.org' @mail.header['List-Owner'] = 'test-owner@lists.example.org' @mail.header['List-Post'] = ' (Subscribers only)' @mail.header['List-Unsubscribe'] = 'bar' @mail.header['Date'] = 'Sun, 25 Dec 2016 16:56:52 -0500' @mail.header['OpenPGP'] = 'id=0x0123456789abcdef0123456789abcdefdeadbeef (present on keyservers); (Only encrypted and signed emails are accepted)' @mail.deliver end should 'keep custom header value' do assert_equal 'foo', @mails.first.header['Auto-Submitted'].value assert_equal 'https://lists.example.org/help/', @mails.first.header['List-Help'].value assert_equal 'test.lists.example.org', @mails.first.header['List-Id'].value assert_equal 'test-owner@lists.example.org', @mails.first.header['List-Owner'].value assert_equal ' (Subscribers only)', @mails.first.header['List-Post'].value assert_equal 'bar', @mails.first.header['List-Unsubscribe'].value assert_equal 'Sun, 25 Dec 2016 16:56:52 -0500', @mails.first.header['Date'].value assert_equal 'id=0x0123456789abcdef0123456789abcdefdeadbeef (present on keyservers); (Only encrypted and signed emails are accepted)', @mails.first.header['OpenPGP'].value end should "deliver signed mail" do assert_equal 1, @mails.size assert m = @mails.first assert_equal 'test', m.subject assert !m.encrypted? assert m.signed? assert m.multipart? assert sign_part = m.parts.last GPGME::Crypto.new.verify(sign_part.body.to_s, signed_text: m.parts.first.encoded) do |sig| assert sig.valid? end end should 'verify signed mail' do assert m = @mails.first assert verified = m.verify assert verified.signature_valid? assert !verified.multipart? assert_equal 'i am unencrypted', verified.body.to_s end should "fail signature on tampered body" do assert_equal 1, @mails.size assert m = @mails.first assert_equal 'test', m.subject assert !m.encrypted? assert m.signed? assert m.multipart? assert verified = m.verify assert verified.signature_valid? m.parts.first.body = 'replaced body' assert verified = m.verify assert !verified.signature_valid? end end end context 'with encryption and signing' do setup do @mail.gpg encrypt: true, sign: true, password: 'abc' @mail.deliver end should 'decrypt and check signature' do assert_equal 1, @mails.size assert m = @mails.first assert_equal 'test', m.subject assert m.multipart? assert m.encrypted? assert decrypted = m.decrypt(:password => 'abc', verify: true) assert_equal 'test', decrypted.subject assert decrypted == @mail assert_equal 'i am unencrypted', decrypted.body.to_s assert decrypted.signature_valid? assert_equal 1, decrypted.signatures.size end end context "with gpg turned on" do setup do @mail.gpg encrypt: true end context "with missing key" do setup do @mail.to = 'user@host.com' end should "raise encryption error" do assert_raises(Mail::Gpg::MissingKeysError){ @mail.deliver } end should "not raise error when encryption errors are turned off" do @mail.raise_encryption_errors = false @mail.deliver assert_equal 0, @mails.size end end context "" do setup do @mail.deliver end should "deliver encrypted mail" do assert_equal 1, @mails.size assert m = @mails.first assert_equal 'test', m.subject assert m.multipart? assert m.encrypted? assert enc_part = m.parts.last assert clear = GPGME::Crypto.new.decrypt(enc_part.body.to_s, password: 'abc').to_s assert m = Mail::Message.new(clear) assert !m.multipart? assert_equal 'i am unencrypted', m.body.to_s end should "decrypt" do assert_equal 1, @mails.size assert m = @mails.first assert_equal 'test', m.subject assert m.multipart? assert m.encrypted? assert decrypted = m.decrypt(:password => 'abc') assert_equal 'test', decrypted.subject assert decrypted == @mail assert_equal 'i am unencrypted', decrypted.body.to_s end should "raise bad passphrase on decrypt" do assert_equal 1, @mails.size assert m = @mails.first assert_equal 'test', m.subject # incorrect passphrase if @gpg_utils.preset_passphrases? set_passphrase('incorrect') # expected_exception = GPGME::Error::DecryptFailed # I dont know why. expected_exception = EOFError else expected_exception = GPGME::Error::BadPassphrase end assert_raises(expected_exception){ m.decrypt(:password => 'incorrect') } # no passphrase assert_raises(expected_exception){ m.decrypt } end end end should "respond to gpg method" do assert Mail::Message.new.respond_to?(:gpg) end context "gpg method" do should "set and unset delivery_handler" do m = Mail.new do gpg encrypt: true end assert m.gpg assert dh = m.delivery_handler assert_equal Mail::Gpg::DeliveryHandler, dh m.gpg false assert_nil m.delivery_handler assert_nil m.gpg end end end end mail-gpg-0.4.2/test/sign_part_test.rb000066400000000000000000000010251353433656600175570ustar00rootroot00000000000000require 'test_helper' require 'mail/gpg/sign_part' class SignPartTest < MailGpgTestCase context 'SignPart' do setup do set_passphrase('abc') @mail = Mail.new do to 'jane@foo.bar' from 'joe@foo.bar' subject 'test' body 'i am unsigned' end end should 'roundtrip successfully' do set_passphrase('abc') signature_part = Mail::Gpg::SignPart.new(@mail, password: 'abc') assert Mail::Gpg::SignPart.signature_valid?(@mail, signature_part) end end end mail-gpg-0.4.2/test/test_helper.rb000066400000000000000000000073061353433656600170600ustar00rootroot00000000000000require 'open3' require 'test/unit' require 'shoulda/context' require 'mail-gpg' require 'action_mailer' require 'securerandom' require 'byebug' Mail.defaults do delivery_method :test end ActionMailer::Base.delivery_method = :test class MailGpgTestCase < Test::Unit::TestCase def setup @gpg_utils = GPGTestUtils.new(ENV['GPG_BIN']) @gpg_utils.setup end def set_passphrase(*args) @gpg_utils.set_passphrase(*args) end end class GPGTestUtils attr_reader :gpg_engine def initialize(gpg_bin = nil) @home = File.join File.dirname(__FILE__), 'gpghome' @gpg_bin = gpg_bin ENV['GPG_AGENT_INFO'] = '' # disable gpg agent ENV['GNUPGHOME'] = @home if @gpg_bin GPGME::Engine.set_info(GPGME::PROTOCOL_OpenPGP, @gpg_bin, @home) else GPGME::Engine.home_dir = @home end @gpg_engine = GPGME::Engine.info.find {|e| e.protocol == GPGME::PROTOCOL_OpenPGP } @gpg_bin ||= @gpg_engine.file_name if Gem::Version.new(@gpg_engine.version) >= Gem::Version.new("2.1.0") @preset_passphrases = true else @preset_passphrases = false end end def preset_passphrases? !!@preset_passphrases end def setup gen_keys unless File.directory? @home if @preset_passphrases libexecdir = `gpgconf --list-dir`.lines.grep(/^libexecdir:/).first.split(':').last.strip @gpp_bin = File.join(libexecdir, 'gpg-preset-passphrase') @keygrip_jane = get_keygrip('jane@foo.bar') @keygrip_joe = get_keygrip('joe@foo.bar') end end def gen_keys puts "setting up keydir #{@home}" FileUtils.mkdir_p @home (File.open(File.join(@home, "gpg-agent.conf"), "wb") << "allow-preset-passphrase\nbatch\n").close GPGME::Ctx.new do |gpg| gpg.generate_key <<-END 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 END gpg.generate_key <<-END Key-Type: DSA Key-Length: 1024 Subkey-Type: ELG-E Subkey-Length: 1024 Name-Real: Jane Doe Name-Comment: with stupid passphrase Name-Email: jane@foo.bar Expire-Date: 0 Passphrase: abc END end end # Put passphrase into gpg-agent (required with GnuPG v2). def set_passphrase(passphrase) if preset_passphrases? ensure_gpg_agent call_gpp(@keygrip_jane, passphrase) call_gpp(@keygrip_joe, passphrase) end end private def get_keygrip(uid) output = `#{@gpg_bin} --list-secret-keys --with-keygrip --with-colons #{uid} 2>&1` if line = output.lines.grep(/^grp/).first line.split(':')[9] else puts "malformed key list output:\n#{output}" raise end end def ensure_gpg_agent # Make sure the gpg-agent is running (doesn't start automatically when # gpg-preset-passphrase is calling). output = `gpgconf --launch gpg-agent 2>&1` if ! output.empty? $stderr.puts "Launching gpg-agent returned: #{output}" end end def call_gpp(keygrip, passphrase) output, status = Open3.capture2e(@gpp_bin, '--homedir', ENV['GNUPGHOME'], '--preset', keygrip, {stdin_data: passphrase}) if ! output.empty? $stderr.puts "#{@gpp_bin} returned status #{status.exitstatus}: #{output}" end end end gpg_utils = GPGTestUtils.new(ENV['GPG_BIN']) v = Gem::Version.new(gpg_utils.gpg_engine.version) if v >= Gem::Version.new("2.1.0") puts "Running with GPG >= 2.1" elsif v >= Gem::Version.new("2.0.0") puts "Running with GPG 2.0, this isn't going well since we cannot set passphrases non-interactively" else puts "Running with GPG < 2.0" end gpg_utils.setup mail-gpg-0.4.2/test/version_part_test.rb000066400000000000000000000020311353433656600203020ustar00rootroot00000000000000require 'test_helper' require 'mail/gpg/version_part' class VersionPartTest < MailGpgTestCase context 'VersionPart' do should 'roundtrip successfully' do part = Mail::Gpg::VersionPart.new() assert Mail::Gpg::VersionPart.isVersionPart?(part) end should 'return false for non gpg mime type' do part = Mail::Gpg::VersionPart.new() part.content_type = 'text/plain' assert !Mail::Gpg::VersionPart.isVersionPart?(part) end should 'return false for empty body' do part = Mail::Gpg::VersionPart.new() part.body = nil assert !Mail::Gpg::VersionPart.isVersionPart?(part) end should 'return false for foul body' do part = Mail::Gpg::VersionPart.new() part.body = 'non gpg body' assert !Mail::Gpg::VersionPart.isVersionPart?(part) end should 'return true for body with extra content' do part = Mail::Gpg::VersionPart.new() part.body = "#{part.body} extra content" assert Mail::Gpg::VersionPart.isVersionPart?(part) end end end