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