akero 1.0.0
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/.gitignore +19 -0
- data/.travis.yml +9 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/Guardfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +98 -0
- data/Rakefile +25 -0
- data/akero.gemspec +29 -0
- data/benchmark/bm_rate.png +0 -0
- data/benchmark/bm_size.png +0 -0
- data/lib/akero.rb +361 -0
- data/lib/akero/benchmark.rb +124 -0
- data/lib/akero/version.rb +3 -0
- data/spec/akero_spec.rb +250 -0
- data/spec/spec_helper.rb +4 -0
- metadata +214 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private
|
data/Gemfile
ADDED
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 [](https://travis-ci.org/busyloop/akero) [](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
|
+

|
71
|
+

|
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
|
data/spec/akero_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
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:
|