slosilo 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|