File: crypto.rb

package info (click to toggle)
ruby-gpgme 2.0.23-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,920 kB
  • sloc: ruby: 3,129; ansic: 2,559; sh: 7; makefile: 5
file content (357 lines) | stat: -rw-r--r-- 12,319 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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
module GPGME

  ##
  # Different, independent methods providing the simplest possible API to
  # execute crypto operations via GPG. All methods accept as options the same
  # common options as {GPGME::Ctx.new}. Read the documentation for that class to
  # know how to customize things further (like output stuff in ASCII armored
  # format, for example).
  #
  # @example
  #   crypto = GPGME::Crypto.new :armor => true
  #   encrypted = crypto.encrypt 'Plain text'
  #
  class Crypto

    attr_reader :default_options

    def initialize(options = {})
      @default_options = options
    end

    ##
    # Encrypts an element
    #
    #  crypto.encrypt something, options
    #
    # Will return a {GPGME::Data} element which can then be read.
    #
    # Must have some key imported, look for {GPGME::Key.import} to know how
    # to import one, or the gpg documentation to know how to create one
    #
    # @param plain
    #  Must be something that can be converted into a {GPGME::Data} object, or
    #  a {GPGME::Data} object itself.
    #
    # @param [Hash] options
    #  The optional parameters are as follows:
    #   * +:recipients+ for which recipient do you want to encrypt this file. It
    #     will pick the first one available if none specified. Can be an array of
    #     identifiers or just one (a string).
    #   * +:symmetric+ if set to true, will ignore +:recipients+, and will perform
    #     a symmetric encryption. Must provide a password via the +:password+
    #     option.
    #   * +:always_trust+ if set to true specifies all the recipients to be
    #     trusted, thus not requiring confirmation.
    #   * +:sign+ if set to true, performs a combined sign and encrypt operation.
    #   * +:signers+ if +:sign+ specified to true, a list of additional possible
    #     signers. Must be an array of sign identifiers.
    #   * +:output+ if specified, it will write the output into it. It will be
    #     converted to a {GPGME::Data} object, so it could be a file for example.
    #   * Any other option accepted by {GPGME::Ctx.new}
    #
    # @return [GPGME::Data] a {GPGME::Data} object that can be read.
    #
    # @example returns a {GPGME::Data} that can be later encrypted
    #  encrypted = crypto.encrypt "Hello world!"
    #  encrypted.read # => Encrypted stuff
    #
    # @example to be decrypted by someone@example.com.
    #  crypto.encrypt "Hello", :recipients => "someone@example.com"
    #
    # @example If I didn't trust any of my keys by default
    #  crypto.encrypt "Hello" # => GPGME::Error::General
    #  crypto.encrypt "Hello", :always_trust => true # => Will work fine
    #
    # @example encrypted string that can be decrypted and/or *verified*
    #  crypto.encrypt "Hello", :sign => true
    #
    # @example multiple signers
    #  crypto.encrypt "Hello", :sign => true, :signers => "extra@example.com"
    #
    # @example writing to a file instead
    #  file = File.open("signed.sec","w+")
    #  crypto.encrypt "Hello", :output => file # output written to signed.sec
    #
    # @raise [GPGME::Error::General] when trying to encrypt with a key that is
    #   not trusted, and +:always_trust+ wasn't specified
    #
    def encrypt(plain, options = {})
      options = @default_options.merge options

      plain_data  = Data.new(plain)
      cipher_data = Data.new(options[:output])
      keys        = Key.find(:public, options[:recipients])
      keys        = nil if options[:symmetric]

      flags = 0
      flags |= GPGME::ENCRYPT_ALWAYS_TRUST if options[:always_trust]

      GPGME::Ctx.new(options) do |ctx|
        begin
          if options[:sign]
            if options[:signers]
              signers = Key.find(:public, options[:signers], :sign)
              ctx.add_signer(*signers)
            end
            ctx.encrypt_sign(keys, plain_data, cipher_data, flags)
          else
            ctx.encrypt(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

    ##
    # Decrypts a previously encrypted element
    #
    #   crypto.decrypt cipher, options, &block
    #
    # Must have the appropiate key to be able to decrypt, of course. Returns
    # a {GPGME::Data} object which can then be read.
    #
    # @param cipher
    #   Must be something that can be converted into a {GPGME::Data} object,
    #   or a {GPGME::Data} object itself. It is the element that will be
    #   decrypted.
    #
    # @param [Hash] options
    #   The optional parameters:
    #   * +:output+ if specified, it will write the output into it. It will
    #     me converted to a {GPGME::Data} object, so it can also be a file,
    #     for example.
    #   * If the file was encrypted with symmetric encryption, must provide
    #     a :password option.
    #   * Any other option accepted by {GPGME::Ctx.new}
    #
    # @param &block
    #   In the block all the signatures are yielded, so one could verify them.
    #   See examples.
    #
    # @return [GPGME::Data] a {GPGME::Data} that can be read.
    #
    # @example Simple decrypt
    #   crypto.decrypt encrypted_data
    #
    # @example symmetric encryption, or passwored key
    #   crypto.decrypt encrypted_data, :password => "gpgme"
    #
    # @example Output to file
    #   file = File.open("decrypted.txt", "w+")
    #   crypto.decrypt encrypted_data, :output => file
    #
    # @example Verifying signatures
    #   crypto.decrypt encrypted_data do |signature|
    #     raise "Signature could not be verified" unless signature.valid?
    #   end
    #
    # @raise [GPGME::Error::UnsupportedAlgorithm] when the cipher was encrypted
    #   using an algorithm that's not supported currently.
    #
    # @raise [GPGME::Error::WrongKeyUsage] TODO Don't know when
    #
    # @raise [GPGME::Error::DecryptFailed] when the cipher was encrypted
    #   for a key that's not available currently.
    def decrypt(cipher, options = {})
      options = @default_options.merge options

      plain_data   = Data.new(options[:output])
      cipher_data  = Data.new(cipher)

      GPGME::Ctx.new(options) do |ctx|
        begin
          ctx.decrypt_verify(cipher_data, plain_data)
        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

        verify_result = ctx.verify_result
        if verify_result && block_given?
          verify_result.signatures.each do |signature|
            yield signature
          end
        end

      end

      plain_data.seek(0)
      plain_data
    end

    ##
    # Creates a signature of a text
    #
    #   crypto.sign text, options
    #
    # Must have the appropiate key to be able to decrypt, of course. Returns
    # a {GPGME::Data} object which can then be read.
    #
    # @param text
    #   The object that will be signed. Must be something that can be converted
    #   to {GPGME::Data}.
    #
    # @param [Hash] options
    #  Optional parameters.
    #   * +:signer+ sign identifier to sign the text with. Will use the first
    #    key it finds if none specified.
    #   * +:output+ if specified, it will write the output into it. It will be
    #     converted to a {GPGME::Data} object, so it could be a file for example.
    #   * +:mode+ Desired type of signature. Options are:
    #    - +GPGME::SIG_MODE_NORMAL+ for a normal signature. The default one if
    #      not specified.
    #    - +GPGME::SIG_MODE_DETACH+ for a detached signature
    #    - +GPGME::SIG_MODE_CLEAR+ for a cleartext signature
    #   * Any other option accepted by {GPGME::Ctx.new}
    #
    # @return [GPGME::Data] a {GPGME::Data} that can be read.
    #
    # @example normal sign
    #   crypto.sign "Hi there"
    #
    # @example outputing to a file
    #   file = File.open("text.sign", "w+")
    #   crypto.sign "Hi there", :options => file
    #
    # @example doing a detached signature
    #   crypto.sign "Hi there", :mode => GPGME::SIG_MODE_DETACH
    #
    # @example specifying the signer
    #   crypto.sign "Hi there", :signer => "mrsimo@example.com"
    #
    # @raise [GPGME::Error::UnusableSecretKey] TODO don't know when
    def sign(text, options = {})
      options = @default_options.merge options

      plain  = Data.new(text)
      output = Data.new(options[:output])
      mode   = options[:mode] || GPGME::SIG_MODE_NORMAL

      GPGME::Ctx.new(options) do |ctx|
        if options[:signer]
          signers = Key.find(:secret, options[:signer], :sign)
          ctx.add_signer(*signers)
        end

        begin
          ctx.sign(plain, output, mode)
        rescue GPGME::Error::UnusableSecretKey => exc
          exc.keys = ctx.sign_result.invalid_signers
          raise exc
        end
      end

      output.seek(0)
      output
    end

    # Verifies a previously signed element
    #
    #   crypto.verify sig, options, &block
    #
    # Must have the proper keys available.
    #
    # @param sig
    #   The signature itself. Must be possible to convert into a {GPGME::Data}
    #   object, so can be a file.
    #
    # @param [Hash] options
    #   * +:signed_text+ if the sign is detached, then must be the plain text
    #     for which the signature was created.
    #   * +:output+ where to store the result of the signature. Will be
    #     converted to a {GPGME::Data} object.
    #   * Any other option accepted by {GPGME::Ctx.new}
    #
    # @param &block
    #   In the block all the signatures are yielded, so one could verify them.
    #   See examples.
    #
    # @return [GPGME::Data] unless the sign is detached, the {GPGME::Data}
    #   object with the plain text. If the sign is detached, will return nil.
    #
    # @example simple verification
    #   sign = crypto.sign("Hi there")
    #   data = crypto.verify(sign) { |signature| signature.valid? }
    #   data.read # => "Hi there"
    #
    # @example saving output to file
    #   sign = crypto.sign("Hi there")
    #   out  = File.open("test.asc", "w+")
    #   crypto.verify(sign, :output => out) {|signature| signature.valid?}
    #   out.read # => "Hi there"
    #
    # @example verifying a detached signature
    #   sign = crypto.detach_sign("Hi there")
    #   # Will fail
    #   crypto.verify(sign) { |signature| signature.valid? }
    #   # Will succeed
    #   crypto.verify(sign, :signed_text => "hi there") do |signature|
    #     signature.valid?
    #   end
    #
    def verify(sig, options = {})
      options = @default_options.merge options

      sig         = Data.new(sig)
      signed_text = Data.new(options[:signed_text])
      output      = Data.new(options[:output]) unless options[:signed_text]

      GPGME::Ctx.new(options) do |ctx|
        ctx.verify(sig, signed_text, output)
        ctx.verify_result.signatures.each do |signature|
          yield signature
        end
      end

      if output
        output.seek(0)
        output
      end
    end

    # Clearsigns an element
    #
    #   crypto.clearsign text, options
    #
    # Same functionality of {.sign} only doing clearsigns by default.
    #
    def clearsign(text, options = {})
      sign text, options.merge(:mode => GPGME::SIG_MODE_CLEAR)
    end

    # Creates a detached signature of an element
    #
    #   crypto.detach_sign text, options
    #
    # Same functionality of {.sign} only doing detached signs by default.
    #
    def detach_sign(text, options = {})
      sign text, options.merge(:mode => GPGME::SIG_MODE_DETACH)
    end

    ##
    # Allows calling of methods directly in the module without the need to
    # create a new instance.
    def self.method_missing(method, *args, &block)
      if GPGME::Crypto.instance_methods(false).include?(method)
        crypto = GPGME::Crypto.new
        crypto.send method, *args, &block
      else
        super
      end
    end

  end # module Crypto
end # module GPGME