sym 0.1.0 → 2.0.0

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