opensecret 0.0.962 → 0.0.988
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/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
|