ubiq-security 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,256 +13,266 @@
13
13
  #
14
14
  # https://ubiqsecurity.com/legal
15
15
  #
16
+
17
+ # frozen_string_literal: true
18
+
16
19
  require 'rb-readline'
17
20
  require 'byebug'
18
21
  require 'httparty'
19
- require "active_support/all"
22
+ require 'active_support/all'
20
23
  require_relative './auth.rb'
21
24
  require_relative './algo.rb'
22
25
  require_relative './encrypt.rb'
23
26
  require 'webrick'
24
27
 
28
+ # Ubiq Security Modules for encrypting / decrypting data
25
29
  module Ubiq
30
+ # Class to provide data decryption, either as a simple
31
+ # single function call or as a piecewise where the
32
+ # entire data element isn't available at once or is
33
+ # too large to process in a single call.
34
+ class Decryption
35
+ def initialize(creds)
36
+ # Initialize the decryption module object
37
+ # Set the credentials in instance varibales to be used among methods
38
+ # the server to which to make the request
39
+ raise 'Some of your credentials are missing, please check!' unless validate_creds(creds)
26
40
 
27
- class Decryption
28
- def initialize(creds)
29
- # Initialize the decryption module object
30
- # Set the credentials in instance varibales to be used among methods
31
- # the server to which to make the request
32
- raise RuntimeError, 'Some of your credentials are missing, please check!' if !validate_creds(creds)
33
- @host = creds.host.blank? ? UBIQ_HOST : creds.host
41
+ @host = creds.host.blank? ? UBIQ_HOST : creds.host
34
42
 
35
- # The client's public API key (used to identify the client to the server
36
- @papi = creds.access_key_id
43
+ # The client's public API key (used to identify the client to the server
44
+ @papi = creds.access_key_id
37
45
 
38
- # The client's secret API key (used to authenticate HTTP requests)
39
- @sapi = creds.secret_signing_key
46
+ # The client's secret API key (used to authenticate HTTP requests)
47
+ @sapi = creds.secret_signing_key
40
48
 
41
- # The client's secret RSA encryption key/password (used to decrypt the client's RSA key from the server). This key is not retained by this object.
42
- @srsa = creds.secret_crypto_access_key
49
+ # The client's secret RSA encryption key/password (used to decrypt the
50
+ # client's RSA key from the server). This key is not retained by this object.
51
+ @srsa = creds.secret_crypto_access_key
43
52
 
44
- @decryption_ready = true
45
- @decryption_started = false
53
+ @decryption_ready = true
54
+ @decryption_started = false
46
55
 
47
- end
56
+ end
48
57
 
49
- def endpoint_base
50
- @host + '/api/v0'
51
- end
58
+ def endpoint_base
59
+ @host + '/api/v0'
60
+ end
52
61
 
53
- def endpoint
54
- '/api/v0/decryption/key'
55
- end
62
+ def endpoint
63
+ '/api/v0/decryption/key'
64
+ end
56
65
 
57
- def begin
58
- # Begin the decryption process
66
+ def begin
67
+ # Begin the decryption process
59
68
 
60
- # This interface does not take any cipher text in its arguments
61
- # in an attempt to maintain an API that corresponds to the
62
- # encryption object. In doing so, the work that can take place
63
- # in this function is limited. without any data, there is no
64
- # way to determine which key is in use or decrypt any data.
65
- #
66
- # this function simply throws an error if starting an decryption
67
- # while one is already in progress, and initializes the internal
68
- # buffer
69
+ # This interface does not take any cipher text in its arguments
70
+ # in an attempt to maintain an API that corresponds to the
71
+ # encryption object. In doing so, the work that can take place
72
+ # in this function is limited. without any data, there is no
73
+ # way to determine which key is in use or decrypt any data.
74
+ #
75
+ # this function simply throws an error if starting an decryption
76
+ # while one is already in progress, and initializes the internal
77
+ # buffer
69
78
 
70
- raise RuntimeError, 'Decryption is not ready' if !@decryption_ready
79
+ raise 'Decryption is not ready' unless @decryption_ready
71
80
 
72
- raise RuntimeError, 'Decryption Already Started' if @decryption_started
81
+ raise 'Decryption Already Started' if @decryption_started
73
82
 
74
- raise RuntimeError, 'Decryption already in progress' if @key.present? and @key.key?("dec")
75
- @decryption_started = true
76
- @data = ''
77
- end
83
+ raise 'Decryption already in progress' if @key.present? && @key.key?('dec')
84
+
85
+ @decryption_started = true
86
+ @data = ''
87
+ end
78
88
 
