safedb 0.01.0001

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.yardopts +3 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +21 -0
  6. data/README.md +793 -0
  7. data/Rakefile +16 -0
  8. data/bin/safe +5 -0
  9. data/lib/configs/README.md +58 -0
  10. data/lib/extension/array.rb +162 -0
  11. data/lib/extension/dir.rb +35 -0
  12. data/lib/extension/file.rb +123 -0
  13. data/lib/extension/hash.rb +33 -0
  14. data/lib/extension/string.rb +572 -0
  15. data/lib/factbase/facts.safedb.net.ini +38 -0
  16. data/lib/interprete.rb +462 -0
  17. data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
  18. data/lib/keytools/kdf.api.rb +243 -0
  19. data/lib/keytools/kdf.bcrypt.rb +265 -0
  20. data/lib/keytools/kdf.pbkdf2.rb +262 -0
  21. data/lib/keytools/kdf.scrypt.rb +190 -0
  22. data/lib/keytools/key.64.rb +326 -0
  23. data/lib/keytools/key.algo.rb +109 -0
  24. data/lib/keytools/key.api.rb +1391 -0
  25. data/lib/keytools/key.db.rb +330 -0
  26. data/lib/keytools/key.docs.rb +195 -0
  27. data/lib/keytools/key.error.rb +110 -0
  28. data/lib/keytools/key.id.rb +271 -0
  29. data/lib/keytools/key.ident.rb +243 -0
  30. data/lib/keytools/key.iv.rb +107 -0
  31. data/lib/keytools/key.local.rb +259 -0
  32. data/lib/keytools/key.now.rb +402 -0
  33. data/lib/keytools/key.pair.rb +259 -0
  34. data/lib/keytools/key.pass.rb +120 -0
  35. data/lib/keytools/key.rb +585 -0
  36. data/lib/logging/gem.logging.rb +132 -0
  37. data/lib/modules/README.md +43 -0
  38. data/lib/modules/cryptology/aes-256.rb +154 -0
  39. data/lib/modules/cryptology/amalgam.rb +70 -0
  40. data/lib/modules/cryptology/blowfish.rb +130 -0
  41. data/lib/modules/cryptology/cipher.rb +207 -0
  42. data/lib/modules/cryptology/collect.rb +138 -0
  43. data/lib/modules/cryptology/crypt.io.rb +225 -0
  44. data/lib/modules/cryptology/engineer.rb +99 -0
  45. data/lib/modules/mappers/dictionary.rb +288 -0
  46. data/lib/modules/storage/coldstore.rb +186 -0
  47. data/lib/modules/storage/git.store.rb +399 -0
  48. data/lib/session/fact.finder.rb +334 -0
  49. data/lib/session/require.gem.rb +112 -0
  50. data/lib/session/time.stamp.rb +340 -0
  51. data/lib/session/user.home.rb +49 -0
  52. data/lib/usecase/cmd.rb +487 -0
  53. data/lib/usecase/config/README.md +57 -0
  54. data/lib/usecase/docker/README.md +146 -0
  55. data/lib/usecase/docker/docker.rb +49 -0
  56. data/lib/usecase/edit/README.md +43 -0
  57. data/lib/usecase/edit/delete.rb +46 -0
  58. data/lib/usecase/export.rb +40 -0
  59. data/lib/usecase/files/README.md +37 -0
  60. data/lib/usecase/files/eject.rb +56 -0
  61. data/lib/usecase/files/file_me.rb +78 -0
  62. data/lib/usecase/files/read.rb +169 -0
  63. data/lib/usecase/files/write.rb +89 -0
  64. data/lib/usecase/goto.rb +57 -0
  65. data/lib/usecase/id.rb +36 -0
  66. data/lib/usecase/import.rb +157 -0
  67. data/lib/usecase/init.rb +63 -0
  68. data/lib/usecase/jenkins/README.md +146 -0
  69. data/lib/usecase/jenkins/jenkins.rb +208 -0
  70. data/lib/usecase/login.rb +71 -0
  71. data/lib/usecase/logout.rb +28 -0
  72. data/lib/usecase/open.rb +71 -0
  73. data/lib/usecase/print.rb +40 -0
  74. data/lib/usecase/put.rb +81 -0
  75. data/lib/usecase/set.rb +44 -0
  76. data/lib/usecase/show.rb +138 -0
  77. data/lib/usecase/terraform/README.md +91 -0
  78. data/lib/usecase/terraform/terraform.rb +121 -0
  79. data/lib/usecase/token.rb +35 -0
  80. data/lib/usecase/update/README.md +55 -0
  81. data/lib/usecase/update/rename.rb +180 -0
  82. data/lib/usecase/use.rb +41 -0
  83. data/lib/usecase/verse.rb +20 -0
  84. data/lib/usecase/view.rb +71 -0
  85. data/lib/usecase/vpn/README.md +150 -0
  86. data/lib/usecase/vpn/vpn.ini +31 -0
  87. data/lib/usecase/vpn/vpn.rb +54 -0
  88. data/lib/version.rb +3 -0
  89. data/safedb.gemspec +34 -0
  90. 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> &raquo; 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