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.
- data/AUTHORS +2 -0
- data/README +55 -48
- data/UNLICENSE +24 -0
- data/VERSION +1 -1
- data/lib/openpgp.rb +16 -5
- data/lib/openpgp/algorithm.rb +1 -1
- data/lib/openpgp/armor.rb +55 -49
- data/lib/openpgp/buffer.rb +100 -0
- data/lib/openpgp/cipher.rb +117 -0
- data/lib/openpgp/cipher/3des.rb +9 -0
- data/lib/openpgp/cipher/aes.rb +26 -0
- data/lib/openpgp/cipher/blowfish.rb +9 -0
- data/lib/openpgp/cipher/cast5.rb +9 -0
- data/lib/openpgp/cipher/idea.rb +9 -0
- data/lib/openpgp/cipher/twofish.rb +9 -0
- data/lib/openpgp/client/gnupg.rb +583 -0
- data/lib/openpgp/digest.rb +60 -0
- data/lib/openpgp/digest/md5.rb +7 -0
- data/lib/openpgp/digest/rmd160.rb +7 -0
- data/lib/openpgp/digest/sha1.rb +7 -0
- data/lib/openpgp/digest/sha2.rb +19 -0
- data/lib/openpgp/engine.rb +46 -0
- data/lib/openpgp/{gnupg.rb → engine/gnupg.rb} +31 -7
- data/lib/openpgp/engine/openssl.rb +47 -0
- data/lib/openpgp/message.rb +55 -4
- data/lib/openpgp/packet.rb +240 -27
- data/lib/openpgp/random.rb +31 -0
- data/lib/openpgp/s2k.rb +186 -0
- data/lib/openpgp/util.rb +65 -0
- data/lib/openpgp/version.rb +5 -2
- metadata +40 -9
- data/LICENSE +0 -19
@@ -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,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
|
-
|
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
|
data/lib/openpgp/message.rb
CHANGED
@@ -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
|
-
|
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
|
32
|
-
|
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
|
data/lib/openpgp/packet.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
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
|
-
|
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(
|
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(
|
79
|
+
Packet.for(tag).parse_body(Buffer.new(data_length ? data.read(data_length) : data.read), :tag => tag)
|
71
80
|
end
|
72
81
|
|
73
|
-
|
74
|
-
|
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
|
78
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
206
|
-
|
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
|
-
|
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
|
-
|
413
|
+
self.new(:name => $1.strip, :comment => nil, :email => $2.strip)
|
214
414
|
# User IDs of the form: "name"
|
215
415
|
when /^([^<]+)$/
|
216
|
-
|
416
|
+
self.new(:name => $1.strip, :comment => nil, :email => nil)
|
217
417
|
# User IDs of the form: "<email>"
|
218
418
|
when /^<([^>]+)>$/
|
219
|
-
|
419
|
+
self.new(:name => nil, :comment => nil, :email => $1.strip)
|
220
420
|
else
|
221
|
-
|
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
|
-
|
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
|
##
|