79
- def update(data)
80
- # Decryption of cipher text is performed here
81
- # Cipher text must be passed to this function in the order in which it was output from the encryption.update function.
82
-
83
- # Each encryption has a header on it that identifies the algorithm
84
- # used and an encryption of the data key that was used to encrypt
85
- # the original plain text. there is no guarantee how much of that
86
- # data will be passed to this function or how many times this
87
- # function will be called to process all of the data. to that end,
88
- # this function buffers data internally, when it is unable to
89
- # process it.
90
- #
91
- # The function buffers data internally until the entire header is
92
- # received. once the header has been received, the encrypted data
93
- # key is sent to the server for decryption. after the header has
94
- # been successfully handled, this function always decrypts all of
95
- # the data in its internal buffer *except* for however many bytes
96
- # are specified by the algorithm's tag size. see the end() function
97
- # for details.
98
-
99
- raise RuntimeError, 'Decryption is not Started' if !@decryption_started
100
-
101
- # Append the incoming data in the internal data buffer
102
- @data = @data + data
103
-
104
- # if there is no key or 'dec' member of key, then the code is still trying to build a complete header
105
- if !@key.present? or !@key.key?("dec")
106
- struct_length = [1,1,1,1,1].pack('CCCCn').length
107
- packed_struct = @data[0...struct_length]
108
-
109
- # Does the buffer contain enough of the header to
110
- # determine the lengths of the initialization vector
111
- # and the key?
112
- if @data.length > struct_length
113
- # Unpack the values packed in encryption
114
- version, flag_for_later, algorithm_id, iv_length, key_length = packed_struct.unpack('CCCCn')
115
-
116
- # verify flag and version are 0
117
- raise RuntimeError, 'invalid encryption header' if version != 0 or flag_for_later != 0
118
-
119
- # Does the buffer contain the entire header?
120
- if @data.length > struct_length + iv_length + key_length
121
- # Extract the initialization vector
122
- iv = @data[struct_length...iv_length + struct_length]
123
- # Extract the encryped key
124
- encrypted_key = @data[struct_length + iv_length...key_length + struct_length + iv_length]
125
- # Remove the header from the buffer
126
- @data = @data[struct_length + iv_length + key_length..-1]
127
-
128
- # generate a local identifier for the key
129
- hash_sha512 = OpenSSL::Digest::SHA512.new
130
- hash_sha512 << encrypted_key
131
- client_id = hash_sha512.digest
132
-
133
- if @key.present?
134
- if @key['client_id'] != client_id
135
- close()
89
+ def update(data)
90
+ # Decryption of cipher text is performed here
91
+ # Cipher text must be passed to this function in the order in which
92
+ # it was output from the encryption.update function.
93
+
94
+ # Each encryption has a header on it that identifies the algorithm
95
+ # used and an encryption of the data key that was used to encrypt
96
+ # the original plain text. there is no guarantee how much of that
97
+ # data will be passed to this function or how many times this
98
+ # function will be called to process all of the data. to that end,
99
+ # this function buffers data internally, when it is unable to
100
+ # process it.
101
+ #
102
+ # The function buffers data internally until the entire header is
103
+ # received. once the header has been received, the encrypted data
104
+ # key is sent to the server for decryption. after the header has
105
+ # been successfully handled, this function always decrypts all of
106
+ # the data in its internal buffer *except* for however many bytes
107
+ # are specified by the algorithm's tag size. see the end() function
108
+ # for details.
109
+
110
+ raise 'Decryption is not Started' unless @decryption_started
111
+
112
+ # Append the incoming data in the internal data buffer
113
+ @data += data
114
+
115
+ # if there is no key or 'dec' member of key, then the code is
116
+ # still trying to build a complete header
117
+ if !@key.present? || !@key.key?('dec')
118
+ struct_length = [1, 1, 1, 1, 1].pack('CCCCn').length
119
+ packed_struct = @data[0...struct_length]
120
+
121
+ # Does the buffer contain enough of the header to
122
+ # determine the lengths of the initialization vector
123
+ # and the key?
124
+ if @data.length > struct_length
125
+ # Unpack the values packed in encryption
126
+ version, flag_for_later, algorithm_id, iv_length, key_length = packed_struct.unpack('CCCCn')
127
+
128
+ # verify flag and version are 0
129
+ raise 'invalid encryption header' if (version != 0) || (flag_for_later != 0)
130
+
131
+ # Does the buffer contain the entire header?
132
+ if @data.length > struct_length + iv_length + key_length
133
+ # Extract the initialization vector
134
+ iv = @data[struct_length...iv_length + struct_length]
135
+ # Extract the encryped key
136
+ encrypted_key = @data[struct_length + iv_length...key_length + struct_length + iv_length]
137
+ # Remove the header from the buffer
138
+ @data = @data[struct_length + iv_length + key_length..-1]
139
+
140
+ # generate a local identifier for the key
141
+ hash_sha512 = OpenSSL::Digest::SHA512.new
142
+ hash_sha512 << encrypted_key
143
+ client_id = hash_sha512.digest
144
+
145
+ if @key.present?
146
+ close if @key['client_id'] != client_id
136
147
  end
