secrets-cipher-base64 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +25 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +13 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/MANAGING-KEYS.md +67 -0
- data/README.md +314 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/keychain +38 -0
- data/exe/secrets +8 -0
- data/lib/secrets.rb +73 -0
- data/lib/secrets/app.rb +42 -0
- data/lib/secrets/app/cli.rb +197 -0
- data/lib/secrets/app/commands.rb +27 -0
- data/lib/secrets/app/commands/command.rb +55 -0
- data/lib/secrets/app/commands/delete_keychain_key.rb +15 -0
- data/lib/secrets/app/commands/encrypt_decrypt.rb +22 -0
- data/lib/secrets/app/commands/generate_key.rb +41 -0
- data/lib/secrets/app/commands/open_editor.rb +90 -0
- data/lib/secrets/app/commands/show_examples.rb +63 -0
- data/lib/secrets/app/commands/show_help.rb +13 -0
- data/lib/secrets/app/commands/show_version.rb +13 -0
- data/lib/secrets/app/keychain.rb +136 -0
- data/lib/secrets/app/outputs/to_file.rb +27 -0
- data/lib/secrets/app/outputs/to_stdout.rb +11 -0
- data/lib/secrets/app/password_handler.rb +39 -0
- data/lib/secrets/cipher_handler.rb +45 -0
- data/lib/secrets/configuration.rb +23 -0
- data/lib/secrets/data.rb +23 -0
- data/lib/secrets/data/decoder.rb +24 -0
- data/lib/secrets/data/encoder.rb +24 -0
- data/lib/secrets/data/wrapper_struct.rb +43 -0
- data/lib/secrets/errors.rb +27 -0
- data/lib/secrets/extensions/class_methods.rb +12 -0
- data/lib/secrets/extensions/instance_methods.rb +110 -0
- data/lib/secrets/version.rb +3 -0
- data/secrets-cipher-base64.gemspec +33 -0
- metadata +243 -0
data/bin/setup
ADDED
data/exe/keychain
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib_path = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH << lib_path if File.exist?(lib_path) && !$LOAD_PATH.include?(lib_path)
|
5
|
+
|
6
|
+
require 'secrets'
|
7
|
+
require 'secrets/app'
|
8
|
+
require 'secrets/app/keychain'
|
9
|
+
require 'colored2'
|
10
|
+
|
11
|
+
def usage
|
12
|
+
puts 'Usage: ' + 'keychain'.bold.blue + ' item [ add <contents> | find | delete ]'.bold.green
|
13
|
+
exit 0
|
14
|
+
end
|
15
|
+
|
16
|
+
usage if ARGV.empty?
|
17
|
+
|
18
|
+
key_name, action, data = ARGV
|
19
|
+
|
20
|
+
unless %i(add find delete).include?(action.to_sym)
|
21
|
+
puts "Error: operation #{action.bold.red} is not recognized"
|
22
|
+
usage
|
23
|
+
end
|
24
|
+
|
25
|
+
if action.eql?('add') && data.nil?
|
26
|
+
puts "Error: please provide data to store with the #{'add'.bold.green} operation."
|
27
|
+
usage
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
puts data ? \
|
32
|
+
Secrets::App::KeyChain.new(key_name).send(action.to_sym, data) :
|
33
|
+
Secrets::App::KeyChain.new(key_name).send(action.to_sym)
|
34
|
+
rescue StandardError => e
|
35
|
+
STDERR.puts "#{e.message.red}"
|
36
|
+
end
|
37
|
+
|
38
|
+
|
data/exe/secrets
ADDED
data/lib/secrets.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'require_dir'
|
2
|
+
require 'colored2'
|
3
|
+
require 'zlib'
|
4
|
+
|
5
|
+
require_relative 'secrets/configuration'
|
6
|
+
|
7
|
+
Secrets::Configuration.configure do |config|
|
8
|
+
config.password_cipher = 'AES-128-CBC'
|
9
|
+
config.data_cipher = 'AES-256-CBC'
|
10
|
+
config.private_key_cipher = config.data_cipher
|
11
|
+
config.compression_enabled = true
|
12
|
+
config.compression_level = Zlib::BEST_COMPRESSION
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# _Include_ +Secrets+ in your class to enable functionality of this library.
|
17
|
+
#
|
18
|
+
# Once included, you would normally use +#encr+ and +#decr+ instance methods to perform
|
19
|
+
# encryption and decryption of object of any type using a symmetric key encryption.
|
20
|
+
#
|
21
|
+
# You could also use +#encr_password+ and +#decr_password+ if you prefer to encrypt
|
22
|
+
# with a password instead. The encryption key is generated from the password in that
|
23
|
+
# case.
|
24
|
+
#
|
25
|
+
# Create a new key with +#create_private_key+ class method, which returns a new key every
|
26
|
+
# time it's called, or with +#private_key+ class method, which either assigns, or creates
|
27
|
+
# and caches the private key at a class level.
|
28
|
+
#
|
29
|
+
# ```ruby
|
30
|
+
# require 'secrets'
|
31
|
+
# class TestClass
|
32
|
+
# include Secrets
|
33
|
+
# private_key ENV['PRIVATE_KEY']
|
34
|
+
#
|
35
|
+
# def sensitive_value=(value)
|
36
|
+
# @sensitive_value = encr(value, self.class.private_key)
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# def sensitive_value
|
40
|
+
# decr(@sensitive_value, self.class.private_key)
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
# ```
|
44
|
+
module Secrets
|
45
|
+
extend RequireDir
|
46
|
+
init(__FILE__)
|
47
|
+
end
|
48
|
+
|
49
|
+
Secrets.dir 'secrets/extensions'
|
50
|
+
|
51
|
+
module Secrets
|
52
|
+
def self.included(klass)
|
53
|
+
klass.instance_eval do
|
54
|
+
include ::Secrets::Extensions::InstanceMethods
|
55
|
+
extend ::Secrets::Extensions::ClassMethods
|
56
|
+
class << self
|
57
|
+
def private_key(value = nil)
|
58
|
+
if value
|
59
|
+
@private_key= value
|
60
|
+
elsif @private_key
|
61
|
+
@private_key
|
62
|
+
else
|
63
|
+
@private_key= self.create_private_key
|
64
|
+
end
|
65
|
+
@private_key
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Secrets.dir 'secrets'
|
73
|
+
Secrets.dir 'secrets/app/commands'
|
data/lib/secrets/app.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'secrets/data'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
module Secrets
|
4
|
+
# The +App+ Module is responsible for handing user input and executing commands.
|
5
|
+
# Central class in this module is the +CLI+ class.
|
6
|
+
|
7
|
+
# This module is responsible for printing pretty errors and maintaining the
|
8
|
+
# future exit code class-global variable.
|
9
|
+
|
10
|
+
module App
|
11
|
+
class << self
|
12
|
+
attr_accessor :exit_code
|
13
|
+
end
|
14
|
+
|
15
|
+
self.exit_code = 0
|
16
|
+
|
17
|
+
def self.out
|
18
|
+
STDERR
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.error(
|
22
|
+
config: {},
|
23
|
+
exception: nil,
|
24
|
+
type: nil,
|
25
|
+
details: nil,
|
26
|
+
reason: nil)
|
27
|
+
|
28
|
+
self.out.puts([\
|
29
|
+
"#{(type || exception.class.name).titleize}:".red.bold.underlined +
|
30
|
+
(sprintf ' %s', details || exception.message).red.italic,
|
31
|
+
reason ? "\n#{reason.blue.bold.italic}" : nil].compact.join("\n"))
|
32
|
+
self.out.puts "\n" + exception.backtrace.join("\n").bold.red if exception && config && config[:trace]
|
33
|
+
self.exit_code = 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.is_osx?
|
37
|
+
Gem::Platform.local.os.eql?('darwin')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Secrets.dir_r 'secrets/app'
|
@@ -0,0 +1,197 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'slop'
|
3
|
+
require 'secrets'
|
4
|
+
require 'colored2'
|
5
|
+
require 'yaml'
|
6
|
+
require 'openssl'
|
7
|
+
require 'secrets/app'
|
8
|
+
require 'secrets/errors'
|
9
|
+
require 'secrets/app/commands'
|
10
|
+
require 'secrets/app/keychain'
|
11
|
+
require 'highline'
|
12
|
+
|
13
|
+
require_relative 'outputs/to_file'
|
14
|
+
require_relative 'outputs/to_stdout'
|
15
|
+
|
16
|
+
module Secrets
|
17
|
+
module App
|
18
|
+
class CLI
|
19
|
+
include Secrets
|
20
|
+
|
21
|
+
attr_accessor :opts,
|
22
|
+
:output_proc,
|
23
|
+
:action,
|
24
|
+
:print_proc,
|
25
|
+
:write_proc,
|
26
|
+
:password,
|
27
|
+
:key
|
28
|
+
|
29
|
+
def initialize(argv)
|
30
|
+
begin
|
31
|
+
self.opts = parse(argv.dup)
|
32
|
+
rescue StandardError => e
|
33
|
+
error exception: e
|
34
|
+
return
|
35
|
+
end
|
36
|
+
configure_color(argv)
|
37
|
+
define_output
|
38
|
+
self.action = { opts[:encrypt] => :encr, opts[:decrypt] => :decr }[true]
|
39
|
+
end
|
40
|
+
|
41
|
+
def command
|
42
|
+
@command_class ||= Secrets::App::Commands.find_command_class(opts)
|
43
|
+
@command ||= @command_class.new(self) if @command_class
|
44
|
+
end
|
45
|
+
|
46
|
+
def run
|
47
|
+
return Secrets::App.exit_code if Secrets::App.exit_code != 0
|
48
|
+
|
49
|
+
define_private_key
|
50
|
+
decrypt_private_key if should_decrypt_private_key?
|
51
|
+
verify_private_key_encoding if key
|
52
|
+
|
53
|
+
if command
|
54
|
+
return self.output_proc.call(command.run)
|
55
|
+
else
|
56
|
+
# command was not found. Reset output to printing, and return an error.
|
57
|
+
self.output_proc = print_proc
|
58
|
+
command_not_found_error!
|
59
|
+
end
|
60
|
+
|
61
|
+
rescue ::OpenSSL::Cipher::CipherError => e
|
62
|
+
error type: 'Cipher Error',
|
63
|
+
details: e.message,
|
64
|
+
reason: 'Perhaps either the secret is invalid, or encrypted data is corrupt.',
|
65
|
+
exception: e
|
66
|
+
|
67
|
+
rescue Secrets::Errors::InvalidEncodingPrivateKey => e
|
68
|
+
error type: 'Private Key Error',
|
69
|
+
details: 'Private key does not appear to be properly encoded. ',
|
70
|
+
reason: (opts[:password] ? nil : 'Perhaps the key is password-protected?'),
|
71
|
+
exception: e
|
72
|
+
|
73
|
+
rescue Secrets::Errors::Error => e
|
74
|
+
error type: 'Error',
|
75
|
+
details: e.message,
|
76
|
+
exception: e
|
77
|
+
|
78
|
+
rescue StandardError => e
|
79
|
+
error exception: e
|
80
|
+
end
|
81
|
+
|
82
|
+
def error(hash)
|
83
|
+
Secrets::App.error(hash.merge(config: (opts ? opts.to_hash : {})))
|
84
|
+
end
|
85
|
+
|
86
|
+
def editor
|
87
|
+
ENV['EDITOR'] || '/bin/vi'
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def should_decrypt_private_key?
|
93
|
+
key && (key.length > 45 || opts[:password])
|
94
|
+
end
|
95
|
+
|
96
|
+
def define_output
|
97
|
+
self.print_proc = Secrets::App::Outputs::ToStdout.new(self).output_proc
|
98
|
+
self.write_proc = Secrets::App::Outputs::ToFile.new(self).output_proc
|
99
|
+
self.output_proc = opts[:output] ? self.write_proc : self.print_proc
|
100
|
+
end
|
101
|
+
|
102
|
+
def define_private_key
|
103
|
+
begin
|
104
|
+
opts[:private_key] = File.read(opts[:key_file]) if opts[:key_file]
|
105
|
+
rescue Errno::ENOENT
|
106
|
+
raise Secrets::Errors::FileNotFound.new("Encryption key file #{opts[:key_file]} was not found.")
|
107
|
+
end
|
108
|
+
|
109
|
+
opts[:private_key] ||= PasswordHandler.handle_user_input('Private Key: ', :magenta) if opts[:interactive]
|
110
|
+
opts[:private_key] ||= KeyChain.new(opts[:keychain]).find if opts[:keychain]
|
111
|
+
self.key = opts[:private_key]
|
112
|
+
end
|
113
|
+
|
114
|
+
def configure_color(argv)
|
115
|
+
if opts[:no_color]
|
116
|
+
Colored2.disable! # reparse options without the colors to create new help msg
|
117
|
+
self.opts = parse(argv.dup)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def verify_private_key_encoding
|
122
|
+
begin
|
123
|
+
Base64.urlsafe_decode64(key)
|
124
|
+
rescue ArgumentError => e
|
125
|
+
raise Secrets::Errors::InvalidEncodingPrivateKey.new(e)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def decrypt_private_key
|
130
|
+
handler = Secrets::App::PasswordHandler.new(opts)
|
131
|
+
decrypted_key = nil
|
132
|
+
begin
|
133
|
+
retries ||= 0
|
134
|
+
handler.ask
|
135
|
+
decrypted_key = decr_password(key, handler.password)
|
136
|
+
rescue ::OpenSSL::Cipher::CipherError => e
|
137
|
+
STDERR.puts 'Invalid password. Please try again.'
|
138
|
+
((retries += 1) < 3) ? retry : raise(Secrets::Errors::InvalidPasswordPrivateKey.new(e))
|
139
|
+
end
|
140
|
+
self.key = decrypted_key
|
141
|
+
end
|
142
|
+
|
143
|
+
def command_not_found_error!
|
144
|
+
if key
|
145
|
+
h = opts.to_hash
|
146
|
+
supplied_opts = h.keys.select { |k| h[k] }.join(', ')
|
147
|
+
error type: 'Options Error',
|
148
|
+
details: 'Unable to determined what command to run',
|
149
|
+
reason: "You provided the following options: #{supplied_opts.bold.yellow}"
|
150
|
+
output_proc.call(opts.to_s)
|
151
|
+
else
|
152
|
+
raise Secrets::Errors::NoPrivateKeyFound.new('Private key is required')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def parse(arguments)
|
157
|
+
Slop.parse(arguments) do |o|
|
158
|
+
o.banner = 'Usage:'.bold.yellow
|
159
|
+
o.separator ' secrets [options]'.bold.green
|
160
|
+
o.separator 'Modes:'.bold.yellow
|
161
|
+
o.bool '-h', '--help', ' show help'
|
162
|
+
o.bool '-d', '--decrypt', ' decrypt mode'
|
163
|
+
o.bool '-t', '--edit', ' decrypt, open an encr. file in ' + editor
|
164
|
+
o.separator 'Create a private key:'.bold.yellow
|
165
|
+
o.bool '-g', '--generate', ' generate a new private key'
|
166
|
+
o.bool '-p', '--password', ' encrypt the key with a password'
|
167
|
+
o.bool '-c', '--copy', ' copy the new key to the clipboard'
|
168
|
+
o.separator 'Provide a private key:'.bold.yellow
|
169
|
+
o.bool '-i', '--interactive', ' Paste or type the key interactively'
|
170
|
+
o.string '-k', '--private-key', '[key] '.bold.blue + ' private key as a string'
|
171
|
+
o.string '-K', '--key-file', '[key-file]'.bold.blue + ' private key from a file'
|
172
|
+
if Secrets::App.is_osx?
|
173
|
+
o.string '-x', '--keychain', '[key-name] '.bold.blue + 'private key to/from a password entry'
|
174
|
+
o.string '-X', '--keychain-del', '[key-name] '.bold.blue + 'delete keychain entry with that name'
|
175
|
+
end
|
176
|
+
o.separator 'Data:'.bold.yellow
|
177
|
+
o.string '-s', '--string', '[string]'.bold.blue + ' specify a string to encrypt/decrypt'
|
178
|
+
o.string '-f', '--file', '[file] '.bold.blue + ' filename to read from'
|
179
|
+
o.string '-o', '--output', '[file] '.bold.blue + ' filename to write to'
|
180
|
+
o.bool '-b', '--backup', ' create a backup file in the edit mode'
|
181
|
+
o.separator 'Flags:'.bold.yellow
|
182
|
+
o.bool '-v', '--verbose', ' show additional information'
|
183
|
+
o.bool '-T', '--trace', ' print a backtrace of any errors'
|
184
|
+
o.bool '-E', '--examples', ' show several examples'
|
185
|
+
o.bool '-V', '--version', ' print library version'
|
186
|
+
o.bool '-N', '--no-color', ' disable color output'
|
187
|
+
o.bool '-e', '--encrypt', ' encrypt mode'
|
188
|
+
o.separator ''
|
189
|
+
end
|
190
|
+
rescue StandardError => e
|
191
|
+
error exception: e
|
192
|
+
raise(e)
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module Secrets
|
4
|
+
module App
|
5
|
+
module Commands
|
6
|
+
class << self
|
7
|
+
attr_accessor :commands
|
8
|
+
end
|
9
|
+
|
10
|
+
self.commands = Set.new
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def find_command_class(opts)
|
14
|
+
self.commands.each do |command_class|
|
15
|
+
return command_class if command_class.options_satisfied_by?(opts.to_hash)
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def register(command_class)
|
21
|
+
self.commands << command_class
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
module Secrets
|
3
|
+
module App
|
4
|
+
module Commands
|
5
|
+
class Command
|
6
|
+
|
7
|
+
def self.inherited(klass)
|
8
|
+
klass.instance_eval do
|
9
|
+
@required_options = Set.new
|
10
|
+
class << self
|
11
|
+
def required_options(*args)
|
12
|
+
@required_options.merge(args) if args
|
13
|
+
@required_options
|
14
|
+
end
|
15
|
+
|
16
|
+
def short_name
|
17
|
+
name.split(/::/)[-1].underscore
|
18
|
+
end
|
19
|
+
|
20
|
+
def options_satisfied_by?(opts_hash)
|
21
|
+
proc = required_options.find { |option| option.is_a?(Proc) }
|
22
|
+
return true if proc && proc.call(opts_hash)
|
23
|
+
|
24
|
+
required_options.to_a.delete_if { |o| o.is_a?(Proc) }.all? { |o|
|
25
|
+
o.is_a?(Array) ? o.any? { |opt| opts_hash[opt] } : opts_hash[o]
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
# Register this command with the global list.
|
30
|
+
Secrets::App::Commands.register klass
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_accessor :cli
|
35
|
+
|
36
|
+
def initialize(cli)
|
37
|
+
self.cli = cli
|
38
|
+
end
|
39
|
+
|
40
|
+
def opts
|
41
|
+
cli.opts
|
42
|
+
end
|
43
|
+
|
44
|
+
def key
|
45
|
+
@key ||= opts[:private_key]
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
raise Secrets::Errors::AbstractMethodCalled.new(:run)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|