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 ADDED
@@ -0,0 +1,2 @@
1
+ * Arto Bendiken <arto.bendiken@gmail.com> (Lead developer)
2
+ * Kévin Lacointe <kevinlacointe@gmail.com> (Some GnuPG patches)
data/README CHANGED
@@ -1,9 +1,12 @@
1
- = OpenPGP.rb
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
- === About OpenPGP
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
- == Features
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
- == 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
31
+ require 'openpgp'
35
32
 
36
- msg = OpenPGP::Message.parse(OpenPGP.dearmor(text))
33
+ ### Decoding an ASCII-armored message
37
34
 
35
+ require 'open-uri'
36
+ text = open('http://ar.to/pgp.txt').read
38
37
 
39
- === Generating a new keypair
38
+ msg = OpenPGP::Message.parse(OpenPGP.dearmor(text))
40
39
 
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
- })
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
- == Documentation
54
+ Documentation
55
+ -------------
55
56
 
56
- * http://openpgp.rubyforge.org
57
+ * <http://openpgp.rubyforge.org/>
57
58
 
58
-
59
- == Download
59
+ Download
60
+ --------
60
61
 
61
62
  To get a local working copy of the development repository, do:
62
63
 
63
- % git clone git://github.com/bendiken/openpgp.git
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
- % wget http://github.com/bendiken/openpgp/tarball/master
69
-
69
+ % wget http://github.com/bendiken/openpgp/tarball/master
70
70
 
71
- == Installation
71
+ Installation
72
+ ------------
72
73
 
73
74
  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:
75
+ official release from Gemcutter, do:
80
76
 
81
- % [sudo] gem install bendiken-openpgp -s http://gems.github.com
77
+ % [sudo] gem install openpgp
82
78
 
79
+ Resources
80
+ ---------
83
81
 
84
- == Authors
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
- * Arto Bendiken (mailto:arto.bendiken@gmail.com) - http://ar.to
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
- == License
95
+ License
96
+ -------
90
97
 
91
- All source code is available under the terms of the MIT license. For more
92
- information, see the accompanying LICENSE file.
98
+ OpenPGP.rb is free and unencumbered public domain software. For more
99
+ information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
@@ -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
1
+ 0.0.2
@@ -1,6 +1,17 @@
1
1
  require 'openpgp/version'
2
- require 'openpgp/armor'
3
- require 'openpgp/message'
4
- require 'openpgp/packet'
5
- require 'openpgp/algorithm'
6
- require 'openpgp/gnupg'
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!
@@ -44,7 +44,7 @@ module OpenPGP
44
44
  # OpenPGP hash algorithms.
45
45
  #
46
46
  # @see http://tools.ietf.org/html/rfc4880#section-9.4
47
- module Hash
47
+ module Digest
48
48
  MD5 = 1
49
49
  SHA1 = 2
50
50
  RIPEMD160 = 3
@@ -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.to_s.upcase}-----"
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.to_s.upcase}-----"
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 = '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
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
- require 'stringio'
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.to_s.upcase == $1
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" << Base64.decode64($1)).unpack('N').first
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 << Base64.decode64(line)
76
+ data << decode64(line)
84
77
  end
85
78
  when :end
86
79
  break
87
80
  end
88
81
  end
89
- data.string
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
- # @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?
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
- crc &= 0x00ffffff
110
- end
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