safedb 0.01.0001
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 +7 -0
- data/.gitignore +8 -0
- data/.yardopts +3 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +793 -0
- data/Rakefile +16 -0
- data/bin/safe +5 -0
- data/lib/configs/README.md +58 -0
- data/lib/extension/array.rb +162 -0
- data/lib/extension/dir.rb +35 -0
- data/lib/extension/file.rb +123 -0
- data/lib/extension/hash.rb +33 -0
- data/lib/extension/string.rb +572 -0
- data/lib/factbase/facts.safedb.net.ini +38 -0
- data/lib/interprete.rb +462 -0
- data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
- data/lib/keytools/kdf.api.rb +243 -0
- data/lib/keytools/kdf.bcrypt.rb +265 -0
- data/lib/keytools/kdf.pbkdf2.rb +262 -0
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1391 -0
- data/lib/keytools/key.db.rb +330 -0
- data/lib/keytools/key.docs.rb +195 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.ident.rb +243 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +259 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +585 -0
- data/lib/logging/gem.logging.rb +132 -0
- data/lib/modules/README.md +43 -0
- data/lib/modules/cryptology/aes-256.rb +154 -0
- data/lib/modules/cryptology/amalgam.rb +70 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +138 -0
- data/lib/modules/cryptology/crypt.io.rb +225 -0
- data/lib/modules/cryptology/engineer.rb +99 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/modules/storage/git.store.rb +399 -0
- data/lib/session/fact.finder.rb +334 -0
- data/lib/session/require.gem.rb +112 -0
- data/lib/session/time.stamp.rb +340 -0
- data/lib/session/user.home.rb +49 -0
- data/lib/usecase/cmd.rb +487 -0
- data/lib/usecase/config/README.md +57 -0
- data/lib/usecase/docker/README.md +146 -0
- data/lib/usecase/docker/docker.rb +49 -0
- data/lib/usecase/edit/README.md +43 -0
- data/lib/usecase/edit/delete.rb +46 -0
- data/lib/usecase/export.rb +40 -0
- data/lib/usecase/files/README.md +37 -0
- data/lib/usecase/files/eject.rb +56 -0
- data/lib/usecase/files/file_me.rb +78 -0
- data/lib/usecase/files/read.rb +169 -0
- data/lib/usecase/files/write.rb +89 -0
- data/lib/usecase/goto.rb +57 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +157 -0
- data/lib/usecase/init.rb +63 -0
- data/lib/usecase/jenkins/README.md +146 -0
- data/lib/usecase/jenkins/jenkins.rb +208 -0
- data/lib/usecase/login.rb +71 -0
- data/lib/usecase/logout.rb +28 -0
- data/lib/usecase/open.rb +71 -0
- data/lib/usecase/print.rb +40 -0
- data/lib/usecase/put.rb +81 -0
- data/lib/usecase/set.rb +44 -0
- data/lib/usecase/show.rb +138 -0
- data/lib/usecase/terraform/README.md +91 -0
- data/lib/usecase/terraform/terraform.rb +121 -0
- data/lib/usecase/token.rb +35 -0
- data/lib/usecase/update/README.md +55 -0
- data/lib/usecase/update/rename.rb +180 -0
- data/lib/usecase/use.rb +41 -0
- data/lib/usecase/verse.rb +20 -0
- data/lib/usecase/view.rb +71 -0
- data/lib/usecase/vpn/README.md +150 -0
- data/lib/usecase/vpn/vpn.ini +31 -0
- data/lib/usecase/vpn/vpn.rb +54 -0
- data/lib/version.rb +3 -0
- data/safedb.gemspec +34 -0
- metadata +193 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module SafeDb
|
5
|
+
|
6
|
+
module ToolBelt
|
7
|
+
|
8
|
+
require "base64"
|
9
|
+
|
10
|
+
# {SafeDb::Cipher} is a base class that enables cipher varieties
|
11
|
+
# to be plugged and played with minimal effort. This Cipher implements much
|
12
|
+
# of the use case functionality - all extension classes need to do, is
|
13
|
+
# to subclass and implement only the core behaviour that define its identity.
|
14
|
+
#
|
15
|
+
# == Double Encryption | Cipher Parent vs Cipher Child
|
16
|
+
#
|
17
|
+
# Double encryption first with a symmetric and then an asymmetric one fulfills
|
18
|
+
# the +safe+ promise of making the stored ciphertext utterly worthless.
|
19
|
+
#
|
20
|
+
# The child ciphers implement the inner symmetric encyption whilst the parent
|
21
|
+
# implements the outer asymmetric encryption algorithm.
|
22
|
+
#
|
23
|
+
# The process is done twice resulting in two stores that are mirrored in structure.
|
24
|
+
# The front end store holds doubly encrypted keys whist the backend store holds
|
25
|
+
# the doubly encrypted secrets.
|
26
|
+
#
|
27
|
+
# Attackers wouldn't be able to distinguish one from the other. Even if they
|
28
|
+
# theoretically cracked the asymmetric encryption - they would then be faced
|
29
|
+
# with a powerful symmetric encryption algorithm which could be any one of the
|
30
|
+
# leading ciphers such as TwoFish or the Advanced Encryption Standard (AES).
|
31
|
+
#
|
32
|
+
# == Ciphers at 3 Levels
|
33
|
+
#
|
34
|
+
# Ciphers are implemented at three distinct levels.
|
35
|
+
#
|
36
|
+
# <b>Low Level Ciphers</b>
|
37
|
+
#
|
38
|
+
# Low level ciphers are given text to encrypt and an instantiated dictionary
|
39
|
+
# in which to place the encryption parameters such as keys and initialization
|
40
|
+
# vectors (iv)s.
|
41
|
+
#
|
42
|
+
# Some more specific ciphers can handle authorization data for example the
|
43
|
+
# Galois Counter Mode (GCM) cipher.
|
44
|
+
#
|
45
|
+
# Low level ciphers know nothing about text IO nor reading and writing to
|
46
|
+
# persistence structures like files, queues and databases.
|
47
|
+
#
|
48
|
+
# <b>Mid Level Ciphers</b>
|
49
|
+
#
|
50
|
+
# Mid level ciphers talk to the low level ciphers and bring in input and output
|
51
|
+
# textual formats like SafeDb's two-part block structures.
|
52
|
+
#
|
53
|
+
# Mid level ciphers still know nothing of persistence structures like files,
|
54
|
+
# queues and databases.
|
55
|
+
#
|
56
|
+
# <b>Use Case Level Ciphers</b>
|
57
|
+
#
|
58
|
+
# The ciphers operating at the use case level talk to mid level ciphers. They
|
59
|
+
# interact with the <b>safe store API</b> which brings persistence
|
60
|
+
# functions such as <b>read/write</b> as well as remoting functions such as
|
61
|
+
# <b>push/pull</b>.
|
62
|
+
#
|
63
|
+
# Use Case level ciphers interact with the latest crypt technologies due to
|
64
|
+
# interface separation. Also they talk classes implementing persistence stores
|
65
|
+
# allowing assets liek Git, S3, DropBox, simple files, SSH filesystems, Samba
|
66
|
+
# to hold locked key and material crypts.
|
67
|
+
#
|
68
|
+
# Databases stores will be introduced soon allowing safe to plug in and
|
69
|
+
# exploit database managers like Mongo, Hadoop, MySQL, Maria, and PostgreSQL.
|
70
|
+
#
|
71
|
+
# Plugging into DevOps orchestration platforms like Terraform, Ansible, Chef
|
72
|
+
# and Puppet will soon be available. Add this with integrations to other credential
|
73
|
+
# managers like HashiCorp's Vault, Credstash, Amazon KMS, Git Secrets, PGP,
|
74
|
+
# LastPass, KeePass and KeePassX.
|
75
|
+
#
|
76
|
+
# == How to Implement a Cipher
|
77
|
+
#
|
78
|
+
# Extend this base class to inherit lots of +unexciting+ functionality
|
79
|
+
# that essentially
|
80
|
+
#
|
81
|
+
# - manages the main encryption and decryption use case flow
|
82
|
+
# - +concatenates+ the symmetric encryption meta data with ciphertext +after encryption+
|
83
|
+
# - _splits_ and objectifies the key/value metadata plus ciphertext +before decryption+
|
84
|
+
# - +handles file read/writes+ in conjunction with the store plugins
|
85
|
+
# - handles +exceptions+ and +malicious input detection+ and incubation
|
86
|
+
# - +_performs the asymmetric encryption_+ of the cipher's symmetrically encrypted output
|
87
|
+
#
|
88
|
+
# == What Behaviour Must Ciphers Implement
|
89
|
+
#
|
90
|
+
# Ciphers bring the cryptographic mathematics and implementation algorithms
|
91
|
+
# to the table. So when at home they must implement
|
92
|
+
#
|
93
|
+
# - <tt>do_symmetric_encryption(plain_text)</tt> - resulting in ciphertext
|
94
|
+
# - <tt>do_symmetric_decryption(ciphertext, encryption_dictionary)</tt> » plaintext
|
95
|
+
#
|
96
|
+
# and also set the <tt>@dictionary</tt> hash (map) of pertinent
|
97
|
+
# key/value pairs including the encryption algorithm, the encryption key and
|
98
|
+
# the ciphertext signature to thwart any at-rest tampering.
|
99
|
+
#
|
100
|
+
# That's It. Cipher children can rely on the {SafeDb::Cipher} parent to
|
101
|
+
# do the nitty gritty of file-handling plus managing stores and paths.
|
102
|
+
class Cipher
|
103
|
+
|
104
|
+
# Ciphers use <b>symmetric algorithms</b> to encrypt the given text, which
|
105
|
+
# is then wrapped up along with the encryption key and other <b>metadata</b>
|
106
|
+
# pertinent to the algorithm, they then encrypt this bundle with the
|
107
|
+
# <b>public key</b> provided and return the text that can safely be stored in
|
108
|
+
# a text file.
|
109
|
+
#
|
110
|
+
# Ciphers should never interact with the filesystem which makes them
|
111
|
+
# reusable in API and remote store scenarios.
|
112
|
+
#
|
113
|
+
# Binary files should be converted into the base64 format before being
|
114
|
+
# presented to ciphers.
|
115
|
+
#
|
116
|
+
# Every component in the pipeline bears the responsibility for nullifying
|
117
|
+
# and rejecting malicious content.
|
118
|
+
#
|
119
|
+
# @param public_key [OpenSSL::PKey::RSA]
|
120
|
+
# an {OpenSSL::PKey::RSA} public key. The unique selling point of
|
121
|
+
# asymmetric encryption is it can be done without recourse to the heavily
|
122
|
+
# protected private key. Thus the encryption process can continue with
|
123
|
+
# just a public key as long as its authenticity is assured.
|
124
|
+
#
|
125
|
+
# @param payload_text [String]
|
126
|
+
# plaintext (or base64 encoded) text to encrypt
|
127
|
+
#
|
128
|
+
# @return [String] doubly (symmetric and asymmetric) encrypted cipher text
|
129
|
+
def self.encrypt_it public_key, payload_text
|
130
|
+
|
131
|
+
crypt_data = {}
|
132
|
+
crypted_payload = Base64.encode64( Aes256.do_encrypt( crypt_data, payload_text ) )
|
133
|
+
unified_material = CryptIO.inner_crypt_serialize crypt_data, crypted_payload
|
134
|
+
|
135
|
+
outer_crypt_key = Engineer.strong_key( 128 )
|
136
|
+
crypted_cryptkey = Base64.encode64( public_key.public_encrypt( outer_crypt_key ) )
|
137
|
+
|
138
|
+
crypted_material = Base64.encode64(Blowfish.encryptor unified_material, outer_crypt_key)
|
139
|
+
|
140
|
+
return CryptIO.outer_crypt_serialize( crypted_cryptkey, crypted_material )
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
# This method takes and <b><em>safe formatted</em></b> cipher-text block
|
146
|
+
# generated by {self.encrypt_it} and returns the original message that has effectively
|
147
|
+
# been doubly encrypted using a symmetric and asymmetric cipher. This type of
|
148
|
+
# encryption is standard best practice when serializing secrets.
|
149
|
+
#
|
150
|
+
# safe cipher-text blocks <b><em>look like a two(2) part bundle</em></b>
|
151
|
+
# but they are <b><em>actually a three(3) part bundle</em></b> because the second
|
152
|
+
# part is in itself an amalgam of two distinct objects, serialized as text blocks.
|
153
|
+
#
|
154
|
+
# <b>The 3 SafeDb Blocks</b>
|
155
|
+
#
|
156
|
+
# Even though the incoming text <b><em>appears to contain two (2) blocks</em></b>,
|
157
|
+
# it <b><em>actually contains three (3)</em></b>.
|
158
|
+
#
|
159
|
+
# - a massive symmetric encryption key (locked by an asymmetric keypair)
|
160
|
+
# - a dictionary denoting the algorithm and parameters used to encrypt the 3rd block
|
161
|
+
# - the true message whose encryption is parametized by the dictionary (in 2nd block)
|
162
|
+
#
|
163
|
+
# The second and third block are only revealed by asymmetrically decrypting
|
164
|
+
# the key in the first block and using it to symmetrically decrypt what appears
|
165
|
+
# to be a unified second block.
|
166
|
+
#
|
167
|
+
# @param private_key [OpenSSL::PKey::RSA]
|
168
|
+
# the <b>asymmetric private key</b> whose corresponding public key was
|
169
|
+
# employed during the encryption of a super-strong 128 character symmetric
|
170
|
+
# key embalmed by the first ciphertext block.
|
171
|
+
#
|
172
|
+
# @param os_block_text [String]
|
173
|
+
# the locked cipher text is the safe formatted block which comes
|
174
|
+
# in two main chunks. First is the <b>long strong</b> symmetric encryption
|
175
|
+
# key crypted with the public key portion of the private key in the first
|
176
|
+
# parameter.
|
177
|
+
#
|
178
|
+
# The second chunk is the symmetrically crypted text that was locked with
|
179
|
+
# the encryption key revealed in the first chunk.
|
180
|
+
#
|
181
|
+
# @return [String]
|
182
|
+
# the doubly encrypted plain text that is locked by a symmetric key and
|
183
|
+
# that symmetric key itself locked using the public key portion of the
|
184
|
+
# private key whose crypted form is presented in the first parameter.
|
185
|
+
def self.decrypt_it private_key, os_block_text
|
186
|
+
|
187
|
+
first_block = Base64.decode64( CryptIO.outer_crypt_deserialize os_block_text, true )
|
188
|
+
trail_block = Base64.decode64( CryptIO.outer_crypt_deserialize os_block_text, false )
|
189
|
+
|
190
|
+
decrypt_key = private_key.private_decrypt first_block
|
191
|
+
inner_block = Blowfish.decryptor( trail_block, decrypt_key )
|
192
|
+
|
193
|
+
crypt_props = Hash.new
|
194
|
+
cipher_text = CryptIO.inner_crypt_deserialize( crypt_props, inner_block )
|
195
|
+
|
196
|
+
return Aes256.do_decrypt( crypt_props, cipher_text )
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
module ToolBelt
|
6
|
+
|
7
|
+
require 'io/console'
|
8
|
+
|
9
|
+
# This class will be refactored into an interface implemented by a set
|
10
|
+
# of plugins that will capture sensitive information from users from an
|
11
|
+
# Ubuntu, Windows, RHEL, CoreOS, iOS or CentOS command line interface.
|
12
|
+
#
|
13
|
+
# An equivalent REST API will also be available for bringing in sensitive
|
14
|
+
# information in the most secure (but simple) manner.
|
15
|
+
class Collect
|
16
|
+
|
17
|
+
|
18
|
+
# <tt>Collect something sensitive from the command line</tt> with a
|
19
|
+
# minimum length specified in the first parameter. This method can't
|
20
|
+
# know whether the information is a password, a pin number or whatever
|
21
|
+
# so it takes the integer minimum size at its word.
|
22
|
+
#
|
23
|
+
# <b>Question 5 to App Config | What is the Secret?</b>
|
24
|
+
#
|
25
|
+
# The client may need to acquire the secret if the answer to question 4 indicates the need
|
26
|
+
# to instantiate the keys and encrypt the application's plaintext database. The application
|
27
|
+
# should facilitate communication of the secret via
|
28
|
+
#
|
29
|
+
# - an environment variable
|
30
|
+
# - the system clipboard (cleared after reading)
|
31
|
+
# - a file whose path is a command parameter
|
32
|
+
# - a file in a pre-agreed location
|
33
|
+
# - a file in the present directory (with a pre-agreed name)
|
34
|
+
# - a URL from a parameter or pre-agreed
|
35
|
+
# - the shell's secure password reader
|
36
|
+
# - the DConf / GConf or GSettings configuration stores
|
37
|
+
# - a REST API
|
38
|
+
# - password managers like LastPass, KeePassX or 1Pass
|
39
|
+
# - the Amazon KMS (Key Management Store)
|
40
|
+
# - vaults from Ansible, Terraform and Kubernetes
|
41
|
+
# - credential managers like GitSecrets and Credstash
|
42
|
+
#
|
43
|
+
# @param min_size [Integer] the minimum size of the collected secret
|
44
|
+
# whereby one (1) is the least we can expect. The maximum bound is
|
45
|
+
# not constrained here so will fall under what is allowed by the
|
46
|
+
# interface, be it a CLI, Rest API, Web UI or Mobile App.
|
47
|
+
#
|
48
|
+
# @param prompt_twice [Boolean] indicate whether the user should be
|
49
|
+
# prompted twice. If true the prompt_2 text must be provided and
|
50
|
+
# converse is also true. A true value asserts that both times the
|
51
|
+
# user enters the same (case sensitive) string.
|
52
|
+
#
|
53
|
+
# @param prompt_1 [String] the text (aide memoire) used to prompt the user
|
54
|
+
#
|
55
|
+
# @param prompt_2 [String] if the prompt twice boolean is TRUE, this
|
56
|
+
# second prompt (aide memoire) must be provided.
|
57
|
+
#
|
58
|
+
# @return [String] the collected string text ( watch out for non-ascii chars)
|
59
|
+
# @raise [ArgumentError] if the minimum size is less than one
|
60
|
+
def self.secret_text min_size, prompt_twice, prompt_1, prompt_2=nil
|
61
|
+
|
62
|
+
assert_min_size min_size
|
63
|
+
|
64
|
+
sleep(1)
|
65
|
+
puts "\n#{prompt_1} : "
|
66
|
+
first_secret = STDIN.noecho(&:gets).chomp
|
67
|
+
|
68
|
+
assert_input_text_size first_secret.length, min_size
|
69
|
+
return first_secret unless prompt_twice
|
70
|
+
|
71
|
+
sleep(1)
|
72
|
+
puts "\n#{prompt_2} : "
|
73
|
+
check_secret = STDIN.noecho(&:gets).chomp
|
74
|
+
|
75
|
+
assert_same_size_text first_secret, check_secret
|
76
|
+
|
77
|
+
return first_secret
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# --
|
83
|
+
# -- Raise an exception if asked to collect text that is less
|
84
|
+
# -- than 3 characters in length.
|
85
|
+
# --
|
86
|
+
def self.assert_min_size min_size
|
87
|
+
|
88
|
+
min_length_msg = "\n\nCrypts with 2 (or less) characters open up exploitable holes.\n\n"
|
89
|
+
raise ArgumentError.new min_length_msg if min_size < 3
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
# --
|
95
|
+
# -- Output an error message and then exit if the entered input
|
96
|
+
# -- text size does not meet the minimum requirements.
|
97
|
+
# --
|
98
|
+
def self.assert_input_text_size input_size, min_size
|
99
|
+
|
100
|
+
if( input_size < min_size )
|
101
|
+
|
102
|
+
puts
|
103
|
+
puts "Input is too short. Please enter at least #{min_size} characters."
|
104
|
+
puts
|
105
|
+
|
106
|
+
exit
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# --
|
114
|
+
# -- Assert that the text entered the second time is exactly (case sensitive)
|
115
|
+
# -- the same as the text entered the first time.
|
116
|
+
# --
|
117
|
+
def self.assert_same_size_text first_text, second_text
|
118
|
+
|
119
|
+
unless( first_text.eql? second_text )
|
120
|
+
|
121
|
+
puts
|
122
|
+
puts "Those two bits of text are not the same (in my book)!"
|
123
|
+
puts
|
124
|
+
|
125
|
+
exit
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module SafeDb
|
5
|
+
|
6
|
+
module ToolBelt
|
7
|
+
|
8
|
+
# CryptIO concentrates on injecting and ingesting crypt properties into and
|
9
|
+
# out of a key/value dictionary as well as injecting and ingesting cryptographic
|
10
|
+
# materials into and out of text files.
|
11
|
+
#
|
12
|
+
# == Cryptographic Properties
|
13
|
+
#
|
14
|
+
# A crypt properties dictionary acts as <b>output from every encryption event</b>
|
15
|
+
# and <b>input to every decryption event</b>. The most common properties include
|
16
|
+
#
|
17
|
+
# - the symmetric key used for the encryption and decryption
|
18
|
+
# - the iv (initialization vector) that adds another dimension of strength
|
19
|
+
# - authorization data that thwarts switch attacks by tying context to content
|
20
|
+
# - the cipher algorithm, its implementation and its encryption strength
|
21
|
+
# - the various glue strings that allow related ciphertext to occupy a file
|
22
|
+
#
|
23
|
+
# == Why Pad?
|
24
|
+
#
|
25
|
+
# Many ciphers (like Blowfish) constrains plain text lengths to multiples
|
26
|
+
# of 8 (or 16) and a common <b>right pad with spaces</b> strategy is employed
|
27
|
+
# as a workaround. safe does it diferently.
|
28
|
+
#
|
29
|
+
# == Why isn't Space Padding Used?
|
30
|
+
#
|
31
|
+
# If safe padded plaintext (ending in one or more spaces) with
|
32
|
+
# spaces, the decrypt phase (after right stripping spaces) would return
|
33
|
+
# plain text string <b>shorter than the original</b>.
|
34
|
+
#
|
35
|
+
# == Why Unusual Padding and Separators
|
36
|
+
#
|
37
|
+
# Why does safe employ unusual strings for padding and separation.
|
38
|
+
#
|
39
|
+
# The separator string must be unusual to make it unlikely for it to occur in any
|
40
|
+
# of the map's key value pairs nor indeed the chunk of text being glued. Were
|
41
|
+
# this to happen, the separate and reconstitute phase may not accurately return
|
42
|
+
# the same two entities we are employed to unite.
|
43
|
+
#
|
44
|
+
# == So How is Padding Done?
|
45
|
+
#
|
46
|
+
# Instead of single space padding - safe uses an unlikely 7 character
|
47
|
+
# padder which is repeated until the multiple is reached.
|
48
|
+
#
|
49
|
+
# <tt><-|@|-></tt>
|
50
|
+
#
|
51
|
+
# == So How is Padding Done?
|
52
|
+
#
|
53
|
+
# The <b>padder length must be a prime number</b> or infinite loops could occur.
|
54
|
+
#
|
55
|
+
# If the padder string is likely to occur in the plain text, another
|
56
|
+
# padder (or strategy) should and could be employed.
|
57
|
+
#
|
58
|
+
class CryptIO
|
59
|
+
|
60
|
+
|
61
|
+
# The safe text padder. See the class description for an analysis
|
62
|
+
# of the use of this type of padder.
|
63
|
+
TEXT_PADDER = "<-|@|->"
|
64
|
+
|
65
|
+
# An unusual string that glues together an encryption dictionary and
|
66
|
+
# a chunk of base64 encoded and encrypted ciphertext.
|
67
|
+
# The string must be unusual enough to ensure it does not occur within
|
68
|
+
# the dictionary metadata keys or values.
|
69
|
+
INNER_GLUE_STRING = "\n<-|@| < || safe inner crypt material axis || > |@|->\n\n"
|
70
|
+
|
71
|
+
# An unusual string that glues together the asymmetrically encrypted outer
|
72
|
+
# encryption key with the outer crypted text.
|
73
|
+
OUTER_GLUE_STRING = "\n<-|@| < || safe outer crypt material axis || > |@|->\n\n"
|
74
|
+
|
75
|
+
# Text header for key-value pairs hash map that will be serialized.
|
76
|
+
DICT_HEADER_NAME = "crypt.properties"
|
77
|
+
|
78
|
+
# Name for the class of cipher employed.
|
79
|
+
DICT_CIPHER_NAME = "cipher.class"
|
80
|
+
|
81
|
+
# Name for the {Base64} encoded symmetric (lock/unlock) crypt key.
|
82
|
+
DICT_CRYPT_KEY = "encryption.key"
|
83
|
+
|
84
|
+
# Dictionary name for the encryption iv (initialization vector)
|
85
|
+
DICT_CRYPT_IV = "encryption.iv"
|
86
|
+
|
87
|
+
# Dictionary name for the Base64 (urlsafe) encoded plaintext digest.
|
88
|
+
DICT_TEXT_DIGEST = "plaintext.digest"
|
89
|
+
|
90
|
+
|
91
|
+
# Serialize and then unify a hash map and a textual chunk using
|
92
|
+
# a known but unusual separator string in a manner that protects
|
93
|
+
# content integrity during the serialize / deserialize process.
|
94
|
+
#
|
95
|
+
# This crypt serialization uses a specific "inner glue" as the
|
96
|
+
# string that separates the serialized key/value dictionary and
|
97
|
+
# the encoded textual block.
|
98
|
+
#
|
99
|
+
# @param hash_map [String]
|
100
|
+
# this hash (dictionary) will be serialized into INI formatted text
|
101
|
+
# using behaviour from {Hash} and {IniFile}.
|
102
|
+
#
|
103
|
+
# @param text_chunk [String]
|
104
|
+
# the usually Base64 encrypted textual block to be glued at the
|
105
|
+
# bottom of the returned block.
|
106
|
+
#
|
107
|
+
# @return [String] serialized and glued together result of map plus text
|
108
|
+
#
|
109
|
+
# @raise [ArgumentError]
|
110
|
+
# if the dictionary hash_map is either nil or empty.
|
111
|
+
def self.inner_crypt_serialize hash_map, text_chunk
|
112
|
+
|
113
|
+
nil_or_empty_hash = hash_map.nil? || hash_map.empty?
|
114
|
+
raise ArgumentError, "Cannot serialize nil or empty properties." if nil_or_empty_hash
|
115
|
+
ini_map = IniFile.new
|
116
|
+
ini_map[ DICT_HEADER_NAME ] = hash_map
|
117
|
+
return ini_map.to_s + INNER_GLUE_STRING + text_chunk
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# Deserialize an safe formatted text which contains an
|
123
|
+
# encryption properties dictionary (serialized in INI format)
|
124
|
+
# and a Base64 encoded crypt block which is the subject of the
|
125
|
+
# encryption dictionary.
|
126
|
+
#
|
127
|
+
# The crypt serialization used a specific "inner glue" as the
|
128
|
+
# string that separates the serialized key/value dictionary and
|
129
|
+
# the encoded textual block. We now employ this glue to split
|
130
|
+
# the serialized dictionary from the textual block.
|
131
|
+
#
|
132
|
+
# @param hash_map [String]
|
133
|
+
# send an instantiated hash (dictionary) which will be populated
|
134
|
+
# by this deserialize operation. The dictionary propeties can
|
135
|
+
# then be used to decrypt the returned ciphertext.
|
136
|
+
#
|
137
|
+
# @param text_block [String]
|
138
|
+
# the first of a two-part amalgamation is a hash (dictionary) in
|
139
|
+
# INI serialized form and the second part is a Base64 encrypted
|
140
|
+
# textual block.
|
141
|
+
#
|
142
|
+
# The deserialized key/value pairs will be stuffed into the
|
143
|
+
# non nil (usually empty) hash map in the first parameter and
|
144
|
+
# the block (in the 2nd part) will be Base64 decoded and
|
145
|
+
# returned by this method.
|
146
|
+
#
|
147
|
+
# @return [String]
|
148
|
+
# The encoded block in the 2nd part of the 2nd parameter will be
|
149
|
+
# Base64 decoded and returned.
|
150
|
+
#
|
151
|
+
# @raise [ArgumentError]
|
152
|
+
# if the dictionary hash_map is either nil or empty. Also if
|
153
|
+
# the inner glue tying the two parts together is missing an
|
154
|
+
# {ArgumentError} will be thrown.
|
155
|
+
def self.inner_crypt_deserialize hash_map, text_block
|
156
|
+
|
157
|
+
raise ArgumentError, "Cannot populate a nil hash map." if hash_map.nil?
|
158
|
+
assert_contains_glue text_block, INNER_GLUE_STRING
|
159
|
+
|
160
|
+
serialized_map = text_block.split(INNER_GLUE_STRING).first.strip
|
161
|
+
encoded64_text = text_block.split(INNER_GLUE_STRING).last.strip
|
162
|
+
ini_props_hash = IniFile.new( :content => serialized_map )
|
163
|
+
encrypt_values = ini_props_hash[DICT_HEADER_NAME]
|
164
|
+
|
165
|
+
hash_map.merge!( encrypt_values )
|
166
|
+
return Base64.decode64( encoded64_text )
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
# Using an outer divider (glue) - attach the asymmetrically encrypted outer
|
172
|
+
# encryption key with the outer encrypted text.
|
173
|
+
#
|
174
|
+
# @param crypt_material_x [String] asymmetrically encrypted (encoded) outer encryption key
|
175
|
+
# @param crypt_material_y [String] symmetrically encrypted inner metadata and payload crypt
|
176
|
+
#
|
177
|
+
# @return [String] concatenated result of the two crypt materials and divider string
|
178
|
+
def self.outer_crypt_serialize crypt_material_x, crypt_material_y
|
179
|
+
return crypt_material_x + OUTER_GLUE_STRING + crypt_material_y
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
# Given two blocks of text that were bounded together by the
|
184
|
+
# {self.outer_crypt_serialize} method we must return either the
|
185
|
+
# first block (true) or the second (false).
|
186
|
+
#
|
187
|
+
# @param crypt_material [String]
|
188
|
+
# large block of text in two parts that is divided by the
|
189
|
+
# outer glue string.
|
190
|
+
#
|
191
|
+
# @param top_block [Boolean]
|
192
|
+
# if true the top (of the two) blocks will be returned
|
193
|
+
# otherwise the bottom block is returned.
|
194
|
+
#
|
195
|
+
# @return [String] either the first or second block of text
|
196
|
+
#
|
197
|
+
# @raise [ArgumentError]
|
198
|
+
# If the outer glue string tying the two parts together is
|
199
|
+
# missing an {ArgumentError} will be thrown.
|
200
|
+
def self.outer_crypt_deserialize os_material, top_block
|
201
|
+
|
202
|
+
assert_contains_glue os_material, OUTER_GLUE_STRING
|
203
|
+
return os_material.split(OUTER_GLUE_STRING).first.strip if top_block
|
204
|
+
return os_material.split(OUTER_GLUE_STRING).last.strip
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def self.assert_contains_glue os_crypted_block, glue_string
|
212
|
+
|
213
|
+
no_glue_msg = "\nGlue string not in safe cipher block.\n#{glue_string}\n"
|
214
|
+
raise ArgumentError, no_glue_msg unless os_crypted_block.include? glue_string
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
module ToolBelt
|
6
|
+
|
7
|
+
|
8
|
+
require 'securerandom'
|
9
|
+
|
10
|
+
# This class will be refactored into an interface implemented by a set
|
11
|
+
# of plugins that will capture sensitive information from users from an
|
12
|
+
# Ubuntu, Windows, RHEL, CoreOS, iOS or CentOS command line interface.
|
13
|
+
#
|
14
|
+
# An equivalent REST API will also be available for bringing in sensitive
|
15
|
+
# information in the most secure (but simple) manner.
|
16
|
+
class Engineer
|
17
|
+
|
18
|
+
|
19
|
+
# --
|
20
|
+
# -- Get a viable machine password taking into account the human
|
21
|
+
# -- password length and the specified mix_ratio.
|
22
|
+
# --
|
23
|
+
# -- machine password length = human password length * mix_ratio - 1
|
24
|
+
# --
|
25
|
+
def self.machine_key human_password_length, mix_ratio
|
26
|
+
|
27
|
+
machine_raw_secret = strong_key( human_password_length * ( mix_ratio + 1) )
|
28
|
+
return machine_raw_secret[ 0..( human_password_length * mix_ratio - 1 ) ]
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# --
|
34
|
+
# -- Engineer a raw password that is similar (approximate) in
|
35
|
+
# -- length to the integer parameter.
|
36
|
+
# --
|
37
|
+
def self.strong_key approx_length
|
38
|
+
|
39
|
+
non_alphanum = SecureRandom.urlsafe_base64(approx_length);
|
40
|
+
return non_alphanum.delete("-_")
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Amalgamate the parameter passwords using a specific mix ratio. This method
|
46
|
+
# produces cryptographically stronger secrets than algorithms that simply
|
47
|
+
# concatenate two string keys together. If knowledge of one key were gained, this
|
48
|
+
# amalgamation algorithm still provides extremely strong protection even when
|
49
|
+
# one of the keys has a single digit length.
|
50
|
+
#
|
51
|
+
# This +length constraint formula+ binds the two input strings together with
|
52
|
+
# the integer mix ratio.
|
53
|
+
#
|
54
|
+
# <tt>machine password length = human password length * mix_ratio - 1</tt>
|
55
|
+
#
|
56
|
+
# @param human_password [String] the first password (shorter one) to amalgamate
|
57
|
+
# @param machine_password [String] the second password (longer one) to amalgamate
|
58
|
+
# @param mix_ratio [Fixnum] the mix ratio that must be respected by the
|
59
|
+
# previous two parameters.
|
60
|
+
# @return [String] an amalgamated (reproducible) union of the 2 parameter passwords
|
61
|
+
#
|
62
|
+
# @raise [ArgumentError] if the length constraint assertion does not hold true
|
63
|
+
def self.get_amalgam_password human_password, machine_password, mix_ratio
|
64
|
+
|
65
|
+
size_error_msg = "Human pass length times mix_ratio must equal machine pass length."
|
66
|
+
lengths_are_perfect = human_password.length * mix_ratio == machine_password.length
|
67
|
+
raise ArgumentError.new size_error_msg unless lengths_are_perfect
|
68
|
+
|
69
|
+
machine_passwd_chunk = 0
|
70
|
+
amalgam_passwd_index = 0
|
71
|
+
amalgamated_password = ""
|
72
|
+
|
73
|
+
human_password.each_char do |passwd_char|
|
74
|
+
|
75
|
+
amalgamated_password[amalgam_passwd_index] = passwd_char
|
76
|
+
amalgam_passwd_index += 1
|
77
|
+
|
78
|
+
for i in 0..(mix_ratio-1) do
|
79
|
+
machine_pass_index = machine_passwd_chunk * mix_ratio + i
|
80
|
+
amalgamated_password[amalgam_passwd_index] = machine_password[machine_pass_index]
|
81
|
+
amalgam_passwd_index += 1
|
82
|
+
end
|
83
|
+
|
84
|
+
machine_passwd_chunk += 1
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
return amalgamated_password
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
end
|