sym 2.3.0 → 2.4.3

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -2
  3. data/README.md +106 -70
  4. data/Rakefile +3 -2
  5. data/bin/sym.completion +6 -2
  6. data/lib/sym.rb +28 -15
  7. data/lib/sym/app.rb +1 -3
  8. data/lib/sym/app/args.rb +5 -1
  9. data/lib/sym/app/cli.rb +54 -6
  10. data/lib/sym/app/cli_slop.rb +35 -23
  11. data/lib/sym/app/commands/base_command.rb +12 -19
  12. data/lib/sym/app/commands/bash_completion.rb +19 -3
  13. data/lib/sym/app/commands/decrypt.rb +1 -1
  14. data/lib/sym/app/commands/encrypt.rb +1 -1
  15. data/lib/sym/app/commands/generate_key.rb +7 -3
  16. data/lib/sym/app/commands/keychain_add_key.rb +7 -2
  17. data/lib/sym/app/commands/open_editor.rb +1 -1
  18. data/lib/sym/app/commands/password_protect_key.rb +9 -4
  19. data/lib/sym/app/commands/print_key.rb +3 -3
  20. data/lib/sym/app/commands/show_examples.rb +25 -17
  21. data/lib/sym/app/commands/show_help.rb +2 -2
  22. data/lib/sym/app/keychain.rb +5 -0
  23. data/lib/sym/app/output/base.rb +3 -7
  24. data/lib/sym/app/output/file.rb +0 -1
  25. data/lib/sym/app/output/noop.rb +2 -1
  26. data/lib/sym/app/password/providers.rb +4 -0
  27. data/lib/sym/app/password/providers/memcached_provider.rb +1 -1
  28. data/lib/sym/app/private_key/base64_decoder.rb +1 -0
  29. data/lib/sym/app/private_key/decryptor.rb +1 -0
  30. data/lib/sym/app/private_key/detector.rb +45 -26
  31. data/lib/sym/app/private_key/handler.rb +20 -25
  32. data/lib/sym/app/private_key/key_source_check.rb +89 -0
  33. data/lib/sym/application.rb +115 -33
  34. data/lib/sym/configuration.rb +1 -0
  35. data/lib/sym/constants.rb +24 -0
  36. data/lib/sym/data/decoder.rb +2 -1
  37. data/lib/sym/data/wrapper_struct.rb +1 -1
  38. data/lib/sym/extensions/stdlib.rb +1 -0
  39. data/lib/sym/version.rb +1 -1
  40. data/sym.gemspec +1 -1
  41. metadata +5 -4
  42. data/lib/sym/encrypted_file.rb +0 -34
data/lib/sym.rb CHANGED
@@ -2,14 +2,19 @@ require 'colored2'
2
2
  require 'zlib'
3
3
  require 'logger'
4
4
 
5
- require_relative 'sym/configuration'
5
+ require 'sym/configuration'
6
+ require 'sym/constants'
7
+ require 'sym/version'
8
+ require 'sym/errors'
6
9
 
7
10
  Sym::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
11
+ config.password_cipher = 'AES-128-CBC'
12
+ config.data_cipher = 'AES-256-CBC'
13
+ config.private_key_cipher = config.data_cipher
14
+ config.compression_enabled = true
15
+ config.compression_level = Zlib::BEST_COMPRESSION
16
+ config.encrypted_file_extension = 'enc'
17
+ config.default_key_file = Sym::Constants::SYM_KEY_FILE
13
18
 
14
19
  config.password_cache_timeout = 300
15
20
 
@@ -117,15 +122,23 @@ module Sym
117
122
  end
118
123
  end
119
124
 
