ubiq-security 1.0.0 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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