yasst 0.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 +15 -0
- data/.gitignore +28 -0
- data/.rspec +2 -0
- data/.rubocop.yml +14 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/Guardfile +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +92 -0
- data/Rakefile +19 -0
- data/TODO.md +14 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/yasst/error.rb +17 -0
- data/lib/yasst/primatives/openssl/metadata.rb +29 -0
- data/lib/yasst/primatives/openssl.rb +146 -0
- data/lib/yasst/profile.rb +8 -0
- data/lib/yasst/profiles/openssl.rb +62 -0
- data/lib/yasst/profiles.rb +8 -0
- data/lib/yasst/provider/openssl.rb +90 -0
- data/lib/yasst/provider.rb +61 -0
- data/lib/yasst/version.rb +3 -0
- data/lib/yasst/yasst_file.rb +7 -0
- data/lib/yasst/yasst_string.rb +36 -0
- data/lib/yasst.rb +12 -0
- data/yasst.gemspec +34 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MTk4MmY5N2RmODI2NDBmZGY4NzkzNGY2NzAyZTQ5M2U5ZTA4ODU4ZQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
N2E0YTU5M2Y5ZjliYzcwNzY0Y2M4MDI1OGE0MGJmNTU0YWQwMTk0NA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
M2EwNDA1MDI5YjQ5N2VkNzhiMDUxZDVlYjU1ZmEyZjgwOWQ3ZGVmZDM2NDJm
|
10
|
+
NzlhOWI3NDQzNTRlNTY5ZjVmNmI0Njg0ZDVhYWYzZjhmYjNlZWVjY2VmYmM2
|
11
|
+
Zjc5ZDNiMDJlZjViYzI5OTU0NjJkYjM4MDc0NzhkZWY2MTZhZWU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YWQ2YjBkNWEwYTZhZTMzZjFjOTU4NzEwNDM3MTZiOWEwNGQ1MmEyNDMzZmFm
|
14
|
+
MTA5NTI0NWZlNmE5NjQwZTk3YTExMDQyN2Q4NGU5MzEyNzdiNGI3MTVlYTVm
|
15
|
+
OWQ3NmZkYTE1YzUyOGQ4ZmUxZjI1ZDc2YTY5OTEyYWMxOTM4ZWE=
|
data/.gitignore
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Default ignores for vim editor
|
2
|
+
.*.sw[a-z]
|
3
|
+
*.un~
|
4
|
+
Session.vim
|
5
|
+
.netrwhist
|
6
|
+
# Default ignores for emacs editor
|
7
|
+
*~
|
8
|
+
\#*\#
|
9
|
+
/.emacs.desktop
|
10
|
+
/.emacs.desktop.lock
|
11
|
+
.elc
|
12
|
+
auto-save-list
|
13
|
+
tramp
|
14
|
+
.\#*
|
15
|
+
|
16
|
+
# docs
|
17
|
+
/doc
|
18
|
+
|
19
|
+
# as this is a gem, we don't check Gemfile.lock into version control
|
20
|
+
Gemfile.lock
|
21
|
+
|
22
|
+
# test harness
|
23
|
+
pkg/*
|
24
|
+
vendor/bundle
|
25
|
+
vendor/cache
|
26
|
+
.bundle
|
27
|
+
|
28
|
+
tmp
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
Style/RegexpLiteral:
|
3
|
+
Exclude:
|
4
|
+
- 'Guardfile'
|
5
|
+
# TODO - simplify Yasst::Profiles::OpenSSL
|
6
|
+
Metrics/PerceivedComplexity:
|
7
|
+
Exclude:
|
8
|
+
- 'lib/yasst/profiles/openssl.rb'
|
9
|
+
Metrics/CyclomaticComplexity:
|
10
|
+
Exclude:
|
11
|
+
- 'lib/yasst/profiles/openssl.rb'
|
12
|
+
Metrics/AbcSize:
|
13
|
+
Exclude:
|
14
|
+
- 'lib/yasst/profiles/openssl.rb'
|
data/.travis.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.2.3
|
4
|
+
- 2.1.0
|
5
|
+
deploy:
|
6
|
+
provider: rubygems
|
7
|
+
gem: yasst
|
8
|
+
on:
|
9
|
+
tags: true
|
10
|
+
api_key:
|
11
|
+
secure: l2mduN8h2tjuBSr0G/XdAD5DNbiV87vZwQoLoH2PX1KQKdgaSzlJ4Mus9pSQzhq5JsLFRNtNdMvRp1AvxEajAf4Oy+H3tBSoeQEz9/wDfZvkAje1BlZyN6fXcIzj1HkDHoA8Da6y8o5xnA3Q8f+bVDloUJyjadT5Y8tOBDBo1QY4s/qPXoqUIsGLgJtBg29MqKGj/lVrvxf/+GRs62VlJny0jRhWJK+OzyxyKHB17wIs5wg+blayd8studtpJBvFFNMSFlwF1YvTCqsCyU4d9DHaR5GS0ZOev1y63E+EkkgJiXpBh+yDXVzpSjVcm4jygJdo7n6wdW+uv7yUP8rm9ULZG6TZmDIU8I1plVjD7aOMgRBvPckomHZ8maj+1FAz8Axl/bDQhM/TT0uT2ckS6h9sXMv1uSIm/hVyiTmETg7JyETHzvd/VrH4QssGdNo60MdhQJ/Cl0US/mt2xiMlAQB4qZCakWv3A8aCCg6dswouYgewvXkrSG3mUpPpmBAjBGNLkG9IRs1lUISqBOp452HuBQ5nyR9l8ATRJvHI8hdD7tPuGfQILG0k6c9ePpFrVD7odB9KkVTd7Zd8iMh0np4oc/XeZdYVelRyeGLS7vh7QdND3w0dSr5svgfzX45lD5kwYzST6JqpaJcBZu76ctMg37AV3hlZA1Ut6etZWa8=
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
2
|
+
# rspec may be run, below are examples of the most common uses.
|
3
|
+
# * bundler: 'bundle exec rspec'
|
4
|
+
# * bundler binstubs: 'bin/rspec'
|
5
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
6
|
+
# installed the spring binstubs per the docs)
|
7
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
8
|
+
# * 'just' rspec: 'rspec'
|
9
|
+
|
10
|
+
guard :rspec, cmd: 'rspec' do
|
11
|
+
watch(%r{^spec/.+_spec\.rb$})
|
12
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
13
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
14
|
+
end
|
15
|
+
|
16
|
+
guard :rubocop do
|
17
|
+
watch(%r{^spec/.+_spec\.rb$})
|
18
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
19
|
+
watch(%r{[^\/]+\.rb$})
|
20
|
+
watch(%r{.+\.gemspec$})
|
21
|
+
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
22
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Richard Clark
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Yasst
|
2
|
+
|
3
|
+
[](https://travis-ci.org/rdark/yasst)
|
4
|
+
|
5
|
+
Yet Another Secret Stashing Toolkit.
|
6
|
+
|
7
|
+
## Overview
|
8
|
+
|
9
|
+
This project gives convenient methods for encrypting and decrypting `String`
|
10
|
+
and `File` objects (using the [decorator pattern][decorator_pattern], rather
|
11
|
+
than [monkey-patching][monkey_patching]) via the `YasstString` and `YasstFile`
|
12
|
+
classes respectively.
|
13
|
+
|
14
|
+
Encryption and decryption is handled by a `Yasst::Provider`; at the moment only
|
15
|
+
OpenSSL is implemented, though support for an OpenPGP provider is planned.
|
16
|
+
|
17
|
+
Each provider has a configurable `Yasst::Profile`, with sensible defaults set.
|
18
|
+
At the time of writing, the defaults for `Yasst::Profiles::OpenSSL` are:
|
19
|
+
|
20
|
+
* AES-256 cipher in CBC mode
|
21
|
+
* key generation via PBKDF2 HMAC-SHA1 with 50,000 iterations
|
22
|
+
|
23
|
+
Additionally, the OpenSSL provider will ensure that there is:
|
24
|
+
|
25
|
+
* random salt generated for every encrypt action
|
26
|
+
* random IV generated for every encrypt action
|
27
|
+
* new key generated for every encrypt (and decrypt) action
|
28
|
+
* encrypted string output is Base64 (web-safe) encoded
|
29
|
+
|
30
|
+
[decorator_pattern]: https://github.com/nslocum/design-patterns-in-ruby#decorator
|
31
|
+
[monkey_patching]: http://demonastery.org/2012/11/monkey-patching-in-ruby/
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
### YasstString
|
36
|
+
|
37
|
+
provider = Yasst::Provider::OpenSSL.new(passphrase: 'a really strong passphrase')
|
38
|
+
provider.profile.algorithm
|
39
|
+
=> "AES-256-CBC"
|
40
|
+
provider.profile.key_gen_method
|
41
|
+
=> :pbkdf2
|
42
|
+
provider.profile.pbkdf2_iterations
|
43
|
+
=> 50000
|
44
|
+
secrets = YasstString.new('some really secret data')
|
45
|
+
secrets.encrypted?
|
46
|
+
=> false
|
47
|
+
secrets.encrypt(provider)
|
48
|
+
=> "Ubvxrj7-E7QCNqiof00RwxTka5V2debHX6gdIPdAmdRvsgB2YpjGD4IU5EYYN6uFk5iKo76k6mvK4tTIXbcBlhFmnN4mptpG
|
49
|
+
secrets.encrypted?
|
50
|
+
=> true
|
51
|
+
|
52
|
+
### YasstFile
|
53
|
+
|
54
|
+
NotYetImplemented
|
55
|
+
|
56
|
+
## Installation
|
57
|
+
|
58
|
+
Add this line to your application's Gemfile:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
gem 'yasst'
|
62
|
+
```
|
63
|
+
|
64
|
+
And then execute:
|
65
|
+
|
66
|
+
$ bundle
|
67
|
+
|
68
|
+
Or install it yourself as:
|
69
|
+
|
70
|
+
$ gem install yasst
|
71
|
+
|
72
|
+
## Development
|
73
|
+
|
74
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
75
|
+
|
76
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rdark/yasst. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
81
|
+
|
82
|
+
|
83
|
+
## License
|
84
|
+
|
85
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
86
|
+
|
87
|
+
## Disclaimer
|
88
|
+
|
89
|
+
* This security provided by this project has not been independently verified
|
90
|
+
* Only AES ciphers are currently supported/tested, and primarily focus has so
|
91
|
+
far been on CBC mode.
|
92
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
|
5
|
+
namespace :test do
|
6
|
+
# default unit tests
|
7
|
+
desc 'Run all unit tests in default spec suite'
|
8
|
+
RSpec::Core::RakeTask.new(:unit) do |task|
|
9
|
+
task.pattern = 'spec/unit/**/*_spec.rb'
|
10
|
+
end
|
11
|
+
# add rubocop tasks to test namespace
|
12
|
+
RuboCop::RakeTask.new
|
13
|
+
# run all non-integration/default tests
|
14
|
+
desc 'Run all default test suites'
|
15
|
+
task default: [:unit, :rubocop]
|
16
|
+
end
|
17
|
+
|
18
|
+
# default task is to run all the default test suites
|
19
|
+
task default: ['test:default']
|
data/TODO.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# TODO
|
2
|
+
|
3
|
+
## General Functionality
|
4
|
+
|
5
|
+
* YasstFile Implementation
|
6
|
+
* support for larger-than-memory files
|
7
|
+
* support for authenticated cipher modes
|
8
|
+
* support for OpenPGP crypto provider
|
9
|
+
* support for logging
|
10
|
+
|
11
|
+
## Profiles::OpenSSL
|
12
|
+
|
13
|
+
* make banned/recommended/supported list of algorithms
|
14
|
+
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'yasst'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require 'pry'
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/yasst/error.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Yasst
|
2
|
+
# Error classes for Yasst
|
3
|
+
class Error < StandardError
|
4
|
+
attr_reader :error
|
5
|
+
|
6
|
+
def initialize(error = nil)
|
7
|
+
@error = error
|
8
|
+
end
|
9
|
+
|
10
|
+
class InvalidCryptoProvider < self; end
|
11
|
+
class InvalidCryptoProfile < self; end
|
12
|
+
class InvalidCryptoAlgorithm < self; end
|
13
|
+
class InvalidPassPhrase < self; end
|
14
|
+
class AlreadyEncrypted < self; end
|
15
|
+
class AlreadyDecrypted < self; end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Yasst
|
4
|
+
module Primatives
|
5
|
+
module OpenSSL
|
6
|
+
##
|
7
|
+
# Methods for returning various bits of information
|
8
|
+
module Metadata
|
9
|
+
##
|
10
|
+
# List available ciphers
|
11
|
+
def self.list_ciphers
|
12
|
+
::OpenSSL::Cipher.ciphers
|
13
|
+
end
|
14
|
+
|
15
|
+
# Return the key length for a given cipher algorithm
|
16
|
+
def self.key_len_for(alg)
|
17
|
+
cipher = ::OpenSSL::Cipher.new(alg)
|
18
|
+
cipher.key_len
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return the key length for a given cipher algorithm
|
22
|
+
def self.iv_len_for(alg)
|
23
|
+
cipher = ::OpenSSL::Cipher.new(alg)
|
24
|
+
cipher.iv_len
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
require 'digest/sha2'
|
4
|
+
|
5
|
+
module Yasst
|
6
|
+
module Primatives
|
7
|
+
# OpenSSL primatives mixin
|
8
|
+
module OpenSSL
|
9
|
+
private
|
10
|
+
|
11
|
+
# Generate a random salt
|
12
|
+
# ===== Parameters
|
13
|
+
# - +salt_bytes+
|
14
|
+
# Size of the salt to be generated in bytes
|
15
|
+
def p_salt(salt_bytes)
|
16
|
+
::OpenSSL::Random.random_bytes(salt_bytes)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return a cipher object
|
20
|
+
#
|
21
|
+
# ===== Parameters
|
22
|
+
# - +algorithm+
|
23
|
+
# The algorithm to be used for the cipher (E.G 'AES-256-CBC')
|
24
|
+
def p_cipher(algorithm)
|
25
|
+
::OpenSSL::Cipher.new(algorithm)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return a PBKDF2 HMAC SHA1 key
|
29
|
+
#
|
30
|
+
# ===== Parameters
|
31
|
+
# - +passphrase+
|
32
|
+
# The passphrase to be used for generation of the key
|
33
|
+
# - +salt+
|
34
|
+
# A random salt to be used for generation of the key
|
35
|
+
# - +iterations+
|
36
|
+
# How many rounds of SHA1 hashing to use (recommend minimum 20k)
|
37
|
+
# - +key_length+
|
38
|
+
# How long the key should be (should match the requirements of your
|
39
|
+
# cipher algorithm)
|
40
|
+
def p_pbkdf2_key(passphrase, salt, iterations, key_length)
|
41
|
+
::OpenSSL::PKCS5.pbkdf2_hmac_sha1(
|
42
|
+
passphrase,
|
43
|
+
salt,
|
44
|
+
iterations,
|
45
|
+
key_length
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Encrypt a string
|
50
|
+
#
|
51
|
+
# ===== Parameters
|
52
|
+
# - +string+
|
53
|
+
# Data to be encrypted, presented as a string
|
54
|
+
# - +key+
|
55
|
+
# Encryption key to be used
|
56
|
+
# - +algorithm+
|
57
|
+
# Algorithm to use for the cipher
|
58
|
+
#
|
59
|
+
# ===== Returns
|
60
|
+
# - +iv+
|
61
|
+
# Initialisation vector used for the encryption
|
62
|
+
# - +ciphertext+
|
63
|
+
# The ciphertext
|
64
|
+
def p_encrypt_string(string, key, algorithm)
|
65
|
+
cipher = p_cipher(algorithm)
|
66
|
+
cipher.encrypt
|
67
|
+
cipher.key = key
|
68
|
+
# random_iv sets + returns simultaneously, grab output for prepending
|
69
|
+
iv = cipher.random_iv
|
70
|
+
ciphertext = cipher.update(Base64.urlsafe_encode64(string))
|
71
|
+
# not required for streaming ciphers but compatible/recommended anyway
|
72
|
+
ciphertext << cipher.final
|
73
|
+
[iv, ciphertext]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Decrypt ciphertext string
|
77
|
+
#
|
78
|
+
# ===== Parameters
|
79
|
+
# - +ciphertext+
|
80
|
+
# Raw ciphertext as a string
|
81
|
+
# - +key+
|
82
|
+
# Key that will be used to decrypt the ciphertext
|
83
|
+
# - +iv+
|
84
|
+
# Initialisation Vector for the ciphertext
|
85
|
+
# - +algorithm+
|
86
|
+
# Algorithm to use for the cipher
|
87
|
+
def p_decrypt_string(ciphertext, key, iv, algorithm)
|
88
|
+
cipher = p_cipher(algorithm)
|
89
|
+
cipher.key = key
|
90
|
+
cipher.iv = iv
|
91
|
+
cipher.decrypt
|
92
|
+
# decrypt ciphertext
|
93
|
+
encoded_plain = cipher.update(ciphertext)
|
94
|
+
encoded_plain << cipher.final
|
95
|
+
# return the decoded plaintext
|
96
|
+
Base64.urlsafe_decode64(encoded_plain)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Pack a string ready for storing
|
100
|
+
#
|
101
|
+
# ===== Parameters
|
102
|
+
# - +iv+
|
103
|
+
# The IV used during encryption of the ciphertext
|
104
|
+
# - +salt+
|
105
|
+
# The salt used for generation of the encryption key
|
106
|
+
def p_pack_string(iv, salt, ciphertext)
|
107
|
+
output = ciphertext
|
108
|
+
output.prepend(iv)
|
109
|
+
output.prepend(salt)
|
110
|
+
Base64.urlsafe_encode64(output)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Unpack a string ready for decryption
|
114
|
+
#
|
115
|
+
# ===== Parameters
|
116
|
+
# - +string+
|
117
|
+
# Base64 encoded string containing the ciphertext with prepended salt
|
118
|
+
# and IV
|
119
|
+
# - +key_length+
|
120
|
+
# The size (in bytes) of the encryption key used to encrypt the data
|
121
|
+
# - +iv_length+
|
122
|
+
# The size (in bytes) of the IV used to encrypt the data
|
123
|
+
# - +salt_bytes+
|
124
|
+
# The size (in bytes) of the salt used to generate the encryption key
|
125
|
+
#
|
126
|
+
# ===== Returns
|
127
|
+
# - +salt+
|
128
|
+
# The salt that was prepended to the string
|
129
|
+
# - +iv+
|
130
|
+
# The IV that was prepended to the string
|
131
|
+
# - +ciphertext+
|
132
|
+
# The ciphertext (which should still be base64 encoded)
|
133
|
+
def p_unpack_string(string, key_length, iv_length, salt_bytes)
|
134
|
+
# remove base64 wrapping
|
135
|
+
string = Base64.urlsafe_decode64(string)
|
136
|
+
# pull out prepended salt and build the key using it
|
137
|
+
salt = string.byteslice(0..(salt_bytes - 1))
|
138
|
+
# pull out prepended IV
|
139
|
+
iv = string.byteslice(salt_bytes..(key_length - 1))
|
140
|
+
# pull out remaining data, which is the ciphertext
|
141
|
+
ciphertext = string.byteslice((salt_bytes + iv_length)..string.bytesize)
|
142
|
+
[salt, iv, ciphertext]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'yasst/primatives/openssl/metadata'
|
2
|
+
|
3
|
+
module Yasst
|
4
|
+
module Profiles
|
5
|
+
##
|
6
|
+
# OpenSSL Profile
|
7
|
+
class OpenSSL < Yasst::Profile
|
8
|
+
attr_reader :algorithm, :key_gen_method, :pbkdf2_iterations,
|
9
|
+
:key_len, :iv_len
|
10
|
+
attr_accessor :salt_bytes
|
11
|
+
|
12
|
+
DEFAULT_SALT_BYTES = 8
|
13
|
+
DEFAULT_KEY_GEN_METHOD = :pbkdf2
|
14
|
+
DEFAULT_PBKDF2_ITERATIONS = 50_000
|
15
|
+
DEFAULT_ALGORITHM = 'AES-256-CBC'.freeze
|
16
|
+
SUPPORTED_KEY_GEN_METHODS = [:pbkdf2].freeze
|
17
|
+
|
18
|
+
include Yasst::Primatives::OpenSSL::Metadata
|
19
|
+
|
20
|
+
def initialize(**args)
|
21
|
+
args[:algorithm].nil? && (self.algorithm = DEFAULT_ALGORITHM) ||
|
22
|
+
(self.algorithm = args[:algorithm])
|
23
|
+
args[:salt_bytes].nil? && (@salt_bytes = DEFAULT_SALT_BYTES) ||
|
24
|
+
(self.salt_bytes = args[:salt_bytes])
|
25
|
+
args[:key_gen_method].nil? &&
|
26
|
+
(self.key_gen_method = DEFAULT_KEY_GEN_METHOD) ||
|
27
|
+
(self.key_gen_method = args[:key_gen_method])
|
28
|
+
args[:pbkdf2_iterations].nil? &&
|
29
|
+
(self.pbkdf2_iterations = DEFAULT_PBKDF2_ITERATIONS) ||
|
30
|
+
(self.pbkdf2_iterations = args[:pbkdf2_iterations])
|
31
|
+
end
|
32
|
+
|
33
|
+
# setter method for algorithm
|
34
|
+
def algorithm=(alg)
|
35
|
+
valgs = Yasst::Primatives::OpenSSL::Metadata.list_ciphers
|
36
|
+
unless valgs.include? alg
|
37
|
+
raise Yasst::Error::InvalidCryptoAlgorithm,
|
38
|
+
"Invalid algorithm. Valid algorithms are #{valgs.join(', ')}"
|
39
|
+
end
|
40
|
+
@key_len = Yasst::Primatives::OpenSSL::Metadata.key_len_for(alg)
|
41
|
+
@iv_len = Yasst::Primatives::OpenSSL::Metadata.iv_len_for(alg)
|
42
|
+
@algorithm = alg
|
43
|
+
end
|
44
|
+
|
45
|
+
def key_gen_method=(method)
|
46
|
+
if SUPPORTED_KEY_GEN_METHODS.include? method
|
47
|
+
@key_gen_method = method
|
48
|
+
else
|
49
|
+
raise NotImplementedError,
|
50
|
+
'Invalid or not-yet-implemented key generation method. Valid ' \
|
51
|
+
"methods are #{SUPPORTED_KEY_GEN_METHODS.join(', ')}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# set number of pbkdf2_iterations. Only set if key_gen_method is :pbkdf2
|
56
|
+
def pbkdf2_iterations=(iterations)
|
57
|
+
@key_gen_method == :pbkdf2 && (@pbkdf2_iterations = iterations) ||
|
58
|
+
(@pbkdf2_iterations = nil)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'yasst/primatives/openssl'
|
2
|
+
|
3
|
+
module Yasst
|
4
|
+
class Provider
|
5
|
+
##
|
6
|
+
# OpenSSL provider
|
7
|
+
# ===== Parameters
|
8
|
+
# - *profile*
|
9
|
+
# ===== Required Parameters
|
10
|
+
# - *passphrase*
|
11
|
+
class OpenSSL < Yasst::Provider
|
12
|
+
include Yasst::Primatives::OpenSSL
|
13
|
+
|
14
|
+
attr_reader :profile
|
15
|
+
|
16
|
+
##
|
17
|
+
# initialize hook for superclass
|
18
|
+
def post_initialize(**args)
|
19
|
+
args[:profile].nil? && (@profile = Yasst::Profiles::OpenSSL.new) ||
|
20
|
+
(@profile = args[:profile])
|
21
|
+
validate_passphrase(@passphrase)
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Whether or not a passphrase is required for this provider
|
26
|
+
def passphrase_required?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Encrypt a string using a unique salt + key for every encrypt action,
|
32
|
+
# and then package it up in a usable base64'd string with iv + salt
|
33
|
+
# prepended
|
34
|
+
#
|
35
|
+
# ===== Parameters
|
36
|
+
# - +string+
|
37
|
+
# A string to encrypt
|
38
|
+
# ===== Returns
|
39
|
+
# - String
|
40
|
+
def encrypt(string)
|
41
|
+
e_salt = salt
|
42
|
+
e_key = key(e_salt)
|
43
|
+
e_iv, ciphertext = p_encrypt_string(string, e_key, profile.algorithm)
|
44
|
+
p_pack_string(e_iv, e_salt, ciphertext)
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# De-encode, unpack and decrypt a string
|
49
|
+
#
|
50
|
+
# ===== Parameters
|
51
|
+
# - +string+
|
52
|
+
# A base64-encoded string to decrypt. Must be in the same format as the
|
53
|
+
# encrypt method produces
|
54
|
+
# ===== Returns
|
55
|
+
def decrypt(string)
|
56
|
+
d_salt, d_iv, ciphertext = p_unpack_string(
|
57
|
+
string,
|
58
|
+
profile.key_len,
|
59
|
+
profile.iv_len,
|
60
|
+
profile.salt_bytes
|
61
|
+
)
|
62
|
+
p_decrypt_string(ciphertext, key(d_salt), d_iv, profile.algorithm)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
##
|
68
|
+
# Returns a brand new salt
|
69
|
+
def salt
|
70
|
+
p_salt(profile.salt_bytes)
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# Returns a new key for the configured key gen method.
|
75
|
+
# Called with no parameters, a fresh salt will be used
|
76
|
+
# ===== Parameters
|
77
|
+
# - +salt+
|
78
|
+
# Optional salt value to use when generating the key
|
79
|
+
def key(salt = nil)
|
80
|
+
salt.nil? && salt = new_salt
|
81
|
+
# Profile should raise NotImplementedErrror if unsupported key
|
82
|
+
# generation method is used
|
83
|
+
if profile.key_gen_method == :pbkdf2
|
84
|
+
return p_pbkdf2_key(@passphrase, salt,
|
85
|
+
profile.pbkdf2_iterations, profile.key_len)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'yasst/provider/openssl'
|
2
|
+
module Yasst
|
3
|
+
##
|
4
|
+
# Represents a Crypto Provider
|
5
|
+
#
|
6
|
+
# === Parameters
|
7
|
+
# - *passphrase*
|
8
|
+
# The passphrase used for the provider instance. This is optional since
|
9
|
+
# some providers may not require a passphrase. Where passphrase is given,
|
10
|
+
# it must conform to minimum complexity requirements.
|
11
|
+
class Provider
|
12
|
+
attr_reader :passphrase
|
13
|
+
|
14
|
+
PASSPHRASE_MIN_LENGTH = 8
|
15
|
+
|
16
|
+
def initialize(**args)
|
17
|
+
self.passphrase = args[:passphrase]
|
18
|
+
post_initialize(args)
|
19
|
+
end
|
20
|
+
|
21
|
+
# post initialize hook for subclasses
|
22
|
+
def post_initialize(**_args)
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# setter method for passphrase
|
27
|
+
def passphrase=(pass)
|
28
|
+
validate_passphrase(pass) && @passphrase = pass
|
29
|
+
end
|
30
|
+
|
31
|
+
# validates a passphrase and raise on error
|
32
|
+
def validate_passphrase(pass = @passphrase)
|
33
|
+
unless passphrase_valid?(pass)
|
34
|
+
raise Yasst::Error::InvalidPassPhrase,
|
35
|
+
'Passphrase does not meet minimum requirements'
|
36
|
+
end
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Whether or not a passphrase is required for this provider instance
|
41
|
+
# Should be overridden in the provider subclass if it requires a passphrase
|
42
|
+
def passphrase_required?
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Validate a passphrase
|
47
|
+
# ===== Parameters
|
48
|
+
# - +pass+
|
49
|
+
# String. The passphrase to be validated
|
50
|
+
# ===== Returns
|
51
|
+
# - true/false if the passphrase is valid or not
|
52
|
+
def passphrase_valid?(pass = @passphrase)
|
53
|
+
if pass.nil?
|
54
|
+
return false if passphrase_required?
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
return false unless pass.length >= PASSPHRASE_MIN_LENGTH
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
##
|
2
|
+
# Decorator pattern for String
|
3
|
+
#
|
4
|
+
# TODO: only way to tell an already encrypted new YasstString object that it is
|
5
|
+
# encrypted is by telling it so at initialization time. Is there a more elegant
|
6
|
+
# way of doing this without imposing overhead?
|
7
|
+
class YasstString < String
|
8
|
+
def initialize(str = '', encrypted = false)
|
9
|
+
super(str)
|
10
|
+
@encrypted = encrypted
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Encrypt self using a Yasst::Provider
|
15
|
+
def encrypt(provider)
|
16
|
+
raise Yasst::Error::AlreadyEncrypted,
|
17
|
+
'File is already encrypted' if encrypted?
|
18
|
+
@encrypted = true
|
19
|
+
replace(provider.encrypt(to_s))
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Whether or not the encrypt method has been called
|
24
|
+
def encrypted?
|
25
|
+
@encrypted ||= false
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Decrypt self using a Yasst::Provider
|
30
|
+
def decrypt(provider)
|
31
|
+
raise Yasst::Error::AlreadyDecrypted,
|
32
|
+
'File is already decrypted' unless encrypted?
|
33
|
+
@encrypted = false
|
34
|
+
replace(provider.decrypt(to_s))
|
35
|
+
end
|
36
|
+
end
|
data/lib/yasst.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'yasst/version'
|
2
|
+
require 'yasst/error'
|
3
|
+
require 'yasst/profile'
|
4
|
+
require 'yasst/profiles'
|
5
|
+
require 'yasst/provider'
|
6
|
+
require 'yasst/yasst_string'
|
7
|
+
require 'yasst/yasst_file'
|
8
|
+
|
9
|
+
##
|
10
|
+
# Yet Another Secret Stashing Toolset
|
11
|
+
module Yasst
|
12
|
+
end
|
data/yasst.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'yasst/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'yasst'
|
8
|
+
spec.version = Yasst::VERSION
|
9
|
+
spec.authors = ['Richard Clark']
|
10
|
+
spec.email = ['richard@fohnet.co.uk']
|
11
|
+
|
12
|
+
spec.summary = 'Yet Another Secret Stashing Toolkit'
|
13
|
+
spec.description = 'Yasst is a toolset for managing encryption and ' \
|
14
|
+
'decryption of secrets'
|
15
|
+
spec.homepage = 'https://github.com/rdark/yasst'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f|
|
20
|
+
f.match(%r{^(TODO|test|spec|features)/})
|
21
|
+
}
|
22
|
+
# ensure gem is built out of versioned files
|
23
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map { |f|
|
24
|
+
File.basename(f)
|
25
|
+
}
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1'
|
28
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.4.0'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0.36'
|
31
|
+
spec.add_development_dependency 'guard', '~> 2.13.0'
|
32
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.6.4'
|
33
|
+
spec.add_development_dependency 'guard-rubocop', '~> 1.2.0'
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yasst
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Clark
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.4.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.4.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.36'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.36'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.13.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.13.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.6.4
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.6.4
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard-rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.2.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.2.0
|
111
|
+
description: Yasst is a toolset for managing encryption and decryption of secrets
|
112
|
+
email:
|
113
|
+
- richard@fohnet.co.uk
|
114
|
+
executables:
|
115
|
+
- console
|
116
|
+
- setup
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- .gitignore
|
121
|
+
- .rspec
|
122
|
+
- .rubocop.yml
|
123
|
+
- .travis.yml
|
124
|
+
- Gemfile
|
125
|
+
- Guardfile
|
126
|
+
- LICENSE.txt
|
127
|
+
- README.md
|
128
|
+
- Rakefile
|
129
|
+
- TODO.md
|
130
|
+
- bin/console
|
131
|
+
- bin/setup
|
132
|
+
- lib/yasst.rb
|
133
|
+
- lib/yasst/error.rb
|
134
|
+
- lib/yasst/primatives/openssl.rb
|
135
|
+
- lib/yasst/primatives/openssl/metadata.rb
|
136
|
+
- lib/yasst/profile.rb
|
137
|
+
- lib/yasst/profiles.rb
|
138
|
+
- lib/yasst/profiles/openssl.rb
|
139
|
+
- lib/yasst/provider.rb
|
140
|
+
- lib/yasst/provider/openssl.rb
|
141
|
+
- lib/yasst/version.rb
|
142
|
+
- lib/yasst/yasst_file.rb
|
143
|
+
- lib/yasst/yasst_string.rb
|
144
|
+
- yasst.gemspec
|
145
|
+
homepage: https://github.com/rdark/yasst
|
146
|
+
licenses:
|
147
|
+
- MIT
|
148
|
+
metadata: {}
|
149
|
+
post_install_message:
|
150
|
+
rdoc_options: []
|
151
|
+
require_paths:
|
152
|
+
- lib
|
153
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ! '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
requirements: []
|
164
|
+
rubyforge_project:
|
165
|
+
rubygems_version: 2.4.5
|
166
|
+
signing_key:
|
167
|
+
specification_version: 4
|
168
|
+
summary: Yet Another Secret Stashing Toolkit
|
169
|
+
test_files: []
|