120
- COMPLETION_FILE = '.sym.completion'.freeze
121
- COMPLETION_PATH = "#{ENV['HOME']}/#{COMPLETION_FILE}".freeze
122
- NIL_LOGGER = Logger.new(nil).freeze # empty logger
123
- LOGGER = Logger.new(STDOUT).freeze
124
- ENV_ARGS_VARIABLE_NAME = 'SYM_ARGS'.freeze
125
+ class << self
126
+ def config
127
+ Sym::Configuration.config
128
+ end
129
+
130
+ def default_key_file
131
+ config.default_key_file
132
+ end
133
+
134
+ def default_key
135
+ File.read(default_key_file) rescue nil
136
+ end
137
+
138
+ def default_key?
139
+ File.exist?(default_key_file)
140
+ end
125
141
 
126
- BASH_COMPLETION = {
127
- file: File.expand_path('../../bin/sym.completion', __FILE__),
128
- script: "[[ -f '#{COMPLETION_PATH}' ]] && source '#{COMPLETION_PATH}'",
129
- }.freeze
142
+ end
130
143
  end
131
144
 
data/lib/sym/app.rb CHANGED
@@ -26,7 +26,7 @@ module Sym
26
26
  end
27
27
 
28
28
  def self.log(level, *args, **opts)
29
- Sym::LOGGER.send(level, *args) if opts[:debug]
29
+ Sym::Constants::Log::LOG.send(level, *args) if opts[:debug]
30
30
  end
31
31
 
