openpgp 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  ##