klacointe-openpgp 0.0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,194 @@
1
+ module OpenPGP class Engine
2
+ ##
3
+ # GNU Privacy Guard (GnuPG) wrapper.
4
+ #
5
+ # @see http://www.gnupg.org/
6
+ class GnuPG < Engine
7
+ class Error < IOError; end
8
+
9
+ def self.available?
10
+ self.new.available?
11
+ end
12
+
13
+ OPTIONS = {
14
+ :batch => true,
15
+ :quiet => true,
16
+ :no_verbose => true,
17
+ :no_tty => true,
18
+ :no_permission_warning => true,
19
+ :no_random_seed_file => true,
20
+ }
21
+
22
+ attr_accessor :where
23
+ attr_accessor :options
24
+
25
+ def initialize(options = {})
26
+ @where = '/usr/bin/env gpg' # FIXME
27
+ @options = OPTIONS.merge!(options)
28
+ end
29
+
30
+ ##
31
+ # Determines if GnuPG is available.
32
+ def available?
33
+ !!version
34
+ end
35
+
36
+ ##
37
+ # Returns the GnuPG version number.
38
+ def version
39
+ exec(:version).readline =~ /^gpg \(GnuPG\) (.*)$/ ? $1 : nil
40
+ end
41
+
42
+ ##
43
+ # Generates a new OpenPGP keypair and stores it GnuPG's keyring.
44
+ def gen_key(info = {})
45
+ stdin, stdout, stderr = exec3(:gen_key) do |stdin, stdout, stderr|
46
+ stdin.puts "Key-Type: #{info[:key_type]}" if info[:key_type]
47
+ stdin.puts "Key-Length: #{info[:key_length]}" if info[:key_length]
48
+ stdin.puts "Subkey-Type: #{info[:subkey_type]}" if info[:subkey_type]
49
+ stdin.puts "Subkey-Length: #{info[:subkey_length]}" if info[:subkey_length]
50
+ stdin.puts "Name-Real: #{info[:name]}" if info[:name]
51
+ stdin.puts "Name-Comment: #{info[:comment]}" if info[:comment]
52
+ stdin.puts "Name-Email: #{info[:email]}" if info[:email]
53
+ stdin.puts "Expire-Date: #{info[:expire_date]}" if info[:expire_date]
54
+ stdin.puts "Passphrase: #{info[:passphrase]}" if info[:passphrase]
55
+ stdin.puts "%commit"
56
+ end
57
+ stderr.each_line do |line|
58
+ if (line = line.chomp) =~ /^gpg: key ([0-9A-F]+) marked as ultimately trusted/
59
+ return $1.to_i(16) # the key ID
60
+ end
61
+ end
62
+ return nil
63
+ end
64
+
65
+ ##
66
+ # Exports a specified key from the GnuPG keyring.
67
+ def export(key_id = nil, opts = {})
68
+ OpenPGP::Message.parse(exec([:export, *[key_id].flatten], opts ).read)
69
+ end
70
+
71
+ ##
72
+ ##
73
+ # Imports a specified keyfile into the GnuPG keyring.
74
+ def import()
75
+ # TODO
76
+ end
77
+
78
+ def delete_secret_and_public_key(key_id)
79
+ opts = {:batch => true}
80
+ OpenPGP::Message.parse(exec([:delete_secret_and_public_key, key_fingerprint(key_id)], opts ).read)
81
+ end
82
+
83
+ def key_fingerprint(key_id, opts = {})
84
+ message = exec([:fingerprint, *[key_id].flatten], opts ).read
85
+ if message =~ /Key fingerprint = (.*)\n/
86
+ return $1.delete(" ")
87
+ end
88
+ nil
89
+ end
90
+
91
+ ##
92
+ # Returns an array of key IDs/titles of the keys in the public keyring.
93
+ def list_keys()
94
+ # TODO
95
+ end
96
+
97
+ ##
98
+ # Encrypts the given plaintext to the specified recipients.
99
+ def encrypt(plaintext, options = {})
100
+ # TODO
101
+ end
102
+
103
+ ##
104
+ # Decrypts the given ciphertext using the specified key ID.
105
+ def decrypt(ciphertext, options = {})
106
+ # TODO
107
+ end
108
+
109
+ ##
110
+ # Makes an OpenPGP signature.
111
+ def sign()
112
+ # TODO
113
+ end
114
+
115
+ ##
116
+ # Makes an OpenPGP signature.
117
+ def sign_file(key_id, file, passphrase)
118
+ OpenPGP::Message.parse(exec([:sign, file],{ :local_user => key_id, :passphrase => passphrase}).read)
119
+ end
120
+
121
+ ##
122
+ # Makes a clear text OpenPGP signature.
123
+ def clearsign()
124
+ # TODO
125
+ end
126
+
127
+ ##
128
+ # Makes a detached OpenPGP signature.
129
+ def detach_sign()
130
+ # TODO
131
+ end
132
+
133
+ ##
134
+ # Verifies an OpenPGP signature.
135
+ def verify(key_id, file)
136
+ OpenPGP::Message.parse(exec([:verify, file],{ :local_user => key_id}).read)
137
+ end
138
+
139
+ ##
140
+ # Executes a GnuPG command, yielding the standard input and returning
141
+ # the standard output.
142
+ def exec(command, options = {}, &block) #:yields: stdin
143
+ exec4(command, options) do |pid, stdin, stdout, stderr|
144
+ block.call(stdin) if block_given?
145
+ stdin.close_write
146
+ pid, status = Process.waitpid2(pid)
147
+ raise Error, stderr.read.chomp if status.exitstatus.nonzero?
148
+ stdout
149
+ end
150
+ end
151
+
152
+ ##
153
+ # Executes a GnuPG command, yielding and returning the standard input,
154
+ # output and error.
155
+ def exec3(command, options = {}, &block) #:yields: stdin, stdout, stderr
156
+ exec4(command, options) do |pid, stdin, stdout, stderr|
157
+ block.call(stdin, stdout, stderr) if block_given?
158
+ stdin.close_write
159
+ pid, status = Process.waitpid2(pid)
160
+ raise Error, stderr.read.chomp if status.exitstatus.nonzero?
161
+ [stdin, stdout, stderr]
162
+ end
163
+ end
164
+
165
+ ##
166
+ # Executes a GnuPG command, yielding the process identifier as well as
167
+ # the standard input, output and error.
168
+ def exec4(command, options = {}, &block) #:yields: pid, stdin, stdout, stderr
169
+ require 'rubygems'
170
+ require 'open4'
171
+ p Open4.popen4(cmdline(command, options))
172
+ block.call(*Open4.popen4(cmdline(command, options)))
173
+ end
174
+
175
+ protected
176
+
177
+ ##
178
+ # Constructs the GnuPG command-line for use with +exec+.
179
+ def cmdline(command, options = {})
180
+ command = [command].flatten
181
+ cmdline = [where]
182
+ cmdline += @options.merge(options).map { |k, v| !v ? nil : "#{option(k)} #{v == true ? '' : v.to_s}".rstrip }.compact
183
+ cmdline << option(command.shift)
184
+ cmdline += command
185
+ cmdline.flatten.join(' ').strip
186
+ end
187
+
188
+ ##
189
+ # Translates Ruby symbols into GnuPG option arguments.
190
+ def option(option)
191
+ "--" << option.to_s.gsub('_', '-')
192
+ end
193
+ end
194
+ end end
@@ -0,0 +1,47 @@
1
+ module OpenPGP
2
+ class Engine
3
+ class OpenSSL < Engine
4
+ def self.load!(reload = false)
5
+ require 'openssl' unless defined?(::OpenSSL) || reload
6
+ end
7
+
8
+ def self.install!
9
+ load!
10
+ [Random, Digest].each { |mod| install_extensions! mod }
11
+ end
12
+
13
+ module Random #:nodoc:
14
+ def number(bits = 32, options = {})
15
+ ::OpenSSL::BN.rand(bits)
16
+ end
17
+
18
+ def prime(bits, options = {})
19
+ ::OpenSSL::BN.generate_prime(bits, options[:safe])
20
+ end
21
+
22
+ def bytes(count, &block)
23
+ ::OpenSSL::Random.random_bytes(count)
24
+ end
25
+ end
26
+
27
+ module Digest #:nodoc:
28
+ def size
29
+ ::OpenSSL::Digest.new(algorithm.to_s).digest_length
30
+ end
31
+
32
+ def hexdigest(data)
33
+ ::OpenSSL::Digest.hexdigest(algorithm.to_s, data).upcase
34
+ end
35
+
36
+ def digest(data)
37
+ ::OpenSSL::Digest.digest(algorithm.to_s, data)
38
+ end
39
+ end
40
+
41
+ module Cipher #:nodoc:
42
+ # TODO
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,46 @@
1
+ module OpenPGP
2
+ class Engine
3
+ autoload :GnuPG, 'openpgp/engine/gnupg'
4
+ autoload :OpenSSL, 'openpgp/engine/openssl'
5
+
6
+ def self.available?
7
+ begin
8
+ load!(true)
9
+ return true
10
+ rescue LoadError => e
11
+ return false
12
+ end
13
+ end
14
+
15
+ def self.load!(reload = false)
16
+ raise LoadError
17
+ end
18
+
19
+ def self.install!
20
+ load!
21
+ end
22
+
23
+ def self.use(&block)
24
+ load!
25
+ block.call(self)
26
+ end
27
+
28
+ protected
29
+
30
+ def self.install_extensions!(extension)
31
+ name = extension.name.split('::').last.to_sym
32
+
33
+ klass = OpenPGP.const_get(name)
34
+ extension.constants.each do |const|
35
+ klass.send(:remove_const, const)
36
+ klass.const_set(const, extension.const_get(const))
37
+ end
38
+
39
+ target = (class << klass; self; end)
40
+ extension.instance_methods(false).each do |method|
41
+ target.send(:remove_method, method)
42
+ target.send(:include, extension)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,106 @@
1
+ module OpenPGP
2
+ ##
3
+ # OpenPGP message.
4
+ #
5
+ # @see http://tools.ietf.org/html/rfc4880#section-4.1
6
+ # @see http://tools.ietf.org/html/rfc4880#section-11
7
+ # @see http://tools.ietf.org/html/rfc4880#section-11.3
8
+ class Message
9
+ include Enumerable
10
+
11
+ attr_accessor :packets
12
+
13
+ ##
14
+ # Creates an encrypted OpenPGP message.
15
+ def self.encrypt(data, options = {}, &block)
16
+ if options[:symmetric]
17
+ key = (options[:key] || S2K::DEFAULT.new(options[:passphrase]))
18
+ cipher = (options[:cipher] || Cipher::DEFAULT).new(key)
19
+
20
+ msg = self.new do |msg|
21
+ msg << Packet::SymmetricSessionKey.new(:algorithm => cipher.identifier, :s2k => key)
22
+ msg << Packet::EncryptedData.new do |packet|
23
+ plaintext = self.write do |msg|
24
+ case data
25
+ when Message then data.each { |packet| msg << packet }
26
+ when Packet then msg << data
27
+ else msg << Packet::LiteralData.new(:data => data)
28
+ end
29
+ end
30
+ packet.data = cipher.encrypt(plaintext)
31
+ end
32
+ end
33
+
34
+ block_given? ? block.call(msg) : msg
35
+ else
36
+ raise NotImplementedError # TODO
37
+ end
38
+ end
39
+
40
+ ##
41
+ def self.decrypt(data, options = {}, &block)
42
+ raise NotImplementedError # TODO
43
+ end
44
+
45
+ ##
46
+ # Parses an OpenPGP message.
47
+ #
48
+ # @see http://tools.ietf.org/html/rfc4880#section-4.1
49
+ # @see http://tools.ietf.org/html/rfc4880#section-4.2
50
+ def self.parse(data)
51
+ data = Buffer.new(data.to_str) if data.respond_to?(:to_str)
52
+
53
+ msg = self.new
54
+ until data.eof?
55
+ if packet = OpenPGP::Packet.parse(data)
56
+ msg << packet
57
+ else
58
+ raise "Invalid OpenPGP message data at position #{data.pos}"
59
+ end
60
+ end
61
+ msg
62
+ end
63
+
64
+ def self.write(io = nil, &block)
65
+ data = self.new(&block).to_s
66
+ io.respond_to?(:write) ? io.write(data) : data
67
+ end
68
+
69
+ def initialize(*packets, &block)
70
+ @packets = packets.flatten
71
+ block.call(self) if block_given?
72
+ end
73
+
74
+ def each(&block) # :yields: packet
75
+ packets.each(&block)
76
+ end
77
+
78
+ def to_a
79
+ packets.to_a
80
+ end
81
+
82
+ def <<(packet)
83
+ packets << packet
84
+ end
85
+
86
+ def empty?
87
+ packets.empty?
88
+ end
89
+
90
+ def size
91
+ inject(0) { |sum, packet| sum + packet.size }
92
+ end
93
+
94
+ def to_s
95
+ Buffer.write do |buffer|
96
+ packets.each do |packet|
97
+ if body = packet.body
98
+ buffer.write_byte(packet.class.tag | 0xC0)
99
+ buffer.write_byte(body.size)
100
+ buffer.write_bytes(body)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end