32
32
  def self.error(config: {},
@@ -70,9 +70,7 @@ module Sym
70
70
  end
71
71
  end
72
72
 
73
- require 'sym/version'
74
73
  require 'sym/app/short_name'
75
-
76
74
  require 'sym/app/args'
77
75
  require 'sym/app/cli'
78
76
  require 'sym/app/commands'
data/lib/sym/app/args.rb CHANGED
@@ -5,7 +5,7 @@ module Sym
5
5
 
6
6
  OPTIONS_REQUIRE_KEY = %i(encrypt decrypt edit)
7
7
  OPTIONS_KEY_CREATED = %i(generate)
8
- OPTIONS_SPECIFY_KEY = %i(private_key interactive keyfile keychain)
8
+ OPTIONS_SPECIFY_KEY = %i(key interactive keychain)
9
9
  OPTIONS_SPECIFY_OUTPUT = %i(output quiet)
10
10
 
11
11
  attr_accessor :opts, :selected_options
@@ -32,6 +32,10 @@ module Sym
32
32
  Sym::App::Output.outputs[output_type]
33
33
  end
34
34
 
35
+ def provided_options
36
+ opts.to_hash.keys.reject { |k| !opts[k] }
37
+ end
38
+
35
39
  private
36
40
  def do?(list)
37
41
  !(list & selected_options).empty?
data/lib/sym/app/cli.rb CHANGED
@@ -58,12 +58,14 @@ module Sym
58
58
 
59
59
  attr_accessor :opts, :application, :outputs, :output_proc
60
60
 
61
- def initialize(argv_original)
62
- env_args = ENV[ENV_ARGS_VARIABLE_NAME]
61
+ def initialize(argv)
63
62
  begin
64
- argv = argv_original.dup
65
- argv << env_args.split(' ') if env_args && !(argv.include?('-M') or argv.include?('--no-environment'))
63
+ argv << args_from_environment(argv)
66
64
  argv.flatten!
65
+ argv.compact!
66
+ argv_original = argv.dup
67
+ # Re-map any leg acy options to the new options
68
+ argv = CLI.replace_argv(argv)
67
69
  dict = argv.delete('--dictionary')
68
70
  self.opts = parse(argv)
69
71
  command_dictionary if dict
@@ -72,11 +74,23 @@ module Sym
72
74
  return
73
75
  end
74
76
 
75
- command_no_color(argv_original) if opts[:no_color]
77
+ # Disable coloring if requested, or if piping STDOUT
78
+ if opts[:no_color] || !STDOUT.tty?
79
+ command_no_color(argv_original)
80
+ end
81
+
76
82
  self.application = ::Sym::Application.new(opts)
77
83
  select_output_stream
78
84
  end
79
85
 
86
+ def args_from_environment(argv)
87
+ env_args = ENV[Sym::Constants::ENV_ARGS_VARIABLE_NAME]
88
+ if env_args && !(argv.include?('-M') or argv.include?('--no-environment'))
89
+ env_args.split(' ')
90
+ else
91
+ []
92
+ end
93
+ end
80
94
 
81
95
  def execute
82
96
  return Sym::App.exit_code if Sym::App.exit_code != 0
@@ -95,6 +109,40 @@ module Sym
95
109
  @command ||= self.application&.command
96
110
  end
97
111
 
112
+ def opts_present
113
+ o = opts.to_hash
114
+ o.keys.map { |k| opts[k] ? nil : k }.compact.each { |k| o.delete(k) }
115
+ o
116
+ end
117
+
118
+ class << self
119
+ # Re-map any legacy options to the new options
120
+ ARGV_FLAG_REPLACE_MAP = {
121
+ 'C' => 'c'
122
+ }
123
+
124
+ def replace_regex(from)
125
+ %r{^-([\w]*)#{from}([\w]*)$}
126
+ end
127
+
128
+ def replace_argv(argv)
129
+ argv = argv.dup
130
+ replacements = []
131
+ ARGV_FLAG_REPLACE_MAP.each_pair do |from, to|
132
+ argv.map! do |a|
133
+ match = replace_regex(from).match(a)
134
+ if match
135
+ replacements << from
136
+ "-#{match[1]}#{to}#{match[2]}"
137
+ else
138
+ a
139
+ end
140
+ end
141
+ end
142
+ argv
143
+ end
144
+ end
145
+
98
146
  private
99
147
 
100
148
  def command_dictionary
@@ -114,7 +162,7 @@ module Sym
114
162
  unless output_klass && output_klass.is_a?(Class)
115
163
  raise "Can not determine output class from arguments #{opts.to_hash}"
116
164
  end
117
- self.output_proc = output_klass.new(self).output_proc
165
+ self.output_proc = output_klass.new(application.opts).output_proc
118
166
  end
119
167
 
120
168
  def command_no_color(argv)
@@ -1,3 +1,6 @@
1
+ require 'sym/version'
2
+ require 'sym/app/password/providers'
3
+
1
4
  module Sym
2
5
  module App
3
6
  module CLISlop
@@ -6,25 +9,34 @@ module Sym
6
9
 
7
10
  o.banner = "Sym (#{Sym::VERSION}) – encrypt/decrypt data with a private key\n".bold.white
8
11
  o.separator 'Usage:'.yellow
9
- o.separator ' # Generate a new key...'.dark
10
- o.separator ' sym -g '.green.bold + '[ -p ] [ -x keychain | -o keyfile | -q | ] '.green
12
+ o.separator ' # Generate a new key, optionally password protected, and save it'.dark
13
+ o.separator ' # in one of: keychain, file, or STDOUT (-q turns off STDOUT) '.dark
14
+ o.separator ' sym -g '.green.bold + '[ -p/--password ] [ -x keychain | -o file | ] [ -q ] '.green
15
+ o.separator ''
16
+ o.separator ' # To specify encryption key, provide the key as '.dark
17
+ o.separator ' # 1) a string, 2) a file path, 3) an OS-X Keychain, 4) env variable, '.dark
18
+ o.separator ' # 5) use -i to paste/type the key, 6) default key file, if present.'.dark
19
+ o.separator ' ' + key_spec + ' = -k/--key [ key | file | keychain | env ]'.green.bold
20
+ o.separator ' -i/--interactive'.green.bold
21
+
11
22
  o.separator ''
12
- o.separator ' # To specify a key for an operation use one of...'.dark
13
- o.separator ' ' + key_spec + ' = -k key | -K file | -x keychain | -i '.green.bold
23
+ o.separator ' # Encrypt/Decrypt from STDIN/file/args, to STDOUT/file:'.dark
24
+ o.separator ' sym -e/--encrypt '.green.bold + key_spec + ' [-f [file | - ] | -s string ] [-o file] '.green
25
+ o.separator ' sym -d/--decrypt '.green.bold + key_spec + ' [-f [file | - ] | -s string ] [-o file] '.green
14
26
  o.separator ''
15
- o.separator ' # Encrypt/Decrypt to STDOUT or an output file '.dark
16
- o.separator ' sym -e '.green.bold + key_spec + ' [-f <file> | -s <string>] [-o <file>] '.green
17
- o.separator ' sym -d '.green.bold + key_spec + ' [-f <file> | -s <string>] [-o <file>] '.green
27
+ o.separator ' # Auto-detect mode based on a special file extension '.dark + '".enc"'.dark.bold
28
+ o.separator ' sym -n/--negate '.green.bold + key_spec + ' file[.enc] '.green
18
29
  o.separator ' '
19
30
  o.separator ' # Edit an encrypted file in $EDITOR '.dark
20
- o.separator ' sym -t '.green.bold + key_spec + ' -f <file> [ -b ]'.green.bold
31
+ o.separator ' sym -t/--edit '.green.bold + key_spec + ' -f file [ -b/--backup ]'.green.bold
21
32
 
22
33
  o.separator ' '
23
- o.separator ' # Specify any common flags in the BASH variable:'.dark
24
- o.separator ' export SYM_ARGS="'.green + '-x staging -C'.bold.green + '"'.green
34
+ o.separator ' # Save commonly used flags in a BASH variable. Below we save KeyChain '.dark
35
+ o.separator ' # "staging" as the default key source, and enable password caching.'.dark
36
+ o.separator ' export SYM_ARGS="'.green + '-ck staging'.bold.green + '"'.green
25
37
  o.separator ' '
26
- o.separator ' # And now encrypt without having to specify key location:'.dark
27
- o.separator ' sym -e '.green.bold '-f <file>'.green.bold
38
+ o.separator ' # And now encrypt using default key location '.dark + Sym.default_key_file.magenta.bold
39
+ o.separator ' sym -e '.green.bold '-f file'.green.bold
28
40
  o.separator ' # May need to disable SYM_ARGS with -M, eg for help:'.dark
29
41
  o.separator ' sym -h -M '.green.bold
30
42
 
@@ -38,36 +50,36 @@ module Sym
38
50
  o.separator 'Create a new private key:'.yellow
39
51
  o.bool '-g', '--generate', ' generate a new private key'
40
52
  o.bool '-p', '--password', ' encrypt the key with a password'
53
+ if Sym::App.is_osx?
54
+ o.string '-x', '--keychain', '[key-name] '.blue + 'write the key to OS-X Keychain'
55
+ end
41
56
 
42
57
  o.separator ' '
43
58
  o.separator 'Read existing private key from:'.yellow
44
- o.string '-k', '--private-key', '[key] '.blue + ' private key (or key file)'
45
- o.string '-K', '--keyfile', '[key-file]'.blue + ' private key from a file'
46
- if Sym::App.is_osx?
47
- o.string '-x', '--keychain', '[key-name] '.blue + 'add to (or read from) the OS-X Keychain'
48
- end
59
+ o.string '-k', '--key', '[key-spec]'.blue + ' private key, key file, or keychain'
49
60
  o.bool '-i', '--interactive', ' Paste or type the key interactively'
50
61
 
51
62
  o.separator ' '
52
63
  o.separator 'Password Cache:'.yellow
53
- o.bool '-C', '--cache-password', ' enable the cache (off by default)'
54
- o.integer '-T', '--cache-for', '[seconds]'.blue + ' to cache the password for'
55
- o.string '-P', '--cache-provider', '[provider]'.blue + ' type of cache, one of: ' + "\n\t\t\t\t " +
56
- "[ #{Sym::App::Password::Providers.registry.keys.map(&:to_s).join(', ').blue.bold} ]"
64
+ o.bool '-c', '--cache-passwords', ' enable password cache'
65
+ o.integer '-u', '--cache-timeout', '[seconds]'.blue + ' expire passwords after'
66
+ o.string '-r', '--cache-provider', '[provider]'.blue + ' cache provider, one of ' + "#{Sym::App::Password::Providers.provider_list}"
57
67
 
58
68
  o.separator ' '
59
69
  o.separator 'Data to Encrypt/Decrypt:'.yellow
60
70
  o.string '-s', '--string', '[string]'.blue + ' specify a string to encrypt/decrypt'
61
71
  o.string '-f', '--file', '[file] '.blue + ' filename to read from'
62
72
  o.string '-o', '--output', '[file] '.blue + ' filename to write to'
73
+ o.string '-n', '--negate', '[file] '.blue + " encrypts any regular #{'file'.green} into #{'file.enc'.green}" + "\n" +
74
+ " conversely decrypts #{'file.enc'.green} into #{'file'.green}."
63
75
 
64
76
  o.separator ' '
65
77
  o.separator 'Flags:'.yellow
66
78
  o.bool '-b', '--backup', ' create a backup file in the edit mode'
67
79
  o.bool '-v', '--verbose', ' show additional information'
68
- o.bool '-A', '--trace', ' print a backtrace of any errors'
69
- o.bool '-D', '--debug', ' print debugging information'
70
80
  o.bool '-q', '--quiet', ' do not print to STDOUT'
81
+ o.bool '-T', '--trace', ' print a backtrace of any errors'
82
+ o.bool '-D', '--debug', ' print debugging information'
71
83
  o.bool '-V', '--version', ' print library version'
72
84
  o.bool '-N', '--no-color', ' disable color output'
73
85
  o.bool '-M', '--no-environment', ' disable reading flags from SYM_ARGS'
@@ -1,6 +1,6 @@
1
1
  require 'sym'
2
2
  require 'sym/app'
3
-
3
+ require 'forwardable'
4
4
  require 'active_support/inflector'
5
5
 
6
6
  module Sym
@@ -47,30 +47,21 @@ module Sym
47
47
  end
48
48
 
49
49
  include Sym
50
+ extend Forwardable
50
51
 
51
52
  attr_accessor :application
53
+ def_delegators :@application, :opts, :opts_original, :key
52
54
 
53
55
  def initialize(application)
54
56
  self.application = application
55
57
  end
56
58
 
57
- def opts
58
- application.opts
59
- end
60
- def opts_hash
61
- application.opts_hash
62
- end
63
-
64
- def key
65
- @key ||= application.key
66
- end
67
-
68
59
  def execute
69
60
  raise Sym::Errors::AbstractMethodCalled.new(:run)
70
61
  end
71
62
 
72
63
  def content
73
- @content ||= (opts[:string] || (opts[:file].eql?('-') ? STDIN.read : File.read(opts[:file])))
64
+ @content ||= (opts[:string] || (opts[:file].eql?('-') ? STDIN.read : File.read(opts[:file]).chomp))
74
65
  end
75
66
 
76
67
  def to_s
@@ -89,12 +80,14 @@ module Sym
89
80
  end
90
81
  end
91
82
 
92
- def encrypt_password_if_needed(key)
93
- if opts[:password]
94
- encr_password(key, application.input_handler.new_password)
95
- else
96
- key
97
- end
83
+ def encrypt_with_password(key)
84
+ password = application.input_handler.new_password
85
+ return encr_password(key, password), password
86
+ end
87
+
88
+
89
+ def add_password_to_the_cache(encrypted_key, password)
90
+ self.application.password_cache[encrypted_key] = password
98
91
  end
99
92
  end
100
93
  end
@@ -11,7 +11,7 @@ module Sym
11
11
  install_completion_file
12
12
  file = opts[:bash_completion]
13
13
  if File.exist?(file)
14
- if File.read(file).include?(Sym::BASH_COMPLETION[:script])
14
+ if File.read(file).include?(script)
15
15
  "#{'Hmmm'.bold.yellow}: #{file.bold.yellow} had completion for #{'sym'.bold.red} already installed\n"
16
16
  else
17
17
  append_completion_script(file)
@@ -23,15 +23,31 @@ module Sym
23
23
  end
24
24
  end
25
25
 
26
+ private
27
+
26
28
  def install_completion_file
27
- FileUtils.cp(Sym::BASH_COMPLETION[:file], Sym::COMPLETION_PATH)
29
+ FileUtils.cp(source_file, path)
28
30
  end
29
31
 
30
32
  def append_completion_script(file)
31
33
  File.open(file, 'a') do |fd|
32
- fd.write(Sym::BASH_COMPLETION[:script])
34
+ fd.write(script)
33
35
  end
34
36
  end
37
+
38
+
39
+ def script
40
+ Sym::Constants::Completion::Config[:script]
41
+ end
42
+
43
+ def source_file
44
+ Sym::Constants::Completion::Config[:file]
45
+ end
46
+
47
+ def path
48
+ Sym::Constants::Completion::PATH
49
+ end
50
+
35
51
  end
36
52
  end
37
53
  end
@@ -5,7 +5,7 @@ module Sym
5
5
  class Decrypt < BaseCommand
6
6
  include Sym
7
7
 
8
- required_options [ :private_key, :keyfile, :keychain, :interactive ],
8
+ required_options [ :key, :interactive ],
9
9
  [ :decrypt ],
10
10
  [ :file, :string ]
11
11
 
@@ -5,7 +5,7 @@ module Sym
5
5
  class Encrypt < BaseCommand
6
6
  include Sym
7
7
 
8
- required_options [ :private_key, :keyfile, :keychain, :interactive ],
8
+ required_options [ :key, :interactive ],
9
9
  [ :encrypt ],
10
10
  [ :file, :string ]
11
11
 
@@ -13,15 +13,19 @@ module Sym
13
13
  retries ||= 0
14
14
 
15
15
  the_key = create_key
16
- the_key = encrypt_password_if_needed(the_key)
17
- add_to_keychain_if_needed(the_key)
18
16
 
17
+ if opts[:password]
18
+ encrypted_key, password = encrypt_with_password(the_key)
19
+ add_password_to_the_cache(encrypted_key, password)
20
+ the_key = encrypted_key
21
+ end
22
+
23
+ add_to_keychain_if_needed(the_key)
19
24
  the_key
20
25
  rescue Sym::Errors::PasswordsDontMatch, Sym::Errors::PasswordTooShort => e
21
26
  STDERR.puts e.message.bold
22
27
  retry if (retries += 1) < 3
23
28
  end
24
-
25
29
  end
26
30
  end
27
31
  end
@@ -1,17 +1,22 @@
1
1
  require 'sym/app/commands/base_command'
2
2
  require 'sym/app/keychain'
3
+ require 'sym/errors'
3
4
  module Sym
4
5
  module App
5
6
  module Commands
6
7
  class KeychainAddKey < BaseCommand
7
8
 
8
- required_options [:private_key, :keyfile, :interactive],
9
+ required_options [:key, :interactive],
9
10
  :keychain
10
-
11
+ incompatible_options %i(examples help version bash_completion)
11
12
  try_after :generate_key, :encrypt, :decrypt, :password_protect_key
12
13
 
13
14
  def execute
15
+ if Sym.default_key? && Sym.default_key == self.key
16
+ raise 'Refusing to import key specified in the default key file ' + Sym.default_key_file.italic
17
+ end
14
18
  add_to_keychain_if_needed(self.key)
19
+ self.key unless opts[:quiet]
15
20
  end
16
21
  end
17
22
  end