secrets-cipher-base64 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +25 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/.travis.yml +13 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE +22 -0
  9. data/MANAGING-KEYS.md +67 -0
  10. data/README.md +314 -0
  11. data/Rakefile +13 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/exe/keychain +38 -0
  15. data/exe/secrets +8 -0
  16. data/lib/secrets.rb +73 -0
  17. data/lib/secrets/app.rb +42 -0
  18. data/lib/secrets/app/cli.rb +197 -0
  19. data/lib/secrets/app/commands.rb +27 -0
  20. data/lib/secrets/app/commands/command.rb +55 -0
  21. data/lib/secrets/app/commands/delete_keychain_key.rb +15 -0
  22. data/lib/secrets/app/commands/encrypt_decrypt.rb +22 -0
  23. data/lib/secrets/app/commands/generate_key.rb +41 -0
  24. data/lib/secrets/app/commands/open_editor.rb +90 -0
  25. data/lib/secrets/app/commands/show_examples.rb +63 -0
  26. data/lib/secrets/app/commands/show_help.rb +13 -0
  27. data/lib/secrets/app/commands/show_version.rb +13 -0
  28. data/lib/secrets/app/keychain.rb +136 -0
  29. data/lib/secrets/app/outputs/to_file.rb +27 -0
  30. data/lib/secrets/app/outputs/to_stdout.rb +11 -0
  31. data/lib/secrets/app/password_handler.rb +39 -0
  32. data/lib/secrets/cipher_handler.rb +45 -0
  33. data/lib/secrets/configuration.rb +23 -0
  34. data/lib/secrets/data.rb +23 -0
  35. data/lib/secrets/data/decoder.rb +24 -0
  36. data/lib/secrets/data/encoder.rb +24 -0
  37. data/lib/secrets/data/wrapper_struct.rb +43 -0
  38. data/lib/secrets/errors.rb +27 -0
  39. data/lib/secrets/extensions/class_methods.rb +12 -0
  40. data/lib/secrets/extensions/instance_methods.rb +110 -0
  41. data/lib/secrets/version.rb +3 -0
  42. data/secrets-cipher-base64.gemspec +33 -0
  43. metadata +243 -0
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
+
@@ -0,0 +1,8 @@
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
+
8
+ Secrets::App::CLI.new(ARGV.dup).run
@@ -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'
@@ -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