openpgp 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/AUTHORS
ADDED
data/README
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
|
1
|
+
OpenPGP.rb: OpenPGP for Ruby
|
2
|
+
============================
|
2
3
|
|
3
4
|
This is a pure-Ruby implementation of the OpenPGP Message Format (RFC 4880).
|
4
5
|
|
6
|
+
* <http://openpgp.rubyforge.org/>
|
7
|
+
* <http://github.com/bendiken/openpgp>
|
5
8
|
|
6
|
-
|
9
|
+
### About OpenPGP
|
7
10
|
|
8
11
|
OpenPGP is the most widely-used e-mail encryption standard in the world. It
|
9
12
|
is defined by the OpenPGP Working Group of the Internet Engineering Task
|
@@ -11,82 +14,86 @@ Force (IETF) Proposed Standard RFC 4880. The OpenPGP standard was originally
|
|
11
14
|
derived from PGP (Pretty Good Privacy), first created by Phil Zimmermann in
|
12
15
|
1991.
|
13
16
|
|
14
|
-
* http://tools.ietf.org/html/rfc4880
|
15
|
-
* http://www.openpgp.org
|
17
|
+
* <http://tools.ietf.org/html/rfc4880>
|
18
|
+
* <http://www.openpgp.org/>
|
16
19
|
|
17
|
-
|
18
|
-
|
20
|
+
Features
|
21
|
+
--------
|
19
22
|
|
20
23
|
* Encodes and decodes ASCII-armored OpenPGP messages.
|
21
24
|
* Parses OpenPGP messages into their constituent packets.
|
22
25
|
* Supports both old-format (PGP 2.6.x) and new-format (RFC 4880) packets.
|
23
26
|
* Includes a GnuPG wrapper for features that are not natively supported.
|
24
27
|
|
28
|
+
Examples
|
29
|
+
--------
|
25
30
|
|
26
|
-
|
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
|
31
|
+
require 'openpgp'
|
35
32
|
|
36
|
-
|
33
|
+
### Decoding an ASCII-armored message
|
37
34
|
|
35
|
+
require 'open-uri'
|
36
|
+
text = open('http://ar.to/pgp.txt').read
|
38
37
|
|
39
|
-
|
38
|
+
msg = OpenPGP::Message.parse(OpenPGP.dearmor(text))
|
40
39
|
|
41
|
-
|
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
|
-
})
|
40
|
+
### Generating a new keypair
|
52
41
|
|
42
|
+
gpg = OpenPGP::Engine::GnuPG.new(:homedir => '~/.gnupg')
|
43
|
+
key_id = gpg.gen_key({
|
44
|
+
:key_type => 'DSA',
|
45
|
+
:key_length => 1024,
|
46
|
+
:subkey_type => 'ELG-E',
|
47
|
+
:subkey_length => 1024,
|
48
|
+
:name => 'J. Random Hacker',
|
49
|
+
:comment => nil,
|
50
|
+
:email => 'jhacker@example.org',
|
51
|
+
:passphrase => 'secret passphrase',
|
52
|
+
})
|
53
53
|
|
54
|
-
|
54
|
+
Documentation
|
55
|
+
-------------
|
55
56
|
|
56
|
-
* http://openpgp.rubyforge.org
|
57
|
+
* <http://openpgp.rubyforge.org/>
|
57
58
|
|
58
|
-
|
59
|
-
|
59
|
+
Download
|
60
|
+
--------
|
60
61
|
|
61
62
|
To get a local working copy of the development repository, do:
|
62
63
|
|
63
|
-
|
64
|
+
% git clone git://github.com/bendiken/openpgp.git
|
64
65
|
|
65
66
|
Alternatively, you can download the latest development version as a tarball
|
66
67
|
as follows:
|
67
68
|
|
68
|
-
|
69
|
-
|
69
|
+
% wget http://github.com/bendiken/openpgp/tarball/master
|
70
70
|
|
71
|
-
|
71
|
+
Installation
|
72
|
+
------------
|
72
73
|
|
73
74
|
The recommended installation method is via RubyGems. To install the latest
|
74
|
-
official release from
|
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:
|
75
|
+
official release from Gemcutter, do:
|
80
76
|
|
81
|
-
|
77
|
+
% [sudo] gem install openpgp
|
82
78
|
|
79
|
+
Resources
|
80
|
+
---------
|
83
81
|
|
84
|
-
|
82
|
+
* <http://openpgp.rubyforge.org/>
|
83
|
+
* <http://github.com/bendiken/openpgp>
|
84
|
+
* <http://gemcutter.org/gems/crm114>
|
85
|
+
* <http://rubyforge.org/projects/openpgp>
|
86
|
+
* <http://raa.ruby-lang.org/project/openpgp/>
|
87
|
+
* <http://www.ohloh.net/p/openpgp>
|
85
88
|
|
86
|
-
|
89
|
+
Authors
|
90
|
+
-------
|
87
91
|
|
92
|
+
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
|
93
|
+
* [Kévin Lacointe](mailto:kevinlacointe@gmail.com)
|
88
94
|
|
89
|
-
|
95
|
+
License
|
96
|
+
-------
|
90
97
|
|
91
|
-
|
92
|
-
information, see the accompanying
|
98
|
+
OpenPGP.rb is free and unencumbered public domain software. For more
|
99
|
+
information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
|
data/UNLICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/lib/openpgp.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
require 'openpgp/version'
|
2
|
-
require 'openpgp/
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
require 'openpgp/util'
|
3
|
+
|
4
|
+
module OpenPGP
|
5
|
+
autoload :Algorithm, 'openpgp/algorithm'
|
6
|
+
autoload :Armor, 'openpgp/armor'
|
7
|
+
autoload :Buffer, 'openpgp/buffer'
|
8
|
+
autoload :Cipher, 'openpgp/cipher'
|
9
|
+
autoload :Engine, 'openpgp/engine'
|
10
|
+
autoload :Digest, 'openpgp/digest'
|
11
|
+
autoload :Message, 'openpgp/message'
|
12
|
+
autoload :Packet, 'openpgp/packet'
|
13
|
+
autoload :Random, 'openpgp/random'
|
14
|
+
autoload :S2K, 'openpgp/s2k'
|
15
|
+
end
|
16
|
+
|
17
|
+
OpenPGP::Engine::OpenSSL.install!
|
data/lib/openpgp/algorithm.rb
CHANGED
data/lib/openpgp/armor.rb
CHANGED
@@ -1,16 +1,4 @@
|
|
1
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
2
|
##
|
15
3
|
# OpenPGP ASCII Armor utilities.
|
16
4
|
#
|
@@ -23,91 +11,109 @@ module OpenPGP
|
|
23
11
|
PUBLIC_KEY_BLOCK = 'PUBLIC KEY BLOCK'
|
24
12
|
PRIVATE_KEY_BLOCK = 'PRIVATE KEY BLOCK'
|
25
13
|
SIGNATURE = 'SIGNATURE'
|
14
|
+
ARMORED_FILE = 'ARMORED FILE' # a GnuPG extension
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.marker(marker)
|
18
|
+
marker = Markers.const_get(marker.to_s.upcase.to_sym) if marker.is_a?(Symbol)
|
19
|
+
marker.to_s.upcase
|
26
20
|
end
|
27
21
|
|
28
22
|
##
|
29
23
|
# @see http://tools.ietf.org/html/rfc4880#section-6.2
|
30
24
|
def self.header(marker)
|
31
|
-
"-----BEGIN PGP #{marker
|
25
|
+
"-----BEGIN PGP #{marker(marker)}-----"
|
32
26
|
end
|
33
27
|
|
34
28
|
##
|
35
29
|
# @see http://tools.ietf.org/html/rfc4880#section-6.2
|
36
30
|
def self.footer(marker)
|
37
|
-
"-----END PGP #{marker
|
31
|
+
"-----END PGP #{marker(marker)}-----"
|
38
32
|
end
|
39
33
|
|
40
34
|
##
|
41
35
|
# @see http://tools.ietf.org/html/rfc4880#section-6
|
42
36
|
# @see http://tools.ietf.org/html/rfc4880#section-6.2
|
43
37
|
# @see http://tools.ietf.org/html/rfc2045
|
44
|
-
def self.encode(data, marker =
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
38
|
+
def self.encode(data, marker = :message, options = {})
|
39
|
+
Buffer.write do |text|
|
40
|
+
text << self.header(marker) << "\n"
|
41
|
+
text << "Version: #{options[:version]}\n" if options[:version]
|
42
|
+
text << "Comment: #{options[:comment]}\n" if options[:comment]
|
43
|
+
if options[:headers]
|
44
|
+
options[:headers].each { |key, value| text << "#{key}: #{value}\n" }
|
45
|
+
end
|
46
|
+
text << "\n" << encode64(data, options[:line_length])
|
47
|
+
text << "=" << encode64([OpenPGP.crc24(data)].pack('N')[1, 3])
|
48
|
+
text << self.footer(marker) << "\n"
|
49
|
+
end
|
55
50
|
end
|
56
51
|
|
57
52
|
##
|
58
53
|
# @see http://tools.ietf.org/html/rfc4880#section-6
|
59
54
|
# @see http://tools.ietf.org/html/rfc2045
|
60
|
-
def self.decode(text, marker = nil)
|
61
|
-
|
62
|
-
require 'base64'
|
55
|
+
def self.decode(text, marker = nil, options = {})
|
56
|
+
data, crc, state = Buffer.new, nil, :begin
|
63
57
|
|
64
|
-
data, crc, state = StringIO.new, nil, :begin
|
65
58
|
text.each_line do |line|
|
66
59
|
line.chomp!
|
67
60
|
case state
|
68
61
|
when :begin
|
69
62
|
case line
|
70
63
|
when /^-----BEGIN PGP ([^-]+)-----$/
|
71
|
-
state = :head if marker.nil? || marker
|
64
|
+
state = :head if marker.nil? || marker(marker) == $1
|
72
65
|
end
|
73
66
|
when :head
|
74
67
|
state = :body if line =~ /^\s*$/
|
75
68
|
when :body
|
76
69
|
case line
|
77
70
|
when /^=(....)$/
|
78
|
-
crc = ("\0" <<
|
71
|
+
crc = ("\0" << decode64($1)).unpack('N').first
|
79
72
|
state = :end
|
80
73
|
when /^-----END PGP ([^-]+)-----$/
|
81
74
|
state = :end
|
82
75
|
else
|
83
|
-
data <<
|
76
|
+
data << decode64(line)
|
84
77
|
end
|
85
78
|
when :end
|
86
79
|
break
|
87
80
|
end
|
88
81
|
end
|
89
|
-
|
82
|
+
|
83
|
+
data = data.string
|
84
|
+
if options[:crc] && crc != (crc_data = OpenPGP.crc24(data))
|
85
|
+
raise CRCError.new("ASCII armor says 0x#{crc.to_s(16)}, but data has 0x#{crc_data.to_s(16)}")
|
86
|
+
end
|
87
|
+
data
|
90
88
|
end
|
91
89
|
|
92
|
-
|
93
|
-
# @see http://tools.ietf.org/html/rfc4880#section-6.1
|
94
|
-
CRC24_INIT = 0x00b704ce
|
95
|
-
CRC24_POLY = 0x01864cfb
|
90
|
+
class CRCError < IOError; end
|
96
91
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
92
|
+
protected
|
93
|
+
|
94
|
+
##
|
95
|
+
# Returns the Base64-encoded version of +input+, with a configurable
|
96
|
+
# output line length.
|
97
|
+
def self.encode64(input, line_length = nil)
|
98
|
+
if line_length.nil?
|
99
|
+
[input].pack('m')
|
100
|
+
elsif line_length % 4 == 0
|
101
|
+
[input].pack("m#{(line_length / 4) * 3}")
|
102
|
+
else
|
103
|
+
output = []
|
104
|
+
[input].pack('m').delete("\n").scan(/.{1,#{line_length}}/) do
|
105
|
+
output << $&
|
106
|
+
end
|
107
|
+
output << ''
|
108
|
+
output.join("\n")
|
107
109
|
end
|
108
110
|
end
|
109
|
-
|
110
|
-
|
111
|
+
|
112
|
+
##
|
113
|
+
# Returns the Base64-decoded version of +input+.
|
114
|
+
def self.decode64(input)
|
115
|
+
input.unpack('m').first
|
116
|
+
end
|
111
117
|
end
|
112
118
|
|
113
119
|
include Armor::Markers
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module OpenPGP
|
4
|
+
##
|
5
|
+
class Buffer < StringIO
|
6
|
+
def self.write(*args, &block)
|
7
|
+
buffer = self.new(*args, &block)
|
8
|
+
buffer.string
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(*args, &block)
|
12
|
+
super
|
13
|
+
block.call(self) if block_given?
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
def read_string
|
18
|
+
read_bytes(length = read_byte)
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
def write_string(value)
|
23
|
+
value = value.to_s
|
24
|
+
self << [value.size].pack('C')
|
25
|
+
self << value unless value.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @see http://tools.ietf.org/html/rfc4880#section-3.5
|
30
|
+
def read_timestamp
|
31
|
+
read_unpacked(4, 'N')
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# @see http://tools.ietf.org/html/rfc4880#section-3.5
|
36
|
+
def write_timestamp(value)
|
37
|
+
self << [value.to_i].pack('N')
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# @see http://tools.ietf.org/html/rfc4880#section-3.1
|
42
|
+
def read_number(count, base = nil)
|
43
|
+
number, shift = 0, count * 8
|
44
|
+
read_bytes(count).each_byte do |octet|
|
45
|
+
number += octet << (shift -= 8)
|
46
|
+
end
|
47
|
+
!base ? number : number.to_s(base).upcase
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# @see http://tools.ietf.org/html/rfc4880#section-3.1
|
52
|
+
def write_number
|
53
|
+
# TODO
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# @see http://tools.ietf.org/html/rfc4880#section-3.2
|
58
|
+
def read_mpi
|
59
|
+
length = read_unpacked(2, 'n') # length in bits
|
60
|
+
length = ((length + 7) / 8.0).floor # length in bytes
|
61
|
+
read_bytes(length)
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# @see http://tools.ietf.org/html/rfc4880#section-3.2
|
66
|
+
def write_mpi
|
67
|
+
# TODO
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# @see http://tools.ietf.org/html/rfc4880#section-3.7
|
72
|
+
def read_s2k() S2K.parse(self) end
|
73
|
+
|
74
|
+
def write_s2k(s2k) s2k.write(self) end
|
75
|
+
|
76
|
+
def read_unpacked(count, format)
|
77
|
+
read_bytes(count).unpack(format).first
|
78
|
+
end
|
79
|
+
|
80
|
+
def write_unpacked
|
81
|
+
# TODO
|
82
|
+
end
|
83
|
+
|
84
|
+
def read_bytes(count)
|
85
|
+
read(count)
|
86
|
+
end
|
87
|
+
|
88
|
+
def write_bytes(value)
|
89
|
+
self << value
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_byte
|
93
|
+
getc
|
94
|
+
end
|
95
|
+
|
96
|
+
def write_byte(value)
|
97
|
+
self << (value.respond_to?(:chr) ? value : value.to_s[0]).chr
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|