klacointe-openpgp 0.0.1.3

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,507 @@
1
+ module OpenPGP
2
+ ##
3
+ # OpenPGP packet.
4
+ #
5
+ # @see http://tools.ietf.org/html/rfc4880#section-4.1
6
+ # @see http://tools.ietf.org/html/rfc4880#section-4.3
7
+ class Packet
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
21
+
22
+ ##
23
+ # Parses an OpenPGP packet.
24
+ #
25
+ # @see http://tools.ietf.org/html/rfc4880#section-4.2
26
+ def self.parse(data)
27
+ data = Buffer.new(data.to_str) if data.respond_to?(:to_str)
28
+
29
+ unless data.eof?
30
+ new = ((tag = data.getc) & 64).nonzero? # bit 6 indicates new packet format if set
31
+ data.ungetc(tag)
32
+ send(new ? :parse_new_format : :parse_old_format, data)
33
+ end
34
+ end
35
+
36
+ ##
37
+ # Parses a new-format (RFC 4880) OpenPGP packet.
38
+ #
39
+ # @see http://tools.ietf.org/html/rfc4880#section-4.2.2
40
+ def self.parse_new_format(data)
41
+ tag = data.getc & 63
42
+ len = data.getc
43
+
44
+ case len
45
+ when 0..191 # 4.2.2.1. One-Octet Lengths
46
+ data_length = len
47
+ when 192..223 # 4.2.2.2. Two-Octet Lengths
48
+ data_length = ((len - 192) << 8) + data.getc + 192
49
+ when 224..254 # 4.2.2.4. Partial Body Lengths
50
+ data_length = 1 << (len & 0x1f)
51
+ when 255 # 4.2.2.3. Five-Octet Lengths
52
+ data_length = (data.getc << 24) | (data.getc << 16) | (data.getc << 8) | data.getc
53
+ end
54
+
55
+ Packet.for(tag).parse_body(Buffer.new(data.read(data_length)), :tag => tag)
56
+ end
57
+
58
+ ##
59
+ # Parses an old-format (PGP 2.6.x) OpenPGP packet.
60
+ #
61
+ # @see http://tools.ietf.org/html/rfc4880#section-4.2.1
62
+ def self.parse_old_format(data)
63
+ len = (tag = data.getc) & 3
64
+ tag = (tag >> 2) & 15
65
+
66
+ case len
67
+ when 0 # The packet has a one-octet length. The header is 2 octets long.
68
+ data_length = data.getc
69
+ when 1 # The packet has a two-octet length. The header is 3 octets long.
70
+ data_length = data.read(2).unpack('n').first
71
+ when 2 # The packet has a four-octet length. The header is 5 octets long.
72
+ data_length = data.read(4).unpack('N').first
73
+ when 3 # The packet is of indeterminate length. The header is 1 octet long.
74
+ data_length = false # read to EOF
75
+ else
76
+ raise "Invalid OpenPGP packet length-type: expected 0..3 but got #{len}"
77
+ end
78
+
79
+ Packet.for(tag).parse_body(Buffer.new(data_length ? data.read(data_length) : data.read), :tag => tag)
80
+ end
81
+
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?
90
+ end
91
+
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) } : ""
98
+ end
99
+
100
+ ##
101
+ # OpenPGP Public-Key Encrypted Session Key packet (tag 1).
102
+ #
103
+ # @see http://tools.ietf.org/html/rfc4880#section-5.1
104
+ # @see http://tools.ietf.org/html/rfc4880#section-13.1
105
+ class AsymmetricSessionKey < Packet
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
117
+ end
118
+
119
+ ##
120
+ # OpenPGP Signature packet (tag 2).
121
+ #
122
+ # @see http://tools.ietf.org/html/rfc4880#section-5.2
123
+ class Signature < Packet
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
174
+ end
175
+
176
+ ##
177
+ # OpenPGP Symmetric-Key Encrypted Session Key packet (tag 3).
178
+ #
179
+ # @see http://tools.ietf.org/html/rfc4880#section-5.3
180
+ class SymmetricSessionKey < Packet
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
206
+ end
207
+
208
+ ##
209
+ # OpenPGP One-Pass Signature packet (tag 4).
210
+ #
211
+ # @see http://tools.ietf.org/html/rfc4880#section-5.4
212
+ class OnePassSignature < Packet
213
+ # TODO
214
+ end
215
+
216
+ ##
217
+ # OpenPGP Public-Key packet (tag 6).
218
+ #
219
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.1.1
220
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.2
221
+ # @see http://tools.ietf.org/html/rfc4880#section-11.1
222
+ # @see http://tools.ietf.org/html/rfc4880#section-12
223
+ class PublicKey < Packet
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
271
+ end
272
+
273
+ ##
274
+ # OpenPGP Public-Subkey packet (tag 14).
275
+ #
276
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.1.2
277
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.2
278
+ # @see http://tools.ietf.org/html/rfc4880#section-11.1
279
+ # @see http://tools.ietf.org/html/rfc4880#section-12
280
+ class PublicSubkey < PublicKey
281
+ # TODO
282
+ end
283
+
284
+ ##
285
+ # OpenPGP Secret-Key packet (tag 5).
286
+ #
287
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.1.3
288
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.3
289
+ # @see http://tools.ietf.org/html/rfc4880#section-11.2
290
+ # @see http://tools.ietf.org/html/rfc4880#section-12
291
+ class SecretKey < PublicKey
292
+ # TODO
293
+ end
294
+
295
+ ##
296
+ # OpenPGP Secret-Subkey packet (tag 7).
297
+ #
298
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.1.4
299
+ # @see http://tools.ietf.org/html/rfc4880#section-5.5.3
300
+ # @see http://tools.ietf.org/html/rfc4880#section-11.2
301
+ # @see http://tools.ietf.org/html/rfc4880#section-12
302
+ class SecretSubkey < SecretKey
303
+ # TODO
304
+ end
305
+
306
+ ##
307
+ # OpenPGP Compressed Data packet (tag 8).
308
+ #
309
+ # @see http://tools.ietf.org/html/rfc4880#section-5.6
310
+ class CompressedData < Packet
311
+ # TODO
312
+ end
313
+
314
+ ##
315
+ # OpenPGP Symmetrically Encrypted Data packet (tag 9).
316
+ #
317
+ # @see http://tools.ietf.org/html/rfc4880#section-5.7
318
+ class EncryptedData < Packet
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
332
+ end
333
+
334
+ ##
335
+ # OpenPGP Marker packet (tag 10).
336
+ #
337
+ # @see http://tools.ietf.org/html/rfc4880#section-5.8
338
+ class Marker < Packet
339
+ # TODO
340
+ end
341
+
342
+ ##
343
+ # OpenPGP Literal Data packet (tag 11).
344
+ #
345
+ # @see http://tools.ietf.org/html/rfc4880#section-5.9
346
+ class LiteralData < Packet
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
380
+ end
381
+
382
+ ##
383
+ # OpenPGP Trust packet (tag 12).
384
+ #
385
+ # @see http://tools.ietf.org/html/rfc4880#section-5.10
386
+ class Trust < Packet
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
396
+ end
397
+
398
+ ##
399
+ # OpenPGP User ID packet (tag 13).
400
+ #
401
+ # @see http://tools.ietf.org/html/rfc4880#section-5.11
402
+ # @see http://tools.ietf.org/html/rfc2822
403
+ class UserID < Packet
404
+ attr_accessor :name, :comment, :email
405
+
406
+ def self.parse_body(body, options = {})
407
+ case body.read
408
+ # User IDs of the form: "name (comment) <email>"
409
+ when /^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/
410
+ self.new(:name => $1.strip, :comment => $2.strip, :email => $3.strip)
411
+ # User IDs of the form: "name <email>"
412
+ when /^([^<]+)\s+<([^>]+)>$/
413
+ self.new(:name => $1.strip, :comment => nil, :email => $2.strip)
414
+ # User IDs of the form: "name"
415
+ when /^([^<]+)$/
416
+ self.new(:name => $1.strip, :comment => nil, :email => nil)
417
+ # User IDs of the form: "<email>"
418
+ when /^<([^>]+)>$/
419
+ self.new(:name => nil, :comment => nil, :email => $1.strip)
420
+ else
421
+ self.new(:name => nil, :comment => nil, :email => nil)
422
+ end
423
+ end
424
+
425
+ def write_body(buffer)
426
+ buffer.write(to_s)
427
+ end
428
+
429
+ def to_s
430
+ text = []
431
+ text << name if name
432
+ text << "(#{comment})" if comment
433
+ text << "<#{email}>" if email
434
+ text.join(' ')
435
+ end
436
+ end
437
+
438
+ ##
439
+ # OpenPGP User Attribute packet (tag 17).
440
+ #
441
+ # @see http://tools.ietf.org/html/rfc4880#section-5.12
442
+ # @see http://tools.ietf.org/html/rfc4880#section-11.1
443
+ class UserAttribute < Packet
444
+ attr_accessor :packets
445
+
446
+ # TODO
447
+ end
448
+
449
+ ##
450
+ # OpenPGP Sym. Encrypted Integrity Protected Data packet (tag 18).
451
+ #
452
+ # @see http://tools.ietf.org/html/rfc4880#section-5.13
453
+ class IntegrityProtectedData < Packet
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
464
+ end
465
+
466
+ ##
467
+ # OpenPGP Modification Detection Code packet (tag 19).
468
+ #
469
+ # @see http://tools.ietf.org/html/rfc4880#section-5.14
470
+ class ModificationDetectionCode < Packet
471
+ # TODO
472
+ end
473
+
474
+ ##
475
+ # OpenPGP Private or Experimental packet (tags 60..63).
476
+ #
477
+ # @see http://tools.ietf.org/html/rfc4880#section-4.3
478
+ class Experimental < Packet; end
479
+
480
+ protected
481
+ ##
482
+ # @see http://tools.ietf.org/html/rfc4880#section-4.3
483
+ @@tags = {
484
+ 1 => AsymmetricSessionKey, # Public-Key Encrypted Session Key
485
+ 2 => Signature, # Signature Packet
486
+ 3 => SymmetricSessionKey, # Symmetric-Key Encrypted Session Key Packet
487
+ 4 => OnePassSignature, # One-Pass Signature Packet
488
+ 5 => SecretKey, # Secret-Key Packet
489
+ 6 => PublicKey, # Public-Key Packet
490
+ 7 => SecretSubkey, # Secret-Subkey Packet
491
+ 8 => CompressedData, # Compressed Data Packet
492
+ 9 => EncryptedData, # Symmetrically Encrypted Data Packet
493
+ 10 => Marker, # Marker Packet
494
+ 11 => LiteralData, # Literal Data Packet
495
+ 12 => Trust, # Trust Packet
496
+ 13 => UserID, # User ID Packet
497
+ 14 => PublicSubkey, # Public-Subkey Packet
498
+ 17 => UserAttribute, # User Attribute Packet
499
+ 18 => IntegrityProtectedData, # Sym. Encrypted and Integrity Protected Data Packet
500
+ 19 => ModificationDetectionCode, # Modification Detection Code Packet
501
+ 60 => Experimental, # Private or Experimental Values
502
+ 61 => Experimental, # Private or Experimental Values
503
+ 62 => Experimental, # Private or Experimental Values
504
+ 63 => Experimental, # Private or Experimental Values
505
+ }
506
+ end
507
+ end
@@ -0,0 +1,31 @@
1
+ module OpenPGP
2
+ module Random
3
+ ##
4
+ # Generates a random number.
5
+ def self.number(bits = 32, options = {})
6
+ octets = bytes((bits / 8.0).ceil).unpack('C*')
7
+ number = octets.inject { |number, octet| number = (number << 8) | octet }
8
+ number & ((1 << bits) - 1)
9
+ end
10
+
11
+ ##
12
+ # Generates a pseudo-random prime number of the specified bit length.
13
+ #
14
+ # @see http://openssl.org/docs/crypto/BN_generate_prime.html
15
+ # @see http://openssl.org/docs/apps/genrsa.html
16
+ def self.prime(bits, options = {})
17
+ raise NotImplementedError # TODO
18
+ end
19
+
20
+ ##
21
+ # Generates a random byte.
22
+ def self.byte() bytes(1) end
23
+
24
+ ##
25
+ # Generates a string of random bytes.
26
+ def self.bytes(count, &block)
27
+ octets = File.open('/dev/urandom', 'r') {|f| f.read(count) } # FIXME
28
+ block_given? ? octets.each_byte(&block) : octets
29
+ end
30
+ end
31
+ end