File: gpgme_helper.rb

package info (click to toggle)
ruby-mail-gpg 0.4.4-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 328 kB
  • sloc: ruby: 2,289; makefile: 6
file content (157 lines) | stat: -rw-r--r-- 5,373 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
require '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