akero 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.swp
2
+ .rbenv-version
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.8.7"
4
+ - "1.9.2"
5
+ - "1.9.3"
6
+ - jruby-18mode # JRuby in 1.8 mode
7
+ - jruby-19mode # JRuby in 1.9 mode
8
+ - rbx-18mode
9
+ - rbx-19mode
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in smime.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,18 @@
1
+ my_code = <<'RUBY_CODE'
2
+ if type == 'failed'
3
+ system "ssh 192.168.1.106 '~/bin/keyboard_leds -c1 >/dev/null'"
4
+ system "ssh 192.168.1.106 'afplay ~/.fail.wav'"
5
+ end
6
+ if type == 'success'
7
+ system "ssh 192.168.1.106 '~/bin/keyboard_leds -c0 >/dev/null'"
8
+ system "ssh 192.168.1.106 'afplay ~/.success.wav'"
9
+ end
10
+ RUBY_CODE
11
+
12
+ notification :eval_notifier, :code => my_code
13
+
14
+ guard 'rspec', :cli => '--color --format doc' do
15
+ watch(%r{^spec/.+_spec\.rb$})
16
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
17
+ watch('spec/spec_helper.rb') { "spec" }
18
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 moe@busyloop.net
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
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
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Akero [![Build Status](https://travis-ci.org/busyloop/akero.png?branch=master)](https://travis-ci.org/busyloop/akero) [![Dependency Status](https://gemnasium.com/busyloop/akero.png)](https://gemnasium.com/busyloop/akero)
2
+
3
+ Akero ([ἄγγελος](http://en.wiktionary.org/wiki/%F0%90%80%80%F0%90%80%90%F0%90%80%AB), messenger) is an easy-to-use library for peer-to-peer [public key cryptography](http://en.wikipedia.org/wiki/Public-key_cryptography). It enables two or more endpoints to exchange encrypted and/or signed messages without requiring a pre-shared secret.
4
+
5
+ Under the hood Akero uses standard OpenSSL primitives. Each instance wraps a [RSA](http://en.wikipedia.org/wiki/RSA)-keypair, a corresponding [X.509 certificate](http://en.wikipedia.org/wiki/X.509) and exchanges self-signed messages ([PKCS#7](https://tools.ietf.org/html/rfc2315)) with other instances.
6
+
7
+ Akero does not try to be a substitute for a fully featured [PKI](http://en.wikipedia.org/wiki/Public_key_infrastructure). It is meant to be used as a building block in scenarios where trust-relationships and keyrings can be externally managed, and where the complexity of traditional solutions (X.509 PKI, OpenPGP, homegrown RSA) yields no tangible benefits.
8
+
9
+ ## Features
10
+
11
+ * Secure 1-to-n messaging (sign-only -or- sign->encrypt->sign)
12
+ * Low complexity; easy to use, understand and review (only 166 lines of code)
13
+ * Transport agnostic; messages and certificates are self-contained, ascii-armored (base64)
14
+ * Built on standard OpenSSL primitives, no homegrown algorithms
15
+ * [100%](https://busyloop.net/oss/akero/coverage/) test coverage
16
+
17
+
18
+ ## Usage
19
+
20
+ ```ruby
21
+ # Alice, Bob and Charlie are Akero instances
22
+ alice = Akero.new
23
+ bob = Akero.new
24
+ charlie = Akero.new
25
+
26
+ # Inspect Alice's keypair fingerprint
27
+ alice.id # => "AK:12:34:56:..."
28
+
29
+ # Alice signs a message
30
+ signed_msg = alice.sign("Hello world!")
31
+
32
+ # Anyone can receive this message and extract
33
+ # Alice's fingerprint and public key from it
34
+ msg = bob.receive(signed_msg)
35
+ msg.body # => "Hello world!"
36
+ msg.from # => "AK:12:34:56:..."
37
+ msg.from_pk # => "(alice-public-key)"
38
+
39
+ # Bob encrypts a message for Alice
40
+ bobs_msg = bob.encrypt(msg.from_pk, "Hello Alice!")
41
+
42
+ # Alice can receive it...
43
+ msg = alice.receive(bobs_msg)
44
+ msg.body # => "Hello Alice!"
45
+ msg.from # => "AK:ab:cd:ef:..."
46
+ msg.from_pk # => "(bob-public-key)"
47
+
48
+ # ...and Charlie can't
49
+ msg = charlie.receive(bobs_msg) # => Exception is raised
50
+
51
+ # Alice encrypts a message for Bob and Charlie
52
+ msg = alice.encrypt([bob.public_key, charlie.public_key], "Hello!")
53
+
54
+ # Save Alice to a file
55
+ File.open('/tmp/alice.akr', 'w') { |f| f.write(alice.private_key) }
56
+
57
+ # And load her again
58
+ new_alice = Akero.load(File.read('/tmp/alice.akr'))
59
+
60
+ ```
61
+
62
+ ## Documentation
63
+
64
+ * [API Docs](http://rubydoc.info/gems/akero/Akero)
65
+ * [Spec](https://github.com/busyloop/akero/blob/master/spec/akero_spec.rb)
66
+
67
+
68
+ ## Benchmarks
69
+
70
+ ![Throughput](http://github.com/busyloop/akero/raw/master/benchmark/bm_rate.png)
71
+ ![Message size](http://github.com/busyloop/akero/raw/master/benchmark/bm_size.png)
72
+
73
+ The above charts were generated on an [AMD Turion II Neo N40L CPU](http://www.cpubenchmark.net/cpu.php?cpu=AMD+Turion+II+Neo+N40L+Dual-Core).
74
+ You may run the benchmarks on your own machine with `rake benchmark`.
75
+
76
+ ## License (MIT)
77
+
78
+ Copyright (c) 2012 moe@busyloop.net
79
+
80
+ Permission is hereby granted, free of charge, to any person obtaining
81
+ a copy of this software and associated documentation files (the
82
+ "Software"), to deal in the Software without restriction, including
83
+ without limitation the rights to use, copy, modify, merge, publish,
84
+ distribute, sublicense, and/or sell copies of the Software, and to
85
+ permit persons to whom the Software is furnished to do so, subject to
86
+ the following conditions:
87
+
88
+ The above copyright notice and this permission notice shall be
89
+ included in all copies or substantial portions of the Software.
90
+
91
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
92
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
93
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
94
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
95
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
96
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
97
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
98
+
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ task :default => :test
6
+
7
+ RSpec::Core::RakeTask.new("test:spec") do |t|
8
+ t.pattern = 'spec/**/*_spec.rb'
9
+ t.rspec_opts = '--fail-fast -b -c -f documentation --tag ~benchmark'
10
+ end
11
+
12
+ desc 'Run test suite'
13
+ task :test => [ 'test:spec' ]
14
+
15
+ desc "Run benchmark suite"
16
+ task :benchmark do
17
+ require 'akero/benchmark'
18
+ Akero::Benchmark.run!
19
+ end
20
+
21
+ namespace :docs do
22
+ task :push do
23
+ `bl_www_sync akero coverage`
24
+ end
25
+ end
data/akero.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'akero/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "akero"
8
+ gem.version = Akero::VERSION
9
+ gem.authors = ["Moe"]
10
+ gem.email = ["moe@busyloop.net"]
11
+ gem.description = %q{Easy peer-to-peer public key cryptography}
12
+ gem.summary = %q{Easy peer-to-peer public key cryptography}
13
+ gem.homepage = "https://github.com/busyloop/akero"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency 'rb-inotify'
21
+ gem.add_development_dependency 'guard'
22
+ gem.add_development_dependency 'guard-rspec'
23
+ gem.add_development_dependency 'rake'
24
+ gem.add_development_dependency 'simplecov'
25
+ gem.add_development_dependency 'rspec'
26
+ gem.add_development_dependency 'b'
27
+ gem.add_development_dependency 'gnuplot'
28
+ gem.add_development_dependency 'yard'
29
+ end
Binary file
Binary file
data/lib/akero.rb ADDED
@@ -0,0 +1,361 @@
1
+ # Copyright (c) 2012 moe@busyloop.net
2
+ #
3
+ # MIT License
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
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
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ require "akero/version"
25
+
26
+ require 'openssl'
27
+ require 'base64'
28
+
29
+ # Akero is an easy-to-use library for peer-to-peer public key cryptography.
30
+ class Akero
31
+ # Akero::Message wraps a received message.
32
+ class Message
33
+ # @return [String] Message body
34
+ attr_reader :body
35
+ # @return [Symbol] Message type (:signed or :encrypted)
36
+ attr_reader :type
37
+
38
+ # @private
39
+ def initialize(body, signer_cert, type)
40
+ @body, @signer_cert, @type = body, signer_cert, type
41
+ end
42
+
43
+ # @private
44
+ def inspect
45
+ "#<Akero::Message @type=#{@type} @from=#{from} @body=(#{@body.length} bytes)>"
46
+ end
47
+
48
+ # @!attribute [r] from
49
+ # @return [String] Sender Fingerprint
50
+ def from
51
+ Akero.fingerprint_from_cert(@signer_cert)
52
+ end
53
+
54
+ # @!attribute [r] from_pk
55
+ # @return [String] Sender public key
56
+ def from_pk
57
+ Akero.replate(@signer_cert.to_s, PLATE_CERT)
58
+ end
59
+ end
60
+ end
61
+
62
+ class Akero
63
+ ERR_MSG_MALFORMED_ENV = "Malformed message: Could not parse envelope" # @private
64
+ ERR_MSG_MALFORMED_BODY = "Malformed message: Could not parse body; POSSIBLE SPOOF ATTEMPT" # @private
65
+ ERR_PKEY_CORRUPT = "Invalid private key (checksum mismatch)" # @private
66
+ ERR_CERT_CORRUPT = "Invalid certificate" # @private
67
+ ERR_INVALID_RECIPIENT = "Invalid recipient (must be a String)" # @private
68
+ ERR_INVALID_RECIPIENT_CERT = "Invalid recipient (corrupt public key?)" # @private
69
+ ERR_DECRYPT = "Could not decrypt message" # @private
70
+ ERR_MSG_NOT_STRING_NOR_PKCS7 = "Message must be of type String or OpenSSL::PKCS7" # @private
71
+ ERR_MSG_CORRUPT_CERT = "Malformed message: Embedded certificate could not be verified; POSSIBLE SPOOF ATTEMPT!" # @private
72
+ ERR_MSG_TOO_MANY_SIGNERS = "Corrupt message: Zero or multiple signers, expected exactly 1; POSSIBLE SPOOF ATTEMPT" # @private
73
+ ERR_MSG_SIG_MISMATCH = "Inner signature does not match outer signature; POSSIBLE SPOOF ATTEMPT" # @private
74
+
75
+ PKEY_HEADER = "-----BEGIN AKERO PRIVATE KEY-----\n" # @private
76
+ PKEY_FOOTER = "-----END AKERO PRIVATE KEY-----\n" # @private
77
+ PLATE_CERT = ['CERTIFICATE','AKERO PUBLIC KEY'] # @private
78
+ PLATE_SIGNED = ['PKCS7', 'AKERO SIGNED MESSAGE'] # @private
79
+ PLATE_CRYPTED = ['PKCS7', 'AKERO SECRET MESSAGE'] # @private
80
+
81
+ DEFAULT_RSA_BITS = 2048
82
+
83
+ # Unique fingerprint of this Akero keypair.
84
+ #
85
+ # @return [String] Akero fingerprint
86
+ def id
87
+ Akero.fingerprint_from_cert(@cert)
88
+ end
89
+
90
+ # Create a new Akero instance.
91
+ #
92
+ # @example Create new Akero instance with default settings
93
+ # Akero.new
94
+ #
95
+ # @example Create new Akero instance with a 4096-bit key
96
+ # Akero.new(4096)
97
+ #
98
+ # @example Create new Akero instance with a 4096-bit key and SHA512 digest
99
+ # Akero.new(4096, OpenSSL::Digest::SHA512)
100
+ #
101
+ # @param [Integer] rsa_bits RSA key length
102
+ # @param [OpenSSL::Digest] sign_digest Signature digest
103
+ # @return [Akero] New Akero instance
104
+ def initialize(rsa_bits=DEFAULT_RSA_BITS, sign_digest=OpenSSL::Digest::SHA512)
105
+ @key, @cert = generate_keypair(rsa_bits, sign_digest) unless rsa_bits.nil?
106
+ end
107
+
108
+ # Load an Akero identity.
109
+ #
110
+ # @example Load previously stored private key
111
+ # Akero.load(File.read('/tmp/alice.akr'))
112
+ #
113
+ # @param [String] private_key Akero private key
114
+ # @return [Akero] New Akero instance
115
+ def self.load(private_key)
116
+ inner = Base64.decode64(private_key[PKEY_HEADER.length..private_key.length-PKEY_FOOTER.length])
117
+ if inner[0..63] != OpenSSL::Digest::SHA512.new(inner[64..-1]).digest
118
+ raise RuntimeError, ERR_PKEY_CORRUPT
119
+ end
120
+ cert_len = inner[64..65].unpack('S')[0]
121
+ akero = Akero.new(nil)
122
+ akero.instance_variable_set(:@cert, OpenSSL::X509::Certificate.new(inner[66..66+cert_len]))
123
+ akero.instance_variable_set(:@key, OpenSSL::PKey::RSA.new(inner[66+cert_len..-1]))
124
+ akero
125
+ end
126
+
127
+ # Akero public key.
128
+ #
129
+ # Share this with other Akero instances that you
130
+ # wish to receive encrypted messages from.
131
+ #
132
+ # @return [String] Public key (ascii armored)
133
+ def public_key
134
+ Akero::replate(@cert.to_s, Akero::PLATE_CERT)
135
+ end
136
+
137
+ # Private key (do not share this with anyone!)
138
+ #
139
+ # @example Save and load an Akero identity
140
+ # alice = Akero.new
141
+ # # Save
142
+ # File.open('/tmp/alice.akr', 'w') { |f| f.write(alice.private_key) }
143
+ # # Load
144
+ # new_alice = Akero.load(File.read('/tmp/alice.akr'))
145
+ #
146
+ # @return [String] Private key (ascii armored)
147
+ # @see Akero#load
148
+ def private_key
149
+ # We do not use PKCS#12 ("PFX") for serialization here
150
+ # because of http://www.cs.auckland.ac.nz/~pgut001/pubs/pfx.html
151
+ cert_der = @cert.to_der
152
+ out = [cert_der.length].pack('S')
153
+ out << cert_der
154
+ out << @key.to_der
155
+ out.prepend(OpenSSL::Digest::SHA512.new(out).digest)
156
+ PKEY_HEADER+Base64.encode64(out)+PKEY_FOOTER
157
+ end
158
+
159
+ # Sign a message.
160
+ #
161
+ # @param [String] plaintext The message to sign (binary safe)
162
+ # @return [String] Akero signed message
163
+ def sign(plaintext)
164
+ Akero.replate(_sign(plaintext).to_s, Akero::PLATE_SIGNED)
165
+ end
166
+
167
+ # Sign->encrypt->sign a message for 1 or more recipients.
168
+ #
169
+ # Only the listed recipients can decrypt the message-body
170
+ # but anyone can extract the sender's public key.
171
+ #
172
+ # @example Alice encrypts a message to Bob
173
+ # alice = Akero.new
174
+ # bob = Akero.new
175
+ # ciphertext = alice.encrypt(bob.public_key, "Hello Bob!")
176
+ #
177
+ # @example Alice encrypts a message to Bob and Charlie
178
+ # alice = Akero.new
179
+ # bob = Akero.new
180
+ # charlie = Akero.new
181
+ # ciphertext = alice.encrypt([bob.public_key, charlie.public_key], "Hello Bob and Charlie!")
182
+ #
183
+ # @param [Array] to Akero public keys of recipients
184
+ # @param [String] plaintext The message to encrypt (binary safe)
185
+ # @return [String] Akero secret message
186
+ def encrypt(to, plaintext)
187
+ to = [to] unless to.is_a? Array
188
+ to = to.map { |e|
189
+ case e
190
+ when String
191
+ begin
192
+ OpenSSL::X509::Certificate.new(Akero.replate(e, Akero::PLATE_CERT, true))
193
+ rescue OpenSSL::X509::CertificateError
194
+ raise RuntimeError, ERR_INVALID_RECIPIENT_CERT
195
+ end
196
+ else
197
+ raise RuntimeError, ERR_INVALID_RECIPIENT
198
+ end
199
+ }
200
+ Akero.replate(_sign(_encrypt(to, _sign(plaintext))).to_s, PLATE_CRYPTED)
201
+ end
202
+
203
+ # Receive an Akero message.
204
+ #
205
+ # @param [String] ciphertext Akero Message
206
+ # @return [Akero::Message] Message_body, signer_certificate, body_type
207
+ def receive(ciphertext)
208
+ ciphertext = Akero.replate(ciphertext, Akero::PLATE_CRYPTED, true)
209
+ begin
210
+ body, outer_signer_cert, body_type = verify(ciphertext)
211
+ rescue ArgumentError
212
+ raise RuntimeError, ERR_MSG_MALFORMED_ENV
213
+ end
214
+
215
+ case body_type.ord
216
+ when 0x00
217
+ # public message (signed)
218
+ return Message.new(body, outer_signer_cert, :signed)
219
+ when 0x01
220
+ # private message (signed, crypted, signed)
221
+ signed_plaintext = _decrypt(body)
222
+ plaintext, inner_signer_cert, body_type = verify(signed_plaintext)
223
+ msg = Message.new(plaintext, inner_signer_cert, :encrypted)
224
+
225
+ if msg.from != Akero.fingerprint_from_cert(outer_signer_cert)
226
+ raise RuntimeError, ERR_MSG_SIG_MISMATCH
227
+ end
228
+ return msg
229
+ end
230
+ raise RuntimeError, ERR_MSG_MALFORMED_BODY
231
+ end
232
+
233
+ # @private
234
+ def inspect
235
+ "#<Akero id=#{id}>"
236
+ end
237
+
238
+ # @private
239
+ def to_s
240
+ inspect
241
+ end
242
+
243
+ #---------------------------------------------------------------------------
244
+ protected
245
+
246
+ # Swap the "license plates" on an ascii-armored message.
247
+ # This is done for user-friendliness, so stored Akero
248
+ # messages and keys can be easily identified at a glance.
249
+ #
250
+ # @param [String] msg Message to be replated
251
+ # @param [Array] plates Array of the two plates to swap
252
+ # @param [Boolean] reverse Reverse the swap?
253
+ # @return [String] The replated message
254
+ def self.replate(msg, plates, reverse=false)
255
+ a,b = reverse ? [1,0] : [0,1]
256
+ "-----BEGIN #{plates[b]}-----#{msg.strip[plates[a].length+16..-(plates[a].length+15)]}-----END #{plates[b]}-----\n"
257
+ end
258
+
259
+ # Extract fingerprint from an Akero public key.
260
+ #
261
+ # @return [String] Akero fingerprint
262
+ def self.fingerprint_from_cert(cert)
263
+ cert.extensions.map.each do |e|
264
+ return "AK:#{e.value}" if e.oid == 'subjectKeyIdentifier'
265
+ end
266
+ raise RuntimeError, ERR_CERT_CORRUPT
267
+ end
268
+
269
+ #---------------------------------------------------------------------------
270
+ private
271
+
272
+ def _decrypt(crypted_msg)
273
+ begin
274
+ OpenSSL::PKCS7.new(crypted_msg).decrypt(@key, @cert)
275
+ rescue OpenSSL::PKCS7::PKCS7Error, "decrypt error"
276
+ raise RuntimeError, ERR_DECRYPT
277
+ end
278
+ end
279
+
280
+ # Encrypt a message for one ore more recipients.
281
+ #
282
+ # @return [Array] Message_body, signer_certificate, body_type
283
+ def _encrypt(to, msg, cipher=nil)
284
+ cipher ||= OpenSSL::Cipher::new("AES-256-CFB")
285
+ OpenSSL::PKCS7::encrypt(to, msg.to_der, cipher, OpenSSL::PKCS7::BINARY)
286
+ end
287
+
288
+ # Sign a message.
289
+ #
290
+ # @overload _sign(message)
291
+ # Sign a message.
292
+ # @param [String] message Message
293
+ # @return [OpenSSL::PKCS7] Signed message
294
+ # @overload _sign(message)
295
+ # Sign a message.
296
+ # @param [OpenSSL::PKCS7] message Message
297
+ # @return [OpenSSL::PKCS7] Signed message
298
+ def _sign(message)
299
+ case message
300
+ when String
301
+ type = 0x00
302
+ when OpenSSL::PKCS7
303
+ type = 0x01
304
+ else
305
+ raise RuntimeError, ERR_MSG_NOT_STRING_NOR_PKCS7
306
+ end
307
+ message = message.to_der if message.is_a? OpenSSL::PKCS7
308
+ OpenSSL::PKCS7::sign(@cert, @key, type.chr + message, [], OpenSSL::PKCS7::BINARY)
309
+ end
310
+
311
+ # Verify a signed message against its embedded certificate.
312
+ #
313
+ # @return [Array] message_body, signer_certificate, body_type
314
+ def verify(signed_msg)
315
+ signed_msg = OpenSSL::PKCS7.new(signed_msg) if signed_msg.is_a? String
316
+ store = OpenSSL::X509::Store.new
317
+ if signed_msg.certificates.nil? or signed_msg.certificates.length != 1
318
+ raise RuntimeError, ERR_MSG_TOO_MANY_SIGNERS
319
+ end
320
+
321
+ signer_cert = signed_msg.certificates[0]
322
+ unless signed_msg.verify([signer_cert], store, nil, OpenSSL::PKCS7::NOINTERN | OpenSSL::PKCS7::NOVERIFY)
323
+ raise RuntimeError, ERR_MSG_CORRUPT_CERT
324
+ end
325
+
326
+ [signed_msg.data[1..-1], signer_cert, signed_msg.data[0]]
327
+ end
328
+
329
+ # Generate new RSA keypair and certificate.
330
+ #
331
+ # @param [Integer] rsa_bits RSA key length
332
+ # @param [OpenSSL::Digest] sign_digest Signature digest
333
+ # @return [Array] rsa_keypair, certificate
334
+ def generate_keypair(rsa_bits=DEFAULT_RSA_BITS, sign_digest=OpenSSL::Digest::SHA512)
335
+ cn = "Akero #{Akero::VERSION}"
336
+ rsa = OpenSSL::PKey::RSA.new(rsa_bits)
337
+
338
+ cert = OpenSSL::X509::Certificate.new
339
+ cert.version = 3
340
+ cert.serial = rand(2**42)
341
+ name = OpenSSL::X509::Name.parse("/CN=#{cn}")
342
+ cert.subject = name
343
+ cert.issuer = name
344
+ cert.not_before = Time.now
345
+ cert.not_after = 64060588800
346
+ cert.public_key = rsa.public_key
347
+
348
+ ef = OpenSSL::X509::ExtensionFactory.new(nil, cert)
349
+ ef.issuer_certificate = cert
350
+ cert.extensions = [
351
+ ef.create_extension("basicConstraints","CA:FALSE"),
352
+ ef.create_extension("subjectKeyIdentifier", "hash"),
353
+ ]
354
+ aki = ef.create_extension("authorityKeyIdentifier",
355
+ "keyid:always,issuer:always")
356
+ cert.add_extension(aki)
357
+ cert.sign(rsa, sign_digest.new)
358
+ [rsa, cert]
359
+ end
360
+ end
361
+
@@ -0,0 +1,124 @@
1
+ require 'akero'
2
+ require 'gnuplot'
3
+ require 'b'
4
+
5
+ class Akero
6
+ # @private
7
+ class Benchmark
8
+ class << self
9
+ def run!
10
+ b_size
11
+ b_timing
12
+ end
13
+
14
+ def b_size
15
+ puts "Running size benchmark..."
16
+
17
+ rnd = Random.new
18
+ msg_sizes = (8..13).map{|x| 2**x}
19
+ key_sizes = [2048, 4096]
20
+ results = {}
21
+ key_sizes.each do |ksize|
22
+ alice = Akero.new(ksize)
23
+ bob = Akero.new(ksize)
24
+ msg_sizes.each do |msize|
25
+ msg = rnd.bytes(msize)
26
+ ciphertext = alice.encrypt(bob.public_key, msg)
27
+ (results["ENCRYPT #{ksize} bits"] ||= []) << [msize, ciphertext.length / msg.length.to_f]
28
+ end
29
+ end
30
+
31
+ plot('benchmark/bm_size.png', results, 'Message size overhead', 'Input size (bytes)', 'x')
32
+ end
33
+
34
+ def b_timing
35
+ puts "Running timing benchmark..."
36
+
37
+ msg_sizes = (4..18).map{|x| 2**x}
38
+ key_sizes = [2048, 4096]
39
+
40
+ rnd = Random.new
41
+
42
+ rounds = 50
43
+ results = []
44
+ key_sizes.each do |ksize|
45
+ results << B.enchmark("ENCRYPT #{ksize} bits", :rounds => rounds, :compare => :mean) do
46
+ alice = Akero.new(ksize)
47
+ bob = Akero.new(ksize)
48
+ msg_sizes.each_with_index do |msize, i|
49
+ msg = rnd.bytes(msize)
50
+ job "msg_size #{msize}" do
51
+ alice.encrypt(bob.public_key, msg)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ key_sizes.each do |ksize|
58
+ results << B.enchmark("SIGN #{ksize} bits", :rounds => rounds, :compare => :mean) do
59
+ alice = Akero.new(ksize)
60
+ bob = Akero.new(ksize)
61
+ msg_sizes.each_with_index do |msize, i|
62
+ msg = rnd.bytes(msize)
63
+ job "msg_size #{msize}" do
64
+ alice.sign(msg)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ key_sizes.each do |ksize|
71
+ results << B.enchmark("DECRYPT #{ksize} bits", :rounds => rounds, :compare => :mean) do
72
+ alice = Akero.new(ksize)
73
+ bob = Akero.new(ksize)
74
+ msg_sizes.each_with_index do |msize, i|
75
+ msg = rnd.bytes(msize)
76
+ msg = alice.encrypt(bob.public_key, msg)
77
+ job "msg_size #{msize}" do
78
+ bob.receive(msg)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ ds = {}
85
+ title = []
86
+ results.each_with_index do |r, i|
87
+ r.each_with_index do |v,j|
88
+ (ds[v[:group]] ||= []) << [msg_sizes[j],v[:rate]]
89
+ end
90
+ end
91
+
92
+ plot('benchmark/bm_rate.png', ds, "Throughput", 'Input size (bytes)', 'Messages/sec')
93
+ end
94
+
95
+ def plot(path, ds, label, xlabel, ylabel)
96
+ Gnuplot.open do |gp|
97
+ Gnuplot::Plot.new( gp ) do |plot|
98
+ plot.terminal "png"
99
+ plot.output path
100
+ plot.title label
101
+ plot.xlabel xlabel
102
+ plot.ylabel ylabel
103
+ plot.xtics :axis
104
+ plot.style 'line 1 lc rgb "#8b1a0e" pt 1 ps 1 lt 1 lw 2'
105
+ plot.style 'line 2 lc rgb "#5e9c36" pt 6 ps 1 lt 1 lw 2'
106
+ plot.style 'line 11 lc rgb "#808080" lt 1'
107
+ plot.set 'border 3 back ls 11'
108
+ plot.set 'tics nomirror'
109
+ plot.style 'line 12 lc rgb "#808080" lt 0 lw 1'
110
+ plot.set 'grid back ls 12'
111
+ i=0
112
+ ds.each do |k, v|
113
+ plot.data << Gnuplot::DataSet.new([v.collect {|x| x[0]}, v.collect {|x| x[1]}]) do |_ds|
114
+ _ds.with = "lp ls #{i+1}"
115
+ _ds.title = k
116
+ end
117
+ i+=1
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,3 @@
1
+ class Akero
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,250 @@
1
+ require 'spec_helper'
2
+ require 'openssl'
3
+
4
+ describe Akero do
5
+ subject { Akero.new(1024) }
6
+ describe '#new' do
7
+ it "returns instance with unique fingerprint" do
8
+ memo = {}
9
+ 10.times do
10
+ id = Akero.new.id
11
+ memo.should_not include id
12
+ memo[id] = true
13
+ end
14
+ end
15
+ end
16
+
17
+ describe '#id' do
18
+ it "returns a String that looks like an Akero fingerprint" do
19
+ subject.id.should be_a String
20
+ subject.id.should match /^AK(:([a-fA-Z0-9]){2}){20}$/
21
+ end
22
+ end
23
+
24
+ describe '#private_key' do
25
+ describe 'return value' do
26
+ it "is a String that looks like an Akero private key" do
27
+ subject.private_key.should be_a String
28
+ subject.private_key.should match /^#{Akero::PKEY_HEADER}/
29
+ subject.private_key.should match /\n#{Akero::PKEY_FOOTER}$/
30
+ end
31
+
32
+ it "can be loaded by Akero" do
33
+ bob = Akero.load(subject.private_key)
34
+ bob.id.should == subject.id
35
+ bob.public_key.should == subject.public_key
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#public_key' do
41
+ describe 'return value' do
42
+ it "is a String that looks like an Akero public key" do
43
+ subject.public_key.should be_a String
44
+ subject.public_key.should match /^-----BEGIN #{Akero::PLATE_CERT[1]}-----\n/
45
+ subject.public_key.should match /\n-----END #{Akero::PLATE_CERT[1]}-----\n$/
46
+ end
47
+
48
+ it "raises RuntimeError when trying to load as private key" do
49
+ lambda {
50
+ bob = Akero.load(subject.public_key)
51
+ bob.id.should == subject.id
52
+ bob.public_key.should == subject.public_key
53
+ }.should raise_error RuntimeError, Akero::ERR_PKEY_CORRUPT
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#sign' do
59
+ describe 'return value' do
60
+ it "is a String that looks like an Akero signed message" do
61
+ plaintext = "Hello world!"
62
+ signed_msg = subject.sign(plaintext)
63
+ signed_msg.should be_a String
64
+ signed_msg.should match /^-----BEGIN #{Akero::PLATE_SIGNED[1]}-----\n/
65
+ signed_msg.should match /\n-----END #{Akero::PLATE_SIGNED[1]}-----\n$/
66
+ end
67
+
68
+ it "contains valid signature" do
69
+ plaintext = "Hello world!"
70
+ signed_msg = subject.sign(plaintext)
71
+ bob = Akero.new
72
+ msg = bob.receive(signed_msg)
73
+ msg.from.should == subject.id
74
+ msg.from_pk.should == subject.public_key
75
+ msg.body.should == plaintext
76
+ msg.type.should == :signed
77
+ end
78
+ end
79
+ end
80
+
81
+ describe '#encrypt' do
82
+ describe 'return value' do
83
+ it "is a String that looks like an Akero secret message" do
84
+ plaintext = "Hello world!"
85
+ ciphertext = subject.encrypt(subject.public_key, plaintext)
86
+ ciphertext.should be_a String
87
+ ciphertext.should match /^-----BEGIN #{Akero::PLATE_CRYPTED[1]}-----\n/
88
+ ciphertext.should match /\n-----END #{Akero::PLATE_CRYPTED[1]}-----\n$/
89
+ end
90
+
91
+ it "contains valid signature" do
92
+ plaintext = "Hello world!"
93
+ signed_msg = subject.sign(plaintext)
94
+ bob = Akero.new
95
+ msg = bob.receive(signed_msg)
96
+ msg.from == subject.id
97
+ msg.from_pk.should == subject.public_key
98
+ msg.body.should == plaintext
99
+ msg.type.should == :signed
100
+ end
101
+
102
+ it "raises RuntimeError on invalid recipient (invalid public key)" do
103
+ lambda {
104
+ msg = "Hello world!"
105
+ ciphertext = subject.encrypt([subject.public_key, 'foo'], msg)
106
+ }.should raise_error RuntimeError, Akero::ERR_INVALID_RECIPIENT_CERT
107
+ end
108
+
109
+ it "raises RuntimeError on invalid recipient (wrong type)" do
110
+ lambda {
111
+ msg = "Hello world!"
112
+ ciphertext = subject.encrypt([subject.public_key, 42], msg)
113
+ }.should raise_error RuntimeError, Akero::ERR_INVALID_RECIPIENT
114
+ end
115
+
116
+ it "raises RuntimeError when message is not String" do
117
+ lambda {
118
+ msg = "Hello world!"
119
+ ciphertext = subject.encrypt(subject.public_key, 42)
120
+ }.should raise_error RuntimeError, Akero::ERR_MSG_NOT_STRING_NOR_PKCS7
121
+ end
122
+ end
123
+ end
124
+
125
+ describe '#receive' do
126
+ it "decrypts message that was encrypted for self" do
127
+ plaintext = "Hello world!"
128
+ ciphertext = subject.encrypt(subject.public_key, plaintext)
129
+ msg = subject.receive(ciphertext)
130
+ msg.body.should == plaintext
131
+ msg.type.should == :encrypted
132
+ end
133
+
134
+ it "decrypts message that was encrypted for self and other recipients" do
135
+ plaintext = "Hello world!"
136
+ alice = Akero.new
137
+ bob = Akero.new
138
+ ciphertext = subject.encrypt([alice.public_key, subject.public_key, bob.public_key], plaintext)
139
+ msg = subject.receive(ciphertext)
140
+ msg.body.should == plaintext
141
+ msg.type.should == :encrypted
142
+ end
143
+
144
+ it "fails to decrypt message that was encrypted only for other recipients" do
145
+ lambda {
146
+ plaintext = "Hello world!"
147
+ alice = Akero.new
148
+ bob = Akero.new
149
+ ciphertext = subject.encrypt([alice.public_key, bob.public_key], plaintext)
150
+ msg = subject.receive(ciphertext)
151
+ msg.body.should == plaintext
152
+ msg.type.should == :encrypted
153
+ }.should raise_error RuntimeError, Akero::ERR_DECRYPT
154
+ end
155
+
156
+ it "extracts signature from signed message" do
157
+ plaintext = "Hello world!"
158
+ alice = Akero.new
159
+ signed_msg = subject.sign(plaintext)
160
+ msg = alice.receive(signed_msg)
161
+ msg.body.should == plaintext
162
+ msg.type.should == :signed
163
+ end
164
+
165
+ it "raises RuntimeError on invalid message" do
166
+ lambda {
167
+ subject.receive("foobar")
168
+ }.should raise_error RuntimeError, Akero::ERR_MSG_MALFORMED_ENV
169
+ end
170
+
171
+ it "raises RuntimeError when inner does not match outer signature" do
172
+ lambda {
173
+ oscar = Akero.new
174
+ raw_key = subject.send(:instance_variable_get, '@cert')
175
+ a = subject.send(:_encrypt, [raw_key], subject.send(:_sign, 'foobar'))
176
+ b = oscar.send(:_sign, a).to_s
177
+ c = Akero.replate(b, Akero::PLATE_CRYPTED)
178
+ subject.receive(c)
179
+ }.should raise_error RuntimeError, Akero::ERR_MSG_SIG_MISMATCH
180
+ end
181
+
182
+ it "raises RuntimeError on malformed inner message" do
183
+ lambda {
184
+ key, cert = subject.send(:generate_keypair, 1024)
185
+ env = OpenSSL::PKCS7::sign(cert, key, 0xff.chr, [], OpenSSL::PKCS7::BINARY)
186
+ broken_msg = Akero.replate(env.to_s, Akero::PLATE_CRYPTED)
187
+ subject.receive(broken_msg)
188
+ }.should raise_error RuntimeError, Akero::ERR_MSG_MALFORMED_BODY
189
+ end
190
+
191
+ it "raises RuntimeError on unsigned message" do
192
+ lambda {
193
+ raw_key = subject.send(:instance_variable_get, '@cert')
194
+ env = OpenSSL::PKCS7::encrypt([raw_key], 'foobar', OpenSSL::Cipher::new("AES-256-CFB"), OpenSSL::PKCS7::BINARY)
195
+ broken_msg = Akero.replate(env.to_s, Akero::PLATE_CRYPTED)
196
+ subject.receive(broken_msg)
197
+ }.should raise_error RuntimeError, Akero::ERR_MSG_TOO_MANY_SIGNERS
198
+ end
199
+ end
200
+
201
+ describe '#verify' do
202
+ it "raises RuntimeError when embedded certificate can not be verified" do
203
+ lambda {
204
+ fake_msg = mock('fake_msg')
205
+ fake_msg.stub(:verify).and_return(false)
206
+ fake_msg.stub_chain(:certificates, :length).and_return(1)
207
+ fake_msg.stub_chain(:certificates, :[]).and_return(nil)
208
+ subject.send(:verify, fake_msg)
209
+ }.should raise_error RuntimeError, Akero::ERR_MSG_CORRUPT_CERT
210
+ end
211
+
212
+ end
213
+
214
+ describe '#inspect' do
215
+ it "returns a summary String" do
216
+ s = subject.inspect
217
+ s.should match /id=AK:/
218
+ end
219
+ end
220
+
221
+ describe '#to_s' do
222
+ it "returns the same value as #inspect" do
223
+ subject.to_s.should == subject.inspect
224
+ end
225
+ end
226
+
227
+ describe '.fingerprint_from_cert' do
228
+ it "raises RuntimeError on invalid cert" do
229
+ mock_cert = mock('mock_cert')
230
+ mock_cert.stub_chain(:extensions, :map, :each).and_return(nil)
231
+ lambda {
232
+ Akero.fingerprint_from_cert(mock_cert)
233
+ }.should raise_error RuntimeError, Akero::ERR_CERT_CORRUPT
234
+ end
235
+ end
236
+
237
+ describe Akero::Message do
238
+ describe '#inspect' do
239
+ it "returns a summary String" do
240
+ signed_msg = subject.sign('')
241
+ msg = subject.receive(signed_msg)
242
+ s = msg.inspect
243
+ s.should be_a String
244
+ s.should match /@type=/
245
+ s.should match /@from=/
246
+ s.should match /@body=/
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,4 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'akero'
metadata ADDED
@@ -0,0 +1,214 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: akero
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Moe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rb-inotify
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: guard
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: guard-rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: simplecov
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: b
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: gnuplot
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: yard
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ description: Easy peer-to-peer public key cryptography
159
+ email:
160
+ - moe@busyloop.net
161
+ executables: []
162
+ extensions: []
163
+ extra_rdoc_files: []
164
+ files:
165
+ - .gitignore
166
+ - .travis.yml
167
+ - .yardopts
168
+ - Gemfile
169
+ - Guardfile
170
+ - LICENSE.txt
171
+ - README.md
172
+ - Rakefile
173
+ - akero.gemspec
174
+ - benchmark/bm_rate.png
175
+ - benchmark/bm_size.png
176
+ - lib/akero.rb
177
+ - lib/akero/benchmark.rb
178
+ - lib/akero/version.rb
179
+ - spec/akero_spec.rb
180
+ - spec/spec_helper.rb
181
+ homepage: https://github.com/busyloop/akero
182
+ licenses: []
183
+ post_install_message:
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ none: false
189
+ requirements:
190
+ - - ! '>='
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ segments:
194
+ - 0
195
+ hash: -4483146424576861364
196
+ required_rubygems_version: !ruby/object:Gem::Requirement
197
+ none: false
198
+ requirements:
199
+ - - ! '>='
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ segments:
203
+ - 0
204
+ hash: -4483146424576861364
205
+ requirements: []
206
+ rubyforge_project:
207
+ rubygems_version: 1.8.23
208
+ signing_key:
209
+ specification_version: 3
210
+ summary: Easy peer-to-peer public key cryptography
211
+ test_files:
212
+ - spec/akero_spec.rb
213
+ - spec/spec_helper.rb
214
+ has_rdoc: