sym 0.1.0 → 2.0.0

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +25 -0
  3. data/.document +2 -0
  4. data/.gitignore +6 -2
  5. data/.rspec +1 -1
  6. data/.rubocop.yml +1156 -0
  7. data/.travis.yml +10 -2
  8. data/.yardopts +5 -0
  9. data/Gemfile +2 -0
  10. data/LICENSE +22 -0
  11. data/MANAGING-KEYS.md +67 -0
  12. data/README.md +444 -12
  13. data/Rakefile +10 -2
  14. data/bin/sym.bash-completion +24 -0
  15. data/exe/keychain +38 -0
  16. data/exe/sym +20 -0
  17. data/lib/sym.rb +110 -2
  18. data/lib/sym/app.rb +56 -0
  19. data/lib/sym/app/args.rb +42 -0
  20. data/lib/sym/app/cli.rb +192 -0
  21. data/lib/sym/app/commands.rb +56 -0
  22. data/lib/sym/app/commands/command.rb +77 -0
  23. data/lib/sym/app/commands/delete_keychain_item.rb +17 -0
  24. data/lib/sym/app/commands/encrypt_decrypt.rb +26 -0
  25. data/lib/sym/app/commands/generate_key.rb +37 -0
  26. data/lib/sym/app/commands/open_editor.rb +97 -0
  27. data/lib/sym/app/commands/print_key.rb +15 -0
  28. data/lib/sym/app/commands/show_examples.rb +76 -0
  29. data/lib/sym/app/commands/show_help.rb +16 -0
  30. data/lib/sym/app/commands/show_language_examples.rb +81 -0
  31. data/lib/sym/app/commands/show_version.rb +14 -0
  32. data/lib/sym/app/input/handler.rb +41 -0
  33. data/lib/sym/app/keychain.rb +135 -0
  34. data/lib/sym/app/nlp.rb +18 -0
  35. data/lib/sym/app/nlp/constants.rb +32 -0
  36. data/lib/sym/app/nlp/translator.rb +61 -0
  37. data/lib/sym/app/nlp/usage.rb +72 -0
  38. data/lib/sym/app/output.rb +15 -0
  39. data/lib/sym/app/output/base.rb +61 -0
  40. data/lib/sym/app/output/file.rb +18 -0
  41. data/lib/sym/app/output/noop.rb +14 -0
  42. data/lib/sym/app/output/stdout.rb +13 -0
  43. data/lib/sym/app/password/cache.rb +63 -0
  44. data/lib/sym/app/private_key/base64_decoder.rb +17 -0
  45. data/lib/sym/app/private_key/decryptor.rb +71 -0
  46. data/lib/sym/app/private_key/detector.rb +42 -0
  47. data/lib/sym/app/private_key/handler.rb +44 -0
  48. data/lib/sym/app/short_name.rb +10 -0
  49. data/lib/sym/application.rb +114 -0
  50. data/lib/sym/cipher_handler.rb +46 -0
  51. data/lib/sym/configuration.rb +39 -0
  52. data/lib/sym/data.rb +23 -0
  53. data/lib/sym/data/decoder.rb +28 -0
  54. data/lib/sym/data/encoder.rb +24 -0
  55. data/lib/sym/data/wrapper_struct.rb +43 -0
  56. data/lib/sym/encrypted_file.rb +34 -0
  57. data/lib/sym/errors.rb +37 -0
  58. data/lib/sym/extensions/class_methods.rb +12 -0
  59. data/lib/sym/extensions/instance_methods.rb +114 -0
  60. data/lib/sym/version.rb +1 -1
  61. data/sym.gemspec +34 -15
  62. metadata +224 -9
@@ -0,0 +1,18 @@
1
+ require 'sym/app/output/base'
2
+ module Sym
3
+ module App
4
+ module Output
5
+ class File < ::Sym::App::Output::Base
6
+
7
+ required_option :output
8
+
9
+
10
+ def output_proc
11
+ ->(data) {
12
+ ::File.open(opts[:output], 'w') { |f| f.write(data) }
13
+ }
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'base'
2
+ module Sym
3
+ module App
4
+ module Output
5
+ class Noop < Sym::App::Output::Base
6
+ required_option :quiet
7
+
8
+ def output_proc
9
+ ->(*) { ; }
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ require 'sym/app/output/file'
2
+ module Sym
3
+ module App
4
+ module Output
5
+ class Stdout < ::Sym::App::Output::Base
6
+ required_option nil
7
+ def output_proc
8
+ ->(argument) { puts argument }
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,63 @@
1
+ require 'coin'
2
+ require 'digest'
3
+ require 'singleton'
4
+ require 'colored2'
5
+
6
+ module Sym
7
+ module App
8
+ module Password
9
+ class Cache
10
+ URI = 'druby://127.0.0.1:24924'
11
+ DEFAULT_TIMEOUT = 300
12
+
13
+ include Singleton
14
+
15
+ attr_accessor :provider, :enabled, :timeout
16
+
17
+ def configure(provider: Coin, enabled: true, timeout: DEFAULT_TIMEOUT)
18
+ Coin.uri = URI if provider == Coin
19
+
20
+ self.provider = provider
21
+ self.enabled = enabled
22
+ self.timeout = timeout
23
+
24
+ self
25
+ end
26
+
27
+ TRIES = 2
28
+
29
+ def operation
30
+ retries ||= TRIES
31
+ yield if self.enabled
32
+ rescue StandardError => e
33
+ if retries == TRIES && Coin.remote_uri.nil?
34
+ Coin.remote_uri = URI if provider == Coin
35
+ retries -= 1
36
+ retry
37
+ end
38
+ puts 'WARNING: error reading from DRB server: ' + e.message.red
39
+ nil
40
+ end
41
+
42
+
43
+ def [] (key)
44
+ cache = self
45
+ operation do
46
+ cache.provider.read(cache.md5(key))
47
+ end
48
+ end
49
+
50
+ def []=(key, value)
51
+ cache = self
52
+ operation do
53
+ cache.provider.write(cache.md5(key), value, cache.timeout)
54
+ end
55
+ end
56
+
57
+ def md5(string)
58
+ Digest::MD5.base64digest(string)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,17 @@
1
+ module Sym
2
+ module App
3
+ module PrivateKey
4
+ class Base64Decoder < Struct.new(:encoded_key)
5
+
6
+ def key
7
+ return nil if encoded_key.nil?
8
+ begin
9
+ Base64.urlsafe_decode64(encoded_key)
10
+ rescue ArgumentError
11
+ encoded_key
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,71 @@
1
+ require 'sym/app/private_key/decryptor'
2
+ require 'sym/app/password/cache'
3
+ module Sym
4
+ module App
5
+ module PrivateKey
6
+ class Decryptor
7
+ include Sym
8
+
9
+ attr_accessor :encrypted_key, :input_handler, :password_cache
10
+
11
+ def initialize(encrypted_key, input_handler, password_cache)
12
+ self.encrypted_key = encrypted_key
13
+ self.input_handler = input_handler
14
+ self.password_cache = password_cache
15
+ @cache_checked = false
16
+ end
17
+
18
+ def key
19
+ return nil if encrypted_key.nil?
20
+ decrypted_key = nil
21
+ if should_decrypt?
22
+ begin
23
+ retries ||= 0
24
+ p = determine_key_password
25
+ decrypted_key = decrypt(p)
26
+
27
+ # if the password is valid, let's add it to the cache.
28
+ password_cache[encrypted_key] = p
29
+
30
+ rescue ::OpenSSL::Cipher::CipherError => e
31
+ input_handler.puts 'Invalid password. Please try again.'
32
+
33
+ if ((retries += 1) < 3)
34
+ retry
35
+ else
36
+ raise(Sym::Errors::InvalidPasswordPrivateKey.new('Invalid password.'))
37
+ end
38
+ end
39
+ else
40
+ decrypted_key = encrypted_key
41
+ end
42
+ decrypted_key
43
+ end
44
+
45
+ private
46
+
47
+ def should_decrypt?
48
+ encrypted_key && (encrypted_key.length > 32)
49
+ end
50
+
51
+ def decrypt(password)
52
+ decr_password(encrypted_key, password)
53
+ end
54
+
55
+ def determine_key_password
56
+ check_cache || ask_user
57
+ end
58
+
59
+ def ask_user
60
+ input_handler.ask
61
+ end
62
+
63
+ def check_cache
64
+ return nil if @cache_checked
65
+ @cache_checked = true
66
+ password_cache[encrypted_key]
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,42 @@
1
+ module Sym
2
+ module App
3
+ module PrivateKey
4
+ class Detector < Struct.new(:opts, :input_handler) # :nodoc:s
5
+ @mapping = Hash.new
6
+ class << self
7
+ attr_reader :mapping
8
+
9
+ def register(argument, proc)
10
+ self.mapping[argument] = proc
11
+ end
12
+ end
13
+
14
+ def key
15
+ self.class.mapping.each_pair do |options_key, key_proc|
16
+ return key_proc.call(opts[options_key], self) if opts[options_key]
17
+ end
18
+ nil
19
+ end
20
+ end
21
+
22
+ Detector.register :private_key,
23
+ ->(key, *) { key }
24
+
25
+ Detector.register :interactive,
26
+ ->(*, detector) { detector.input_handler.prompt('Please paste your private key: ', :magenta) }
27
+
28
+ Detector.register :keychain,
29
+ ->(key_name, * ) { KeyChain.new(key_name).find rescue nil }
30
+
31
+ Detector.register :keyfile,
32
+ ->(file, *) {
33
+ begin
34
+ ::File.read(file)
35
+ rescue Errno::ENOENT
36
+ raise Sym::Errors::FileNotFound.new("Encryption key file #{file} was not found.")
37
+ nil
38
+ end
39
+ }
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'detector'
2
+ require_relative 'base64_decoder'
3
+ require_relative 'decryptor'
4
+ require_relative '../input/handler'
5
+ module Sym
6
+ module App
7
+ module PrivateKey
8
+ # This class figures out what is the private key that is
9
+ # provided to be used.
10
+ class Handler
11
+ include Sym
12
+
13
+ attr_accessor :opts, :input_handler, :password_cache
14
+ attr_writer :key
15
+
16
+ def initialize(opts, input_handler, password_cache)
17
+ self.opts = opts
18
+ self.input_handler = input_handler
19
+ self.password_cache = password_cache
20
+ end
21
+
22
+
23
+ # @return [String] key Private key detected
24
+ def key
25
+ return @key if @key
26
+
27
+ @key = begin
28
+ Detector.new(opts, input_handler).key
29
+ rescue Sym::Errors::Error => e
30
+ if Sym::App::Args.new(opts).specify_key? && key.nil?
31
+ raise e
32
+ end
33
+ end
34
+
35
+ if @key && @key.length > 45
36
+ @key = Decryptor.new(Base64Decoder.new(key).key, input_handler, password_cache).key
37
+ end
38
+
39
+ @key
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,10 @@
1
+ require 'active_support/inflector'
2
+ module Sym
3
+ module App
4
+ module ShortName
5
+ def short_name
6
+ self.name.split(/::/)[-1].underscore.to_sym
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,114 @@
1
+ require 'colored2'
2
+ require 'sym'
3
+ require_dir 'sym/app'
4
+
5
+ module Sym
6
+ class Application
7
+
8
+ attr_accessor :opts,
9
+ :opts_hash,
10
+ :args,
11
+ :action,
12
+ :key,
13
+ :input_handler,
14
+ :key_handler,
15
+ :result,
16
+ :password_cache
17
+
18
+ def initialize(opts)
19
+ self.opts = opts
20
+ self.opts_hash = opts.respond_to?(:to_hash) ? opts.to_hash : opts
21
+ self.args = ::Sym::App::Args.new(opts_hash)
22
+
23
+ initialize_password_cache
24
+ initialize_input_handler
25
+ initialize_key_handler
26
+ initialize_action
27
+ end
28
+
29
+ def initialize_action
30
+ self.action = if opts[:encrypt] then
31
+ :encr
32
+ elsif opts[:decrypt]
33
+ :decr
34
+ end
35
+ end
36
+
37
+ def execute!
38
+ if !args.generate_key? &&
39
+ (args.require_key? || args.specify_key?)
40
+ self.key = Sym::App::PrivateKey::Handler.new(opts, input_handler, password_cache).key
41
+ raise Sym::Errors::NoPrivateKeyFound.new('Private key is required') unless self.key
42
+ end
43
+
44
+ unless command
45
+ raise Sym::Errors::InsufficientOptionsError.new(
46
+ 'Can not determine what to do from the options ' + opts_hash.keys.reject { |k| !opts[k] }.to_s)
47
+ end
48
+ self.result = command.execute
49
+ end
50
+
51
+ def execute
52
+ execute!
53
+
54
+ rescue ::OpenSSL::Cipher::CipherError => e
55
+ error type: 'Cipher Error',
56
+ details: e.message,
57
+ reason: 'Perhaps either the secret is invalid, or encrypted data is corrupt.',
58
+ exception: e
59
+
60
+ rescue Sym::Errors::Error => e
61
+ error type: e.class.name.split(/::/)[-1],
62
+ details: e.message
63
+
64
+ rescue StandardError => e
65
+ error exception: e
66
+ end
67
+
68
+ def command
69
+ @command_class ||= Sym::App::Commands.find_command_class(opts)
70
+ @command ||= @command_class.new(self) if @command_class
71
+ @command
72
+ end
73
+
74
+ def editor
75
+ editors_to_try.find { |editor| File.exist?(editor) }
76
+ end
77
+
78
+ def editors_to_try
79
+ [
80
+ ENV['EDITOR'],
81
+ '/usr/bin/vim',
82
+ '/usr/local/bin/vim',
83
+ '/bin/vim',
84
+ '/sbin/vim',
85
+ '/usr/sbin/vim',
86
+ '/usr/bin/vi',
87
+ '/usr/local/bin/vi',
88
+ '/bin/vi',
89
+ '/sbin/vi'
90
+ ]
91
+ end
92
+
93
+ def error(hash)
94
+ hash
95
+ end
96
+
97
+ def initialize_input_handler(handler = ::Sym::App::Input::Handler.new)
98
+ self.input_handler = handler
99
+ end
100
+
101
+ def initialize_key_handler
102
+ self.key_handler = ::Sym::App::PrivateKey::Handler.new(self.opts, input_handler, password_cache)
103
+ end
104
+
105
+ def initialize_password_cache
106
+ args = {}
107
+ args[:provider] = Coin
108
+ args[:timeout] = opts[:password_timeout].to_i if opts[:password_timeout]
109
+ args[:enabled] = false if opts[:no_password_cache]
110
+
111
+ self.password_cache = Sym::App::Password::Cache.instance.configure(args)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,46 @@
1
+ require 'base64'
2
+ require_relative 'configuration'
3
+
4
+ module Sym
5
+
6
+ # {Sym::CipherHandler} contains cipher-related utilities necessary to create
7
+ # ciphers, and seed them with the salt or iV vector. It also defines the
8
+ # internal structure {Sym::CipherHandler::CipherStruct} which is a key
9
+ # struct used in constructing cipher and saving it with the data packet.
10
+ module CipherHandler
11
+
12
+ CREATE_CIPHER = ->(name) { ::OpenSSL::Cipher.new(name) }
13
+
14
+ CipherStruct = Struct.new(:cipher, :iv, :salt)
15
+
16
+ def create_cipher(direction:,
17
+ cipher_name:,
18
+ iv: nil,
19
+ salt: nil)
20
+
21
+ cipher = new_cipher(cipher_name)
22
+ cipher.send(direction)
23
+ iv ||= cipher.random_iv
24
+ cipher.iv = iv
25
+ CipherStruct.new(cipher, iv, salt)
26
+ end
27
+
28
+ def new_cipher(cipher_name)
29
+ CREATE_CIPHER.call(cipher_name)
30
+ end
31
+
32
+ def update_cipher(cipher, value)
33
+ data = cipher.update(value)
34
+ data << cipher.final
35
+ data
36
+ end
37
+
38
+ module ClassMethods
39
+ def create_private_key
40
+ key = CREATE_CIPHER.call(Sym::Configuration.property(:private_key_cipher)).random_key
41
+ ::Base64.urlsafe_encode64(key)
42
+ end
43
+ end
44
+ end
45
+ end
46
+