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,60 @@
1
+ module OpenPGP
2
+ ##
3
+ # OpenPGP message digest algorithm.
4
+ #
5
+ # @see http://tools.ietf.org/html/rfc4880#section-9.4
6
+ class Digest
7
+ autoload :MD5, 'openpgp/digest/md5'
8
+ autoload :SHA1, 'openpgp/digest/sha1'
9
+ autoload :RIPEMD160, 'openpgp/digest/rmd160'
10
+ autoload :SHA256, 'openpgp/digest/sha2'
11
+ autoload :SHA384, 'openpgp/digest/sha2'
12
+ autoload :SHA512, 'openpgp/digest/sha2'
13
+ autoload :SHA224, 'openpgp/digest/sha2'
14
+
15
+ DEFAULT = SHA1
16
+
17
+ def self.for(identifier)
18
+ case identifier
19
+ when Symbol then const_get(identifier.to_s.upcase)
20
+ when String then const_get(identifier.upcase.to_sym)
21
+ when 1 then const_get(:MD5)
22
+ when 2 then const_get(:SHA1)
23
+ when 3 then const_get(:RIPEMD160)
24
+ when 8 then const_get(:SHA256)
25
+ when 9 then const_get(:SHA384)
26
+ when 10 then const_get(:SHA512)
27
+ when 11 then const_get(:SHA224)
28
+ end
29
+ end
30
+
31
+ def self.to_i() identifier end
32
+
33
+ def self.identifier
34
+ const_get(:IDENTIFIER)
35
+ end
36
+
37
+ def self.algorithm
38
+ name.split('::').last.to_sym unless self == Digest
39
+ end
40
+
41
+ def self.hexsize
42
+ size * 2
43
+ end
44
+
45
+ def self.size
46
+ require 'digest' unless defined?(::Digest)
47
+ ::Digest.const_get(algorithm).new.digest_length
48
+ end
49
+
50
+ def self.hexdigest(data)
51
+ require 'digest' unless defined?(::Digest)
52
+ ::Digest.const_get(algorithm).hexdigest(data).upcase
53
+ end
54
+
55
+ def self.digest(data)
56
+ require 'digest' unless defined?(::Digest)
57
+ ::Digest.const_get(algorithm).digest(data)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ module OpenPGP
2
+ class Digest
3
+ class MD5 < Digest
4
+ IDENTIFIER = 1
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module OpenPGP
2
+ class Digest
3
+ class RIPEMD160 < Digest
4
+ IDENTIFIER = 3
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module OpenPGP
2
+ class Digest
3
+ class SHA1 < Digest
4
+ IDENTIFIER = 2
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module OpenPGP
2
+ class Digest
3
+ class SHA224 < Digest
4
+ IDENTIFIER = 11
5
+ end
6
+
7
+ class SHA256 < Digest
8
+ IDENTIFIER = 8
9
+ end
10
+
11
+ class SHA384 < Digest
12
+ IDENTIFIER = 9
13
+ end
14
+
15
+ class SHA512 < Digest
16
+ IDENTIFIER = 10
17
+ end
18
+ end
19
+ 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
@@ -1,11 +1,15 @@
1
- module OpenPGP
1
+ module OpenPGP class Engine
2
2
  ##
3
3
  # GNU Privacy Guard (GnuPG) wrapper.
4
4
  #
5
5
  # @see http://www.gnupg.org/
6
- class GnuPG
6
+ class GnuPG < Engine
7
7
  class Error < IOError; end
8
8
 
