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.
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