137
- end
138
148
 
139
- # IF key object not exists, request a new one from the server
140
- if !@key.present?
141
- url = endpoint_base + "/decryption/key"
142
- query = {encrypted_data_key: Base64.strict_encode64(encrypted_key)}
143
- headers = Auth.build_headers(@papi, @sapi, endpoint, query, @host, 'post')
144
-
145
- response = HTTParty.post(
146
- url,
147
- body: query.to_json,
148
- headers: headers
149
- )
150
-
151
- # Response status is 200 OK
152
- if response.code == WEBrick::HTTPStatus::RC_OK
153
- @key = {}
154
- @key['finger_print'] = response['key_fingerprint']
155
- @key['client_id'] = client_id
156
- @key['session'] = response['encryption_session']
157
-
158
- @key['algorithm'] = 'aes-256-gcm'
159
-
160
- encrypted_private_key = response['encrypted_private_key']
161
- # Decrypt the encryped private key using SRSA
162
- private_key = OpenSSL::PKey::RSA.new(encrypted_private_key,@srsa)
163
-
164
- wrapped_data_key = response['wrapped_data_key']
165
- # Decode WDK from base64 format
166
- wdk = Base64.strict_decode64(wrapped_data_key)
167
- # Use private key to decrypt the wrapped data key
168
- dk = private_key.private_decrypt(wdk,OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
169
-
170
- @key['raw'] = dk
171
- @key['uses'] = 0
172
- else
173
- # Raise the error if response is not 200
174
- raise RuntimeError, "HTTPError Response: Expected 201, got #{response.code}"
149
+ # IF key object not exists, request a new one from the server
150
+ unless @key.present?
151
+ url = endpoint_base + '/decryption/key'
152
+ query = { encrypted_data_key: Base64.strict_encode64(encrypted_key) }
153
+ headers = Auth.build_headers(@papi, @sapi, endpoint, query, @host, 'post')
154
+
155
+ response = HTTParty.post(
156
+ url,
157
+ body: query.to_json,
158
+ headers: headers
159
+ )
160
+
161
+ # Response status is 200 OK
162
+ if response.code == WEBrick::HTTPStatus::RC_OK
163
+ @key = {}
164
+ @key['finger_print'] = response['key_fingerprint']
165
+ @key['client_id'] = client_id
166
+ @key['session'] = response['encryption_session']
167
+
168
+ @key['algorithm'] = 'aes-256-gcm'
169
+
170
+ encrypted_private_key = response['encrypted_private_key']
171
+ # Decrypt the encryped private key using SRSA
172
+ private_key = OpenSSL::PKey::RSA.new(encrypted_private_key, @srsa)
173
+
174
+ wrapped_data_key = response['wrapped_data_key']
175
+ # Decode WDK from base64 format
176
+ wdk = Base64.strict_decode64(wrapped_data_key)
177
+ # Use private key to decrypt the wrapped data key
178
+ dk = private_key.private_decrypt(wdk, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
179
+
180
+ @key['raw'] = dk
181
+ @key['uses'] = 0
182
+ else
183
+ # Raise the error if response is not 200
184
+ raise "HTTPError Response: Expected 201, got #{response.code}"
185
+ end
175
186
  end
176
- end
177
187
 
178
- # If the key object exists, create a new decryptor
179
- # with the initialization vector from the header and
180
- # the decrypted key (which is either new from the
181
- # server or cached from the previous decryption). in
182
- # either case, increment the key usage
188
+ # If the key object exists, create a new decryptor
189
+ # with the initialization vector from the header and
190
+ # the decrypted key (which is either new from the
191
+ # server or cached from the previous decryption). in
192
+ # either case, increment the key usage
183
193
 
184
- if @key.present?
185
- @algo = Algo.new.get_algo(@key['algorithm'])
186
- @key['dec'] = Algo.new.decryptor(@algo, @key['raw'], iv)
187
- @key['uses'] += 1
194
+ if @key.present?
195
+ @algo = Algo.new.get_algo(@key['algorithm'])
196
+ @key['dec'] = Algo.new.decryptor(@algo, @key['raw'], iv)
197
+ @key['uses'] += 1
198
+ end
188
199
  end
189
200
  end
190
201
  end
191
- end
192
202
 
193
- # if the object has a key and a decryptor, then decrypt whatever
194
- # data is in the buffer, less any data that needs to be saved to
195
- # serve as the tag.
196
- plain_text = ''
197
- if @key.present? and @key.key?("dec")
198
- size = @data.length - @algo[:tag_length]
199
- if size > 0
200
- plain_text = @key['dec'].update(@data[0..size-1])
201
- @data = @data[size..-1]
203
+ # if the object has a key and a decryptor, then decrypt whatever
204
+ # data is in the buffer, less any data that needs to be saved to
205
+ # serve as the tag.
206
+ plain_text = ''
207
+ if @key.present? && @key.key?('dec')
208
+ size = @data.length - @algo[:tag_length]
209
+ if size.positive?
210
+ plain_text = @key['dec'].update(@data[0..size - 1])
211
+ @data = @data[size..-1]
212
+ end
213
+ return plain_text
202
214
  end
203
- return plain_text
204
215
  end
205
216
 
206
- end
207
-
208
- def end
209
- raise RuntimeError, 'Decryption is not Started' if !@decryption_started
210
- # The update function always maintains tag-size bytes in
211
- # the buffer because this function provides no data parameter.
212
- # by the time the caller calls this function, all data must
213
- # have already been input to the decryption object.
214
-
215
- sz = @data.length - @algo[:tag_length]
216
-
217
- raise RuntimeError, 'Invalid Tag!' if sz < 0
218
- if sz == 0
219
- @key['dec'].auth_tag = @data
220
- begin
221
- pt = @key['dec'].final
222
- # Delete the decryptor context
223
- @key.delete('dec')
224
- # Return the decrypted plain data
225
- @decryption_started = false
226
- return pt
227
- rescue Exception => e
228
- print 'Invalid cipher data or tag!'
229
- return ''
217
+ def end
218
+ raise 'Decryption is not Started' unless @decryption_started
219
+
220
+ # The update function always maintains tag-size bytes in
221
+ # the buffer because this function provides no data parameter.
222
+ # by the time the caller calls this function, all data must
223
+ # have already been input to the decryption object.
224
+
225
+ sz = @data.length - @algo[:tag_length]
226
+
227
+ raise 'Invalid Tag!' if sz.negative?
228
+
229
+ if sz.zero?
230
+ @key['dec'].auth_tag = @data
231
+ begin
232
+ pt = @key['dec'].final
233
+ # Delete the decryptor context
234
+ @key.delete('dec')
235
+ # Return the decrypted plain data
236
+ @decryption_started = false
237
+ return pt
238
+ rescue Exception
239
+ print 'Invalid cipher data or tag!'
240
+ return ''
241
+ end
230
242
  end
231
- end
232
- end
243
+ end
233
244
 
234
- def close
235
- raise RuntimeError, 'Decryption currently running' if @decryption_started
236
- # Reset the internal state of the decryption object
237
- if @key.present?
238
- if @key['uses'] > 0
239
- query_url = "#{endpoint}/#{@key['finger_print']}/#{@key['session']}"
240
- url = "#{endpoint_base}/decryption/key/#{@key['finger_print']}/#{@key['session']}"
241
- query = {uses: @key['uses']}
242
- headers = Auth.build_headers(@papi, @sapi, query_url, query, @host, 'patch')
243
- response = HTTParty.patch(
244
- url,
245
- body: query.to_json,
246
- headers: headers
247
- )
248
- remove_instance_variable(:@data)
249
- remove_instance_variable(:@key)
245
+ def close
246
+ raise 'Decryption currently running' if @decryption_started
247
+
248
+ # Reset the internal state of the decryption object
249
+ if @key.present?
250
+ if @key['uses'].positive?
251
+ query_url = "#{endpoint}/#{@key['finger_print']}/#{@key['session']}"
252
+ url = "#{endpoint_base}/decryption/key/#{@key['finger_print']}/#{@key['session']}"
253
+ query = { uses: @key['uses'] }
254
+ headers = Auth.build_headers(@papi, @sapi, query_url, query, @host, 'patch')
255
+ response = HTTParty.patch(
256
+ url,
257
+ body: query.to_json,
258
+ headers: headers
259
+ )
260
+ remove_instance_variable(:@data)
261
+ remove_instance_variable(:@key)
262
+ end
250
263
  end
251
264
  end
252
265
  end
253
266
 
254
- end
255
-
256
- def decrypt(creds, data)
257
- begin
258
- dec = Decryption.new(creds)
259
- res = dec.begin() + dec.update(data) + dec.end()
260
- dec.close()
261
- rescue
262
- dec.close() if dec
263
- raise
267
+ def decrypt(creds, data)
268
+ begin
269
+ dec = Decryption.new(creds)
270
+ res = dec.begin + dec.update(data) + dec.end
271
+ dec.close
272
+ rescue StandardError
273
+ dec&.close
274
+ raise
275
+ end
276
+ return res
264
277
  end
265
- return res
266
-
267
- end
268
278
  end