safedb 0.3.1011 → 0.4.1002
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +56 -19
- data/README.md +15 -15
- data/Rakefile +7 -0
- data/bin/safe +2 -2
- data/lib/{interprete.rb → cli.rb} +168 -121
- data/lib/controller/admin/README.md +47 -0
- data/lib/controller/admin/access.rb +47 -0
- data/lib/controller/admin/checkin.rb +83 -0
- data/lib/controller/admin/checkout.rb +57 -0
- data/lib/controller/admin/diff.rb +75 -0
- data/lib/{usecase → controller/admin}/export.rb +15 -14
- data/lib/controller/admin/goto.rb +52 -0
- data/lib/controller/admin/import.rb +54 -0
- data/lib/controller/admin/init.rb +113 -0
- data/lib/controller/admin/login.rb +88 -0
- data/lib/{usecase → controller/admin}/logout.rb +0 -0
- data/lib/controller/admin/open.rb +39 -0
- data/lib/{usecase → controller/admin}/token.rb +2 -2
- data/lib/controller/admin/tree.md +54 -0
- data/lib/{usecase → controller/admin}/use.rb +0 -0
- data/lib/controller/admin/view.rb +61 -0
- data/lib/{usecase → controller/api}/docker/README.md +0 -0
- data/lib/{usecase → controller/api}/docker/docker.rb +1 -1
- data/lib/{usecase → controller/api}/jenkins/README.md +0 -0
- data/lib/{usecase → controller/api}/jenkins/jenkins.rb +1 -1
- data/lib/{usecase → controller/api}/terraform/README.md +1 -1
- data/lib/{usecase → controller/api}/terraform/terraform.rb +1 -1
- data/lib/{usecase → controller/api}/vpn/README.md +1 -1
- data/lib/{usecase → controller/api}/vpn/vpn.ini +0 -0
- data/lib/{usecase → controller/api}/vpn/vpn.rb +0 -0
- data/lib/{usecase → controller}/config/README.md +0 -0
- data/lib/{usecase → controller}/edit/README.md +0 -0
- data/lib/controller/edit/editverse.rb +48 -0
- data/lib/controller/edit/put.rb +35 -0
- data/lib/controller/edit/remove.rb +29 -0
- data/lib/{usecase/update/README.md → controller/edit/rename.md} +0 -0
- data/lib/{usecase → controller}/files/README.md +1 -1
- data/lib/controller/files/read.rb +36 -0
- data/lib/{usecase/files/eject.rb → controller/files/write.rb} +15 -20
- data/lib/{usecase → controller}/id.rb +0 -0
- data/lib/controller/query/print.rb +26 -0
- data/lib/controller/query/queryverse.rb +39 -0
- data/lib/controller/query/show.rb +50 -0
- data/lib/{session/require.gem.rb → controller/requirer.rb} +13 -9
- data/lib/{usecase → controller}/set.rb +4 -4
- data/lib/controller/usecase.rb +244 -0
- data/lib/{usecase → controller}/verse.rb +0 -0
- data/lib/{usecase → controller}/visit/README.md +0 -0
- data/lib/{usecase → controller}/visit/visit.rb +0 -0
- data/lib/factbase/facts.safedb.net.ini +7 -7
- data/lib/{keytools/key.docs.rb → model/README.md} +102 -66
- data/lib/model/book.rb +484 -0
- data/lib/model/branch.rb +48 -0
- data/lib/model/checkin.feature +33 -0
- data/lib/{configs/README.md → model/configs.md} +4 -4
- data/lib/model/content.rb +214 -0
- data/lib/model/indices.rb +132 -0
- data/lib/model/safe_tree.rb +51 -0
- data/lib/model/state.inspect.rb +221 -0
- data/lib/model/state.migrate.rb +334 -0
- data/lib/model/text_chunk.rb +68 -0
- data/lib/{extension → utils/extend}/array.rb +0 -0
- data/lib/{extension → utils/extend}/dir.rb +0 -0
- data/lib/{extension → utils/extend}/file.rb +0 -0
- data/lib/utils/extend/hash.rb +76 -0
- data/lib/{extension → utils/extend}/string.rb +6 -6
- data/lib/{session/fact.finder.rb → utils/facts/fact.rb} +0 -0
- data/lib/utils/identity/identifier.rb +356 -0
- data/lib/{keytools/key.ident.rb → utils/identity/machine.id.rb} +67 -4
- data/lib/utils/inspect/inspector.rb +81 -0
- data/lib/{keytools/kdf.bcrypt.rb → utils/kdfs/bcrypt.rb} +0 -0
- data/lib/{keytools → utils/kdfs}/kdf.api.rb +16 -16
- data/lib/{keytools/key.local.rb → utils/kdfs/kdfs.rb} +40 -40
- data/lib/{keytools/kdf.pbkdf2.rb → utils/kdfs/pbkdf2.rb} +0 -0
- data/lib/{keytools/kdf.scrypt.rb → utils/kdfs/scrypt.rb} +0 -0
- data/lib/{keytools → utils}/key.error.rb +2 -2
- data/lib/{keytools → utils}/key.pass.rb +2 -2
- data/lib/{keytools → utils/keys}/key.64.rb +0 -0
- data/lib/{keytools → utils/keys}/key.rb +6 -2
- data/lib/{keytools/key.iv.rb → utils/keys/random.iv.rb} +0 -0
- data/lib/{logging/gem.logging.rb → utils/logs/logger.rb} +6 -5
- data/lib/{keytools/key.pair.rb → utils/store/datamap.rb} +48 -30
- data/lib/{keytools/key.db.rb → utils/store/datastore.rb} +38 -104
- data/lib/utils/store/merge-boys-school.json +40 -0
- data/lib/utils/store/merge-girls-school.json +48 -0
- data/lib/utils/store/merge-merged-data.json +56 -0
- data/lib/utils/store/struct.rb +75 -0
- data/lib/utils/store/test-commands.sh +24 -0
- data/lib/{keytools/key.now.rb → utils/time/timestamp.rb} +32 -21
- data/lib/version.rb +1 -1
- metadata +86 -73
- data/lib/extension/hash.rb +0 -33
- data/lib/keytools/key.algo.rb +0 -109
- data/lib/keytools/key.api.rb +0 -1326
- data/lib/keytools/key.id.rb +0 -322
- data/lib/modules/cryptology/amalgam.rb +0 -70
- data/lib/modules/cryptology/engineer.rb +0 -99
- data/lib/modules/mappers/dictionary.rb +0 -288
- data/lib/session/time.stamp.rb +0 -340
- data/lib/session/user.home.rb +0 -49
- data/lib/usecase/cmd.rb +0 -471
- data/lib/usecase/edit/delete.rb +0 -46
- data/lib/usecase/files/file_me.rb +0 -78
- data/lib/usecase/files/read.rb +0 -169
- data/lib/usecase/files/write.rb +0 -89
- data/lib/usecase/goto.rb +0 -57
- data/lib/usecase/import.rb +0 -157
- data/lib/usecase/init.rb +0 -61
- data/lib/usecase/login.rb +0 -72
- data/lib/usecase/open.rb +0 -71
- data/lib/usecase/print.rb +0 -40
- data/lib/usecase/put.rb +0 -81
- data/lib/usecase/show.rb +0 -138
- data/lib/usecase/update/rename.rb +0 -180
- data/lib/usecase/view.rb +0 -71
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
module SafeDb
|
4
|
+
|
5
|
+
# These textual chunks will hold information blocks that are printed into files
|
6
|
+
# or printed out for user consumption on standard out.
|
7
|
+
class TextChunk
|
8
|
+
|
9
|
+
# Construct the header for the ciphertext content files written out
|
10
|
+
# onto the filesystem including information such as the application version
|
11
|
+
# and human readable time.
|
12
|
+
#
|
13
|
+
# @param book_id [String] the identifier of the book that is being queried and edited
|
14
|
+
# @return [String] a textual block that can be printed at the top of crypt files
|
15
|
+
def self.crypt_header( book_id )
|
16
|
+
|
17
|
+
<<-CRYPT_HEADER
|
18
|
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
19
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
20
|
+
#{Indices::SAFE_URL_NAME} ciphertext block
|
21
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
22
|
+
Safe Book Id := #{book_id}
|
23
|
+
Time Created := #{KeyNow.readable()}
|
24
|
+
Safe Version := #{Indices::SAFE_VERSION_STRING}
|
25
|
+
Safe Website := #{Indices::SAFE_GEM_WEBSITE}
|
26
|
+
RubyGems.org := https://rubygems.org/gems/safedb
|
27
|
+
CRYPT_HEADER
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# Print a message stating that the book has not been opened at any
|
33
|
+
# chapter and verse location.
|
34
|
+
def self.not_open_message()
|
35
|
+
|
36
|
+
<<-UNOPENED_MESSAGE
|
37
|
+
|
38
|
+
Please open a chapter and verse to put, edit or query data.
|
39
|
+
|
40
|
+
#{Indices::COMMANDER} open contacts monica
|
41
|
+
|
42
|
+
then add monica's contact details
|
43
|
+
|
44
|
+
#{Indices::COMMANDER} put email monica.lewinsky@gmail.com
|
45
|
+
#{Indices::COMMANDER} put phone +1-357-246-8901
|
46
|
+
#{Indices::COMMANDER} put twitter @monica_x
|
47
|
+
#{Indices::COMMANDER} put skype.id 6363430539
|
48
|
+
#{Indices::COMMANDER} put birthday \"1st April 1978\"
|
49
|
+
|
50
|
+
also hilary's
|
51
|
+
|
52
|
+
#{Indices::COMMANDER} open contacts hilary
|
53
|
+
#{Indices::COMMANDER} put email hilary@whitehouse.gov
|
54
|
+
|
55
|
+
then save the changes to your book and logout."
|
56
|
+
|
57
|
+
#{Indices::COMMANDER} commit"
|
58
|
+
#{Indices::COMMANDER} logout"
|
59
|
+
|
60
|
+
UNOPENED_MESSAGE
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
# Reopen the core ruby Hash class and add the below methods to it.
|
4
|
+
class Hash
|
5
|
+
|
6
|
+
|
7
|
+
# Recursively merge (deep merge) this {Hash} data structure with another
|
8
|
+
# given in the parameter.
|
9
|
+
#
|
10
|
+
# The core ruby {Hash.merge()} instance method only performs first level
|
11
|
+
# merges which is not ideal in order to intelligently merge a deep tree.
|
12
|
+
#
|
13
|
+
# <tt>Merge Behaviour</tt>
|
14
|
+
#
|
15
|
+
# Currently this behaviour works only for Hash data structures that have
|
16
|
+
# string keys (only) and either string or Hash values. It is interesting
|
17
|
+
# only when duplicate keys are encountered at the same level. If the
|
18
|
+
# duplicate key's value is
|
19
|
+
#
|
20
|
+
# 1. a String - the incoming value is rejected and logged
|
21
|
+
# 2. a Hash - the method is recalled to recursively merge
|
22
|
+
#
|
23
|
+
# @param merge_me [Hash] the incoming hash data structure to merge
|
24
|
+
def merge_recursively!( merge_me )
|
25
|
+
|
26
|
+
self.merge!( merge_me ) do | key, value_1, value_2 |
|
27
|
+
|
28
|
+
is_mergeable = value_1.kind_of?( Hash ) && value_2.kind_of?( Hash )
|
29
|
+
are_both_str = value_1.kind_of?( String ) && value_2.kind_of?( String )
|
30
|
+
not_the_same = are_both_str && ( value_1 != value_2 )
|
31
|
+
|
32
|
+
reject_message( key, value_1, value_2 ) if not_the_same
|
33
|
+
value_1.merge_recursively!( value_2 ) if is_mergeable
|
34
|
+
value_1
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# Print a line to standard out stating that a same key (duplicate) map
|
42
|
+
# entry has been encountered and here is the one we are rejecting.
|
43
|
+
def reject_message( key, value_1, value_2 )
|
44
|
+
puts "Refused to allow { #{key} => #{value_2} } to overwrite { #{key} => #{value_1} }"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# This method adds (logging its own contents) behaviour to
|
49
|
+
# the standard library {Hash} class.
|
50
|
+
#
|
51
|
+
# @note This behaviour does not consider that SECRETS may be inside
|
52
|
+
# the key value maps - it logs itself without a care in the world.
|
53
|
+
# This functionality must be included if this behaviourr is used by
|
54
|
+
# any cryptography classes.
|
55
|
+
#
|
56
|
+
# The <tt>DEBUG</tt> log level is used for logging. To change this
|
57
|
+
# create a new parameterized method.
|
58
|
+
def log_contents
|
59
|
+
|
60
|
+
log.debug(x) { "# --- ----------------------------------------------" }
|
61
|
+
log.debug(x) { "# --- Map has [#{self.length}] key/value pairs." }
|
62
|
+
log.debug(x) { "# --- ----------------------------------------------" }
|
63
|
+
|
64
|
+
self.each do |the_key, the_value|
|
65
|
+
|
66
|
+
padded_key = sprintf '%-33s', the_key
|
67
|
+
log.debug(x) { "# --- #{padded_key} => #{the_value}" }
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
log.debug(x) { "# --- ----------------------------------------------" }
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
end
|
@@ -28,7 +28,7 @@ class String
|
|
28
28
|
# @example
|
29
29
|
# cipher_text = "Hello crypt world".encrypt_block_encode "ABC123XYZ"
|
30
30
|
# original_txt = cipher_text.block_decode_decrypt "ABC123XYZ"
|
31
|
-
#
|
31
|
+
# (print out) original_txt # "Hello crypt world"
|
32
32
|
#
|
33
33
|
# @param crypt_key [String]
|
34
34
|
# a strong long encryption key that is used to encrypt this string before
|
@@ -47,7 +47,7 @@ class String
|
|
47
47
|
# @example
|
48
48
|
# cipher_text = "Hello crypt world".decrypt_block_encode "ABC123XYZ"
|
49
49
|
# original_txt = cipher_text.block_decode_decrypt "ABC123XYZ"
|
50
|
-
#
|
50
|
+
# (print out) original_txt # "Hello crypt world"
|
51
51
|
#
|
52
52
|
# @param crypt_key [String]
|
53
53
|
# a strong long decryption key that is used to decrypt this string after
|
@@ -76,7 +76,7 @@ class String
|
|
76
76
|
# @example
|
77
77
|
# cipher_text = "Hello @:==:@ world".encrypt_url_encode "ABC123XYZ"
|
78
78
|
# original_txt = cipher_text.url_decode_decrypt "ABC123XYZ"
|
79
|
-
#
|
79
|
+
# (print out) original_txt # "Hello @:==:@ world"
|
80
80
|
#
|
81
81
|
# @param crypt_key [String]
|
82
82
|
# a strong long encryption key that is used to encrypt this string before
|
@@ -123,7 +123,7 @@ class String
|
|
123
123
|
# @example
|
124
124
|
# cipher_text = "Hello @:==:@ world".encrypt_url_encode "ABC123XYZ"
|
125
125
|
# original_txt = cipher_text.url_decode_decrypt "ABC123XYZ"
|
126
|
-
#
|
126
|
+
# (print out) original_txt # "Hello @:==:@ world"
|
127
127
|
#
|
128
128
|
# @param crypt_key [String]
|
129
129
|
# a strong long decryption key that is used to decrypt this string after
|
@@ -229,9 +229,9 @@ class String
|
|
229
229
|
raise ArgumentError, "Begin delimiter is NIL or empty." if this_delimiter.nil? || this_delimiter.empty?
|
230
230
|
raise ArgumentError, "End delimiter is NIL or empty." if that_delimiter.nil? || that_delimiter.empty?
|
231
231
|
|
232
|
-
scanner_1 = StringScanner.new self
|
232
|
+
scanner_1 = ::StringScanner.new self
|
233
233
|
scanner_1.scan_until /#{this_delimiter}/
|
234
|
-
scanner_2 = StringScanner.new scanner_1.post_match
|
234
|
+
scanner_2 = ::StringScanner.new scanner_1.post_match
|
235
235
|
scanner_2.scan_until /#{that_delimiter}/
|
236
236
|
|
237
237
|
in_between_text = scanner_2.pre_match.strip
|
File without changes
|
@@ -0,0 +1,356 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
|
5
|
+
module SafeDb
|
6
|
+
|
7
|
+
|
8
|
+
# This class derives <b>non secret but unique identifiers</b> based on different
|
9
|
+
# combinations of the <b>application, shell and machine (compute element)</b>
|
10
|
+
# references.
|
11
|
+
#
|
12
|
+
# == Identifier Are Not Secrets
|
13
|
+
#
|
14
|
+
# <b>And their starting values are retrievable</b>
|
15
|
+
#
|
16
|
+
# Note that the principle and practise of <b>identifiers is not about keeping secrets</b>.
|
17
|
+
# An identifier can easily give up its starting value/s if and when brute force is
|
18
|
+
# applied. The properties of a good iidentifier (ID) are
|
19
|
+
#
|
20
|
+
# - non repeatability (also known as uniqueness)
|
21
|
+
# - non predictability (of the next identifier)
|
22
|
+
# - containing alphanumerics (for file/folder/url names)
|
23
|
+
# - human readable (hence hyphens and separators)
|
24
|
+
# - non offensive (no swear words popping out)
|
25
|
+
#
|
26
|
+
# == Story | Identifiers Speak Volumes
|
27
|
+
#
|
28
|
+
# I told a friend what the turnover of his company was and how many clients he had.
|
29
|
+
# He was shocked and wanted to know how I had gleened this information.
|
30
|
+
#
|
31
|
+
# The invoices he sent me (a year apart). Both his invoice IDs (identifiers) and his
|
32
|
+
# user IDs where integers that counted up. So I could determine how many new clients
|
33
|
+
# he had in the past year, how many clients he had when I got the invoice, and I
|
34
|
+
# determined the turnover by guesstimating the average invoice amount.
|
35
|
+
#
|
36
|
+
# Many successful website attacks are owed to a predictable customer ID or a counter
|
37
|
+
# type branch ID within the cookies.
|
38
|
+
#
|
39
|
+
# == Good Identifiers Need Volumes
|
40
|
+
#
|
41
|
+
# IDs are not secrets - but even so, a large number of properties are required
|
42
|
+
# to produce a high quality ID.
|
43
|
+
#
|
44
|
+
class Identifier
|
45
|
+
|
46
|
+
# The ergonomic list of 24 characters highly suited for use within
|
47
|
+
# human readable and likable identifier strings.
|
48
|
+
ERGONOMIC_LIST = [
|
49
|
+
"b", "c", "d", "e", "h", "k",
|
50
|
+
"m", "n", "r", "t", "v", "w",
|
51
|
+
"x", "z", "0", "1", "2", "3",
|
52
|
+
"4", "5", "6", "7", "8", "9"
|
53
|
+
]
|
54
|
+
|
55
|
+
|
56
|
+
# The identity chunk length is set at four (4) which means each of the
|
57
|
+
# fabricated identifiers comprises of four character segments divided by
|
58
|
+
# hyphens. Only the <b>62 alpha-numerics ( a-z, A-Z and 0-9 )</b> will
|
59
|
+
# appear within identifiers - which maintains simplicity and provides an
|
60
|
+
# opportunity to re-iterate that <b>identifiers</b> are designed to be
|
61
|
+
# <b>unpredictable</b>, but <b>not secret</b>.
|
62
|
+
IDENTITY_CHUNK_LENGTH = 4
|
63
|
+
|
64
|
+
|
65
|
+
# A hyphen is the chosen character for dividing the identifier strings
|
66
|
+
# into chunks of four (4) as per the {IDENTITY_CHUNK_LENGTH} constant.
|
67
|
+
SEGMENT_CHAR = "-"
|
68
|
+
|
69
|
+
ID_TRI_CHUNK_LEN = IDENTITY_CHUNK_LENGTH * 3
|
70
|
+
ID_TRI_TOTAL_LEN = ID_TRI_CHUNK_LEN + 2
|
71
|
+
|
72
|
+
|
73
|
+
# Get an ergonomic identifier that is a <b>one to one mapping</b> for the
|
74
|
+
# parameter string. In as far as is possible, two different input strings
|
75
|
+
# should never produce the same output identifier, nor should one input
|
76
|
+
# string be ambiguously mapped to two output identifiers.
|
77
|
+
#
|
78
|
+
# This algorithm must be brute force tested to verify the above assertions.
|
79
|
+
#
|
80
|
+
# <tt>The 24 Ergonomic Characters</tt>
|
81
|
+
#
|
82
|
+
# The returned identifier is ergonomic in that its characters come from a
|
83
|
+
# pool of 24 of the most suitable ID characters - pleasant to see, easy to
|
84
|
+
# digest and simple to convey.
|
85
|
+
#
|
86
|
+
# <b>How to Derive the Ergonomic Identifier</b>
|
87
|
+
#
|
88
|
+
# We pass the parameter string through a SHA512 digest algorithm and truncate
|
89
|
+
# the final 2 binary digits because 510 is a multiple of six and perfect for
|
90
|
+
# the transformation to a Base64 string.
|
91
|
+
#
|
92
|
+
# The Base64 transform gives us 85 characters from which we remove any non
|
93
|
+
# alphanumerics. We repeat all the above again with the parameter reversed
|
94
|
+
# and append the two resultants together.
|
95
|
+
#
|
96
|
+
# This harvests roughly 160 characters from which we downcase and walk
|
97
|
+
# through picking out a selection of just 24 ergonomic characters.
|
98
|
+
#
|
99
|
+
# @param source [String]
|
100
|
+
# the source string whose characters we digest and filter to produce a
|
101
|
+
# high quality, pleasing ergonomic identifier
|
102
|
+
#
|
103
|
+
# Before processing any leading or trailing whitespace is removed from
|
104
|
+
# the input string.
|
105
|
+
#
|
106
|
+
# @param id_length [Numeric]
|
107
|
+
# the number of identifier characters to return. This parameter must be
|
108
|
+
# even and divisible by 3 in case it needs to be split (for readability)
|
109
|
+
# into two or three segments.
|
110
|
+
#
|
111
|
+
# There is a logical maximum above which it is foolish to venture. The
|
112
|
+
# max is about two-thirds of a sixth of a thousand characters which is
|
113
|
+
# slightly over 100.
|
114
|
+
#
|
115
|
+
# @return [String]
|
116
|
+
# An identifier that is guaranteed to be the same whenever the same
|
117
|
+
# input string is provided.
|
118
|
+
#
|
119
|
+
# This algorithms quality is predicated on the premise that two different
|
120
|
+
# input strings should never produce the same output, nor should one input
|
121
|
+
# string be ambiguously mapped to two output identifiers.
|
122
|
+
#
|
123
|
+
# The default behaviour is to split the output identifier into 2 segments
|
124
|
+
# separated by a hyphen.
|
125
|
+
def self.derive_ergonomic_identifier( source, id_length )
|
126
|
+
|
127
|
+
abort "The source string cannot be nil or empty." if source.nil?() or source.empty?()
|
128
|
+
abort "The source cannot consist only of whitespace." if source.strip().empty?()
|
129
|
+
abort "The ID length must not be less than 2." unless id_length > 1
|
130
|
+
abort "The ID length must be a multiple of 2." unless id_length % 2 == 0
|
131
|
+
abort "Prudent identifiers do not exceed 80 characters." unless id_length < 80
|
132
|
+
|
133
|
+
digested_bits = Key.from_binary( Digest::SHA512.digest( source.strip() ) ).to_s +
|
134
|
+
Key.from_binary( Digest::SHA512.digest( source.strip().reverse() ) ).to_s
|
135
|
+
|
136
|
+
digest_string = Key64.from_bits( digested_bits[ 0 .. ( 1020 - 1 ) ] ).to_alphanumeric
|
137
|
+
filtered_digest = ergonomic_filter( digest_string, id_length )
|
138
|
+
return filtered_digest.insert( id_length/2, SEGMENT_CHAR )
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
# This ergonomic filter produces a pleasing readable identifier that is
|
144
|
+
# down cased and does not contain characters like o, l, s, a, i, or u.
|
145
|
+
#
|
146
|
+
# Swear words can pop up so most vowels are removed to save your blushes.
|
147
|
+
#
|
148
|
+
# @param raw_digest [String]
|
149
|
+
# the source string whose characters we filter in order to produce a
|
150
|
+
# high quality, pleasing ergonomic identifier
|
151
|
+
#
|
152
|
+
# @param id_length [Numeric]
|
153
|
+
# the number of identifier characters to return. This parameter must be
|
154
|
+
# even and divisible by 3 in case it needs to be split (for readability)
|
155
|
+
# into two or three segments.
|
156
|
+
#
|
157
|
+
# @return [String]
|
158
|
+
# The filtered identifier containing only the 24 desirable characters.
|
159
|
+
#
|
160
|
+
def self.ergonomic_filter( raw_digest, id_length )
|
161
|
+
|
162
|
+
id_characters = ""
|
163
|
+
raw_digest.downcase().each_char() do | digest_char |
|
164
|
+
id_characters.concat( digest_char ) if ERGONOMIC_LIST.include?( digest_char )
|
165
|
+
break if id_characters.length() == id_length
|
166
|
+
end
|
167
|
+
|
168
|
+
return id_characters
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
=begin
|
173
|
+
require_relative "../keys/key.64"
|
174
|
+
require_relative "../keys/key"
|
175
|
+
input1 = "apollo@akora"
|
176
|
+
output1 = Identifier.derive_ergonomic_identifier( input1, 90 )
|
177
|
+
puts "Input #{input1} produced output #{output1}"
|
178
|
+
=end
|
179
|
+
|
180
|
+
# This method produces a soft random identifier by grabbing a secure
|
181
|
+
# random binary string, transforming it to base64, removing any and all
|
182
|
+
# hyphens and underscores, downcasing the result and finally truncating
|
183
|
+
# it to produce a random identifier of the desired length.
|
184
|
+
#
|
185
|
+
# Do not use this method to produce passwords or secrets because it
|
186
|
+
# provides IDs from a pool of only 36 characters with a fixed length so
|
187
|
+
# can be brute forced with ease. Only use it for producing identifiers.
|
188
|
+
#
|
189
|
+
# @param id_length [Number]
|
190
|
+
# the length of the returned identifier. This value should not exceed
|
191
|
+
# 50 characters as the source pool is a good size - but is by no means
|
192
|
+
# infinitely long.
|
193
|
+
def self.get_random_identifier( id_length )
|
194
|
+
|
195
|
+
require 'securerandom'
|
196
|
+
random_ref = SecureRandom.urlsafe_base64( id_length ).delete("-_").downcase
|
197
|
+
return random_ref[ 0 .. ( id_length - 1 ) ]
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# The branch ID generated here is a derivative of the 150 character
|
203
|
+
# shell token.
|
204
|
+
#
|
205
|
+
# The algorithm for deriving the branch ID is as follows.
|
206
|
+
#
|
207
|
+
# - convert the 150 characters to an alphanumeric string
|
208
|
+
# - convert the result to a bit string and then to a key
|
209
|
+
# - put the key's binary form through a 384 bit digest
|
210
|
+
# - convert the digest's output to 64 YACHT64 characters
|
211
|
+
# - remove the (on average 2) non-alphanumeric characters
|
212
|
+
# - cherry pick a spread out 12 characters from the pool
|
213
|
+
# - hiphenate the character positions five (5) and ten (10)
|
214
|
+
# - ensure the length of the resultant ID is fourteen (14)
|
215
|
+
#
|
216
|
+
# The resulting branch id will look something like this
|
217
|
+
#
|
218
|
+
# g3sf-pab5-9xvd
|
219
|
+
#
|
220
|
+
# @param shell_token [String]
|
221
|
+
# a triply segmented (and one liner) text token
|
222
|
+
#
|
223
|
+
# @return [String]
|
224
|
+
# a 14 character string that cannot feasibly be repeated
|
225
|
+
# within the keyspace of even a gigantic organisation.
|
226
|
+
#
|
227
|
+
# This method guarantees that the branch id will always be the same when
|
228
|
+
# called by commands within the same shell in the same machine.
|
229
|
+
def self.derive_branch_id( shell_token )
|
230
|
+
|
231
|
+
assert_shell_token_size( shell_token )
|
232
|
+
random_length_id_key = Key.from_char64( shell_token.to_alphanumeric )
|
233
|
+
a_384_bit_key = random_length_id_key.to_384_bit_key()
|
234
|
+
a_64_char_str = a_384_bit_key.to_char64()
|
235
|
+
base_64_chars = a_64_char_str.to_alphanumeric
|
236
|
+
|
237
|
+
id_chars_pool = cherry_picker( ID_TRI_CHUNK_LEN, base_64_chars )
|
238
|
+
id_hyphen_one = id_chars_pool.insert( IDENTITY_CHUNK_LENGTH, SEGMENT_CHAR )
|
239
|
+
id_characters = id_hyphen_one.insert( ( IDENTITY_CHUNK_LENGTH * 2 + 1 ), SEGMENT_CHAR )
|
240
|
+
|
241
|
+
err_msg = "Shell ID needs #{ID_TRI_TOTAL_LEN} not #{id_characters.length} characters."
|
242
|
+
raise RuntimeError, err_msg unless id_characters.length == ID_TRI_TOTAL_LEN
|
243
|
+
|
244
|
+
return id_characters.downcase
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
# Cherry pick a given number of characters from the character pool
|
250
|
+
# so that a good spread is achieved. This picker is the anti-pattern
|
251
|
+
# of just axing the first 5 characters from a 100 character string
|
252
|
+
# essentially wasting over 90% of the available entropy.
|
253
|
+
#
|
254
|
+
# This is the <b>algorithem to cherry pick</b> a spread of characters
|
255
|
+
# from the pool in the second parameter.
|
256
|
+
#
|
257
|
+
# - if the character pool length is a multiple of num_chars all is good otherwise
|
258
|
+
# - constrict to the <b>highest multiple of the pick size below</b> the pool length
|
259
|
+
# - divide that number by num_chars to get the first offset and character spacing
|
260
|
+
# - if spacing is 3, the first character is the 3rd, the second the 6th and so on
|
261
|
+
# - then return the cherry picked characters
|
262
|
+
#
|
263
|
+
# @param pick_size [FixNum] the number of characters to cherry pick
|
264
|
+
# @param char_pool [String] a pool of characters to cherry pick from
|
265
|
+
# @return [String]
|
266
|
+
# a string whose length is the one indicated by the first parameter
|
267
|
+
# and whose characters contain a predictable, repeatable spread from
|
268
|
+
# the character pool parameter
|
269
|
+
def self.cherry_picker( pick_size, char_pool )
|
270
|
+
|
271
|
+
hmb_limit = highest_multiple_below( pick_size, char_pool.length )
|
272
|
+
jump_size = hmb_limit / pick_size
|
273
|
+
read_point = jump_size
|
274
|
+
picked_chars = ""
|
275
|
+
loop do
|
276
|
+
picked_chars += char_pool[ read_point - 1 ]
|
277
|
+
read_point += jump_size
|
278
|
+
break if read_point > hmb_limit
|
279
|
+
end
|
280
|
+
|
281
|
+
err_msg = "Expected cherry pick size to be #{pick_size} but it was #{picked_chars.length}."
|
282
|
+
raise RuntimeError, err_msg unless picked_chars.length == pick_size
|
283
|
+
|
284
|
+
return picked_chars
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
|
289
|
+
# Affectionately known as <b>a hmb</b>, this method returns the
|
290
|
+
# <b>highest multiple</b> of the first parameter that is below
|
291
|
+
# <b>(either less than or equal to)</b> the second parameter.
|
292
|
+
#
|
293
|
+
# - -------- - ------- - ----------------- -
|
294
|
+
# | Small | Big | Highest Multiple |
|
295
|
+
# | Number | Number | Below Big Num |
|
296
|
+
# | -------- - ------- - ----------------- |
|
297
|
+
# | 5 | 25 | 25 |
|
298
|
+
# | 3 | 20 | 18 |
|
299
|
+
# | 8 | 63 | 56 |
|
300
|
+
# | 1 | 1 | 1 |
|
301
|
+
# | 26 | 28 | 26 |
|
302
|
+
# | 1 | 7 | 7 |
|
303
|
+
# | 16 | 16 | 16 |
|
304
|
+
# | -------- - ------- - ----------------- |
|
305
|
+
# | 10 | 8 | ERROR |
|
306
|
+
# | -4 | 17 | ERROR |
|
307
|
+
# | 4 | -17 | ERROR |
|
308
|
+
# | 0 | 32 | ERROR |
|
309
|
+
# | 29 | 0 | ERROR |
|
310
|
+
# | -4 | 0 | ERROR |
|
311
|
+
# | -------- - ------- - ----------------- |
|
312
|
+
# - -------- - ------- - ----------------- -
|
313
|
+
#
|
314
|
+
# Zeroes and negative numbers cannot be entertained, nor can the
|
315
|
+
# small number be larger than the big one.
|
316
|
+
#
|
317
|
+
# @param small_num [FixNum]
|
318
|
+
# the highest multiple of this number below the one in the
|
319
|
+
# next parameter is what will be returned.
|
320
|
+
#
|
321
|
+
# @param big_num [FixNum]
|
322
|
+
# returns either this number or the nearest below it that is
|
323
|
+
# a multiple of the number in the first parameter.
|
324
|
+
#
|
325
|
+
# @raise [ArgumentError]
|
326
|
+
# if the first parameter is greater than the second
|
327
|
+
# if either or both parameters are zero or negative
|
328
|
+
def self.highest_multiple_below small_num, big_num
|
329
|
+
|
330
|
+
arg_issue = (small_num > big_num) || small_num < 1 || big_num < 1
|
331
|
+
err_msg = "Invalid args #{small_num} and #{big_num} to HMB function."
|
332
|
+
raise ArgumentError, err_msg if arg_issue
|
333
|
+
|
334
|
+
for index in 0 .. ( big_num - 1 )
|
335
|
+
invex = big_num - index # an [invex] is an inverted index
|
336
|
+
return invex if invex % small_num == 0
|
337
|
+
end
|
338
|
+
|
339
|
+
raise ArgumentError, "Could not find a multiple of #{small_num} lower than #{big_num}"
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
|
344
|
+
private
|
345
|
+
|
346
|
+
|
347
|
+
def self.assert_shell_token_size shell_token
|
348
|
+
err_msg = "shell token has #{shell_token.length} and not #{KeyDerivation::SHELL_TOKEN_SIZE} chars."
|
349
|
+
raise RuntimeError, err_msg unless shell_token.length == KeyDerivation::SHELL_TOKEN_SIZE
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
end
|