9
+ def self.available?
10
+ self.new.available?
11
+ end
12
+
9
13
  OPTIONS = {
10
14
  :batch => true,
11
15
  :quiet => true,
@@ -60,16 +64,30 @@ module OpenPGP
60
64
 
61
65
  ##
62
66
  # Exports a specified key from the GnuPG keyring.
63
- def export(key_id = nil)
64
- OpenPGP::Message.parse(exec([:export, *[key_id].flatten]).read)
67
+ def export(key_id = nil, opts = {})
68
+ OpenPGP::Message.parse(exec([:export, *[key_id].flatten], opts ).read)
65
69
  end
66
70
 
71
+ ##
67
72
  ##
68
73
  # Imports a specified keyfile into the GnuPG keyring.
69
74
  def import()
70
75
  # TODO
71
76
  end
72
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
+
73
91
  ##
74
92
  # Returns an array of key IDs/titles of the keys in the public keyring.
75
93
  def list_keys()
@@ -94,6 +112,12 @@ module OpenPGP
94
112
  # TODO
95
113
  end
96
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
+
97
121
  ##
98
122
  # Makes a clear text OpenPGP signature.
99
123
  def clearsign()
@@ -108,8 +132,8 @@ module OpenPGP
108
132
 
109
133
  ##
110
134
  # Verifies an OpenPGP signature.
111
- def verify()
112
- # TODO
135
+ def verify(key_id, file)
136
+ OpenPGP::Message.parse(exec([:verify, file],{ :local_user => key_id}).read)
113
137
  end
114
138
 
115
139
  ##
@@ -166,4 +190,4 @@ module OpenPGP
166
190
  "--" << option.to_s.gsub('_', '-')
167
191
  end
168
192
  end
169
- end
193
+ 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
@@ -6,16 +6,49 @@ module OpenPGP
6
6
  # @see http://tools.ietf.org/html/rfc4880#section-11
7
7
  # @see http://tools.ietf.org/html/rfc4880#section-11.3
8
8
  class Message
9
+ include Enumerable
10
+
9
11
  attr_accessor :packets
10
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
+
11
45
  ##
12
46
  # Parses an OpenPGP message.
13
47
  #
14
48
  # @see http://tools.ietf.org/html/rfc4880#section-4.1
15
49
  # @see http://tools.ietf.org/html/rfc4880#section-4.2
16
50
  def self.parse(data)
17
- require 'stringio'
18
- data = StringIO.new(data.to_str) if data.respond_to?(:to_str)
51
+ data = Buffer.new(data.to_str) if data.respond_to?(:to_str)
19
52
 
20
53
  msg = self.new
21
54
  until data.eof?
@@ -28,8 +61,14 @@ module OpenPGP
28
61
  msg
29
62
  end
30
63
 
31
- def initialize(packets = [])
32
- @packets = packets
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?
33
72
  end
34
73
 
35
74
  def each(&block) # :yields: packet
@@ -51,5 +90,17 @@ module OpenPGP
51
90
  def size
52
91
  inject(0) { |sum, packet| sum + packet.size }
53
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
54
105
  end
55
106
  end
@@ -5,17 +5,26 @@ module OpenPGP
5
5
  # @see http://tools.ietf.org/html/rfc4880#section-4.1
6
6
  # @see http://tools.ietf.org/html/rfc4880#section-4.3
7
7
  class Packet
8
- attr_accessor :tag
9
- attr_accessor :size
10
- attr_accessor :data
8
+ attr_accessor :tag, :size, :data
9
+
10
+ ##
11
+ # Returns the implementation class for a packet tag.
12
+ def self.for(tag)
13
+ @@tags[tag.to_i] || self
14
+ end
15
+
16
+ ##
17
+ # Returns the packet tag for this class.
18
+ def self.tag
19
+ @@tags.index(self)
20
+ end
11
21
 
12
22
  ##
13
23
  # Parses an OpenPGP packet.
14
24
  #
15
25
  # @see http://tools.ietf.org/html/rfc4880#section-4.2
16
26
  def self.parse(data)
17
- require 'stringio'
18
- data = StringIO.new(data.to_str) if data.respond_to?(:to_str)
27
+ data = Buffer.new(data.to_str) if data.respond_to?(:to_str)
19
28
 
20
29
  unless data.eof?
21
30
  new = ((tag = data.getc) & 64).nonzero? # bit 6 indicates new packet format if set
@@ -43,7 +52,7 @@ module OpenPGP
43
52
  data_length = (data.getc << 24) | (data.getc << 16) | (data.getc << 8) | data.getc
44
53
  end
45
54
 
46
- Packet.for(tag).new(tag, data.read(data_length))
55
+ Packet.for(tag).parse_body(Buffer.new(data.read(data_length)), :tag => tag)
47
56
  end
48
57
 
49
58
  ##
@@ -67,23 +76,44 @@ module OpenPGP
67
76
  raise "Invalid OpenPGP packet length-type: expected 0..3 but got #{len}"
68
77
  end
69
78
 
70
- Packet.for(tag).new(tag, data_length ? data.read(data_length) : data.read)
79
+ Packet.for(tag).parse_body(Buffer.new(data_length ? data.read(data_length) : data.read), :tag => tag)
71
80
  end
72
81
 
73
- def self.for(tag)
74
- @@tags[tag.to_i] || self
82
+ ##
83
+ def self.parse_body(body, options = {})
84
+ self.new(options)
85
+ end
86
+
87
+ def initialize(options = {}, &block)
88
+ options.each { |k, v| send("#{k}=", v) }
89
+ block.call(self) if block_given?
75
90
  end
76
91
 
77
- def initialize(tag = nil, data = nil)
78
- @tag, @data, @size = tag, data, data ? data.size : 0
92
+ #def to_s() body end
93
+
94
+ def size() body.size end
95
+
96
+ def body
97
+ respond_to?(:write_body) ? Buffer.write { |buffer| write_body(buffer) } : ""
79
98
  end
80
99
 
81
100
  ##
82
101
  # OpenPGP Public-Key Encrypted Session Key packet (tag 1).
83
102
  #
84
103
  # @see http://tools.ietf.org/html/rfc4880#section-5.1
104
+ # @see http://tools.ietf.org/html/rfc4880#section-13.1
85
105
  class AsymmetricSessionKey < Packet
86
- # TODO
106
+ attr_accessor :version, :key_id, :algorithm
107
+
108
+ def self.parse_body(body, options = {})
109
+ case version = body.read_byte
110
+ when 3
111
+ self.new(:version => version, :key_id => body.read_number(8, 16), :algorithm => body.read_byte)
112
+ # TODO: read the encrypted session key.
113
+ else
114
+ raise "Invalid OpenPGP public-key ESK packet version: #{version}"
115
+ end
116
+ end
87
117
  end
88
118
 
89
119
  ##
@@ -91,7 +121,56 @@ module OpenPGP
91
121
  #
92
122
  # @see http://tools.ietf.org/html/rfc4880#section-5.2
93
123
  class Signature < Packet
94
- # TODO
124
+ attr_accessor :version, :type
125
+ attr_accessor :key_algorithm, :hash_algorithm
126
+ attr_accessor :key_id
127
+ attr_accessor :fields
128
+
129
+ def self.parse_body(body, options = {})
130
+ case version = body.read_byte
131
+ when 3 then self.new(:version => 3).send(:read_v3_signature, body)
132
+ when 4 then self.new(:version => 4).send(:read_v4_signature, body)
133
+ else raise "Invalid OpenPGP signature packet version: #{version}"
134
+ end
135
+ end
136
+
137
+ protected
138
+
139
+ ##
140
+ # @see http://tools.ietf.org/html/rfc4880#section-5.2.2
141
+ def read_v3_signature(body)
142
+ raise "Invalid OpenPGP signature packet V3 header" if body.read_byte != 5
143
+ @type, @timestamp, @key_id = body.read_byte, body.read_number(4), body.read_number(8, 16)
144
+ @key_algorithm, @hash_algorithm = body.read_byte, body.read_byte
145
+ body.read_bytes(2)
146
+ read_signature(body)
147
+ self
148
+ end
149
+
150
+ ##
151
+ # @see http://tools.ietf.org/html/rfc4880#section-5.2.3
152
+ def read_v4_signature(body)
153
+ @type = body.read_byte
154
+ @key_algorithm, @hash_algorithm = body.read_byte, body.read_byte
155
+ body.read_bytes(hashed_count = body.read_number(2))
156
+ body.read_bytes(unhashed_count = body.read_number(2))
157
+ body.read_bytes(2)
158
+ read_signature(body)
159
+ self
160
+ end
161
+
162
+ ##
163
+ # @see http://tools.ietf.org/html/rfc4880#section-5.2.2
164
+ def read_signature(body)
165
+ case key_algorithm
166
+ when Algorithm::Asymmetric::RSA
167
+ @fields = [body.read_mpi]
168
+ when Algorithm::Asymmetric::DSA
169
+ @fields = [body.read_mpi, body.read_mpi]
170
+ else
171
+ raise "Unknown OpenPGP signature packet public-key algorithm: #{key_algorithm}"
172
+ end
173
+ end
95
174
  end
96
175
 
97
176
  ##
@@ -99,7 +178,31 @@ module OpenPGP
99
178
  #
100
179
  # @see http://tools.ietf.org/html/rfc4880#section-5.3
101
180
  class SymmetricSessionKey < Packet
102
- # TODO
181
+ attr_accessor :version, :algorithm, :s2k
182
+
183
+ def self.parse_body(body, options = {})
184
+ case version = body.read_byte
185
+ when 4
186
+ self.new({:version => version, :algorithm => body.read_byte, :s2k => body.read_s2k}.merge(options))
187
+ else
188
+ raise "Invalid OpenPGP symmetric-key ESK packet version: #{version}"
189
+ end
190
+ end
191
+
192
+ def initialize(options = {}, &block)
193
+ defaults = {
194
+ :version => 4,
195
+ :algorithm => Cipher::DEFAULT.to_i,
196
+ :s2k => S2K::DEFAULT.new,
197
+ }
198
+ super(defaults.merge(options), &block)
199
+ end
200
+
201
+ def write_body(buffer)
202
+ buffer.write_byte(version)
203
+ buffer.write_byte(algorithm.to_i)
204
+ buffer.write_s2k(s2k)
205
+ end
103
206
  end
104
207
 
105
208
  ##
@@ -118,7 +221,53 @@ module OpenPGP
118
221
  # @see http://tools.ietf.org/html/rfc4880#section-11.1
119
222
  # @see http://tools.ietf.org/html/rfc4880#section-12
120
223
  class PublicKey < Packet
121
- # TODO
224
+ attr_accessor :size
225
+ attr_accessor :version, :timestamp, :algorithm
226
+ attr_accessor :key, :key_fields, :key_id, :fingerprint
227
+
228
+ #def parse(data) # FIXME
229
+ def self.parse_body(body, options = {})
230
+ case version = body.read_byte
231
+ when 2, 3
232
+ # TODO
233
+ when 4
234
+ packet = self.new(:version => version, :timestamp => body.read_timestamp, :algorithm => body.read_byte, :key => {}, :size => body.size)
235
+ packet.read_key_material(body)
236
+ packet
237
+ else
238
+ raise "Invalid OpenPGP public-key packet version: #{version}"
239
+ end
240
+ end
241
+
242
+ ##
243
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.2
244
+ def read_key_material(body)
245
+ @key_fields = case algorithm
246
+ when Algorithm::Asymmetric::RSA then [:n, :e]
247
+ when Algorithm::Asymmetric::ELG_E then [:p, :g, :y]
248
+ when Algorithm::Asymmetric::DSA then [:p, :q, :g, :y]
249
+ else raise "Unknown OpenPGP key algorithm: #{algorithm}"
250
+ end
251
+ @key_fields.each { |field| key[field] = body.read_mpi }
252
+ @key_id = fingerprint[-8..-1]
253
+ end
254
+
255
+ ##
256
+ # @see http://tools.ietf.org/html/rfc4880#section-12.2
257
+ # @see http://tools.ietf.org/html/rfc4880#section-3.3
258
+ def fingerprint
259
+ @fingerprint ||= case version
260
+ when 2, 3
261
+ Digest::MD5.hexdigest([key[:n], key[:e]].join).upcase
262
+ when 4
263
+ material = [0x99.chr, [size].pack('n'), version.chr, [timestamp].pack('N'), algorithm.chr]
264
+ key_fields.each do |key_field|
265
+ material << [OpenPGP.bitlength(key[key_field])].pack('n')
266
+ material << key[key_field]
267
+ end
268
+ Digest::SHA1.hexdigest(material.join).upcase
269
+ end
270
+ end
122
271
  end
123
272
 
124
273
  ##
@@ -167,7 +316,19 @@ module OpenPGP
167
316
  #
168
317
  # @see http://tools.ietf.org/html/rfc4880#section-5.7
169
318
  class EncryptedData < Packet
170
- # TODO
319
+ attr_accessor :data
320
+
321
+ def self.parse_body(body, options = {})
322
+ self.new({:data => body.read}.merge(options))
323
+ end
324
+
325
+ def initialize(options = {}, &block)
326
+ super(options, &block)
327
+ end
328
+
329
+ def write_body(buffer)
330
+ buffer.write(data)
331
+ end
171
332
  end
172
333
 
173
334
  ##
@@ -183,7 +344,39 @@ module OpenPGP
183
344
  #
184
345
  # @see http://tools.ietf.org/html/rfc4880#section-5.9
185
346
  class LiteralData < Packet
186
- # TODO
347
+ attr_accessor :format, :filename, :timestamp, :data
348
+
349
+ def self.parse_body(body, options = {})
350
+ defaults = {
351
+ :format => body.read_byte.chr.to_sym,
352
+ :filename => body.read_string,
353
+ :timestamp => body.read_timestamp,
354
+ :data => body.read,
355
+ }
356
+ self.new(defaults.merge(options))
357
+ end
358
+
359
+ def initialize(options = {}, &block)
360
+ defaults = {
361
+ :format => :b,
362
+ :filename => "",
363
+ :timestamp => 0,
364
+ :data => "",
365
+ }
366
+ super(defaults.merge(options), &block)
367
+ end
368
+
369
+ def write_body(buffer)
370
+ buffer.write_byte(format)
371
+ buffer.write_string(filename)
372
+ buffer.write_timestamp(timestamp)
373
+ buffer.write(data.to_s)
374
+ end
375
+
376
+ EYES_ONLY = '_CONSOLE'
377
+
378
+ def eyes_only!() filename = EYES_ONLY end
379
+ def eyes_only?() filename == EYES_ONLY end
187
380
  end
188
381
 
189
382
  ##
@@ -191,7 +384,15 @@ module OpenPGP
191
384
  #
192
385
  # @see http://tools.ietf.org/html/rfc4880#section-5.10
193
386
  class Trust < Packet
194
- # TODO
387
+ attr_accessor :data
388
+
389
+ def self.parse_body(body, options = {})
390
+ self.new({:data => body.read}.merge(options))
391
+ end
392
+
393
+ def write_body(buffer)
394
+ buffer.write(data)
395
+ end
195
396
  end
196
397
 
197
398
  ##
@@ -202,26 +403,29 @@ module OpenPGP
202
403
  class UserID < Packet
203
404
  attr_accessor :name, :comment, :email
204
405
 
205
- def initialize(tag = nil, data = nil)
206
- super
207
- case data
406
+ def self.parse_body(body, options = {})
407
+ case body.read
208
408
  # User IDs of the form: "name (comment) <email>"
209
409
  when /^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/
210
- @name, @comment, @email = $1, $2, $3
410
+ self.new(:name => $1.strip, :comment => $2.strip, :email => $3.strip)
211
411
  # User IDs of the form: "name <email>"
212
412
  when /^([^<]+)\s+<([^>]+)>$/
213
- @name, @comment, @email = $1, nil, $2
413
+ self.new(:name => $1.strip, :comment => nil, :email => $2.strip)
214
414
  # User IDs of the form: "name"
215
415
  when /^([^<]+)$/
216
- @name, @comment, @email = $1, nil, nil
416
+ self.new(:name => $1.strip, :comment => nil, :email => nil)
217
417
  # User IDs of the form: "<email>"
218
418
  when /^<([^>]+)>$/
219
- @name, @comment, @email = nil, nil, $2
419
+ self.new(:name => nil, :comment => nil, :email => $1.strip)
220
420
  else
221
- @name, @comment, @email = nil
421
+ self.new(:name => nil, :comment => nil, :email => nil)
222
422
  end
223
423
  end
224
424
 
425
+ def write_body(buffer)
426
+ buffer.write(to_s)
427
+ end
428
+
225
429
  def to_s
226
430
  text = []
227
431
  text << name if name
@@ -247,7 +451,16 @@ module OpenPGP
247
451
  #
248
452
  # @see http://tools.ietf.org/html/rfc4880#section-5.13
249
453
  class IntegrityProtectedData < Packet
250
- # TODO
454
+ attr_accessor :version
455
+
456
+ def self.parse_body(body, options = {})
457
+ case version = body.read_byte
458
+ when 1
459
+ self.new(:version => version) # TODO: read the encrypted data.
460
+ else
461
+ raise "Invalid OpenPGP integrity-protected data packet version: #{version}"
462
+ end
463
+ end
251
464
  end
252
465
 
253
466
  ##