slosilo 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +32 -0
- data/Rakefile +17 -0
- data/lib/slosilo/adapters/abstract_adapter.rb +19 -0
- data/lib/slosilo/adapters/mock_adapter.rb +8 -0
- data/lib/slosilo/adapters/sequel_adapter/migration.rb +49 -0
- data/lib/slosilo/adapters/sequel_adapter.rb +34 -0
- data/lib/slosilo/attr_encrypted.rb +59 -0
- data/lib/slosilo/http_request.rb +59 -0
- data/lib/slosilo/key.rb +95 -0
- data/lib/slosilo/keystore.rb +61 -0
- data/lib/slosilo/rack/middleware.rb +123 -0
- data/lib/slosilo/random.rb +11 -0
- data/lib/slosilo/symmetric.rb +33 -0
- data/lib/slosilo/version.rb +3 -0
- data/lib/slosilo.rb +13 -0
- data/lib/tasks/slosilo.rake +22 -0
- data/slosilo.gemspec +25 -0
- data/spec/http_request_spec.rb +107 -0
- data/spec/http_stack_spec.rb +44 -0
- data/spec/key_spec.rb +115 -0
- data/spec/keystore_spec.rb +13 -0
- data/spec/rack_middleware_spec.rb +109 -0
- data/spec/random_spec.rb +9 -0
- data/spec/sequel_adapter_spec.rb +60 -0
- data/spec/slosilo_spec.rb +58 -0
- data/spec/spec_helper.rb +62 -0
- data/spec/symmetric_spec.rb +51 -0
- metadata +185 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Rafał Rzepecki
|
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,32 @@
|
|
1
|
+
# Slosilo
|
2
|
+
|
3
|
+
Slosilo is a keystore in the database. (Currently only works with postgres.)
|
4
|
+
It allows easy storage and retrieval of keys.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'slosilo'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Add a migration to create the necessary table:
|
17
|
+
|
18
|
+
require 'slosilo/adapters/sequel_adapter/migration'
|
19
|
+
|
20
|
+
Remember to migrate your database
|
21
|
+
|
22
|
+
$ rake db:migrate
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
## Contributing
|
27
|
+
|
28
|
+
1. Fork it
|
29
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
30
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
31
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
32
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
rescue LoadError
|
8
|
+
$stderr.puts "RSpec Rake tasks not available in environment #{ENV['RACK_ENV']}"
|
9
|
+
end
|
10
|
+
|
11
|
+
task :jenkins do
|
12
|
+
require 'ci/reporter/rake/rspec'
|
13
|
+
Rake::Task["ci:setup:rspec"].invoke
|
14
|
+
Rake::Task["spec"].invoke
|
15
|
+
end
|
16
|
+
|
17
|
+
task :default => :spec
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'slosilo/attr_encrypted'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
module Adapters
|
5
|
+
class AbstractAdapter
|
6
|
+
def get_key id
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def put_key id, key
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def each
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
module Adapters::SequelAdapter::Migration
|
5
|
+
# The default name of the table to hold the keys
|
6
|
+
DEFAULT_KEYSTORE_TABLE = :slosilo_keystore
|
7
|
+
|
8
|
+
# Sets up default keystore table name
|
9
|
+
def self.extended(db)
|
10
|
+
db.keystore_table ||= DEFAULT_KEYSTORE_TABLE
|
11
|
+
end
|
12
|
+
|
13
|
+
# Keystore table name. If changing this do it immediately after loading the extension.
|
14
|
+
attr_accessor :keystore_table
|
15
|
+
|
16
|
+
# Create the table for holding keys
|
17
|
+
def create_keystore_table
|
18
|
+
create_table keystore_table do
|
19
|
+
String :id, primary_key: true
|
20
|
+
# Note: currently only postgres is supported
|
21
|
+
bytea :key, null: false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Drop the table
|
26
|
+
def drop_keystore_table
|
27
|
+
drop_table keystore_table
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Extension
|
32
|
+
def slosilo_keystore
|
33
|
+
extend Slosilo::Adapters::SequelAdapter::Migration
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Sequel::Database.send :include, Extension
|
38
|
+
end
|
39
|
+
|
40
|
+
Sequel.migration do
|
41
|
+
up do
|
42
|
+
slosilo_keystore
|
43
|
+
create_keystore_table
|
44
|
+
end
|
45
|
+
down do
|
46
|
+
slosilo_keystore
|
47
|
+
drop_keystore_table
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'slosilo/adapters/abstract_adapter'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
module Adapters
|
5
|
+
class SequelAdapter < AbstractAdapter
|
6
|
+
def model
|
7
|
+
@model ||= create_model
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_model
|
11
|
+
model = Sequel::Model(:slosilo_keystore)
|
12
|
+
model.unrestrict_primary_key
|
13
|
+
model.attr_encrypted :key
|
14
|
+
model
|
15
|
+
end
|
16
|
+
|
17
|
+
def put_key id, value
|
18
|
+
model.create id: id, key: value
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_key id
|
22
|
+
stored = model[id]
|
23
|
+
return nil unless stored
|
24
|
+
stored.key
|
25
|
+
end
|
26
|
+
|
27
|
+
def each
|
28
|
+
model.each do |m|
|
29
|
+
yield m.id, m.key
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'slosilo/symmetric'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
# we don't trust the database to keep all backups safe from the prying eyes
|
5
|
+
# so we encrypt sensitive attributes before storing them
|
6
|
+
module EncryptedAttributes
|
7
|
+
module ClassMethods
|
8
|
+
def attr_encrypted *a
|
9
|
+
# push a module onto the inheritance hierarchy
|
10
|
+
# this allows calling super in classes
|
11
|
+
include(accessors = Module.new)
|
12
|
+
accessors.module_eval do
|
13
|
+
a.each do |attr|
|
14
|
+
define_method "#{attr}=" do |value|
|
15
|
+
super(EncryptedAttributes.encrypt value)
|
16
|
+
end
|
17
|
+
define_method attr do
|
18
|
+
EncryptedAttributes.decrypt(super())
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included base
|
26
|
+
base.extend ClassMethods
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def encrypt value
|
31
|
+
return nil unless value
|
32
|
+
cipher.encrypt value, key: key
|
33
|
+
end
|
34
|
+
|
35
|
+
def decrypt ctxt
|
36
|
+
return nil unless ctxt
|
37
|
+
cipher.decrypt ctxt, key: key
|
38
|
+
end
|
39
|
+
|
40
|
+
def key
|
41
|
+
Slosilo::encryption_key || (raise "Please set Slosilo::encryption_key")
|
42
|
+
end
|
43
|
+
|
44
|
+
def cipher
|
45
|
+
@cipher ||= Slosilo::Symmetric.new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
attr_writer :encryption_key
|
52
|
+
|
53
|
+
def encryption_key
|
54
|
+
@encryption_key
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Object.send:include, Slosilo::EncryptedAttributes
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Slosilo
|
2
|
+
# A mixin module which simplifies generating signed and encrypted requests.
|
3
|
+
# It's designed to be mixed into a standard Net::HTTPRequest object
|
4
|
+
# and ensures the request is signed and optionally encrypted before execution.
|
5
|
+
# Requests prepared this way will be recognized by Slosilo::Rack::Middleware.
|
6
|
+
#
|
7
|
+
# As an example, you can use it with RestClient like so:
|
8
|
+
# RestClient.add_before_execution_proc do |req, params|
|
9
|
+
# require 'slosilo'
|
10
|
+
# req.extend Slosilo::HTTPRequest
|
11
|
+
# req.keyname = :somekey
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# The request won't be encrypted unless you set the destination keyname.
|
15
|
+
|
16
|
+
module HTTPRequest
|
17
|
+
# Encrypt the request with key named @keyname from Slosilo::Keystore.
|
18
|
+
# If calling this manually, make sure to encrypt before signing.
|
19
|
+
def encrypt!
|
20
|
+
return unless @keyname
|
21
|
+
return unless body && !body.empty?
|
22
|
+
self.body, key = Slosilo[@keyname].encrypt body
|
23
|
+
self['X-Slosilo-Key'] = Base64::urlsafe_encode64 key
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sign the request with :own key from Slosilo::Keystore.
|
27
|
+
# If calling this manually, make sure to encrypt before signing.
|
28
|
+
def sign!
|
29
|
+
token = Slosilo[:own].signed_token signed_data
|
30
|
+
self['Timestamp'] = token["timestamp"]
|
31
|
+
self['X-Slosilo-Signature'] = token["signature"]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Build the data hash to sign.
|
35
|
+
def signed_data
|
36
|
+
data = { "path" => path, "body" => [body].pack('m0') }
|
37
|
+
if key = self['X-Slosilo-Key']
|
38
|
+
data["key"] = key
|
39
|
+
end
|
40
|
+
if authz = self['Authorization']
|
41
|
+
data["authorization"] = authz
|
42
|
+
end
|
43
|
+
data
|
44
|
+
end
|
45
|
+
|
46
|
+
# Encrypt, sign and execute the request.
|
47
|
+
def exec *a
|
48
|
+
# we need to hook here because the body might be set
|
49
|
+
# in several ways and here it's hopefully finalized
|
50
|
+
encrypt!
|
51
|
+
sign!
|
52
|
+
super *a
|
53
|
+
end
|
54
|
+
|
55
|
+
# Name of the key used to encrypt the request.
|
56
|
+
# Use it to establish the identity of the receiver.
|
57
|
+
attr_accessor :keyname
|
58
|
+
end
|
59
|
+
end
|
data/lib/slosilo/key.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
require 'time'
|
5
|
+
|
6
|
+
module Slosilo
|
7
|
+
class Key
|
8
|
+
def initialize raw_key = nil
|
9
|
+
@key = if raw_key.is_a? OpenSSL::PKey::RSA
|
10
|
+
raw_key
|
11
|
+
elsif !raw_key.nil?
|
12
|
+
OpenSSL::PKey.read raw_key
|
13
|
+
else
|
14
|
+
OpenSSL::PKey::RSA.new 2048
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :key
|
19
|
+
|
20
|
+
def cipher
|
21
|
+
@cipher ||= Slosilo::Symmetric.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def encrypt plaintext
|
25
|
+
key = cipher.random_key
|
26
|
+
ctxt = cipher.encrypt plaintext, key: key
|
27
|
+
key = @key.public_encrypt key
|
28
|
+
[ctxt, key]
|
29
|
+
end
|
30
|
+
|
31
|
+
def decrypt ciphertext, skey
|
32
|
+
key = @key.private_decrypt skey
|
33
|
+
cipher.decrypt ciphertext, key: key
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
@key.public_key.to_pem
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_der
|
41
|
+
@key.to_der
|
42
|
+
end
|
43
|
+
|
44
|
+
def sign value
|
45
|
+
sign_string(stringify value)
|
46
|
+
end
|
47
|
+
|
48
|
+
SIGNATURE_LEN = 256
|
49
|
+
|
50
|
+
def verify_signature data, signature
|
51
|
+
signature, salt = signature.unpack("a#{SIGNATURE_LEN}a*")
|
52
|
+
key.public_decrypt(signature) == hash_function.digest(salt + stringify(data))
|
53
|
+
rescue
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
# create a new timestamped and signed token carrying data
|
58
|
+
def signed_token data
|
59
|
+
token = { "data" => data, "timestamp" => Time.new.utc.to_s }
|
60
|
+
token["signature"] = Base64::urlsafe_encode64(sign token)
|
61
|
+
token
|
62
|
+
end
|
63
|
+
|
64
|
+
def token_valid? token, expiry = 8 * 60
|
65
|
+
token = token.clone
|
66
|
+
signature = Base64::urlsafe_decode64(token.delete "signature")
|
67
|
+
(Time.parse(token["timestamp"]) + expiry > Time.now) && verify_signature(token, signature)
|
68
|
+
end
|
69
|
+
|
70
|
+
def sign_string value
|
71
|
+
_salt = salt
|
72
|
+
key.private_encrypt(hash_function.digest(_salt + value)) + _salt
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def stringify value
|
77
|
+
case value
|
78
|
+
when Hash
|
79
|
+
value.to_a.sort.to_json
|
80
|
+
when String
|
81
|
+
value
|
82
|
+
else
|
83
|
+
value.to_json
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def salt
|
88
|
+
Slosilo::Random::salt
|
89
|
+
end
|
90
|
+
|
91
|
+
def hash_function
|
92
|
+
@hash_function ||= OpenSSL::Digest::SHA256
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'slosilo/key'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
class Keystore
|
5
|
+
def adapter
|
6
|
+
Slosilo::adapter or raise "No Slosilo adapter is configured or available"
|
7
|
+
end
|
8
|
+
|
9
|
+
def put id, key
|
10
|
+
adapter.put_key id.to_s, key.to_der
|
11
|
+
end
|
12
|
+
|
13
|
+
def get id
|
14
|
+
key = adapter.get_key(id.to_s)
|
15
|
+
key && Key.new(key)
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&block)
|
19
|
+
adapter.each(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def any? &block
|
23
|
+
catch :found do
|
24
|
+
adapter.each do |id, k|
|
25
|
+
throw :found if block.call(Key.new(k))
|
26
|
+
end
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def []= id, value
|
35
|
+
keystore.put id, value
|
36
|
+
end
|
37
|
+
|
38
|
+
def [] id
|
39
|
+
keystore.get id
|
40
|
+
end
|
41
|
+
|
42
|
+
def each(&block)
|
43
|
+
keystore.each(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def sign object
|
47
|
+
self[:own].sign object
|
48
|
+
end
|
49
|
+
|
50
|
+
def token_valid? token
|
51
|
+
keystore.any? { |k| k.token_valid? token }
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_accessor :adapter
|
55
|
+
|
56
|
+
private
|
57
|
+
def keystore
|
58
|
+
@keystore ||= Keystore.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Slosilo
|
2
|
+
module Rack
|
3
|
+
# Con perform verification of request signature and decryption of request body.
|
4
|
+
#
|
5
|
+
# Signature verification and body decryption are enabled with constructor switches and are
|
6
|
+
# therefore performed (or not) for all requests.
|
7
|
+
#
|
8
|
+
# When signature verification is performed, the following elements are included in the
|
9
|
+
# signature string:
|
10
|
+
#
|
11
|
+
# 1. Request path and query string
|
12
|
+
# 2. base64 encoded request body
|
13
|
+
# 3. Request timestamp from HTTP_TIMESTAMP
|
14
|
+
# 4. Body encryption key from HTTP_X_SLOSILO_KEY (if present)
|
15
|
+
#
|
16
|
+
# When body decryption is performed, an encryption key for the message body is encrypted
|
17
|
+
# with this service's public key and placed in HTTP_X_SLOSILO_KEY. This middleware
|
18
|
+
# decryps the key using our :own private key, and then decrypts the body using the decrypted key.
|
19
|
+
class Middleware
|
20
|
+
class EncryptionError < SecurityError
|
21
|
+
end
|
22
|
+
class SignatureError < SecurityError
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize app, opts = {}
|
26
|
+
@app = app
|
27
|
+
@encryption_required = opts[:encryption_required] || false
|
28
|
+
@signature_required = opts[:signature_required] || false
|
29
|
+
end
|
30
|
+
|
31
|
+
def call env
|
32
|
+
@env = env
|
33
|
+
@body = env['rack.input'].read rescue ""
|
34
|
+
|
35
|
+
begin
|
36
|
+
verify
|
37
|
+
decrypt
|
38
|
+
rescue EncryptionError
|
39
|
+
return error 403, $!.message
|
40
|
+
rescue SignatureError
|
41
|
+
return error 401, $!.message
|
42
|
+
end
|
43
|
+
|
44
|
+
@app.call env
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def verify
|
49
|
+
if signature
|
50
|
+
raise SignatureError, "Bad signature" unless Slosilo.token_valid?(token)
|
51
|
+
else
|
52
|
+
raise SignatureError, "Signature required" if signature_required?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :env
|
57
|
+
|
58
|
+
def token
|
59
|
+
return nil unless signature
|
60
|
+
t = { "data" => { "path" => path, "body" => [body].pack('m0') }, "timestamp" => timestamp, "signature" => signature }
|
61
|
+
t["data"]["key"] = encoded_key if encoded_key
|
62
|
+
t['data']['authorization'] = env['HTTP_AUTHORIZATION'] if env['HTTP_AUTHORIZATION']
|
63
|
+
t
|
64
|
+
end
|
65
|
+
|
66
|
+
def path
|
67
|
+
env['SCRIPT_NAME'] + env['PATH_INFO'] + query_string
|
68
|
+
end
|
69
|
+
|
70
|
+
def query_string
|
71
|
+
if env['QUERY_STRING'].empty?
|
72
|
+
''
|
73
|
+
else
|
74
|
+
'?' + env['QUERY_STRING']
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_reader :body
|
79
|
+
|
80
|
+
def timestamp
|
81
|
+
env['HTTP_TIMESTAMP']
|
82
|
+
end
|
83
|
+
|
84
|
+
def signature
|
85
|
+
env['HTTP_X_SLOSILO_SIGNATURE']
|
86
|
+
end
|
87
|
+
|
88
|
+
def encoded_key
|
89
|
+
env['HTTP_X_SLOSILO_KEY']
|
90
|
+
end
|
91
|
+
|
92
|
+
def key
|
93
|
+
if encoded_key
|
94
|
+
Base64::urlsafe_decode64(encoded_key)
|
95
|
+
else
|
96
|
+
raise EncryptionError, "Encryption required" if encryption_required?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def decrypt
|
101
|
+
return unless key
|
102
|
+
plaintext = Slosilo[:own].decrypt body, key
|
103
|
+
env['rack.input'] = StringIO.new plaintext
|
104
|
+
rescue EncryptionError
|
105
|
+
raise unless body.empty? || body.nil?
|
106
|
+
rescue Exception => e
|
107
|
+
raise EncryptionError, "Bad encryption", e.backtrace
|
108
|
+
end
|
109
|
+
|
110
|
+
def error status, message
|
111
|
+
[status, { 'Content-Type' => 'text/plain', 'Content-Length' => message.length.to_s }, [message] ]
|
112
|
+
end
|
113
|
+
|
114
|
+
def encryption_required?
|
115
|
+
@encryption_required
|
116
|
+
end
|
117
|
+
|
118
|
+
def signature_required?
|
119
|
+
@signature_required
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Slosilo
|
2
|
+
class Symmetric
|
3
|
+
def initialize
|
4
|
+
@cipher = OpenSSL::Cipher.new 'AES-256-CBC'
|
5
|
+
end
|
6
|
+
|
7
|
+
def encrypt plaintext, opts = {}
|
8
|
+
@cipher.reset
|
9
|
+
@cipher.encrypt
|
10
|
+
@cipher.key = opts[:key]
|
11
|
+
@cipher.iv = iv = random_iv
|
12
|
+
ctxt = @cipher.update(plaintext)
|
13
|
+
iv + ctxt + @cipher.final
|
14
|
+
end
|
15
|
+
|
16
|
+
def decrypt ciphertext, opts = {}
|
17
|
+
@cipher.reset
|
18
|
+
@cipher.decrypt
|
19
|
+
@cipher.key = opts[:key]
|
20
|
+
@cipher.iv, ctxt = ciphertext.unpack("a#{@cipher.iv_len}a*")
|
21
|
+
ptxt = @cipher.update(ctxt)
|
22
|
+
ptxt + @cipher.final
|
23
|
+
end
|
24
|
+
|
25
|
+
def random_iv
|
26
|
+
@cipher.random_iv
|
27
|
+
end
|
28
|
+
|
29
|
+
def random_key
|
30
|
+
@cipher.random_key
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/slosilo.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "slosilo/version"
|
2
|
+
require "slosilo/keystore"
|
3
|
+
require "slosilo/symmetric"
|
4
|
+
require "slosilo/attr_encrypted"
|
5
|
+
require "slosilo/random"
|
6
|
+
require "slosilo/rack/middleware"
|
7
|
+
require "slosilo/http_request"
|
8
|
+
|
9
|
+
if defined? Sequel
|
10
|
+
require 'slosilo/adapters/sequel_adapter'
|
11
|
+
Slosilo::adapter = Slosilo::Adapters::SequelAdapter.new
|
12
|
+
end
|
13
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/*.rake')].each { |ext| load ext } if defined?(Rake)
|