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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +25 -0
- data/.document +2 -0
- data/.gitignore +6 -2
- data/.rspec +1 -1
- data/.rubocop.yml +1156 -0
- data/.travis.yml +10 -2
- data/.yardopts +5 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/MANAGING-KEYS.md +67 -0
- data/README.md +444 -12
- data/Rakefile +10 -2
- data/bin/sym.bash-completion +24 -0
- data/exe/keychain +38 -0
- data/exe/sym +20 -0
- data/lib/sym.rb +110 -2
- data/lib/sym/app.rb +56 -0
- data/lib/sym/app/args.rb +42 -0
- data/lib/sym/app/cli.rb +192 -0
- data/lib/sym/app/commands.rb +56 -0
- data/lib/sym/app/commands/command.rb +77 -0
- data/lib/sym/app/commands/delete_keychain_item.rb +17 -0
- data/lib/sym/app/commands/encrypt_decrypt.rb +26 -0
- data/lib/sym/app/commands/generate_key.rb +37 -0
- data/lib/sym/app/commands/open_editor.rb +97 -0
- data/lib/sym/app/commands/print_key.rb +15 -0
- data/lib/sym/app/commands/show_examples.rb +76 -0
- data/lib/sym/app/commands/show_help.rb +16 -0
- data/lib/sym/app/commands/show_language_examples.rb +81 -0
- data/lib/sym/app/commands/show_version.rb +14 -0
- data/lib/sym/app/input/handler.rb +41 -0
- data/lib/sym/app/keychain.rb +135 -0
- data/lib/sym/app/nlp.rb +18 -0
- data/lib/sym/app/nlp/constants.rb +32 -0
- data/lib/sym/app/nlp/translator.rb +61 -0
- data/lib/sym/app/nlp/usage.rb +72 -0
- data/lib/sym/app/output.rb +15 -0
- data/lib/sym/app/output/base.rb +61 -0
- data/lib/sym/app/output/file.rb +18 -0
- data/lib/sym/app/output/noop.rb +14 -0
- data/lib/sym/app/output/stdout.rb +13 -0
- data/lib/sym/app/password/cache.rb +63 -0
- data/lib/sym/app/private_key/base64_decoder.rb +17 -0
- data/lib/sym/app/private_key/decryptor.rb +71 -0
- data/lib/sym/app/private_key/detector.rb +42 -0
- data/lib/sym/app/private_key/handler.rb +44 -0
- data/lib/sym/app/short_name.rb +10 -0
- data/lib/sym/application.rb +114 -0
- data/lib/sym/cipher_handler.rb +46 -0
- data/lib/sym/configuration.rb +39 -0
- data/lib/sym/data.rb +23 -0
- data/lib/sym/data/decoder.rb +28 -0
- data/lib/sym/data/encoder.rb +24 -0
- data/lib/sym/data/wrapper_struct.rb +43 -0
- data/lib/sym/encrypted_file.rb +34 -0
- data/lib/sym/errors.rb +37 -0
- data/lib/sym/extensions/class_methods.rb +12 -0
- data/lib/sym/extensions/instance_methods.rb +114 -0
- data/lib/sym/version.rb +1 -1
- data/sym.gemspec +34 -15
- 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,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,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
|
+
|