secrets-cipher-base64 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|