akero 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![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
|
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:
|