bendiken-openpgp 0.0.1.1
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/LICENSE +19 -0
- data/README +92 -0
- data/Rakefile +5 -0
- data/VERSION +1 -0
- data/bin/openpgp +3 -0
- data/lib/openpgp/algorithm.rb +57 -0
- data/lib/openpgp/armor.rb +114 -0
- data/lib/openpgp/gnupg.rb +169 -0
- data/lib/openpgp/message.rb +55 -0
- data/lib/openpgp/packet.rb +294 -0
- data/lib/openpgp/version.rb +11 -0
- data/lib/openpgp.rb +6 -0
- metadata +73 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Arto Bendiken <http://ar.to/>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to
|
5
|
+
deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
7
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
19
|
+
IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
= OpenPGP.rb
|
2
|
+
|
3
|
+
This is a pure-Ruby implementation of the OpenPGP Message Format (RFC 4880).
|
4
|
+
|
5
|
+
|
6
|
+
=== About OpenPGP
|
7
|
+
|
8
|
+
OpenPGP is the most widely-used e-mail encryption standard in the world. It
|
9
|
+
is defined by the OpenPGP Working Group of the Internet Engineering Task
|
10
|
+
Force (IETF) Proposed Standard RFC 4880. The OpenPGP standard was originally
|
11
|
+
derived from PGP (Pretty Good Privacy), first created by Phil Zimmermann in
|
12
|
+
1991.
|
13
|
+
|
14
|
+
* http://tools.ietf.org/html/rfc4880
|
15
|
+
* http://www.openpgp.org
|
16
|
+
|
17
|
+
|
18
|
+
== Features
|
19
|
+
|
20
|
+
* Encodes and decodes ASCII-armored OpenPGP messages.
|
21
|
+
* Parses OpenPGP messages into their constituent packets.
|
22
|
+
* Supports both old-format (PGP 2.6.x) and new-format (RFC 4880) packets.
|
23
|
+
* Includes a GnuPG wrapper for features that are not natively supported.
|
24
|
+
|
25
|
+
|
26
|
+
== Examples
|
27
|
+
|
28
|
+
require 'openpgp'
|
29
|
+
|
30
|
+
|
31
|
+
=== Decoding an ASCII-armored message
|
32
|
+
|
33
|
+
require 'open-uri'
|
34
|
+
text = open('http://ar.to/pgp.txt').read
|
35
|
+
|
36
|
+
msg = OpenPGP::Message.parse(OpenPGP.dearmor(text))
|
37
|
+
|
38
|
+
|
39
|
+
=== Generating a new keypair
|
40
|
+
|
41
|
+
gpg = OpenPGP::GnuPG.new(:homedir => '~/.gnupg')
|
42
|
+
key_id = gpg.gen_key({
|
43
|
+
:key_type => 'DSA',
|
44
|
+
:key_length => 1024,
|
45
|
+
:subkey_type => 'ELG-E',
|
46
|
+
:subkey_length => 1024,
|
47
|
+
:name => 'J. Random Hacker',
|
48
|
+
:comment => nil,
|
49
|
+
:email => 'jhacker@example.org',
|
50
|
+
:passphrase => 'secret passphrase',
|
51
|
+
})
|
52
|
+
|
53
|
+
|
54
|
+
== Documentation
|
55
|
+
|
56
|
+
* http://openpgp.rubyforge.org
|
57
|
+
|
58
|
+
|
59
|
+
== Download
|
60
|
+
|
61
|
+
To get a local working copy of the development repository, do:
|
62
|
+
|
63
|
+
% git clone git://github.com/bendiken/openpgp.git
|
64
|
+
|
65
|
+
Alternatively, you can download the latest development version as a tarball
|
66
|
+
as follows:
|
67
|
+
|
68
|
+
% wget http://github.com/bendiken/openpgp/tarball/master
|
69
|
+
|
70
|
+
|
71
|
+
== Installation
|
72
|
+
|
73
|
+
The recommended installation method is via RubyGems. To install the latest
|
74
|
+
official release from RubyForge, do:
|
75
|
+
|
76
|
+
% [sudo] gem install openpgp
|
77
|
+
|
78
|
+
To use the very latest bleeding-edge development version, install the gem
|
79
|
+
directly from GitHub as follows:
|
80
|
+
|
81
|
+
% [sudo] gem install bendiken-openpgp -s http://gems.github.com
|
82
|
+
|
83
|
+
|
84
|
+
== Authors
|
85
|
+
|
86
|
+
* Arto Bendiken (mailto:arto.bendiken@gmail.com) - http://ar.to
|
87
|
+
|
88
|
+
|
89
|
+
== License
|
90
|
+
|
91
|
+
All source code is available under the terms of the MIT license. For more
|
92
|
+
information, see the accompanying LICENSE file.
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1.1
|
data/bin/openpgp
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module OpenPGP
|
2
|
+
module Algorithm
|
3
|
+
##
|
4
|
+
# OpenPGP public-key algorithms.
|
5
|
+
#
|
6
|
+
# @see http://tools.ietf.org/html/rfc4880#section-9.1
|
7
|
+
module Asymmetric
|
8
|
+
RSA = 1
|
9
|
+
RSA_E = 2
|
10
|
+
RSA_S = 3
|
11
|
+
ELG_E = 16
|
12
|
+
DSA = 17
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# OpenPGP symmetric-key algorithms.
|
17
|
+
#
|
18
|
+
# @see http://tools.ietf.org/html/rfc4880#section-9.2
|
19
|
+
module Symmetric
|
20
|
+
NONE = 0
|
21
|
+
IDEA = 1
|
22
|
+
TRIPLEDES = 2
|
23
|
+
CAST5 = 3
|
24
|
+
BLOWFISH = 4
|
25
|
+
AES = 7
|
26
|
+
AES128 = 7
|
27
|
+
AES192 = 8
|
28
|
+
AES256 = 9
|
29
|
+
TWOFISH = 10
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# OpenPGP compression algorithms.
|
34
|
+
#
|
35
|
+
# @see http://tools.ietf.org/html/rfc4880#section-9.3
|
36
|
+
module Compression
|
37
|
+
NONE = 0
|
38
|
+
ZIP = 1
|
39
|
+
ZLIB = 2
|
40
|
+
BZIP2 = 3
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# OpenPGP hash algorithms.
|
45
|
+
#
|
46
|
+
# @see http://tools.ietf.org/html/rfc4880#section-9.4
|
47
|
+
module Hash
|
48
|
+
MD5 = 1
|
49
|
+
SHA1 = 2
|
50
|
+
RIPEMD160 = 3
|
51
|
+
SHA256 = 8
|
52
|
+
SHA384 = 9
|
53
|
+
SHA512 = 10
|
54
|
+
SHA224 = 11
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module OpenPGP
|
2
|
+
##
|
3
|
+
# Alias for OpenPGP::Armor.encode().
|
4
|
+
def self.enarmor(data, marker = 'MESSAGE', headers = {})
|
5
|
+
Armor.encode(data, marker, headers)
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# Alias for OpenPGP::Armor.decode().
|
10
|
+
def self.dearmor(text, marker = nil)
|
11
|
+
Armor.decode(text, marker)
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# OpenPGP ASCII Armor utilities.
|
16
|
+
#
|
17
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6.2
|
18
|
+
module Armor
|
19
|
+
##
|
20
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6.2
|
21
|
+
module Markers
|
22
|
+
MESSAGE = 'MESSAGE'
|
23
|
+
PUBLIC_KEY_BLOCK = 'PUBLIC KEY BLOCK'
|
24
|
+
PRIVATE_KEY_BLOCK = 'PRIVATE KEY BLOCK'
|
25
|
+
SIGNATURE = 'SIGNATURE'
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6.2
|
30
|
+
def self.header(marker)
|
31
|
+
"-----BEGIN PGP #{marker.to_s.upcase}-----"
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6.2
|
36
|
+
def self.footer(marker)
|
37
|
+
"-----END PGP #{marker.to_s.upcase}-----"
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6
|
42
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6.2
|
43
|
+
# @see http://tools.ietf.org/html/rfc2045
|
44
|
+
def self.encode(data, marker = 'MESSAGE', headers = {})
|
45
|
+
require 'stringio'
|
46
|
+
require 'base64'
|
47
|
+
|
48
|
+
text = StringIO.new
|
49
|
+
text << self.header(marker) << "\n"
|
50
|
+
headers.each { |key, value| text << "#{key}: #{value}\n" }
|
51
|
+
text << "\n" << Base64.encode64(data)
|
52
|
+
text << "=" << Base64.encode64([self.crc24(data)].pack('N')[1, 3])
|
53
|
+
text << self.footer(marker) << "\n"
|
54
|
+
text.string
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6
|
59
|
+
# @see http://tools.ietf.org/html/rfc2045
|
60
|
+
def self.decode(text, marker = nil)
|
61
|
+
require 'stringio'
|
62
|
+
require 'base64'
|
63
|
+
|
64
|
+
data, crc, state = StringIO.new, nil, :begin
|
65
|
+
text.each_line do |line|
|
66
|
+
line.chomp!
|
67
|
+
case state
|
68
|
+
when :begin
|
69
|
+
case line
|
70
|
+
when /^-----BEGIN PGP ([^-]+)-----$/
|
71
|
+
state = :head if marker.nil? || marker.to_s.upcase == $1
|
72
|
+
end
|
73
|
+
when :head
|
74
|
+
state = :body if line =~ /^\s*$/
|
75
|
+
when :body
|
76
|
+
case line
|
77
|
+
when /^=(....)$/
|
78
|
+
crc = ("\0" << Base64.decode64($1)).unpack('N').first
|
79
|
+
state = :end
|
80
|
+
when /^-----END PGP ([^-]+)-----$/
|
81
|
+
state = :end
|
82
|
+
else
|
83
|
+
data << Base64.decode64(line)
|
84
|
+
end
|
85
|
+
when :end
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
89
|
+
data.string
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6.1
|
94
|
+
CRC24_INIT = 0x00b704ce
|
95
|
+
CRC24_POLY = 0x01864cfb
|
96
|
+
|
97
|
+
##
|
98
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6
|
99
|
+
# @see http://tools.ietf.org/html/rfc4880#section-6.1
|
100
|
+
def self.crc24(data)
|
101
|
+
crc = CRC24_INIT
|
102
|
+
data.each_byte do |octet|
|
103
|
+
crc ^= octet << 16
|
104
|
+
8.times do
|
105
|
+
crc <<= 1
|
106
|
+
crc ^= CRC24_POLY if (crc & 0x01000000).nonzero?
|
107
|
+
end
|
108
|
+
end
|
109
|
+
crc &= 0x00ffffff
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
include Armor::Markers
|
114
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module OpenPGP
|
2
|
+
##
|
3
|
+
# GNU Privacy Guard (GnuPG) wrapper.
|
4
|
+
#
|
5
|
+
# @see http://www.gnupg.org/
|
6
|
+
class GnuPG
|
7
|
+
class Error < IOError; end
|
8
|
+
|
9
|
+
OPTIONS = {
|
10
|
+
:batch => true,
|
11
|
+
:quiet => true,
|
12
|
+
:no_verbose => true,
|
13
|
+
:no_tty => true,
|
14
|
+
:no_permission_warning => true,
|
15
|
+
:no_random_seed_file => true,
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_accessor :where
|
19
|
+
attr_accessor :options
|
20
|
+
|
21
|
+
def initialize(options = {})
|
22
|
+
@where = '/usr/bin/env gpg' # FIXME
|
23
|
+
@options = OPTIONS.merge!(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Determines if GnuPG is available.
|
28
|
+
def available?
|
29
|
+
!!version
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Returns the GnuPG version number.
|
34
|
+
def version
|
35
|
+
exec(:version).readline =~ /^gpg \(GnuPG\) (.*)$/ ? $1 : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Generates a new OpenPGP keypair and stores it GnuPG's keyring.
|
40
|
+
def gen_key(info = {})
|
41
|
+
stdin, stdout, stderr = exec3(:gen_key) do |stdin, stdout, stderr|
|
42
|
+
stdin.puts "Key-Type: #{info[:key_type]}" if info[:key_type]
|
43
|
+
stdin.puts "Key-Length: #{info[:key_length]}" if info[:key_length]
|
44
|
+
stdin.puts "Subkey-Type: #{info[:subkey_type]}" if info[:subkey_type]
|
45
|
+
stdin.puts "Subkey-Length: #{info[:subkey_length]}" if info[:subkey_length]
|
46
|
+
stdin.puts "Name-Real: #{info[:name]}" if info[:name]
|
47
|
+
stdin.puts "Name-Comment: #{info[:comment]}" if info[:comment]
|
48
|
+
stdin.puts "Name-Email: #{info[:email]}" if info[:email]
|
49
|
+
stdin.puts "Expire-Date: #{info[:expire_date]}" if info[:expire_date]
|
50
|
+
stdin.puts "Passphrase: #{info[:passphrase]}" if info[:passphrase]
|
51
|
+
stdin.puts "%commit"
|
52
|
+
end
|
53
|
+
stderr.each_line do |line|
|
54
|
+
if (line = line.chomp) =~ /^gpg: key ([0-9A-F]+) marked as ultimately trusted/
|
55
|
+
return $1.to_i(16) # the key ID
|
56
|
+
end
|
57
|
+
end
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Exports a specified key from the GnuPG keyring.
|
63
|
+
def export(key_id = nil)
|
64
|
+
OpenPGP::Message.parse(exec([:export, *[key_id].flatten]).read)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Imports a specified keyfile into the GnuPG keyring.
|
69
|
+
def import()
|
70
|
+
# TODO
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Returns an array of key IDs/titles of the keys in the public keyring.
|
75
|
+
def list_keys()
|
76
|
+
# TODO
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Encrypts the given plaintext to the specified recipients.
|
81
|
+
def encrypt(plaintext, options = {})
|
82
|
+
# TODO
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Decrypts the given ciphertext using the specified key ID.
|
87
|
+
def decrypt(ciphertext, options = {})
|
88
|
+
# TODO
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Makes an OpenPGP signature.
|
93
|
+
def sign()
|
94
|
+
# TODO
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Makes a clear text OpenPGP signature.
|
99
|
+
def clearsign()
|
100
|
+
# TODO
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Makes a detached OpenPGP signature.
|
105
|
+
def detach_sign()
|
106
|
+
# TODO
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Verifies an OpenPGP signature.
|
111
|
+
def verify()
|
112
|
+
# TODO
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Executes a GnuPG command, yielding the standard input and returning
|
117
|
+
# the standard output.
|
118
|
+
def exec(command, options = {}, &block) #:yields: stdin
|
119
|
+
exec4(command, options) do |pid, stdin, stdout, stderr|
|
120
|
+
block.call(stdin) if block_given?
|
121
|
+
stdin.close_write
|
122
|
+
pid, status = Process.waitpid2(pid)
|
123
|
+
raise Error, stderr.read.chomp if status.exitstatus.nonzero?
|
124
|
+
stdout
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Executes a GnuPG command, yielding and returning the standard input,
|
130
|
+
# output and error.
|
131
|
+
def exec3(command, options = {}, &block) #:yields: stdin, stdout, stderr
|
132
|
+
exec4(command, options) do |pid, stdin, stdout, stderr|
|
133
|
+
block.call(stdin, stdout, stderr) if block_given?
|
134
|
+
stdin.close_write
|
135
|
+
pid, status = Process.waitpid2(pid)
|
136
|
+
raise Error, stderr.read.chomp if status.exitstatus.nonzero?
|
137
|
+
[stdin, stdout, stderr]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Executes a GnuPG command, yielding the process identifier as well as
|
143
|
+
# the standard input, output and error.
|
144
|
+
def exec4(command, options = {}, &block) #:yields: pid, stdin, stdout, stderr
|
145
|
+
require 'rubygems'
|
146
|
+
require 'open4'
|
147
|
+
block.call(*Open4.popen4(cmdline(command, options)))
|
148
|
+
end
|
149
|
+
|
150
|
+
protected
|
151
|
+
|
152
|
+
##
|
153
|
+
# Constructs the GnuPG command-line for use with +exec+.
|
154
|
+
def cmdline(command, options = {})
|
155
|
+
command = [command].flatten
|
156
|
+
cmdline = [where]
|
157
|
+
cmdline += @options.merge(options).map { |k, v| !v ? nil : "#{option(k)} #{v == true ? '' : v.to_s}".rstrip }.compact
|
158
|
+
cmdline << option(command.shift)
|
159
|
+
cmdline += command
|
160
|
+
cmdline.flatten.join(' ').strip
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Translates Ruby symbols into GnuPG option arguments.
|
165
|
+
def option(option)
|
166
|
+
"--" << option.to_s.gsub('_', '-')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,55 @@
|
|
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
|
+
attr_accessor :packets
|
10
|
+
|
11
|
+
##
|
12
|
+
# Parses an OpenPGP message.
|
13
|
+
#
|
14
|
+
# @see http://tools.ietf.org/html/rfc4880#section-4.1
|
15
|
+
# @see http://tools.ietf.org/html/rfc4880#section-4.2
|
16
|
+
def self.parse(data)
|
17
|
+
require 'stringio'
|
18
|
+
data = StringIO.new(data.to_str) if data.respond_to?(:to_str)
|
19
|
+
|
20
|
+
msg = self.new
|
21
|
+
until data.eof?
|
22
|
+
if packet = OpenPGP::Packet.parse(data)
|
23
|
+
msg << packet
|
24
|
+
else
|
25
|
+
raise "Invalid OpenPGP message data at position #{data.pos}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
msg
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(packets = [])
|
32
|
+
@packets = packets
|
33
|
+
end
|
34
|
+
|
35
|
+
def each(&block) # :yields: packet
|
36
|
+
packets.each(&block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_a
|
40
|
+
packets.to_a
|
41
|
+
end
|
42
|
+
|
43
|
+
def <<(packet)
|
44
|
+
packets << packet
|
45
|
+
end
|
46
|
+
|
47
|
+
def empty?
|
48
|
+
packets.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
def size
|
52
|
+
inject(0) { |sum, packet| sum + packet.size }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,294 @@
|
|
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
|
9
|
+
attr_accessor :size
|
10
|
+
attr_accessor :data
|
11
|
+
|
12
|
+
##
|
13
|
+
# Parses an OpenPGP packet.
|
14
|
+
#
|
15
|
+
# @see http://tools.ietf.org/html/rfc4880#section-4.2
|
16
|
+
def self.parse(data)
|
17
|
+
require 'stringio'
|
18
|
+
data = StringIO.new(data.to_str) if data.respond_to?(:to_str)
|
19
|
+
|
20
|
+
unless data.eof?
|
21
|
+
new = ((tag = data.getc) & 64).nonzero? # bit 6 indicates new packet format if set
|
22
|
+
data.ungetc(tag)
|
23
|
+
send(new ? :parse_new_format : :parse_old_format, data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Parses a new-format (RFC 4880) OpenPGP packet.
|
29
|
+
#
|
30
|
+
# @see http://tools.ietf.org/html/rfc4880#section-4.2.2
|
31
|
+
def self.parse_new_format(data)
|
32
|
+
tag = data.getc & 63
|
33
|
+
len = data.getc
|
34
|
+
|
35
|
+
case len
|
36
|
+
when 0..191 # 4.2.2.1. One-Octet Lengths
|
37
|
+
data_length = len
|
38
|
+
when 192..223 # 4.2.2.2. Two-Octet Lengths
|
39
|
+
data_length = ((len - 192) << 8) + data.getc + 192
|
40
|
+
when 224..254 # 4.2.2.4. Partial Body Lengths
|
41
|
+
data_length = 1 << (len & 0x1f)
|
42
|
+
when 255 # 4.2.2.3. Five-Octet Lengths
|
43
|
+
data_length = (data.getc << 24) | (data.getc << 16) | (data.getc << 8) | data.getc
|
44
|
+
end
|
45
|
+
|
46
|
+
Packet.for(tag).new(tag, data.read(data_length))
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Parses an old-format (PGP 2.6.x) OpenPGP packet.
|
51
|
+
#
|
52
|
+
# @see http://tools.ietf.org/html/rfc4880#section-4.2.1
|
53
|
+
def self.parse_old_format(data)
|
54
|
+
len = (tag = data.getc) & 3
|
55
|
+
tag = (tag >> 2) & 15
|
56
|
+
|
57
|
+
case len
|
58
|
+
when 0 # The packet has a one-octet length. The header is 2 octets long.
|
59
|
+
data_length = data.getc
|
60
|
+
when 1 # The packet has a two-octet length. The header is 3 octets long.
|
61
|
+
data_length = data.read(2).unpack('n').first
|
62
|
+
when 2 # The packet has a four-octet length. The header is 5 octets long.
|
63
|
+
data_length = data.read(4).unpack('N').first
|
64
|
+
when 3 # The packet is of indeterminate length. The header is 1 octet long.
|
65
|
+
data_length = false # read to EOF
|
66
|
+
else
|
67
|
+
raise "Invalid OpenPGP packet length-type: expected 0..3 but got #{len}"
|
68
|
+
end
|
69
|
+
|
70
|
+
Packet.for(tag).new(tag, data_length ? data.read(data_length) : data.read)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.for(tag)
|
74
|
+
@@tags[tag.to_i] || self
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(tag = nil, data = nil)
|
78
|
+
@tag, @data, @size = tag, data, data ? data.size : 0
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# OpenPGP Public-Key Encrypted Session Key packet (tag 1).
|
83
|
+
#
|
84
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.1
|
85
|
+
class AsymmetricSessionKey < Packet
|
86
|
+
# TODO
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# OpenPGP Signature packet (tag 2).
|
91
|
+
#
|
92
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.2
|
93
|
+
class Signature < Packet
|
94
|
+
# TODO
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# OpenPGP Symmetric-Key Encrypted Session Key packet (tag 3).
|
99
|
+
#
|
100
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.3
|
101
|
+
class SymmetricSessionKey < Packet
|
102
|
+
# TODO
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# OpenPGP One-Pass Signature packet (tag 4).
|
107
|
+
#
|
108
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.4
|
109
|
+
class OnePassSignature < Packet
|
110
|
+
# TODO
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# OpenPGP Public-Key packet (tag 6).
|
115
|
+
#
|
116
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.5.1.1
|
117
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.5.2
|
118
|
+
# @see http://tools.ietf.org/html/rfc4880#section-11.1
|
119
|
+
# @see http://tools.ietf.org/html/rfc4880#section-12
|
120
|
+
class PublicKey < Packet
|
121
|
+
# TODO
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# OpenPGP Public-Subkey packet (tag 14).
|
126
|
+
#
|
127
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.5.1.2
|
128
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.5.2
|
129
|
+
# @see http://tools.ietf.org/html/rfc4880#section-11.1
|
130
|
+
# @see http://tools.ietf.org/html/rfc4880#section-12
|
131
|
+
class PublicSubkey < PublicKey
|
132
|
+
# TODO
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# OpenPGP Secret-Key packet (tag 5).
|
137
|
+
#
|
138
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.5.1.3
|
139
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.5.3
|
140
|
+
# @see http://tools.ietf.org/html/rfc4880#section-11.2
|
141
|
+
# @see http://tools.ietf.org/html/rfc4880#section-12
|
142
|
+
class SecretKey < PublicKey
|
143
|
+
# TODO
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# OpenPGP Secret-Subkey packet (tag 7).
|
148
|
+
#
|
149
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.5.1.4
|
150
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.5.3
|
151
|
+
# @see http://tools.ietf.org/html/rfc4880#section-11.2
|
152
|
+
# @see http://tools.ietf.org/html/rfc4880#section-12
|
153
|
+
class SecretSubkey < SecretKey
|
154
|
+
# TODO
|
155
|
+
end
|
156
|
+
|
157
|
+
##
|
158
|
+
# OpenPGP Compressed Data packet (tag 8).
|
159
|
+
#
|
160
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.6
|
161
|
+
class CompressedData < Packet
|
162
|
+
# TODO
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# OpenPGP Symmetrically Encrypted Data packet (tag 9).
|
167
|
+
#
|
168
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.7
|
169
|
+
class EncryptedData < Packet
|
170
|
+
# TODO
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# OpenPGP Marker packet (tag 10).
|
175
|
+
#
|
176
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.8
|
177
|
+
class Marker < Packet
|
178
|
+
# TODO
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# OpenPGP Literal Data packet (tag 11).
|
183
|
+
#
|
184
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.9
|
185
|
+
class LiteralData < Packet
|
186
|
+
# TODO
|
187
|
+
end
|
188
|
+
|
189
|
+
##
|
190
|
+
# OpenPGP Trust packet (tag 12).
|
191
|
+
#
|
192
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.10
|
193
|
+
class Trust < Packet
|
194
|
+
# TODO
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
# OpenPGP User ID packet (tag 13).
|
199
|
+
#
|
200
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.11
|
201
|
+
# @see http://tools.ietf.org/html/rfc2822
|
202
|
+
class UserID < Packet
|
203
|
+
attr_accessor :name, :comment, :email
|
204
|
+
|
205
|
+
def initialize(tag = nil, data = nil)
|
206
|
+
super
|
207
|
+
case data
|
208
|
+
# User IDs of the form: "name (comment) <email>"
|
209
|
+
when /^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/
|
210
|
+
@name, @comment, @email = $1, $2, $3
|
211
|
+
# User IDs of the form: "name <email>"
|
212
|
+
when /^([^<]+)\s+<([^>]+)>$/
|
213
|
+
@name, @comment, @email = $1, nil, $2
|
214
|
+
# User IDs of the form: "name"
|
215
|
+
when /^([^<]+)$/
|
216
|
+
@name, @comment, @email = $1, nil, nil
|
217
|
+
# User IDs of the form: "<email>"
|
218
|
+
when /^<([^>]+)>$/
|
219
|
+
@name, @comment, @email = nil, nil, $2
|
220
|
+
else
|
221
|
+
@name, @comment, @email = nil
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def to_s
|
226
|
+
text = []
|
227
|
+
text << name if name
|
228
|
+
text << "(#{comment})" if comment
|
229
|
+
text << "<#{email}>" if email
|
230
|
+
text.join(' ')
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# OpenPGP User Attribute packet (tag 17).
|
236
|
+
#
|
237
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.12
|
238
|
+
# @see http://tools.ietf.org/html/rfc4880#section-11.1
|
239
|
+
class UserAttribute < Packet
|
240
|
+
attr_accessor :packets
|
241
|
+
|
242
|
+
# TODO
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# OpenPGP Sym. Encrypted Integrity Protected Data packet (tag 18).
|
247
|
+
#
|
248
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.13
|
249
|
+
class IntegrityProtectedData < Packet
|
250
|
+
# TODO
|
251
|
+
end
|
252
|
+
|
253
|
+
##
|
254
|
+
# OpenPGP Modification Detection Code packet (tag 19).
|
255
|
+
#
|
256
|
+
# @see http://tools.ietf.org/html/rfc4880#section-5.14
|
257
|
+
class ModificationDetectionCode < Packet
|
258
|
+
# TODO
|
259
|
+
end
|
260
|
+
|
261
|
+
##
|
262
|
+
# OpenPGP Private or Experimental packet (tags 60..63).
|
263
|
+
#
|
264
|
+
# @see http://tools.ietf.org/html/rfc4880#section-4.3
|
265
|
+
class Experimental < Packet; end
|
266
|
+
|
267
|
+
protected
|
268
|
+
##
|
269
|
+
# @see http://tools.ietf.org/html/rfc4880#section-4.3
|
270
|
+
@@tags = {
|
271
|
+
1 => AsymmetricSessionKey, # Public-Key Encrypted Session Key
|
272
|
+
2 => Signature, # Signature Packet
|
273
|
+
3 => SymmetricSessionKey, # Symmetric-Key Encrypted Session Key Packet
|
274
|
+
4 => OnePassSignature, # One-Pass Signature Packet
|
275
|
+
5 => SecretKey, # Secret-Key Packet
|
276
|
+
6 => PublicKey, # Public-Key Packet
|
277
|
+
7 => SecretSubkey, # Secret-Subkey Packet
|
278
|
+
8 => CompressedData, # Compressed Data Packet
|
279
|
+
9 => EncryptedData, # Symmetrically Encrypted Data Packet
|
280
|
+
10 => Marker, # Marker Packet
|
281
|
+
11 => LiteralData, # Literal Data Packet
|
282
|
+
12 => Trust, # Trust Packet
|
283
|
+
13 => UserID, # User ID Packet
|
284
|
+
14 => PublicSubkey, # Public-Subkey Packet
|
285
|
+
17 => UserAttribute, # User Attribute Packet
|
286
|
+
18 => IntegrityProtectedData, # Sym. Encrypted and Integrity Protected Data Packet
|
287
|
+
19 => ModificationDetectionCode, # Modification Detection Code Packet
|
288
|
+
60 => Experimental, # Private or Experimental Values
|
289
|
+
61 => Experimental, # Private or Experimental Values
|
290
|
+
62 => Experimental, # Private or Experimental Values
|
291
|
+
63 => Experimental, # Private or Experimental Values
|
292
|
+
}
|
293
|
+
end
|
294
|
+
end
|
data/lib/openpgp.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bendiken-openpgp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Arto Bendiken
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-18 00:00:00 -07:00
|
13
|
+
default_executable: openpgp
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rakefile
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: OpenPGP.rb is a pure-Ruby implementation of the OpenPGP Message Format (RFC 4880).
|
26
|
+
email: arto.bendiken@gmail.com
|
27
|
+
executables:
|
28
|
+
- openpgp
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- LICENSE
|
35
|
+
- README
|
36
|
+
- Rakefile
|
37
|
+
- VERSION
|
38
|
+
- bin/openpgp
|
39
|
+
- lib/openpgp.rb
|
40
|
+
- lib/openpgp/algorithm.rb
|
41
|
+
- lib/openpgp/armor.rb
|
42
|
+
- lib/openpgp/gnupg.rb
|
43
|
+
- lib/openpgp/message.rb
|
44
|
+
- lib/openpgp/packet.rb
|
45
|
+
- lib/openpgp/version.rb
|
46
|
+
has_rdoc: false
|
47
|
+
homepage: http://github.com/bendiken/openpgp
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 1.8.2
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements:
|
66
|
+
- GnuPG >= 1.4.7 (not required, but enables extra functionality)
|
67
|
+
rubyforge_project: openpgp
|
68
|
+
rubygems_version: 1.2.0
|
69
|
+
signing_key:
|
70
|
+
specification_version: 2
|
71
|
+
summary: A pure-Ruby implementation of the OpenPGP Message Format (RFC 4880).
|
72
|
+
test_files: []
|
73
|
+
|