openpgp 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,117 @@
1
+ require 'openssl'
2
+
3
+ module OpenPGP
4
+ ##
5
+ # OpenPGP cipher algorithm.
6
+ class Cipher
7
+ autoload :IDEA, 'openpgp/cipher/idea'
8
+ autoload :TripleDES, 'openpgp/cipher/3des'
9
+ autoload :CAST5, 'openpgp/cipher/cast5'
10
+ autoload :Blowfish, 'openpgp/cipher/blowfish'
11
+ autoload :AES128, 'openpgp/cipher/aes'
12
+ autoload :AES192, 'openpgp/cipher/aes'
13
+ autoload :AES256, 'openpgp/cipher/aes'
14
+ autoload :Twofish, 'openpgp/cipher/twofish'
15
+
16
+ DEFAULT = AES128
17
+
18
+ ##
19
+ # @see http://tools.ietf.org/html/rfc4880#section-9.2
20
+ def self.for(identifier)
21
+ case identifier
22
+ when Symbol then const_get(identifier.to_s.upcase)
23
+ when String then const_get(identifier.upcase.to_sym)
24
+ when 1 then IDEA
25
+ when 2 then TripleDES
26
+ when 3 then CAST5
27
+ when 4 then Blowfish
28
+ when 7 then AES128
29
+ when 8 then AES192
30
+ when 9 then AES256
31
+ when 10 then Twofish
32
+ end
33
+ end
34
+
35
+ attr_accessor :key, :options
36
+ attr_accessor :engine
37
+
38
+ def initialize(key, options = {})
39
+ @key = case key
40
+ when S2K then key.to_key(key_size)
41
+ else S2K::Simple.new(key).to_key(key_size)
42
+ end
43
+ @options = options
44
+ end
45
+
46
+ def self.to_i() identifier end
47
+
48
+ def self.identifier
49
+ const_get(:IDENTIFIER)
50
+ end
51
+
52
+ def identifier()
53
+ self.class.identifier
54
+ end
55
+
56
+ def key_size
57
+ @key_size ||= engine.key_len
58
+ end
59
+
60
+ def block_size
61
+ @block_size ||= engine.block_size
62
+ end
63
+
64
+ def engine
65
+ @engine ||= Engine::OpenSSL.use do
66
+ OpenSSL::Cipher.new(self.class.const_get(:ENGINE))
67
+ end
68
+ end
69
+
70
+ ##
71
+ # @see http://tools.ietf.org/html/rfc4880#section-13.9
72
+ def encrypt(plaintext)
73
+ ciphertext = String.new
74
+
75
+ engine.reset
76
+ engine.encrypt
77
+
78
+ # IV
79
+ rblock = Random.bytes(block_size)
80
+ iblock = encrypt_block("\0" * block_size)
81
+ block_size.times do |i|
82
+ ciphertext << (iblock[i] ^= rblock[i]).chr
83
+ end
84
+
85
+ # Checksum
86
+ iblock = encrypt_block(iblock)
87
+ ciphertext << (iblock[0] ^ rblock[block_size - 2]).chr
88
+ ciphertext << (iblock[1] ^ rblock[block_size - 1]).chr
89
+
90
+ # Resync
91
+ iblock = ciphertext[2..-1]
92
+
93
+ # Encrypt
94
+ plaintext.size.times do |n|
95
+ if (i = n % block_size) == 0
96
+ iblock = encrypt_block(iblock)
97
+ end
98
+ ciphertext << (iblock[i] ^= plaintext[n]).chr
99
+ end
100
+
101
+ ciphertext
102
+ end
103
+
104
+ def decrypt(ciphertext)
105
+ # TODO
106
+ engine.reset
107
+ engine.decrypt
108
+ end
109
+
110
+ def encrypt_block(block)
111
+ engine.encrypt
112
+ engine.key = @key
113
+ engine.iv = (@iv ||= "\0" * engine.iv_len)
114
+ engine.update(block) << engine.final
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,9 @@
1
+ module OpenPGP
2
+ class Cipher
3
+ ##
4
+ class TripleDES < Cipher
5
+ IDENTIFIER = 2
6
+ ENGINE = 'DES-EDE3'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ module OpenPGP
2
+ class Cipher
3
+ ##
4
+ class AES < Cipher
5
+ ENGINE = 'AES-128-ECB'
6
+ end
7
+
8
+ ##
9
+ class AES128 < AES
10
+ IDENTIFIER = 7
11
+ ENGINE = 'AES-128-ECB'
12
+ end
13
+
14
+ ##
15
+ class AES192 < AES
16
+ IDENTIFIER = 8
17
+ ENGINE = 'AES-192-ECB'
18
+ end
19
+
20
+ ##
21
+ class AES256 < AES
22
+ IDENTIFIER = 9
23
+ ENGINE = 'AES-256-ECB'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module OpenPGP
2
+ class Cipher
3
+ ##
4
+ class Blowfish < Cipher
5
+ IDENTIFIER = 4
6
+ ENGINE = 'BF-ECB'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module OpenPGP
2
+ class Cipher
3
+ ##
4
+ class CAST5 < Cipher
5
+ IDENTIFIER = 3
6
+ ENGINE = 'CAST5-ECB'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module OpenPGP
2
+ class Cipher
3
+ ##
4
+ class IDEA < Cipher
5
+ IDENTIFIER = 1
6
+ ENGINE = 'IDEA-ECB'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module OpenPGP
2
+ class Cipher
3
+ ##
4
+ class Twofish < Cipher
5
+ IDENTIFIER = 10
6
+ ENGINE = nil # N/A
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,583 @@
1
+ module OpenPGP module Client
2
+ ##
3
+ # GNU Privacy Guard (GnuPG) implementation.
4
+ #
5
+ # @see http://www.gnupg.org/
6
+ class GnuPG
7
+ VERSION = OpenPGP::VERSION
8
+
9
+ attr_accessor :options
10
+
11
+ def initialize(options = {})
12
+ @options = {
13
+ :homedir => ENV['GNUPGHOME'] || '~/.gnupg',
14
+ :version => false,
15
+ }
16
+ @options.merge!(options)
17
+
18
+ if options.has_key?(:options)
19
+ parse_options_file(options[:options])
20
+ end
21
+ end
22
+
23
+ # Commands not specific to the function
24
+
25
+ ##
26
+ # Prints the program version and licensing information.
27
+ def version
28
+ puts "gpg.rb (GnuPG compatible) #{VERSION}"
29
+ puts
30
+ puts "Home: #{options[:homedir]}"
31
+ puts "Supported algorithms:"
32
+ puts "Pubkey: " # TODO
33
+ puts "Cipher: #{cipher_algorithms.keys.map(&:to_s).sort.join(', ')}"
34
+ puts "Hash: #{digest_algorithms.join(', ')}"
35
+ puts "Compression: #{compress_algorithms.keys.map(&:to_s).sort.join(', ')}"
36
+ end
37
+
38
+ ##
39
+ # Prints a usage message summarizing the most useful command-line options.
40
+ def help() end
41
+
42
+ ##
43
+ # Prints warranty information.
44
+ def warranty
45
+ raise NotImplementedError
46
+ end
47
+
48
+ ##
49
+ # Prints a list of all available options and commands.
50
+ def dump_options
51
+ self.class.public_instance_methods(false).each do |command|
52
+ if command =~ /^[\w\d_]+$/
53
+ puts "--#{command.to_s.gsub('_', '-')}"
54
+ end
55
+ end
56
+ # TODO: list available options, too.
57
+ end
58
+
59
+ # Commands to select the type of operation
60
+
61
+ ##
62
+ # Makes a signature.
63
+ def sign
64
+ raise NotImplementedError # TODO
65
+ end
66
+
67
+ ##
68
+ # Makes a clear text signature.
69
+ def clearsign
70
+ raise NotImplementedError # TODO
71
+ end
72
+
73
+ ##
74
+ # Makes a detached signature.
75
+ def detach_sign
76
+ raise NotImplementedError # TODO
77
+ end
78
+
79
+ ##
80
+ # Encrypts data.
81
+ def encrypt
82
+ raise NotImplementedError # TODO
83
+ end
84
+
85
+ ##
86
+ # Encrypts with a symmetric cipher using a passphrase.
87
+ def symmetric(file)
88
+ print OpenPGP.encrypt(File.read(file), {
89
+ :symmetric => true,
90
+ :passphrase => read_passphrase,
91
+ :cipher => cipher_algorithm,
92
+ :digest => digest_algorithm,
93
+ :compress => compress_algorithm,
94
+ })
95
+ end
96
+
97
+ ##
98
+ # Stores only (make a simple RFC1991 literal data packet).
99
+ def store(file)
100
+ Message.write(stdout) do |msg|
101
+ msg << Packet::LiteralData.new({
102
+ :format => :b,
103
+ :filename => File.basename(file),
104
+ :timestamp => File.mtime(file),
105
+ :data => File.read(file),
106
+ })
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Decrypts data.
112
+ def decrypt(file)
113
+ raise NotImplementedError # TODO
114
+ end
115
+
116
+ ##
117
+ # Verifies data.
118
+ def verify(file)
119
+ raise NotImplementedError # TODO
120
+ end
121
+
122
+ ##
123
+ # Identical to --multifile --verify.
124
+ def verify_files(*files)
125
+ options[:multifile] = true
126
+ files.each { |file| verify(file) }
127
+ end
128
+
129
+ ##
130
+ # Identical to --multifile --encrypt.
131
+ def encrypt_files(*files)
132
+ options[:multifile] = true
133
+ files.each { |file| encrypt(file) }
134
+ end
135
+
136
+ ##
137
+ # Identical to --multifile --decrypt.
138
+ def decrypt_files(*files)
139
+ options[:multifile] = true
140
+ files.each { |file| decrypt(file) }
141
+ end
142
+
143
+ ##
144
+ # Lists keys from the public keyrings.
145
+ def list_keys(*keys)
146
+ list_public_keys(*keys)
147
+ end
148
+
149
+ ##
150
+ # Lists keys from the public keyrings.
151
+ def list_public_keys(*keys)
152
+ public_keyrings.each do |keyring_filename, keyring|
153
+ puts (keyring_filename = File.expand_path(keyring_filename))
154
+ print '-' * keyring_filename.size
155
+
156
+ keyring.each do |packet|
157
+ case packet
158
+ when Packet::PublicSubkey
159
+ print_key_listing(packet, :sub)
160
+ when Packet::PublicKey
161
+ print_key_listing(packet, :pub)
162
+ when Packet::UserID
163
+ print_uid_listing(packet)
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ ##
170
+ # Lists keys from the secret keyrings.
171
+ def list_secret_keys(*keys)
172
+ secret_keyrings.each do |keyring_filename, keyring|
173
+ puts (keyring_filename = File.expand_path(keyring_filename))
174
+ print '-' * keyring_filename.size
175
+
176
+ keyring.each do |packet|
177
+ case packet
178
+ when Packet::SecretSubkey
179
+ print_key_listing(packet, :ssb)
180
+ when Packet::SecretKey
181
+ print_key_listing(packet, :sec)
182
+ when Packet::UserID
183
+ print_uid_listing(packet)
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ ##
190
+ # Same as +list_keys+, but the signatures are listed too.
191
+ def list_sigs
192
+ raise NotImplementedError # TODO
193
+ end
194
+
195
+ ##
196
+ # Same as +list_sigs+, but the signatures are verified.
197
+ def check_sigs
198
+ raise NotImplementedError # TODO
199
+ end
200
+
201
+ ##
202
+ # Lists all keys (or the specified ones) along with their fingerprints.
203
+ def fingerprint(*keys)
204
+ options[:fingerprint] = true
205
+ list_keys(*keys)
206
+ end
207
+
208
+ ##
209
+ # Lists only the sequence of packets.
210
+ def list_packets
211
+ raise NotImplementedError # TODO
212
+ end
213
+
214
+ ##
215
+ # Presents a menu to work with a smartcard.
216
+ def card_edit
217
+ raise NotImplementedError # TODO
218
+ end
219
+
220
+ ##
221
+ # Shows the content of the smart card.
222
+ def card_status
223
+ raise NotImplementedError # TODO
224
+ end
225
+
226
+ ##
227
+ # Presents a menu to allow changing the PIN of a smartcard.
228
+ def change_pin
229
+ raise NotImplementedError # TODO
230
+ end
231
+
232
+ ##
233
+ # Removes key from the public keyring.
234
+ def delete_key(name)
235
+ raise NotImplementedError # TODO
236
+ end
237
+
238
+ ##
239
+ # Removes key from the secret and public keyring.
240
+ def delete_secret_key(name)
241
+ raise NotImplementedError # TODO
242
+ end
243
+
244
+ ##
245
+ # Removes key from the secret and public keyring. If a secret key exists, it will be removed first.
246
+ def delete_secret_and_public_key(name)
247
+ raise NotImplementedError # TODO
248
+ end
249
+
250
+ ##
251
+ # Exports keys from the public keyring.
252
+ def export(*keys)
253
+ raise NotImplementedError # TODO
254
+ end
255
+
256
+ ##
257
+ # Sends keys to a keyserver.
258
+ def send_keys(*keys)
259
+ raise NotImplementedError # TODO
260
+ end
261
+
262
+ ##
263
+ # Exports the secret keys.
264
+ def export_secret_keys
265
+ raise NotImplementedError # TODO
266
+ end
267
+
268
+ ##
269
+ # Exports the secret subkeys.
270
+ def export_secret_subkeys
271
+ raise NotImplementedError # TODO
272
+ end
273
+
274
+ ##
275
+ # Imports/merges keys, adding the given keys to the keyring.
276
+ def import(*keys)
277
+ raise NotImplementedError # TODO
278
+ end
279
+
280
+ ##
281
+ # Alias for +import+.
282
+ def fast_import(*keys)
283
+ import(*keys)
284
+ end
285
+
286
+ ##
287
+ # Imports the keys with the given key IDs from a keyserver.
288
+ def recv_keys(*keys)
289
+ raise NotImplementedError # TODO
290
+ end
291
+
292
+ ##
293
+ # Requests updates from a keyserver for keys that already exist on the local keyring.
294
+ def refresh_keys(*keys)
295
+ raise NotImplementedError # TODO
296
+ end
297
+
298
+ ##
299
+ # Searches the keyserver for the given names.
300
+ def search_keys(*names)
301
+ raise NotImplementedError # TODO
302
+ end
303
+
304
+ ##
305
+ # Retrieves keys located at the specified URIs.
306
+ def fetch_keys(*uris)
307
+ require 'open-uri'
308
+ raise NotImplementedError # TODO
309
+ end
310
+
311
+ ##
312
+ # Does trust database maintenance.
313
+ def update_trustdb
314
+ raise NotImplementedError # TODO
315
+ end
316
+
317
+ ##
318
+ # Does trust database maintenance without user interaction.
319
+ def check_trustdb
320
+ raise NotImplementedError # TODO
321
+ end
322
+
323
+ ##
324
+ # Sends the ownertrust values to stdout.
325
+ def export_ownertrust
326
+ raise NotImplementedError # TODO
327
+ end
328
+
329
+ ##
330
+ # Updates the trustdb with the ownertrust values stored in +files+ or stdin.
331
+ def import_ownertrust(*files)
332
+ raise NotImplementedError # TODO
333
+ end
334
+
335
+ ##
336
+ # Creates signature caches in the keyring.
337
+ def rebuild_keydb_caches
338
+ raise NotImplementedError # TODO
339
+ end
340
+
341
+ ##
342
+ # Prints message digest of algorithm +algo+ for all given files or stdin.
343
+ def print_md(algo, *files)
344
+ unless digest_algorithms.include?(algorithm = algo.to_s.upcase.to_sym)
345
+ abort "gpg: invalid hash algorithm `#{algo}'"
346
+ else
347
+ digest = Digest.for(algorithm)
348
+ end
349
+
350
+ files.each do |file|
351
+ puts (prefix = "#{file}: ") << format_fingerprint(digest.file(file).hexdigest, prefix.size)
352
+ end
353
+ end
354
+
355
+ ##
356
+ # Prints message digests of all available algorithms for all given files or stdin.
357
+ def print_mds(*files)
358
+ files.each do |file|
359
+ digest_algorithms.each do |algorithm|
360
+ algorithm = :RMD160 if algorithm == :RIPEMD160
361
+ digest = Digest.for(algorithm)
362
+
363
+ puts (prefix = "#{file}: #{algorithm.to_s.rjust(6)} = ") << format_fingerprint(digest.file(file).hexdigest, prefix.size)
364
+ end
365
+ end
366
+ end
367
+
368
+ ##
369
+ # Emits +count+ random bytes of the given quality level.
370
+ def gen_random(level = 0, count = nil)
371
+ wrong_args "--gen-random 0|1|2 [count]" unless (0..2).include?(level)
372
+
373
+ require 'openssl'
374
+ count = count.to_i if count
375
+ endless = count.nil?
376
+ while endless || count > 0
377
+ n = !endless && count < 99 ? count : 99
378
+ p = Random.bytes(n)
379
+ print options[:armor] ? [p].pack('m').delete("\n") : p
380
+ count -= n unless endless
381
+ end
382
+ puts if options[:armor]
383
+ end
384
+
385
+ ##
386
+ # Generates a prime number.
387
+ def gen_prime(mode, bits, qbits = nil)
388
+ case mode.to_i
389
+ when 1..4
390
+ raise NotImplementedError # TODO
391
+ else
392
+ wrong_args "--gen-prime mode bits [qbits]"
393
+ end
394
+ end
395
+
396
+ ##
397
+ # Packs an arbitrary input into an OpenPGP ASCII armor.
398
+ def enarmor(file)
399
+ text = OpenPGP.enarmor(File.read(file), :armored_file, :comment => 'Use "gpg --dearmor" for unpacking', :line_length => 64)
400
+ puts text # FIXME
401
+ end
402
+
403
+ ##
404
+ # Unpacks an arbitrary input from an OpenPGP ASCII armor.
405
+ def dearmor(file)
406
+ data = OpenPGP.dearmor(File.read(file))
407
+ puts data # FIXME
408
+ end
409
+
410
+ # Commands for key management
411
+
412
+ ##
413
+ # Generates a new key pair.
414
+ def gen_key
415
+ raise NotImplementedError # TODO
416
+ end
417
+
418
+ ##
419
+ # Generates a revocation certificate for the complete key.
420
+ def gen_revoke(name)
421
+ raise NotImplementedError # TODO
422
+ end
423
+
424
+ ##
425
+ # Generates a designated revocation certificate for a key.
426
+ def desig_revoke(name)
427
+ raise NotImplementedError # TODO
428
+ end
429
+
430
+ ##
431
+ # Present a menu which enables you to do most of the key management related tasks.
432
+ def edit_key(key)
433
+ raise NotImplementedError # TODO
434
+ end
435
+
436
+ ##
437
+ # Signs a public key with your secret key.
438
+ def sign_key(name)
439
+ raise NotImplementedError # TODO
440
+ end
441
+
442
+ ##
443
+ # Signs a public key with your secret key but marks it as non-exportable.
444
+ def lsign_key(name)
445
+ raise NotImplementedError # TODO
446
+ end
447
+
448
+ protected
449
+
450
+ def stdin() $stdin end
451
+ def stdout() $stdout end
452
+ def stderr() $stdout end
453
+
454
+ def read_passphrase
455
+ if options[:passphrase]
456
+ options[:passphrase]
457
+ else
458
+ # TODO
459
+ end
460
+ end
461
+
462
+ def public_keyrings
463
+ {public_keyring_file => keyring(public_keyring_file)} # FIXME
464
+ end
465
+
466
+ def secret_keyrings
467
+ {secret_keyring_file => keyring(secret_keyring_file)} # FIXME
468
+ end
469
+
470
+ def keyring(file)
471
+ OpenPGP::Message.parse(File.read(File.expand_path(file)))
472
+ end
473
+
474
+ def public_keyring_file
475
+ File.join(options[:homedir], 'pubring.gpg')
476
+ end
477
+
478
+ def secret_keyring_file
479
+ File.join(options[:homedir], 'secring.gpg')
480
+ end
481
+
482
+ def trustdb_file
483
+ File.join(options[:homedir], 'trustdb.gpg')
484
+ end
485
+
486
+ def print_key_listing(packet, type)
487
+ puts unless (is_sub_key = [:sub, :ssb].include?(type))
488
+ puts "#{type} #{format_keyspec(packet)} #{Time.at(packet.timestamp).strftime('%Y-%m-%d')}"
489
+ if options[:fingerprint] && !is_sub_key
490
+ puts " Key fingerprint = #{format_fingerprint(packet.fingerprint)}"
491
+ end
492
+ end
493
+
494
+ def print_uid_listing(packet)
495
+ puts "uid" + (' ' * 18) + packet.to_s
496
+ end
497
+
498
+ def format_keyspec(key)
499
+ "____?/#{key.key_id}" # TODO
500
+ end
501
+
502
+ def format_fingerprint(input, column = 0)
503
+ group_size = case input.size
504
+ when 32 then 2 # MD5
505
+ when 40 then 4 # SHA1, RIPEMD160
506
+ else 8 # SHA2*
507
+ end
508
+
509
+ lines, line, pos = [], '', 0
510
+ input.upcase!
511
+ input.each_byte do |c|
512
+ line << c
513
+ if (pos += 1) % group_size == 0
514
+ if (line.size + column) >= (80 - group_size)
515
+ lines << line
516
+ line, pos = '', 0
517
+ else
518
+ line << ' '
519
+ end
520
+ end
521
+ end
522
+ lines << line.strip unless line.empty?
523
+
524
+ output = lines.join($/ + (' ' * column))
525
+ output = output.insert(output.size / 2, ' ') if group_size < 8
526
+ return output
527
+ end
528
+
529
+ def parse_options_file(file)
530
+ # TODO
531
+ end
532
+
533
+ def cipher_algorithm
534
+ unless options[:cipher_algo]
535
+ Cipher::CAST5 # this is the default cipher
536
+ else
537
+ algorithm = options[:cipher_algo].to_s.upcase.to_sym
538
+ unless cipher_algorithms.has_key?(algorithm)
539
+ abort "gpg: selected cipher algorithm is invalid"
540
+ end
541
+ cipher_algorithms[algorithm]
542
+ end
543
+ end
544
+
545
+ def digest_algorithm
546
+ options[:digest_algo]
547
+ end
548
+
549
+ def compress_algorithm
550
+ options[:compress_algo]
551
+ end
552
+
553
+ def cipher_algorithms
554
+ {
555
+ :"3DES" => Cipher::TripleDES,
556
+ :CAST5 => Cipher::CAST5,
557
+ :BLOWFISH => Cipher::Blowfish,
558
+ :AES => Cipher::AES128,
559
+ :AES192 => Cipher::AES192,
560
+ :AES256 => Cipher::AES256,
561
+ #:TWOFISH => Cipher::Twofish, # N/A
562
+ }
563
+ end
564
+
565
+ def digest_algorithms
566
+ [:MD5, :SHA1, :RIPEMD160, :SHA256, :SHA384, :SHA512]
567
+ end
568
+
569
+ def compress_algorithms
570
+ {
571
+ :none => nil,
572
+ :ZIP => nil, # TODO
573
+ :ZLIB => nil, # TODO
574
+ :BZIP2 => nil, # TODO
575
+ }
576
+ end
577
+
578
+ def wrong_args(usage)
579
+ abort "usage: gpg.rb [options] #{usage}"
580
+ end
581
+ end
582
+
583
+ end end