opensecret 0.0.962 → 0.0.988
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -10
- data/bin/opensecret +3 -4
- data/bin/ops +5 -0
- data/lib/extension/string.rb +114 -0
- data/lib/factbase/facts.opensecret.io.ini +9 -21
- data/lib/interprete/begin.rb +232 -0
- data/lib/interprete/cmd.rb +621 -0
- data/lib/{plugins/usecases/unlock.rb → interprete/export.rb} +25 -70
- data/lib/interprete/init.rb +205 -0
- data/lib/interprete/key.rb +119 -0
- data/lib/interprete/open.rb +148 -0
- data/lib/{plugins/usecases → interprete}/put.rb +19 -6
- data/lib/{plugins/usecases → interprete}/safe.rb +2 -1
- data/lib/{plugins/usecases/lock.rb → interprete/seal.rb} +24 -34
- data/lib/interprete/set.rb +46 -0
- data/lib/interprete/use.rb +43 -0
- data/lib/interpreter.rb +165 -0
- data/lib/keytools/binary.map.rb +245 -0
- data/lib/keytools/digester.rb +245 -0
- data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +179 -0
- data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +190 -0
- data/lib/keytools/doc.star.schema.strategy.txt +77 -0
- data/lib/keytools/doc.using.pbkdf2.kdf.ruby +95 -0
- data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +266 -0
- data/lib/keytools/kdf.bcrypt.rb +180 -0
- data/lib/keytools/kdf.pbkdf2.rb +164 -0
- data/lib/keytools/key.data.rb +227 -0
- data/lib/keytools/key.derivation.rb +341 -0
- data/lib/keytools/key.module.rb +140 -0
- data/lib/keytools/key.rb +481 -0
- data/lib/logging/gem.logging.rb +1 -2
- data/lib/modules/cryptology.md +43 -0
- data/lib/{plugins/ciphers → modules/cryptology}/aes-256.rb +6 -0
- data/lib/{crypto → modules/cryptology}/amalgam.rb +6 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +118 -0
- data/lib/{plugins → modules/cryptology}/crypt.io.rb +5 -0
- data/lib/{crypto → modules/cryptology}/engineer.rb +7 -1
- data/lib/{crypto → modules/cryptology}/open.bcrypt.rb +0 -0
- data/lib/modules/mappers/collateral.rb +282 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/mappers/envelope.rb +127 -0
- data/lib/modules/mappers/settings.rb +170 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/{opensecret/plugins.io/git/git.flow.rb → modules/storage/git.store.rb} +11 -0
- data/lib/notepad/scratch.pad.rb +17 -0
- data/lib/session/fact.finder.rb +13 -0
- data/lib/session/require.gem.rb +5 -0
- data/lib/store-commands.txt +180 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +5 -6
- metadata +74 -29
- data/lib/crypto/blowfish.rb +0 -85
- data/lib/crypto/collect.rb +0 -140
- data/lib/crypto/verify.rb +0 -33
- data/lib/opensecret.rb +0 -236
- data/lib/plugins/cipher.rb +0 -203
- data/lib/plugins/ciphers/blowfish.rb +0 -126
- data/lib/plugins/coldstore.rb +0 -181
- data/lib/plugins/envelope.rb +0 -116
- data/lib/plugins/secrets.uc.rb +0 -94
- data/lib/plugins/usecase.rb +0 -239
- data/lib/plugins/usecases/init.rb +0 -145
- data/lib/plugins/usecases/open.rb +0 -108
- data/lib/session/attributes.rb +0 -279
- data/lib/session/dictionary.rb +0 -191
- data/lib/session/file.path.rb +0 -53
- data/lib/session/session.rb +0 -80
data/lib/crypto/collect.rb
DELETED
@@ -1,140 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
|
3
|
-
module OpenSecret
|
4
|
-
|
5
|
-
require 'io/console'
|
6
|
-
|
7
|
-
# This class will be refactored into an interface implemented by a set
|
8
|
-
# of plugins that will capture sensitive information from users from an
|
9
|
-
# Ubuntu, Windows, RHEL, CoreOS, iOS or CentOS command line interface.
|
10
|
-
#
|
11
|
-
# An equivalent REST API will also be available for bringing in sensitive
|
12
|
-
# information in the most secure (but simple) manner.
|
13
|
-
class Collect
|
14
|
-
|
15
|
-
|
16
|
-
# <tt>Collect something sensitive from the command line</tt> with a
|
17
|
-
# minimum length specified in the first parameter. This method can't
|
18
|
-
# know whether the information is a password, a pin number or whatever
|
19
|
-
# so it takes the integer minimum size at its word.
|
20
|
-
#
|
21
|
-
# @param min_size [Integer] the minimum size of the collected secret
|
22
|
-
# whereby one (1) is the least we can expect. The maximum bound is
|
23
|
-
# not constrained here so will fall under what is allowed by the
|
24
|
-
# interface, be it a CLI, Rest API, Web UI or Mobile App.
|
25
|
-
#
|
26
|
-
# @param prompt_twice [Boolean] indicate whether the user should be
|
27
|
-
# prompted twice. If true the prompt_2 text must be provided and
|
28
|
-
# converse is also true. A true value asserts that both times the
|
29
|
-
# user enters the same (case sensitive) string.
|
30
|
-
#
|
31
|
-
# @param prompt_1 [String] the text (aide memoire) used to prompt the user
|
32
|
-
#
|
33
|
-
# @param prompt_2 [String] if the prompt twice boolean is TRUE, this
|
34
|
-
# second prompt (aide memoire) must be provided.
|
35
|
-
#
|
36
|
-
# @return [String] the collected string text ( watch out for non-ascii chars)
|
37
|
-
# @raise [ArgumentError] if the minimum size is less than one
|
38
|
-
def self.secret_text min_size, prompt_twice, prompt_1, prompt_2=nil
|
39
|
-
|
40
|
-
# put this into caller class if reading from facts.
|
41
|
-
# put this into caller class if reading from facts.
|
42
|
-
# put this into caller class if reading from facts.
|
43
|
-
# min_msg = "The minimum size #{min_size.to_s} must be an integer."
|
44
|
-
# raise ArgumentError, min_msg unless min_size.instance_of? Integer
|
45
|
-
|
46
|
-
assert_min_size min_size
|
47
|
-
|
48
|
-
sleep(1)
|
49
|
-
puts "\n#{prompt_1} : "
|
50
|
-
first_secret = STDIN.noecho(&:gets).chomp
|
51
|
-
|
52
|
-
assert_input_text_size first_secret.length, min_size
|
53
|
-
return first_secret unless prompt_twice
|
54
|
-
|
55
|
-
sleep(1)
|
56
|
-
puts "\n#{prompt_2} : "
|
57
|
-
check_secret = STDIN.noecho(&:gets).chomp
|
58
|
-
|
59
|
-
assert_same_size_text first_secret, check_secret
|
60
|
-
|
61
|
-
return first_secret
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
|
66
|
-
# --
|
67
|
-
# -- Raise an exception if asked to collect text that is less
|
68
|
-
# -- than 3 characters in length.
|
69
|
-
# --
|
70
|
-
def self.assert_min_size min_size
|
71
|
-
|
72
|
-
min_length_msg = "\n\nCrypts with 2 (or less) characters open up exploitable holes.\n\n"
|
73
|
-
raise ArgumentError.new min_length_msg if min_size < 3
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
|
-
# --
|
79
|
-
# -- Output an error message and then exit if the entered input
|
80
|
-
# -- text size does not meet the minimum requirements.
|
81
|
-
# --
|
82
|
-
def self.assert_input_text_size input_size, min_size
|
83
|
-
|
84
|
-
if( input_size < min_size )
|
85
|
-
|
86
|
-
puts
|
87
|
-
puts "Input is too short. Please enter at least #{min_size} characters."
|
88
|
-
puts
|
89
|
-
|
90
|
-
exit
|
91
|
-
|
92
|
-
end
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
|
97
|
-
# --
|
98
|
-
# -- Assert that the text entered the second time is exactly (case sensitive)
|
99
|
-
# -- the same as the text entered the first time.
|
100
|
-
# --
|
101
|
-
def self.assert_same_size_text first_text, second_text
|
102
|
-
|
103
|
-
unless( first_text.eql? second_text )
|
104
|
-
|
105
|
-
puts
|
106
|
-
puts "Those two bits of text are not the same (in my book)!"
|
107
|
-
puts
|
108
|
-
|
109
|
-
exit
|
110
|
-
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
# --
|
117
|
-
# -- Print out the machine password that is to be kept as an environment variable
|
118
|
-
# -- on any workstation used for material decryption.
|
119
|
-
# --
|
120
|
-
# -- Remember that neither the human nor machine passwords are required for the
|
121
|
-
# -- encryption phase. That is the beauty of assymetric cryptography - you don't
|
122
|
-
# -- need a private key to encrypt - just the end user's public key.
|
123
|
-
# --
|
124
|
-
def self.print_secret_env_var env_var_name, env_var_value
|
125
|
-
|
126
|
-
machine_to_env_txt = "sudo echo \"#{env_var_name}=#{env_var_value}\" >> /etc/environment"
|
127
|
-
|
128
|
-
puts
|
129
|
-
puts "@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
|
130
|
-
puts "@@@ Add as environment variable @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
|
131
|
-
puts "@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
|
132
|
-
puts machine_to_env_txt
|
133
|
-
puts "@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
|
134
|
-
puts
|
135
|
-
|
136
|
-
end
|
137
|
-
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
data/lib/crypto/verify.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
|
3
|
-
module OpenSecret
|
4
|
-
|
5
|
-
# This class verifies domain names, email addresses and othe external
|
6
|
-
# reference data.
|
7
|
-
class Verify
|
8
|
-
|
9
|
-
# Register two fundamental opensecret crypt pointers
|
10
|
-
#
|
11
|
-
# - an opensecret domain like » **lecturers@harvard**
|
12
|
-
# - the url to a backend store like Git, S3 or an SSH accessible drive.
|
13
|
-
#
|
14
|
-
# The domain will be extended to cover verified internet domains.
|
15
|
-
# They will also latch onto LDAP domains so when admins add, revoke
|
16
|
-
# or remove users, their opensecret access is adjusted accordingly.
|
17
|
-
#
|
18
|
-
# @param domain [String] the DOMAIN eg lecturers@harvard for your family or work group.
|
19
|
-
# @param store_url [String] the STORE_URL for connecting to the backend storage service
|
20
|
-
#
|
21
|
-
def self.verify_domain domain, store_url
|
22
|
-
|
23
|
-
# -> read config file map
|
24
|
-
# -> create new domain in map
|
25
|
-
# -> add type and store url to map
|
26
|
-
# -> backup configuration
|
27
|
-
# -> overwrite the ini config file
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
data/lib/opensecret.rb
DELETED
@@ -1,236 +0,0 @@
|
|
1
|
-
require "thor"
|
2
|
-
require "fileutils"
|
3
|
-
|
4
|
-
require "session/time.stamp"
|
5
|
-
require "session/attributes"
|
6
|
-
require "logging/gem.logging"
|
7
|
-
require "session/require.gem"
|
8
|
-
|
9
|
-
# Include the logger mixins so that every class can enjoy "import free"
|
10
|
-
# logging through pointers to the (extended) log behaviour.
|
11
|
-
include OpenLogger
|
12
|
-
|
13
|
-
# This standard out sync command flushes text destined for STDOUT immediately,
|
14
|
-
# without waiting either for a full cache or script completion.
|
15
|
-
$stdout.sync = true
|
16
|
-
|
17
|
-
# Recursively require all gems that are either in or under the directory
|
18
|
-
# that this code is executing from. Only use this tool if your library is
|
19
|
-
# relatively small but highly interconnected. In these instances it raises
|
20
|
-
# productivity and reduces harassing "not found" exceptions.
|
21
|
-
OpenSession::RecursivelyRequire.now( __FILE__ )
|
22
|
-
|
23
|
-
|
24
|
-
# This command line processor extends the Thor gem CLI tools in order to
|
25
|
-
#
|
26
|
-
# - read the posted commands, options and switches
|
27
|
-
# - maps the incoming string data to objects
|
28
|
-
# - assert that the mandatory options exist
|
29
|
-
# - assert the type of each parameter
|
30
|
-
# - ensure that the parameter values are in range
|
31
|
-
# - delegate processing to the registered handlers
|
32
|
-
#
|
33
|
-
class CliInterpreter < Thor
|
34
|
-
|
35
|
-
OpenSession::Session.instance.context = "opensecret"
|
36
|
-
log.info(x) {"opensecret session initiated at [#{OpenSession::Stamp.yyjjj_hhmm_sst}]." }
|
37
|
-
log.info(x) {"opensecret session context is [#{OpenSession::Session.instance.context}]." }
|
38
|
-
|
39
|
-
#
|
40
|
-
# This class option allows every CLI call the option to include
|
41
|
-
# a --debug boolean switch which will up the verbosity of the
|
42
|
-
# content logged to the file .opensecret/opensecret.log
|
43
|
-
#
|
44
|
-
class_option :debug, :type => :boolean
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# Description of the init configuration call.
|
49
|
-
desc "init", "initialize secret keys and check access to the crypt store"
|
50
|
-
|
51
|
-
# Initialize secret keys and check access to the crypt store.
|
52
|
-
#
|
53
|
-
# - checks the installed configuration.
|
54
|
-
def init
|
55
|
-
OpenSecret::Init.new.flow_of_events
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
# Description of the lock use case command line call.
|
60
|
-
desc "lock", "Lock away the (secret stuffed) envelope into key and crypt stores."
|
61
|
-
|
62
|
-
# Lock away the (secret stuffed) envelope into key and crypt stores.
|
63
|
-
def lock
|
64
|
-
OpenSecret::Lock.new.flow_of_events
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
# Description of the open use case command.
|
69
|
-
desc "open OUTER_PATH", "OUTER_PATH to envelope of secrets to stuff and then lock."
|
70
|
-
|
71
|
-
# Open up a conduit from which we can add, subtract, update and list secrets
|
72
|
-
# before they are committed (and pushed) into permanent locked storage.
|
73
|
-
#
|
74
|
-
# @param outer_path [String] the path to USB key for storing encrypted keys
|
75
|
-
def open outer_path
|
76
|
-
|
77
|
-
open_uc = OpenSecret::Open.new
|
78
|
-
open_uc.outer_path = outer_path
|
79
|
-
open_uc.flow_of_events
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
# Description of the unlock use case command.
|
84
|
-
desc "unlock OUTER_PATH", "OUTER_PATH to locked secrets to open for reading or stuffing."
|
85
|
-
|
86
|
-
# If confident that command history cannot be exploited to gain the human password
|
87
|
-
# or if the agent running opensecret is itself a script, the <tt>with</tt> option can
|
88
|
-
# be used to convey the password.
|
89
|
-
option :with
|
90
|
-
|
91
|
-
# Unlock a secrets envelope at the specified outer path so that we can read, put
|
92
|
-
# and discard secrets.
|
93
|
-
#
|
94
|
-
# This use case requires the human (agent) password unless the <tt>--no-human-password</tt>
|
95
|
-
# flag was posted along with the <tt>init</tt> command.
|
96
|
-
#
|
97
|
-
# There are two ways to provide the password (for the <b><em>my/gadgets</em></b> group)
|
98
|
-
#
|
99
|
-
# - <tt>opensecret unlock my/gadgets</tt> and respond to the password prompt (or)
|
100
|
-
# - <tt>opensecret unlock my/gadgets --with="hUM4n-0pen$3cr3t"</tt>
|
101
|
-
#
|
102
|
-
# If providing the password on the command line, one must be confident that the shell's
|
103
|
-
# command history cannot be exploited to capture it.
|
104
|
-
#
|
105
|
-
# @param outer_path [String] the path to the (previously) locked secrets in frozen storage.
|
106
|
-
def unlock outer_path
|
107
|
-
|
108
|
-
unlock_uc = OpenSecret::Unlock.new
|
109
|
-
unlock_uc.outer_path = outer_path
|
110
|
-
unlock_uc.master_p4ss = options[:with] if options[:with]
|
111
|
-
unlock_uc.flow_of_events
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
# Description of the put secret command.
|
116
|
-
desc "put <secret_id> <secret_value>", "put secret like login/username into opened context."
|
117
|
-
|
118
|
-
# Put a secret with an id like login/username and a value like joebloggs into the
|
119
|
-
# context (eg work/laptop) that was opened with the open command.
|
120
|
-
#
|
121
|
-
# @param secret_id [String] the id of the secret to put into the opened context
|
122
|
-
# @param secret_value [String] the value of the secret to put into the opened context
|
123
|
-
def put secret_id, secret_value
|
124
|
-
|
125
|
-
put_uc = OpenSecret::Put.new
|
126
|
-
put_uc.secret_id = secret_id
|
127
|
-
put_uc.secret_value = secret_value
|
128
|
-
put_uc.flow_of_events
|
129
|
-
|
130
|
-
end
|
131
|
-
|
132
|
-
|
133
|
-
# Description of the mandatory safe and (safe directory) configuration.
|
134
|
-
desc "safe SAFE_DIR", "SAFE_DIR full path to the (ideally USB key) storage location"
|
135
|
-
|
136
|
-
#
|
137
|
-
# A USB key drive is the ideal store for the encrypted private
|
138
|
-
# key and the tamper proof configuration file. This method collects
|
139
|
-
# the usb drive path and then
|
140
|
-
#
|
141
|
-
# - checks the path exists
|
142
|
-
# - if not, it attempts to create the path
|
143
|
-
# - if successful it's written into HOME/.opensecret/opensecret.keydir.txt
|
144
|
-
#
|
145
|
-
# @param safe_dir [String] the path to USB key for storing encrypted keys
|
146
|
-
#
|
147
|
-
def safe safe_dir
|
148
|
-
|
149
|
-
configure_safe_uc = OpenSecret::Safe.new
|
150
|
-
configure_safe_uc.safe_path = safe_dir
|
151
|
-
configure_safe_uc.flow_of_events
|
152
|
-
|
153
|
-
end
|
154
|
-
|
155
|
-
|
156
|
-
#
|
157
|
-
# Description of the email-address that is unique
|
158
|
-
# for the domain in question.
|
159
|
-
#
|
160
|
-
desc "email EMAIL_ADDRESS", "EMAIL_ADDRESS Your email address unique for the domain."
|
161
|
-
|
162
|
-
#
|
163
|
-
# This method collects the email address that is unique for the domain in
|
164
|
-
# question. For the email address to be valid it must consist of only alphanumerics,
|
165
|
-
# underscores, periods, hyphens and (at most) one @ symbol.
|
166
|
-
#
|
167
|
-
# Note that underscores, periods, hyphens and @ symbol are permissable if not
|
168
|
-
# at the start or end of the email address nor can they appear consecutively.
|
169
|
-
#
|
170
|
-
# <tt>a@b.cd</tt> is the minimum size of an externally addressable email
|
171
|
-
# address so 6 or more characters is enforced by this configuation method.
|
172
|
-
#
|
173
|
-
# email validation will be added to opensecret including
|
174
|
-
# - validation of the email address character array
|
175
|
-
# - proof of control and ownership of the email address
|
176
|
-
#
|
177
|
-
# If an email address already exists within the domain section of the
|
178
|
-
# configuration file, it is overwritten. If there is no configuration
|
179
|
-
# file yet, one is created within the auspices of the home directory.
|
180
|
-
#
|
181
|
-
# @param email_address [String] email address of the user (eg a@b.cd)
|
182
|
-
#
|
183
|
-
def email email_address
|
184
|
-
|
185
|
-
if email_address.length < 6
|
186
|
-
abort "The tiniest (externally accessible) email address [a@b.cd] has 6 characters."
|
187
|
-
end
|
188
|
-
|
189
|
-
OpenSession::Attributes.stash "opensecret", "opensecret", "email", email_address
|
190
|
-
|
191
|
-
end
|
192
|
-
|
193
|
-
|
194
|
-
desc "store STORE_URL", "STORE_URL denotes the location of the backend crypt store"
|
195
|
-
|
196
|
-
#
|
197
|
-
# Here we define the location (the URL) of the crypt store. The crypt store will hold
|
198
|
-
# the cipher text and is known as <tt>backend storage</tt>.
|
199
|
-
#
|
200
|
-
# The planned list of backend storage systems (each onlined with a plugin), is
|
201
|
-
#
|
202
|
-
# - Git (including GitHub, GitLab, BitBucket, OpenGit and private repositories.
|
203
|
-
# - S3 Buckets from the Amazon Web Services (AWS) cloud.
|
204
|
-
# - SSH, SCP, SFTP connected file-systems
|
205
|
-
# - network storage including Samba, NFS, VMWare vSAN and
|
206
|
-
# - GoogleDrive (only Windows has suitable synchronized support).
|
207
|
-
#
|
208
|
-
# @param store_url [String] the STORE_URL identifying a filesystem or Git or S3 storage location
|
209
|
-
def store store_url
|
210
|
-
|
211
|
-
|
212
|
-
if store_url.strip.length < 3
|
213
|
-
abort "4 characters is the minimum domain name length."
|
214
|
-
end
|
215
|
-
|
216
|
-
OpenSession::Attributes.stash "opensecret", "opensecret", "store", store_url.strip
|
217
|
-
|
218
|
-
### if( File.exists?( store_url ) && !(File.directory? store_url) )
|
219
|
-
### abort "The store url path cannot be a file => #{store_url}"
|
220
|
-
### end
|
221
|
-
|
222
|
-
end
|
223
|
-
|
224
|
-
|
225
|
-
desc "on", "Open a session to encrypt (lock) one or more secrets"
|
226
|
-
|
227
|
-
# The [on] message tells opensecret to prepare to lock one or more secrets.
|
228
|
-
def on
|
229
|
-
|
230
|
-
#### FileUtils.mkdir_p store_url unless File.exists? store_url
|
231
|
-
#### OpenSession::Attributes.stash "opensecret", "store.id.#{store_id}", store_url
|
232
|
-
|
233
|
-
end
|
234
|
-
|
235
|
-
|
236
|
-
end
|
data/lib/plugins/cipher.rb
DELETED
@@ -1,203 +0,0 @@
|
|
1
|
-
#!/usr/bin/ruby
|
2
|
-
# coding: utf-8
|
3
|
-
|
4
|
-
module OpenSecret
|
5
|
-
|
6
|
-
require "base64"
|
7
|
-
|
8
|
-
|
9
|
-
# {OpenSecret::Cipher} is a base class that enables cipher varieties
|
10
|
-
# to be plugged and played with minimal effort. This Cipher implements much
|
11
|
-
# of the use case functionality - all extension classes need to do, is
|
12
|
-
# to subclass and implement only the core behaviour that define its identity.
|
13
|
-
#
|
14
|
-
# == Double Encryption | Cipher Parent vs Cipher Child
|
15
|
-
#
|
16
|
-
# Double encryption first with a symmetric and then an asymmetric one fulfills
|
17
|
-
# the +opensecret+ promise of making the stored ciphertext utterly worthless.
|
18
|
-
#
|
19
|
-
# The child ciphers implement the inner symmetric encyption whilst the parent
|
20
|
-
# implements the outer asymmetric encryption algorithm.
|
21
|
-
#
|
22
|
-
# The process is done twice resulting in two stores that are mirrored in structure.
|
23
|
-
# The front end store holds doubly encrypted keys whist the backend store holds
|
24
|
-
# the doubly encrypted secrets.
|
25
|
-
#
|
26
|
-
# Attackers wouldn't be able to distinguish one from the other. Even if they
|
27
|
-
# theoretically cracked the asymmetric encryption - they would then be faced
|
28
|
-
# with a powerful symmetric encryption algorithm which could be any one of the
|
29
|
-
# leading ciphers such as TwoFish or the Advanced Encryption Standard (AES).
|
30
|
-
#
|
31
|
-
# == Ciphers at 3 Levels
|
32
|
-
#
|
33
|
-
# Ciphers are implemented at three distinct levels.
|
34
|
-
#
|
35
|
-
# <b>Low Level Ciphers</b>
|
36
|
-
#
|
37
|
-
# Low level ciphers are given text to encrypt and an instantiated dictionary
|
38
|
-
# in which to place the encryption parameters such as keys and initialization
|
39
|
-
# vectors (iv)s.
|
40
|
-
#
|
41
|
-
# Some more specific ciphers can handle authorization data for example the
|
42
|
-
# Galois Counter Mode (GCM) cipher.
|
43
|
-
#
|
44
|
-
# Low level ciphers know nothing about text IO nor reading and writing to
|
45
|
-
# persistence structures like files, queues and databases.
|
46
|
-
#
|
47
|
-
# <b>Mid Level Ciphers</b>
|
48
|
-
#
|
49
|
-
# Mid level ciphers talk to the low level ciphers and bring in input and output
|
50
|
-
# textual formats like OpenSecret's two-part block structures.
|
51
|
-
#
|
52
|
-
# Mid level ciphers still know nothing of persistence structures like files,
|
53
|
-
# queues and databases.
|
54
|
-
#
|
55
|
-
# <b>Use Case Level Ciphers</b>
|
56
|
-
#
|
57
|
-
# The ciphers operating at the use case level talk to mid level ciphers. They
|
58
|
-
# interact with the <b>opensecret store API</b> which brings persistence
|
59
|
-
# functions such as <b>read/write</b> as well as remoting functions such as
|
60
|
-
# <b>push/pull</b>.
|
61
|
-
#
|
62
|
-
# Use Case level ciphers interact with the latest crypt technologies due to
|
63
|
-
# interface separation. Also they talk classes implementing persistence stores
|
64
|
-
# allowing assets liek Git, S3, DropBox, simple files, SSH filesystems, Samba
|
65
|
-
# to hold locked key and material crypts.
|
66
|
-
#
|
67
|
-
# Databases stores will be introduced soon allowing opensecret to plug in and
|
68
|
-
# exploit database managers like Mongo, Hadoop, MySQL, Maria, and PostgreSQL.
|
69
|
-
#
|
70
|
-
# Plugging into DevOps orchestration platforms like Terraform, Ansible, Chef
|
71
|
-
# and Puppet will soon be available. Add this with integrations to other credential
|
72
|
-
# managers like HashiCorp's Vault, Credstash, Amazon KMS, Git Secrets, PGP,
|
73
|
-
# LastPass, KeePass and KeePassX.
|
74
|
-
#
|
75
|
-
# == How to Implement a Cipher
|
76
|
-
#
|
77
|
-
# Extend this base class to inherit lots of +unexciting+ functionality
|
78
|
-
# that essentially
|
79
|
-
#
|
80
|
-
# - manages the main encryption and decryption use case flow
|
81
|
-
# - +concatenates+ the symmetric encryption meta data with ciphertext +after encryption+
|
82
|
-
# - _splits_ and objectifies the key/value metadata plus ciphertext +before decryption+
|
83
|
-
# - +handles file read/writes+ in conjunction with the store plugins
|
84
|
-
# - handles +exceptions+ and +malicious input detection+ and incubation
|
85
|
-
# - +_performs the asymmetric encryption_+ of the cipher's symmetrically encrypted output
|
86
|
-
#
|
87
|
-
# == What Behaviour Must Ciphers Implement
|
88
|
-
#
|
89
|
-
# Ciphers bring the cryptographic mathematics and implementation algorithms
|
90
|
-
# to the table. So when at home they must implement
|
91
|
-
#
|
92
|
-
# - <tt>do_symmetric_encryption(plain_text)</tt> - resulting in ciphertext
|
93
|
-
# - <tt>do_symmetric_decryption(ciphertext, encryption_dictionary)</tt> » plaintext
|
94
|
-
#
|
95
|
-
# and also set the <tt>@dictionary</tt> hash (map) of pertinent
|
96
|
-
# key/value pairs including the encryption algorithm, the encryption key and
|
97
|
-
# the ciphertext signature to thwart any at-rest tampering.
|
98
|
-
#
|
99
|
-
# That's It. Cipher children can rely on the {OpenSecret::Cipher} parent to
|
100
|
-
# do the nitty gritty of file-handling plus managing stores and paths.
|
101
|
-
class Cipher
|
102
|
-
|
103
|
-
# Ciphers use <b>symmetric algorithms</b> to encrypt the given text, which
|
104
|
-
# is then wrapped up along with the encryption key and other <b>metadata</b>
|
105
|
-
# pertinent to the algorithm, they then encrypt this bundle with the
|
106
|
-
# <b>public key</b> provided and return the text that can safely be stored in
|
107
|
-
# a text file.
|
108
|
-
#
|
109
|
-
# Ciphers should never interact with the filesystem which makes them
|
110
|
-
# reusable in API and remote store scenarios.
|
111
|
-
#
|
112
|
-
# Binary files should be converted into the base64 format before being
|
113
|
-
# presented to ciphers.
|
114
|
-
#
|
115
|
-
# Every component in the pipeline bears the responsibility for nullifying
|
116
|
-
# and rejecting malicious content.
|
117
|
-
#
|
118
|
-
# @param public_key [OpenSSL::PKey::RSA]
|
119
|
-
# an {OpenSSL::PKey::RSA} public key. The unique selling point of
|
120
|
-
# asymmetric encryption is it can be done without recourse to the heavily
|
121
|
-
# protected private key. Thus the encryption process can continue with
|
122
|
-
# just a public key as long as its authenticity is assured.
|
123
|
-
#
|
124
|
-
# @param payload_text [String]
|
125
|
-
# plaintext (or base64 encoded) text to encrypt
|
126
|
-
#
|
127
|
-
# @return [String] doubly (symmetric and asymmetric) encrypted cipher text
|
128
|
-
def self.encrypt_it public_key, payload_text
|
129
|
-
|
130
|
-
crypt_data = {}
|
131
|
-
crypted_payload = Base64.encode64( Aes256.do_encrypt( crypt_data, payload_text ) )
|
132
|
-
unified_material = CryptIO.inner_crypt_serialize crypt_data, crypted_payload
|
133
|
-
|
134
|
-
outer_crypt_key = OpenSecret::Engineer.strong_key( 128 )
|
135
|
-
crypted_cryptkey = Base64.encode64( public_key.public_encrypt( outer_crypt_key ) )
|
136
|
-
|
137
|
-
crypted_material = Base64.encode64(Blowfish.new.encryptor unified_material, outer_crypt_key)
|
138
|
-
|
139
|
-
return CryptIO.outer_crypt_serialize( crypted_cryptkey, crypted_material )
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
|
144
|
-
# This method takes and <b><em>opensecret formatted</em></b> cipher-text block
|
145
|
-
# generated by {self.encrypt_it} and returns the original message that has effectively
|
146
|
-
# been doubly encrypted using a symmetric and asymmetric cipher. This type of
|
147
|
-
# encryption is standard best practice when serializing secrets.
|
148
|
-
#
|
149
|
-
# opensecret cipher-text blocks <b><em>look like a two(2) part bundle</em></b>
|
150
|
-
# but they are <b><em>actually a three(3) part bundle</em></b> because the second
|
151
|
-
# part is in itself an amalgam of two distinct objects, serialized as text blocks.
|
152
|
-
#
|
153
|
-
# <b>The 3 OpenSecret Blocks</b>
|
154
|
-
#
|
155
|
-
# Even though the incoming text <b><em>appears to contain two (2) blocks</em></b>,
|
156
|
-
# it <b><em>actually contains three (3)</em></b>.
|
157
|
-
#
|
158
|
-
# - a massive symmetric encryption key (locked by an asymmetric keypair)
|
159
|
-
# - a dictionary denoting the algorithm and parameters used to encrypt the 3rd block
|
160
|
-
# - the true message whose encryption is parametized by the dictionary (in 2nd block)
|
161
|
-
#
|
162
|
-
# The second and third block are only revealed by asymmetrically decrypting
|
163
|
-
# the key in the first block and using it to symmetrically decrypt what appears
|
164
|
-
# to be a unified second block.
|
165
|
-
#
|
166
|
-
# @param private_key [OpenSSL::PKey::RSA]
|
167
|
-
# the <b>asymmetric private key</b> whose corresponding public key was
|
168
|
-
# employed during the encryption of a super-strong 128 character symmetric
|
169
|
-
# key embalmed by the first ciphertext block.
|
170
|
-
#
|
171
|
-
# @param os_block_text [String]
|
172
|
-
# the locked cipher text is the opensecret formatted block which comes
|
173
|
-
# in two main chunks. First is the <b>long strong</b> symmetric encryption
|
174
|
-
# key crypted with the public key portion of the private key in the first
|
175
|
-
# parameter.
|
176
|
-
#
|
177
|
-
# The second chunk is the symmetrically crypted text that was locked with
|
178
|
-
# the encryption key revealed in the first chunk.
|
179
|
-
#
|
180
|
-
# @return [String]
|
181
|
-
# the doubly encrypted plain text that is locked by a symmetric key and
|
182
|
-
# that symmetric key itself locked using the public key portion of the
|
183
|
-
# private key whose crypted form is presented in the first parameter.
|
184
|
-
def self.decrypt_it private_key, os_block_text
|
185
|
-
|
186
|
-
first_block = Base64.decode64( CryptIO.outer_crypt_deserialize os_block_text, true )
|
187
|
-
trail_block = Base64.decode64( CryptIO.outer_crypt_deserialize os_block_text, false )
|
188
|
-
|
189
|
-
decrypt_key = private_key.private_decrypt first_block
|
190
|
-
inner_block = Blowfish.new.decryptor( trail_block, decrypt_key )
|
191
|
-
|
192
|
-
crypt_props = Hash.new
|
193
|
-
cipher_text = CryptIO.inner_crypt_deserialize( crypt_props, inner_block )
|
194
|
-
|
195
|
-
return Aes256.do_decrypt( crypt_props, cipher_text )
|
196
|
-
|
197
|
-
end
|
198
|
-
|
199
|
-
|
200
|
-
end
|
201
|
-
|
202
|
-
|
203
|
-
end
|