akero 1.0.4 → 1.1.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.
- checksums.yaml +7 -0
- data/.gitattributes +3 -0
- data/.gitignore +0 -2
- data/.rubocop.yml +43 -0
- data/.travis.yml +3 -7
- data/Gemfile +1 -0
- data/Guardfile +4 -3
- data/Makefile +26 -0
- data/README.md +28 -10
- data/Rakefile +8 -17
- data/akero.gemspec +17 -10
- data/bin/akero +6 -0
- data/coverage/.last_run.json +5 -0
- data/coverage/.resultset.json +367 -0
- data/coverage/.resultset.json.lock +0 -0
- data/coverage/assets/0.10.0/application.css +799 -0
- data/coverage/assets/0.10.0/application.js +1707 -0
- data/coverage/assets/0.10.0/colorbox/border.png +0 -0
- data/coverage/assets/0.10.0/colorbox/controls.png +0 -0
- data/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
- data/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.10.0/favicon_green.png +0 -0
- data/coverage/assets/0.10.0/favicon_red.png +0 -0
- data/coverage/assets/0.10.0/favicon_yellow.png +0 -0
- data/coverage/assets/0.10.0/loading.gif +0 -0
- data/coverage/assets/0.10.0/magnify.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.7.1/application.css +1110 -0
- data/coverage/assets/0.7.1/application.js +626 -0
- data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
- data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
- data/coverage/assets/0.7.1/favicon_green.png +0 -0
- data/coverage/assets/0.7.1/favicon_red.png +0 -0
- data/coverage/assets/0.7.1/favicon_yellow.png +0 -0
- data/coverage/assets/0.7.1/loading.gif +0 -0
- data/coverage/assets/0.7.1/magnify.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/index.html +2248 -0
- data/doc/Akero/Message.html +475 -0
- data/doc/Akero.html +1148 -0
- data/doc/_index.html +125 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +338 -0
- data/doc/file.README.html +186 -0
- data/doc/file_list.html +55 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +186 -0
- data/doc/js/app.js +214 -0
- data/doc/js/full_list.js +173 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +148 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/akero/benchmark.rb +21 -20
- data/lib/akero/cli.rb +74 -0
- data/lib/akero/version.rb +2 -1
- data/lib/akero.rb +92 -90
- data/spec/akero_spec.rb +66 -65
- data/spec/spec_helper.rb +1 -0
- metadata +164 -52
data/lib/akero.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
# Copyright (c) 2012 moe@busyloop.net
|
|
2
|
-
#
|
|
3
|
+
#
|
|
3
4
|
# MIT License
|
|
4
|
-
#
|
|
5
|
+
#
|
|
5
6
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
6
7
|
# a copy of this software and associated documentation files (the
|
|
7
8
|
# "Software"), to deal in the Software without restriction, including
|
|
@@ -9,10 +10,10 @@
|
|
|
9
10
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
11
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
11
12
|
# the following conditions:
|
|
12
|
-
#
|
|
13
|
+
#
|
|
13
14
|
# The above copyright notice and this permission notice shall be
|
|
14
15
|
# included in all copies or substantial portions of the Software.
|
|
15
|
-
#
|
|
16
|
+
#
|
|
16
17
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
18
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
19
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
@@ -21,15 +22,16 @@
|
|
|
21
22
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
23
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
24
|
|
|
24
|
-
require
|
|
25
|
+
require 'akero/version'
|
|
25
26
|
|
|
26
27
|
require 'openssl'
|
|
27
28
|
require 'base64'
|
|
28
29
|
|
|
29
30
|
# Akero is an easy-to-use library for peer-to-peer public key cryptography.
|
|
30
31
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
32
|
+
# The only officially supported ruby runtime is MRI (latest version).
|
|
33
|
+
# Regardless, Akero is known to work on any recent ruby version except JRuby.
|
|
34
|
+
# Tested on: MRI 1.8.7, MRI 1.9.2, MRI 1.9.3, RBX 1.8, RBX 1.9, MRI 2.3.*, and more.
|
|
33
35
|
class Akero
|
|
34
36
|
# Akero::Message wraps a received message.
|
|
35
37
|
class Message
|
|
@@ -40,7 +42,9 @@ class Akero
|
|
|
40
42
|
|
|
41
43
|
# @private
|
|
42
44
|
def initialize(body, signer_cert, type)
|
|
43
|
-
@body
|
|
45
|
+
@body = body
|
|
46
|
+
@signer_cert = signer_cert
|
|
47
|
+
@type = type
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
# @private
|
|
@@ -62,25 +66,26 @@ class Akero
|
|
|
62
66
|
end
|
|
63
67
|
end
|
|
64
68
|
|
|
69
|
+
# Akero
|
|
65
70
|
class Akero
|
|
66
|
-
ERR_MSG_MALFORMED_ENV =
|
|
67
|
-
ERR_MSG_MALFORMED_BODY =
|
|
68
|
-
ERR_PKEY_CORRUPT =
|
|
69
|
-
ERR_CERT_CORRUPT =
|
|
70
|
-
ERR_INVALID_RECIPIENT =
|
|
71
|
-
ERR_INVALID_RECIPIENT_CERT =
|
|
72
|
-
ERR_DECRYPT =
|
|
73
|
-
ERR_MSG_NOT_STRING_NOR_PKCS7 =
|
|
74
|
-
ERR_MSG_CORRUPT_CERT =
|
|
75
|
-
ERR_MSG_TOO_MANY_SIGNERS =
|
|
71
|
+
ERR_MSG_MALFORMED_ENV = 'Malformed message: Could not parse envelope' # @private
|
|
72
|
+
ERR_MSG_MALFORMED_BODY = 'Malformed message: Could not parse body; POSSIBLE SPOOF ATTEMPT' # @private
|
|
73
|
+
ERR_PKEY_CORRUPT = 'Invalid private key (checksum mismatch)' # @private
|
|
74
|
+
ERR_CERT_CORRUPT = 'Invalid certificate' # @private
|
|
75
|
+
ERR_INVALID_RECIPIENT = 'Invalid recipient (must be a String)' # @private
|
|
76
|
+
ERR_INVALID_RECIPIENT_CERT = 'Invalid recipient (corrupt public key?)' # @private
|
|
77
|
+
ERR_DECRYPT = 'Could not decrypt message' # @private
|
|
78
|
+
ERR_MSG_NOT_STRING_NOR_PKCS7 = 'Message must be of type String or OpenSSL::PKCS7' # @private
|
|
79
|
+
ERR_MSG_CORRUPT_CERT = 'Malformed message: Embedded certificate could not be verified; POSSIBLE SPOOF ATTEMPT!' # @private
|
|
80
|
+
ERR_MSG_TOO_MANY_SIGNERS = 'Corrupt message: Zero or multiple signers, expected exactly 1; POSSIBLE SPOOF ATTEMPT' # @private
|
|
76
81
|
|
|
77
82
|
PKEY_HEADER = "-----BEGIN AKERO PRIVATE KEY-----\n" # @private
|
|
78
83
|
PKEY_FOOTER = "-----END AKERO PRIVATE KEY-----\n" # @private
|
|
79
|
-
PLATE_CERT = ['CERTIFICATE','AKERO PUBLIC KEY'] # @private
|
|
80
|
-
PLATE_SIGNED = ['PKCS7', 'AKERO SIGNED MESSAGE'] # @private
|
|
81
|
-
PLATE_CRYPTED = ['PKCS7', 'AKERO SECRET MESSAGE'] # @private
|
|
84
|
+
PLATE_CERT = ['CERTIFICATE', 'AKERO PUBLIC KEY'].freeze # @private
|
|
85
|
+
PLATE_SIGNED = ['PKCS7', 'AKERO SIGNED MESSAGE'].freeze # @private
|
|
86
|
+
PLATE_CRYPTED = ['PKCS7', 'AKERO SECRET MESSAGE'].freeze # @private
|
|
82
87
|
|
|
83
|
-
DEFAULT_RSA_BITS =
|
|
88
|
+
DEFAULT_RSA_BITS = 4096
|
|
84
89
|
DEFAULT_DIGEST = OpenSSL::Digest::SHA512
|
|
85
90
|
|
|
86
91
|
# Unique fingerprint of this Akero keypair.
|
|
@@ -104,26 +109,26 @@ class Akero
|
|
|
104
109
|
# @param [Integer] rsa_bits RSA key length
|
|
105
110
|
# @param [OpenSSL::Digest] digest Signature digest
|
|
106
111
|
# @return [Akero] New Akero instance
|
|
107
|
-
def initialize(rsa_bits=DEFAULT_RSA_BITS, digest=DEFAULT_DIGEST)
|
|
112
|
+
def initialize(rsa_bits = DEFAULT_RSA_BITS, digest = DEFAULT_DIGEST)
|
|
108
113
|
@key, @cert = generate_keypair(rsa_bits, digest) unless rsa_bits.nil?
|
|
109
114
|
end
|
|
110
115
|
|
|
111
116
|
# Load an Akero identity.
|
|
112
|
-
#
|
|
117
|
+
#
|
|
113
118
|
# @example Load previously stored private key
|
|
114
119
|
# Akero.load(File.read('/tmp/alice.akr'))
|
|
115
120
|
#
|
|
116
121
|
# @param [String] private_key Akero private key
|
|
117
122
|
# @return [Akero] New Akero instance
|
|
118
123
|
def self.load(private_key)
|
|
119
|
-
inner = Base64.decode64(private_key[PKEY_HEADER.length..private_key.length-PKEY_FOOTER.length])
|
|
124
|
+
inner = Base64.decode64(private_key[PKEY_HEADER.length..private_key.length - PKEY_FOOTER.length])
|
|
120
125
|
if inner[0..63] != OpenSSL::Digest::SHA512.new(inner[64..-1]).digest
|
|
121
|
-
raise
|
|
126
|
+
raise ERR_PKEY_CORRUPT
|
|
122
127
|
end
|
|
123
128
|
cert_len = inner[64..65].unpack('S')[0]
|
|
124
129
|
akero = Akero.new(nil)
|
|
125
|
-
akero.instance_variable_set(:@cert, OpenSSL::X509::Certificate.new(inner[66..66+cert_len]))
|
|
126
|
-
akero.instance_variable_set(:@key, OpenSSL::PKey::RSA.new(inner[66+cert_len..-1]))
|
|
130
|
+
akero.instance_variable_set(:@cert, OpenSSL::X509::Certificate.new(inner[66..66 + cert_len]))
|
|
131
|
+
akero.instance_variable_set(:@key, OpenSSL::PKey::RSA.new(inner[66 + cert_len..-1]))
|
|
127
132
|
akero
|
|
128
133
|
end
|
|
129
134
|
|
|
@@ -134,18 +139,18 @@ class Akero
|
|
|
134
139
|
#
|
|
135
140
|
# @return [String] Public key (ascii armored)
|
|
136
141
|
def public_key
|
|
137
|
-
Akero
|
|
142
|
+
Akero.replate(@cert.to_s, Akero::PLATE_CERT)
|
|
138
143
|
end
|
|
139
144
|
|
|
140
145
|
# Private key (do not share this with anyone!)
|
|
141
|
-
#
|
|
146
|
+
#
|
|
142
147
|
# @example Save and load an Akero identity
|
|
143
148
|
# alice = Akero.new
|
|
144
149
|
# # Save
|
|
145
150
|
# File.open('/tmp/alice.akr', 'w') { |f| f.write(alice.private_key) }
|
|
146
151
|
# # Load
|
|
147
152
|
# new_alice = Akero.load(File.read('/tmp/alice.akr'))
|
|
148
|
-
#
|
|
153
|
+
#
|
|
149
154
|
# @return [String] Private key (ascii armored)
|
|
150
155
|
# @see Akero#load
|
|
151
156
|
def private_key
|
|
@@ -156,7 +161,7 @@ class Akero
|
|
|
156
161
|
out << cert_der
|
|
157
162
|
out << @key.to_der
|
|
158
163
|
out.insert(0, OpenSSL::Digest::SHA512.new(out).digest)
|
|
159
|
-
PKEY_HEADER+Base64.encode64(out)+PKEY_FOOTER
|
|
164
|
+
PKEY_HEADER + Base64.encode64(out) + PKEY_FOOTER
|
|
160
165
|
end
|
|
161
166
|
|
|
162
167
|
# Sign a message.
|
|
@@ -164,7 +169,7 @@ class Akero
|
|
|
164
169
|
# @param [String] plaintext The message to sign (binary safe)
|
|
165
170
|
# @param [Boolean] ascii_armor Convert the output in base64?
|
|
166
171
|
# @return [String] Akero signed message
|
|
167
|
-
def sign(plaintext, ascii_armor=true)
|
|
172
|
+
def sign(plaintext, ascii_armor = true)
|
|
168
173
|
out = _sign(plaintext)
|
|
169
174
|
ascii_armor ? Akero.replate(out.to_s, Akero::PLATE_SIGNED) : out.to_der
|
|
170
175
|
end
|
|
@@ -189,20 +194,20 @@ class Akero
|
|
|
189
194
|
# @param [String] plaintext The message to encrypt (binary safe)
|
|
190
195
|
# @param [Boolean] ascii_armor Convert the output to base64?
|
|
191
196
|
# @return [String] Akero secret message
|
|
192
|
-
def encrypt(to, plaintext, ascii_armor=true)
|
|
197
|
+
def encrypt(to, plaintext, ascii_armor = true)
|
|
193
198
|
to = [to] unless to.is_a? Array
|
|
194
|
-
to = to.map
|
|
199
|
+
to = to.map do |e|
|
|
195
200
|
case e
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
when String
|
|
202
|
+
begin
|
|
203
|
+
OpenSSL::X509::Certificate.new(Akero.replate(e, Akero::PLATE_CERT, true))
|
|
204
|
+
rescue OpenSSL::X509::CertificateError
|
|
205
|
+
raise ERR_INVALID_RECIPIENT_CERT
|
|
206
|
+
end
|
|
207
|
+
else
|
|
208
|
+
raise ERR_INVALID_RECIPIENT
|
|
204
209
|
end
|
|
205
|
-
|
|
210
|
+
end
|
|
206
211
|
out = _sign(_encrypt(to, _sign(plaintext, false)))
|
|
207
212
|
ascii_armor ? Akero.replate(out.to_s, PLATE_CRYPTED) : out.to_der
|
|
208
213
|
end
|
|
@@ -218,21 +223,21 @@ class Akero
|
|
|
218
223
|
begin
|
|
219
224
|
body, signer_cert, body_type = verify(ciphertext, nil)
|
|
220
225
|
rescue ArgumentError
|
|
221
|
-
raise
|
|
226
|
+
raise ERR_MSG_MALFORMED_ENV
|
|
222
227
|
end
|
|
223
228
|
|
|
224
229
|
case body_type.ord
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
when 0x00
|
|
231
|
+
# public message (signed)
|
|
232
|
+
return Message.new(body, signer_cert, :signed)
|
|
233
|
+
when 0x01
|
|
234
|
+
# private message (signed, crypted, signed)
|
|
235
|
+
signed_plaintext = _decrypt(body)
|
|
236
|
+
plaintext, _verified_cert, _body_type = verify(signed_plaintext, signer_cert)
|
|
237
|
+
msg = Message.new(plaintext, signer_cert, :encrypted)
|
|
238
|
+
return msg
|
|
234
239
|
end
|
|
235
|
-
raise
|
|
240
|
+
raise ERR_MSG_MALFORMED_BODY
|
|
236
241
|
end
|
|
237
242
|
|
|
238
243
|
# @private
|
|
@@ -244,9 +249,9 @@ class Akero
|
|
|
244
249
|
def to_s
|
|
245
250
|
inspect
|
|
246
251
|
end
|
|
247
|
-
|
|
252
|
+
|
|
248
253
|
#---------------------------------------------------------------------------
|
|
249
|
-
protected
|
|
254
|
+
class << self; protected; end
|
|
250
255
|
|
|
251
256
|
# Swap the "license plates" on an ascii-armored message.
|
|
252
257
|
# This is done for user-friendliness, so stored Akero
|
|
@@ -256,9 +261,9 @@ class Akero
|
|
|
256
261
|
# @param [Array] plates Array of the two plates to swap
|
|
257
262
|
# @param [Boolean] reverse Reverse the swap?
|
|
258
263
|
# @return [String] The replated message
|
|
259
|
-
def self.replate(msg, plates, reverse=false)
|
|
260
|
-
a,b = reverse ? [1,0] : [0,1]
|
|
261
|
-
"-----BEGIN #{plates[b]}-----#{msg.strip[plates[a].length+16..-(plates[a].length+15)]}-----END #{plates[b]}-----\n"
|
|
264
|
+
def self.replate(msg, plates, reverse = false)
|
|
265
|
+
a, b = reverse ? [1, 0] : [0, 1]
|
|
266
|
+
"-----BEGIN #{plates[b]}-----#{msg.strip[plates[a].length + 16..-(plates[a].length + 15)]}-----END #{plates[b]}-----\n"
|
|
262
267
|
end
|
|
263
268
|
|
|
264
269
|
# Extract fingerprint from an Akero public key.
|
|
@@ -268,37 +273,35 @@ class Akero
|
|
|
268
273
|
cert.extensions.map.each do |e|
|
|
269
274
|
return "AK:#{e.value}" if e.oid == 'subjectKeyIdentifier'
|
|
270
275
|
end
|
|
271
|
-
raise
|
|
276
|
+
raise ERR_CERT_CORRUPT
|
|
272
277
|
end
|
|
273
|
-
|
|
278
|
+
|
|
274
279
|
#---------------------------------------------------------------------------
|
|
275
|
-
private
|
|
280
|
+
private
|
|
276
281
|
|
|
277
282
|
def _decrypt(crypted_msg)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
raise RuntimeError, ERR_DECRYPT
|
|
282
|
-
end
|
|
283
|
+
OpenSSL::PKCS7.new(crypted_msg).decrypt(@key, @cert)
|
|
284
|
+
rescue OpenSSL::PKCS7::PKCS7Error, 'decrypt error'
|
|
285
|
+
raise ERR_DECRYPT
|
|
283
286
|
end
|
|
284
287
|
|
|
285
|
-
def _encrypt(to, msg, cipher=nil)
|
|
286
|
-
cipher ||= OpenSSL::Cipher
|
|
287
|
-
OpenSSL::PKCS7
|
|
288
|
+
def _encrypt(to, msg, cipher = nil)
|
|
289
|
+
cipher ||= OpenSSL::Cipher.new('AES-256-CFB')
|
|
290
|
+
OpenSSL::PKCS7.encrypt(to, msg.to_der, cipher, OpenSSL::PKCS7::BINARY)
|
|
288
291
|
end
|
|
289
292
|
|
|
290
|
-
def _sign(message, embed_cert=true)
|
|
293
|
+
def _sign(message, embed_cert = true)
|
|
291
294
|
flags = embed_cert ? OpenSSL::PKCS7::BINARY : (OpenSSL::PKCS7::BINARY | OpenSSL::PKCS7::NOCERTS)
|
|
292
295
|
case message
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
when String
|
|
297
|
+
type = 0x00
|
|
298
|
+
when OpenSSL::PKCS7
|
|
299
|
+
type = 0x01
|
|
300
|
+
else
|
|
301
|
+
raise ERR_MSG_NOT_STRING_NOR_PKCS7
|
|
299
302
|
end
|
|
300
303
|
message = message.to_der if message.is_a? OpenSSL::PKCS7
|
|
301
|
-
OpenSSL::PKCS7
|
|
304
|
+
OpenSSL::PKCS7.sign(@cert, @key, type.chr + message, [], flags)
|
|
302
305
|
end
|
|
303
306
|
|
|
304
307
|
def verify(signed_msg, cert)
|
|
@@ -306,15 +309,15 @@ class Akero
|
|
|
306
309
|
store = OpenSSL::X509::Store.new
|
|
307
310
|
|
|
308
311
|
if cert.nil?
|
|
309
|
-
if signed_msg.certificates.nil?
|
|
310
|
-
raise
|
|
312
|
+
if signed_msg.certificates.nil? || signed_msg.certificates.length != 1
|
|
313
|
+
raise ERR_MSG_TOO_MANY_SIGNERS
|
|
311
314
|
end
|
|
312
315
|
|
|
313
316
|
cert = signed_msg.certificates[0]
|
|
314
317
|
end
|
|
315
318
|
|
|
316
319
|
unless signed_msg.verify([cert], store, nil, OpenSSL::PKCS7::NOINTERN | OpenSSL::PKCS7::NOVERIFY)
|
|
317
|
-
raise
|
|
320
|
+
raise ERR_MSG_CORRUPT_CERT
|
|
318
321
|
end
|
|
319
322
|
|
|
320
323
|
[signed_msg.data[1..-1], cert, signed_msg.data[0]]
|
|
@@ -325,7 +328,7 @@ class Akero
|
|
|
325
328
|
# @param [Integer] rsa_bits RSA key length
|
|
326
329
|
# @param [OpenSSL::Digest] digest Signature digest
|
|
327
330
|
# @return [Array] rsa_keypair, certificate
|
|
328
|
-
def generate_keypair(rsa_bits=DEFAULT_RSA_BITS, digest=DEFAULT_DIGEST)
|
|
331
|
+
def generate_keypair(rsa_bits = DEFAULT_RSA_BITS, digest = DEFAULT_DIGEST)
|
|
329
332
|
cn = "Akero #{Akero::VERSION}"
|
|
330
333
|
rsa = OpenSSL::PKey::RSA.new(rsa_bits)
|
|
331
334
|
|
|
@@ -337,20 +340,19 @@ class Akero
|
|
|
337
340
|
cert.issuer = name
|
|
338
341
|
cert.not_before = Time.now
|
|
339
342
|
# valid until 2038-01-19 04:14:06 +0100
|
|
340
|
-
cert.not_after = Time.at(
|
|
343
|
+
cert.not_after = Time.at(2_147_483_646)
|
|
341
344
|
cert.public_key = rsa.public_key
|
|
342
|
-
|
|
345
|
+
|
|
343
346
|
ef = OpenSSL::X509::ExtensionFactory.new(nil, cert)
|
|
344
347
|
ef.issuer_certificate = cert
|
|
345
348
|
cert.extensions = [
|
|
346
|
-
ef.create_extension(
|
|
347
|
-
ef.create_extension(
|
|
349
|
+
ef.create_extension('basicConstraints', 'CA:FALSE'),
|
|
350
|
+
ef.create_extension('subjectKeyIdentifier', 'hash')
|
|
348
351
|
]
|
|
349
|
-
aki = ef.create_extension(
|
|
350
|
-
|
|
352
|
+
aki = ef.create_extension('authorityKeyIdentifier',
|
|
353
|
+
'keyid:always,issuer:always')
|
|
351
354
|
cert.add_extension(aki)
|
|
352
355
|
cert.sign(rsa, digest.new)
|
|
353
356
|
[rsa, cert]
|
|
354
357
|
end
|
|
355
358
|
end
|
|
356
|
-
|
data/spec/akero_spec.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'spec_helper'
|
|
2
3
|
require 'openssl'
|
|
3
4
|
|
|
4
5
|
describe Akero do
|
|
5
6
|
subject { Akero.new(1024) }
|
|
6
7
|
describe '#new' do
|
|
7
|
-
it
|
|
8
|
+
it 'returns instance with unique fingerprint' do
|
|
8
9
|
memo = {}
|
|
9
10
|
10.times do
|
|
10
11
|
id = Akero.new.id
|
|
@@ -15,7 +16,7 @@ describe Akero do
|
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
describe '#id' do
|
|
18
|
-
it
|
|
19
|
+
it 'returns a String that looks like an Akero fingerprint' do
|
|
19
20
|
subject.id.should be_a String
|
|
20
21
|
subject.id.should match /^AK(:([a-fA-Z0-9]){2}){20}$/
|
|
21
22
|
end
|
|
@@ -23,13 +24,13 @@ describe Akero do
|
|
|
23
24
|
|
|
24
25
|
describe '#private_key' do
|
|
25
26
|
describe 'return value' do
|
|
26
|
-
it
|
|
27
|
+
it 'is a String that looks like an Akero private key' do
|
|
27
28
|
subject.private_key.should be_a String
|
|
28
29
|
subject.private_key.should match /^#{Akero::PKEY_HEADER}/
|
|
29
30
|
subject.private_key.should match /\n#{Akero::PKEY_FOOTER}$/
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
it
|
|
33
|
+
it 'can be loaded by Akero' do
|
|
33
34
|
bob = Akero.load(subject.private_key)
|
|
34
35
|
bob.id.should == subject.id
|
|
35
36
|
bob.public_key.should == subject.public_key
|
|
@@ -39,29 +40,29 @@ describe Akero do
|
|
|
39
40
|
|
|
40
41
|
describe '#public_key' do
|
|
41
42
|
describe 'return value' do
|
|
42
|
-
it
|
|
43
|
+
it 'is a String that looks like an Akero public key' do
|
|
43
44
|
subject.public_key.should be_a String
|
|
44
45
|
subject.public_key.should match /^-----BEGIN #{Akero::PLATE_CERT[1]}-----\n/
|
|
45
46
|
subject.public_key.should match /\n-----END #{Akero::PLATE_CERT[1]}-----\n$/
|
|
46
47
|
end
|
|
47
48
|
|
|
48
|
-
it
|
|
49
|
-
lambda
|
|
49
|
+
it 'raises RuntimeError when trying to load as private key' do
|
|
50
|
+
lambda do
|
|
50
51
|
bob = Akero.load(subject.public_key)
|
|
51
52
|
bob.id.should == subject.id
|
|
52
53
|
bob.public_key.should == subject.public_key
|
|
53
|
-
|
|
54
|
+
end.should raise_error RuntimeError, Akero::ERR_PKEY_CORRUPT
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
describe '#sign' do
|
|
59
|
-
|
|
60
|
+
[true, false].each do |ascii_armor|
|
|
60
61
|
describe "ascii_armor=#{ascii_armor}" do
|
|
61
62
|
describe 'return value' do
|
|
62
63
|
if ascii_armor
|
|
63
|
-
it
|
|
64
|
-
plaintext =
|
|
64
|
+
it 'is a String that looks like an Akero signed message' do
|
|
65
|
+
plaintext = 'Hello world!'
|
|
65
66
|
signed_msg = subject.sign(plaintext, ascii_armor)
|
|
66
67
|
signed_msg.should be_a String
|
|
67
68
|
signed_msg.should match /^-----BEGIN #{Akero::PLATE_SIGNED[1]}-----\n/
|
|
@@ -69,8 +70,8 @@ describe Akero do
|
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
it
|
|
73
|
-
plaintext =
|
|
73
|
+
it 'contains valid signature' do
|
|
74
|
+
plaintext = 'Hello world!'
|
|
74
75
|
signed_msg = subject.sign(plaintext, ascii_armor)
|
|
75
76
|
bob = Akero.new
|
|
76
77
|
msg = bob.receive(signed_msg)
|
|
@@ -85,12 +86,12 @@ describe Akero do
|
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
describe '#encrypt' do
|
|
88
|
-
|
|
89
|
+
[true, false].each do |ascii_armor|
|
|
89
90
|
describe "ascii_armor=#{ascii_armor}" do
|
|
90
91
|
describe 'return value' do
|
|
91
92
|
if ascii_armor
|
|
92
|
-
it
|
|
93
|
-
plaintext =
|
|
93
|
+
it 'is a String that looks like an Akero secret message' do
|
|
94
|
+
plaintext = 'Hello world!'
|
|
94
95
|
ciphertext = subject.encrypt(subject.public_key, plaintext, ascii_armor)
|
|
95
96
|
ciphertext.should be_a String
|
|
96
97
|
ciphertext.should match /^-----BEGIN #{Akero::PLATE_CRYPTED[1]}-----\n/
|
|
@@ -98,25 +99,25 @@ describe Akero do
|
|
|
98
99
|
end
|
|
99
100
|
end
|
|
100
101
|
|
|
101
|
-
it
|
|
102
|
-
lambda
|
|
103
|
-
msg =
|
|
102
|
+
it 'raises RuntimeError on invalid recipient (invalid public key)' do
|
|
103
|
+
lambda do
|
|
104
|
+
msg = 'Hello world!'
|
|
104
105
|
ciphertext = subject.encrypt([subject.public_key, 'foo'], msg)
|
|
105
|
-
|
|
106
|
+
end.should raise_error RuntimeError, Akero::ERR_INVALID_RECIPIENT_CERT
|
|
106
107
|
end
|
|
107
108
|
|
|
108
|
-
it
|
|
109
|
-
lambda
|
|
110
|
-
msg =
|
|
109
|
+
it 'raises RuntimeError on invalid recipient (wrong type)' do
|
|
110
|
+
lambda do
|
|
111
|
+
msg = 'Hello world!'
|
|
111
112
|
ciphertext = subject.encrypt([subject.public_key, 42], msg)
|
|
112
|
-
|
|
113
|
+
end.should raise_error RuntimeError, Akero::ERR_INVALID_RECIPIENT
|
|
113
114
|
end
|
|
114
115
|
|
|
115
|
-
it
|
|
116
|
-
lambda
|
|
117
|
-
msg =
|
|
116
|
+
it 'raises RuntimeError when message is not String' do
|
|
117
|
+
lambda do
|
|
118
|
+
msg = 'Hello world!'
|
|
118
119
|
ciphertext = subject.encrypt(subject.public_key, 42)
|
|
119
|
-
|
|
120
|
+
end.should raise_error RuntimeError, Akero::ERR_MSG_NOT_STRING_NOR_PKCS7
|
|
120
121
|
end
|
|
121
122
|
end
|
|
122
123
|
end
|
|
@@ -124,18 +125,18 @@ describe Akero do
|
|
|
124
125
|
end
|
|
125
126
|
|
|
126
127
|
describe '#receive' do
|
|
127
|
-
|
|
128
|
+
[true, false].each do |ascii_armor|
|
|
128
129
|
describe "ascii_armor=#{ascii_armor}" do
|
|
129
|
-
it
|
|
130
|
-
plaintext =
|
|
130
|
+
it 'decrypts message that was encrypted for self' do
|
|
131
|
+
plaintext = 'Hello world!'
|
|
131
132
|
ciphertext = subject.encrypt(subject.public_key, plaintext, ascii_armor)
|
|
132
133
|
msg = subject.receive(ciphertext)
|
|
133
134
|
msg.body.should == plaintext
|
|
134
135
|
msg.type.should == :encrypted
|
|
135
136
|
end
|
|
136
137
|
|
|
137
|
-
it
|
|
138
|
-
plaintext =
|
|
138
|
+
it 'decrypts message that was encrypted for self and other recipients' do
|
|
139
|
+
plaintext = 'Hello world!'
|
|
139
140
|
alice = Akero.new
|
|
140
141
|
bob = Akero.new
|
|
141
142
|
ciphertext = subject.encrypt([alice.public_key, subject.public_key, bob.public_key], plaintext, ascii_armor)
|
|
@@ -144,20 +145,20 @@ describe Akero do
|
|
|
144
145
|
msg.type.should == :encrypted
|
|
145
146
|
end
|
|
146
147
|
|
|
147
|
-
it
|
|
148
|
-
lambda
|
|
149
|
-
plaintext =
|
|
148
|
+
it 'fails to decrypt message that was encrypted only for other recipients' do
|
|
149
|
+
lambda do
|
|
150
|
+
plaintext = 'Hello world!'
|
|
150
151
|
alice = Akero.new
|
|
151
152
|
bob = Akero.new
|
|
152
153
|
ciphertext = subject.encrypt([alice.public_key, bob.public_key], plaintext, ascii_armor)
|
|
153
154
|
msg = subject.receive(ciphertext)
|
|
154
155
|
msg.body.should == plaintext
|
|
155
156
|
msg.type.should == :encrypted
|
|
156
|
-
|
|
157
|
+
end.should raise_error RuntimeError, Akero::ERR_DECRYPT
|
|
157
158
|
end
|
|
158
159
|
|
|
159
|
-
it
|
|
160
|
-
plaintext =
|
|
160
|
+
it 'extracts signature from signed message' do
|
|
161
|
+
plaintext = 'Hello world!'
|
|
161
162
|
alice = Akero.new
|
|
162
163
|
signed_msg = subject.sign(plaintext, ascii_armor)
|
|
163
164
|
msg = alice.receive(signed_msg)
|
|
@@ -165,82 +166,82 @@ describe Akero do
|
|
|
165
166
|
msg.type.should == :signed
|
|
166
167
|
end
|
|
167
168
|
|
|
168
|
-
it
|
|
169
|
-
lambda
|
|
170
|
-
subject.receive(
|
|
171
|
-
|
|
169
|
+
it 'raises RuntimeError on invalid message' do
|
|
170
|
+
lambda do
|
|
171
|
+
subject.receive('foobar')
|
|
172
|
+
end.should raise_error RuntimeError # , Akero::ERR_MSG_MALFORMED_ENV
|
|
172
173
|
end
|
|
173
174
|
|
|
174
|
-
it
|
|
175
|
-
lambda
|
|
175
|
+
it 'raises RuntimeError when payload does not match envelope signature' do
|
|
176
|
+
lambda do
|
|
176
177
|
oscar = Akero.new
|
|
177
178
|
raw_key = subject.send(:instance_variable_get, '@cert')
|
|
178
179
|
a = subject.send(:_encrypt, [raw_key], subject.send(:_sign, 'foobar'))
|
|
179
180
|
b = oscar.send(:_sign, a)
|
|
180
181
|
c = ascii_armor ? Akero.replate(b.to_s, Akero::PLATE_CRYPTED) : b.to_der
|
|
181
182
|
subject.receive(c)
|
|
182
|
-
|
|
183
|
+
end.should raise_error RuntimeError, Akero::ERR_MSG_CORRUPT_CERT
|
|
183
184
|
end
|
|
184
185
|
|
|
185
|
-
it
|
|
186
|
-
lambda
|
|
186
|
+
it 'raises RuntimeError on malformed inner message' do
|
|
187
|
+
lambda do
|
|
187
188
|
key, cert = subject.send(:generate_keypair, 1024)
|
|
188
|
-
env = OpenSSL::PKCS7
|
|
189
|
+
env = OpenSSL::PKCS7.sign(cert, key, 0xff.chr, [], OpenSSL::PKCS7::BINARY)
|
|
189
190
|
broken_msg = Akero.replate(env.to_s, Akero::PLATE_CRYPTED)
|
|
190
191
|
subject.receive(broken_msg)
|
|
191
|
-
|
|
192
|
+
end.should raise_error RuntimeError, Akero::ERR_MSG_MALFORMED_BODY
|
|
192
193
|
end
|
|
193
194
|
|
|
194
|
-
it
|
|
195
|
-
lambda
|
|
195
|
+
it 'raises RuntimeError on unsigned message' do
|
|
196
|
+
lambda do
|
|
196
197
|
raw_key = subject.send(:instance_variable_get, '@cert')
|
|
197
|
-
env = OpenSSL::PKCS7
|
|
198
|
+
env = OpenSSL::PKCS7.encrypt([raw_key], 'foobar', OpenSSL::Cipher.new('AES-256-CFB'), OpenSSL::PKCS7::BINARY)
|
|
198
199
|
broken_msg = Akero.replate(env.to_s, Akero::PLATE_CRYPTED)
|
|
199
200
|
subject.receive(broken_msg)
|
|
200
|
-
|
|
201
|
+
end.should raise_error RuntimeError, Akero::ERR_MSG_TOO_MANY_SIGNERS
|
|
201
202
|
end
|
|
202
203
|
end
|
|
203
204
|
end
|
|
204
205
|
end
|
|
205
|
-
|
|
206
|
+
|
|
206
207
|
describe '#verify' do
|
|
207
|
-
it
|
|
208
|
-
lambda
|
|
208
|
+
it 'raises RuntimeError when embedded certificate can not be verified' do
|
|
209
|
+
lambda do
|
|
209
210
|
fake_msg = mock('fake_msg')
|
|
210
211
|
fake_msg.stub(:verify).and_return(false)
|
|
211
212
|
fake_msg.stub_chain(:certificates, :length).and_return(1)
|
|
212
213
|
fake_msg.stub_chain(:certificates, :[]).and_return(nil)
|
|
213
214
|
subject.send(:verify, fake_msg, nil)
|
|
214
|
-
|
|
215
|
+
end.should raise_error RuntimeError, Akero::ERR_MSG_CORRUPT_CERT
|
|
215
216
|
end
|
|
216
217
|
end
|
|
217
218
|
|
|
218
219
|
describe '#inspect' do
|
|
219
|
-
it
|
|
220
|
+
it 'returns a summary String' do
|
|
220
221
|
s = subject.inspect
|
|
221
222
|
s.should match /id=AK:/
|
|
222
223
|
end
|
|
223
224
|
end
|
|
224
225
|
|
|
225
226
|
describe '#to_s' do
|
|
226
|
-
it
|
|
227
|
+
it 'returns the same value as #inspect' do
|
|
227
228
|
subject.to_s.should == subject.inspect
|
|
228
229
|
end
|
|
229
230
|
end
|
|
230
|
-
|
|
231
|
+
|
|
231
232
|
describe '.fingerprint_from_cert' do
|
|
232
|
-
it
|
|
233
|
+
it 'raises RuntimeError on invalid cert' do
|
|
233
234
|
mock_cert = mock('mock_cert')
|
|
234
235
|
mock_cert.stub_chain(:extensions, :map, :each).and_return(nil)
|
|
235
|
-
lambda
|
|
236
|
+
lambda do
|
|
236
237
|
Akero.fingerprint_from_cert(mock_cert)
|
|
237
|
-
|
|
238
|
+
end.should raise_error RuntimeError, Akero::ERR_CERT_CORRUPT
|
|
238
239
|
end
|
|
239
240
|
end
|
|
240
241
|
|
|
241
242
|
describe Akero::Message do
|
|
242
243
|
describe '#inspect' do
|
|
243
|
-
it
|
|
244
|
+
it 'returns a summary String' do
|
|
244
245
|
signed_msg = subject.sign('')
|
|
245
246
|
msg = subject.receive(signed_msg)
|
|
246
247
|
s = msg.inspect
|
data/spec/spec_helper.rb
CHANGED