akero 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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