ubiq-security 1.0.0 → 1.0.5

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.
@@ -13,256 +13,276 @@
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')
78
84
 
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()
136
- end
137
- end
85
+ @decryption_started = true
86
+ @data = ''
87
+ end
138
88
 
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}"
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, flags, algorithm_id, iv_length, key_length = packed_struct.unpack('CCCCn')
127
+
128
+ # verify flag are correct and version is 0
129
+ raise 'invalid encryption header' if (version != 0 ) || ((flags & ~Algo::UBIQ_HEADER_V0_FLAG_AAD) != 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
175
147
  end
176
- end
177
148
 
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
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
+ # Get the algorithm name from the internal algorithm id in the header
169
+ @key['algorithm'] = Algo.new.find_alg(algorithm_id)
170
+
171
+ encrypted_private_key = response['encrypted_private_key']
172
+ # Decrypt the encryped private key using SRSA
173
+ private_key = OpenSSL::PKey::RSA.new(encrypted_private_key, @srsa)
174
+
175
+ wrapped_data_key = response['wrapped_data_key']
176
+ # Decode WDK from base64 format
177
+ wdk = Base64.strict_decode64(wrapped_data_key)
178
+ # Use private key to decrypt the wrapped data key
179
+ dk = private_key.private_decrypt(wdk, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
180
+
181
+ @key['raw'] = dk
182
+ @key['uses'] = 0
183
+ else
184
+ # Raise the error if response is not 200
185
+ raise "HTTPError Response: Expected 201, got #{response.code}"
186
+ end
187
+ end
183
188
 
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
189
+ # If the key object exists, create a new decryptor
190
+ # with the initialization vector from the header and
191
+ # the decrypted key (which is either new from the
192
+ # server or cached from the previous decryption). in
193
+ # either case, increment the key usage
194
+
195
+ if @key.present?
196
+ @algo = Algo.new.get_algo(@key['algorithm'])
197
+ @key['dec'] = Algo.new.decryptor(@algo, @key['raw'], iv)
198
+ # Documentation indicates the auth_data has to be set AFTER auth_tag
199
+ # but we get an OpenSSL error when it is set AFTER an update call.
200
+ # Checking OpenSSL documentation, there is not a requirement to set
201
+ # auth_data before auth_tag so Ruby documentation seems to be
202
+ # wrong. This approach works and is compatible with the encrypted
203
+ # data produced by the other languages' client library
204
+ if (flags & Algo::UBIQ_HEADER_V0_FLAG_AAD) != 0
205
+ @key['dec'].auth_data = packed_struct + iv + encrypted_key
206
+ end
207
+ @key['uses'] += 1
208
+ end
188
209
  end
189
210
  end
190
211
  end
191
- end
192
212
 
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]
213
+ # if the object has a key and a decryptor, then decrypt whatever
214
+ # data is in the buffer, less any data that needs to be saved to
215
+ # serve as the tag.
216
+ plain_text = ''
217
+ if @key.present? && @key.key?('dec')
218
+ size = @data.length - @algo[:tag_length]
219
+ if size.positive?
220
+ plain_text = @key['dec'].update(@data[0..size - 1])
221
+ @data = @data[size..-1]
222
+ end
223
+ return plain_text
202
224
  end
203
- return plain_text
204
225
  end
205
226
 
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 ''
227
+ def end
228
+ raise 'Decryption is not Started' unless @decryption_started
229
+
230
+ # The update function always maintains tag-size bytes in
231
+ # the buffer because this function provides no data parameter.
232
+ # by the time the caller calls this function, all data must
233
+ # have already been input to the decryption object.
234
+
235
+ sz = @data.length - @algo[:tag_length]
236
+
237
+ raise 'Invalid Tag!' if sz.negative?
238
+
239
+ if sz.zero?
240
+ @key['dec'].auth_tag = @data
241
+ begin
242
+ pt = @key['dec'].final
243
+ # Delete the decryptor context
244
+ @key.delete('dec')
245
+ # Return the decrypted plain data
246
+ @decryption_started = false
247
+ return pt
248
+ rescue Exception
249
+ print 'Invalid cipher data or tag!'
250
+ return ''
251
+ end
230
252
  end
231
- end
232
- end
253
+ end
233
254
 
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)
255
+ def close
256
+ raise 'Decryption currently running' if @decryption_started
257
+
258
+ # Reset the internal state of the decryption object
259
+ if @key.present?
260
+ if @key['uses'].positive?
261
+ query_url = "#{endpoint}/#{@key['finger_print']}/#{@key['session']}"
262
+ url = "#{endpoint_base}/decryption/key/#{@key['finger_print']}/#{@key['session']}"
263
+ query = { uses: @key['uses'] }
264
+ headers = Auth.build_headers(@papi, @sapi, query_url, query, @host, 'patch')
265
+ response = HTTParty.patch(
266
+ url,
267
+ body: query.to_json,
268
+ headers: headers
269
+ )
270
+ remove_instance_variable(:@data)
271
+ remove_instance_variable(:@key)
272
+ end
250
273
  end
251
274
  end
252
275
  end
253
276
 
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
277
+ def decrypt(creds, data)
278
+ begin
279
+ dec = Decryption.new(creds)
280
+ res = dec.begin + dec.update(data) + dec.end
281
+ dec.close
282
+ rescue StandardError
283
+ dec&.close
284
+ raise
285
+ end
286
+ return res
264
287
  end
265
- return res
266
-
267
- end
268
288
  end