security_client 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 055b4ebf4046542527af7b8df744c592ff0921a8e5d08af6f470e2e72c96c8c2
4
+ data.tar.gz: 694fa499f049beaae0480892cb2b2bc0100f1e22fed3eb94ab80c250b026d1a0
5
+ SHA512:
6
+ metadata.gz: 0cb89ec2fca2a529d4a75c39505efcaefd6fd2b1716ec435cc206e56d7b7d03bd8f67b1437295c936147425d062afd6c0422ae5ba2854aefa4da31f01ff476e9
7
+ data.tar.gz: 1e6dc6e527eb4d63796d96f000f37245d5b83cf19a7eb9d72c26c955a797099889138d7f1178492a012ba4a5a9915188b58ccfdfda8db09ccb1ea810a94ff7c1
Binary file
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.gem
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at vinay.ymca@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in security_client.gemspec
4
+ gemspec
@@ -0,0 +1,39 @@
1
+ # SecurityClient
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/security_client`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'security_client'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install security_client
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/security_client. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+ ## Code of Conduct
38
+
39
+ Everyone interacting in the SecurityClient project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/security_client/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "security_client"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,562 @@
1
+ require "security_client/version"
2
+ require 'ostruct'
3
+ require 'httparty'
4
+ require "active_support/all"
5
+ require 'webrick'
6
+
7
+ module SecurityClient
8
+ class Voltron
9
+
10
+ def initialize access_key_id:, secret_signing_key:, secret_crypto_access_key: , host:
11
+ @access_key_id = access_key_id
12
+ @secret_signing_key = secret_signing_key
13
+ @secret_crypto_access_key = secret_crypto_access_key
14
+ @host = host
15
+ end
16
+
17
+ def get_attributes
18
+ return OpenStruct.new(access_key_id: @access_key_id, secret_signing_key: @secret_signing_key, secret_crypto_access_key: @secret_crypto_access_key, host: @host)
19
+ end
20
+
21
+ def encrypt uses:, data:
22
+ creds = self.get_attributes
23
+ begin
24
+ enc = SecurityClient::Encryption.new(creds, 1)
25
+ res = enc.begin() + enc.update(data) + enc.end()
26
+ enc.close()
27
+ rescue
28
+ enc.close() if enc
29
+ raise
30
+ end
31
+ puts res
32
+ return res
33
+ end
34
+
35
+ def decrypt data:
36
+ creds = self.get_attributes
37
+ begin
38
+ dec = Decryption.new(creds)
39
+ res = dec.begin() + dec.update(data) + dec.end()
40
+ dec.close()
41
+ rescue
42
+ dec.close() if dec
43
+ raise
44
+ end
45
+ puts res
46
+ return res
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+ class SecurityClient::Encryption
53
+ def initialize(creds, uses)
54
+
55
+ raise RuntimeError, 'Some of your credentials are missing, please check!' if !validate_creds(creds)
56
+
57
+ # Set host, either the default or the one given by caller
58
+ @host = creds.host.blank? ? VOLTRON_HOST : creds.host
59
+
60
+ # Set the credentials in instance varibales to be used among methods
61
+ # The client's public API key (used to identify the client to the server
62
+ @papi = creds.access_key_id
63
+
64
+ # The client's secret API key (used to authenticate HTTP requests)
65
+ @sapi = creds.secret_signing_key
66
+
67
+ # 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.
68
+ @srsa = creds.secret_crypto_access_key
69
+
70
+ # Build the endpoint URL
71
+ url = endpoint_base + '/encryption/key'
72
+
73
+ # Build the Request Body with the number of uses of key
74
+ query = {uses: uses}
75
+
76
+ # Retrieve the necessary headers to make the request using Auth Object
77
+ headers = SecurityClient::Auth.build_headers(@papi, @sapi, endpoint, query, @host,'post')
78
+
79
+ @encryption_started = false
80
+ @encryption_ready = true
81
+
82
+ # Request a new encryption key from the server. if the request
83
+ # fails, the function raises a HTTPError indicating
84
+ # the status code returned by the server. this exception is
85
+ # propagated back to the caller
86
+
87
+ begin
88
+ response = HTTParty.post(
89
+ url,
90
+ body: query.to_json,
91
+ headers: headers
92
+ )
93
+ rescue HTTParty::Error
94
+ raise RuntimeError, 'Cant reach server'
95
+ end
96
+
97
+ # Response status is 201 Created
98
+ if response.code == WEBrick::HTTPStatus::RC_CREATED
99
+ # The code below largely assumes that the server returns
100
+ # a json object that contains the members and is formatted
101
+ # according to the Voltron REST specification.
102
+
103
+ # Build the key object
104
+ @key = {}
105
+ @key['id'] = response['key_fingerprint']
106
+ @key['session'] = response['encryption_session']
107
+ @key['security_model'] = response['security_model']
108
+ @key['algorithm'] = response['security_model']['algorithm'].downcase
109
+ @key['max_uses'] = response['max_uses']
110
+ @key['uses'] = 0
111
+ @key['encrypted'] = Base64.strict_decode64(response['encrypted_data_key'])
112
+
113
+ # Get encrypted private key from response body
114
+ encrypted_private_key = response['encrypted_private_key']
115
+ # Get wrapped data key from response body
116
+ wrapped_data_key = response['wrapped_data_key']
117
+ # Decrypt the encryped private key using @srsa supplied
118
+ private_key = OpenSSL::PKey::RSA.new(encrypted_private_key,@srsa)
119
+ # Decode WDK from base64 format
120
+ wdk = Base64.strict_decode64(wrapped_data_key)
121
+ # Use private key to decrypt the wrapped data key
122
+ dk = private_key.private_decrypt(wdk,OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
123
+ @key['raw'] = dk
124
+ # Build the algorithm object
125
+ @algo = SecurityClient::Algo.new.get_algo(@key['algorithm'])
126
+ else
127
+ # Raise the error if response is not 201
128
+ raise RuntimeError, "HTTPError Response: Expected 201, got #{response.code}"
129
+ end
130
+
131
+ end
132
+
133
+ def begin
134
+ # Begin the encryption process
135
+
136
+ # When this function is called, the encryption object increments
137
+ # the number of uses of the key and creates a new internal context
138
+ # to be used to encrypt the data.
139
+ # If the encryption object is not yet ready to be used, throw an error
140
+ raise RuntimeError, 'Encryption not ready' if !@encryption_ready
141
+
142
+ # if Encryption cipher context already exists
143
+ raise RuntimeError, 'Encryption already in progress' if @encryption_started
144
+ # If max uses > uses
145
+ raise RuntimeError, 'Maximum key uses exceeded' if @key['uses'] >= @key['max_uses']
146
+ @key['uses'] += 1
147
+ # create a new Encryption context and initialization vector
148
+ @enc , @iv = SecurityClient::Algo.new.encryptor(@algo, @key['raw'])
149
+
150
+ # Pack the result into bytes to get a byte string
151
+ struct = [0, 0, @algo[:id], @iv.length, @key['encrypted'].length].pack('CCCCn')
152
+ @encryption_started = true
153
+ return struct + @iv + @key['encrypted']
154
+ end
155
+
156
+ def update(data)
157
+ raise RuntimeError, 'Encryption is not Started' if !@encryption_started
158
+ # Encryption of some plain text is perfomed here
159
+ # Any cipher text produced by the operation is returned
160
+ @enc.update(data)
161
+ end
162
+
163
+ def end
164
+ raise RuntimeError, 'Encryption is not Started' if !@encryption_started
165
+ # This function finalizes the encryption (producing the final
166
+ # cipher text for the encryption, if necessary) and adds any
167
+ # authentication information (if required by the algorithm).
168
+ # Any data produced is returned by the function.
169
+
170
+ # Finalize an encryption
171
+ res = @enc.final
172
+ if @algo[:tag_length] != 0
173
+ # Add the tag to the cipher text
174
+ res+= @enc.auth_tag
175
+ end
176
+ @encryption_started = false
177
+ # Return the encrypted result
178
+ return res
179
+ end
180
+
181
+ def close
182
+ raise RuntimeError, 'Encryption currently running' if @encryption_started
183
+ # If the key was used less times than was requested, send an update to the server
184
+ if @key['uses'] < @key['max_uses']
185
+ query_url = "#{endpoint}/#{@key['id']}/#{@key['session']}"
186
+ url = "#{endpoint_base}/encryption/key/#{@key['id']}/#{@key['session']}"
187
+ query = {actual: @key['uses'], requested: @key['max_uses']}
188
+ headers = Auth.build_headers(@papi, @sapi, query_url, query, @host, 'patch')
189
+ response = HTTParty.patch(
190
+ url,
191
+ body: query.to_json,
192
+ headers: headers
193
+ )
194
+ remove_instance_variable(:@key)
195
+ @encryption_ready = false;
196
+ end
197
+ end
198
+
199
+ def endpoint_base
200
+ @host + '/api/v0'
201
+ end
202
+
203
+ def endpoint
204
+ '/api/v0/encryption/key'
205
+ end
206
+
207
+ def validate_creds(credentials)
208
+ # This method checks for the presence of the credentials
209
+ !credentials.access_key_id.blank? and !credentials.secret_signing_key.blank? and !credentials.secret_crypto_access_key.blank?
210
+ end
211
+ end
212
+
213
+ class SecurityClient::Algo
214
+ def set_algo
215
+ @algorithm = {
216
+ "aes-256-gcm"=>{
217
+ id:0,
218
+ algorithm: OpenSSL::Cipher::AES256,
219
+ mode: OpenSSL::Cipher::AES256.new(:GCM),
220
+ key_length: 32,
221
+ iv_length: 12,
222
+ tag_length: 16
223
+ },
224
+ }
225
+ end
226
+
227
+ def get_algo(name)
228
+ set_algo[name]
229
+ end
230
+
231
+ def encryptor(obj,key, iv=nil)
232
+ # key : A byte string containing the key to be used with this encryption
233
+ # If the caller specifies the initialization vector, it must be
234
+ # the correct length and, if so, will be used. If it is not
235
+ # specified, the function will generate a new one
236
+
237
+ cipher = obj[:mode]
238
+ raise RuntimeError, 'Invalid key length' if key.length != obj[:key_length]
239
+
240
+ raise RuntimeError, 'Invalid initialization vector length' if (iv!= nil and iv.length != obj[:iv_length])
241
+ cipher.encrypt
242
+ cipher.key = key
243
+ iv = cipher.random_iv
244
+ return cipher, iv
245
+ end
246
+
247
+ def decryptor(obj, key, iv)
248
+ cipher = obj[:mode]
249
+ raise RuntimeError, 'Invalid key length' if key.length != obj[:key_length]
250
+
251
+ raise RuntimeError, 'Invalid initialization vector length' if (iv!= nil and iv.length != obj[:iv_length])
252
+ cipher = obj[:mode]
253
+ cipher.decrypt
254
+ cipher.key = key
255
+ cipher.iv = iv
256
+ return cipher
257
+ end
258
+ end
259
+
260
+ class SecurityClient::Auth
261
+ def self.build_headers(papi, sapi, endpoint, query, host, http_method)
262
+
263
+ # This function calculates the signature for the message, adding the Signature header
264
+ # to contain the data. Certain HTTP headers are required for
265
+ # signature calculation and will be added by this code as
266
+ # necessary. The constructed headers object is returned
267
+
268
+ # the '(request-target)' is part of the signed data.
269
+ # it's value is 'http_method path?query'
270
+ reqt = "#{http_method} #{endpoint}"
271
+
272
+ # The time at which the signature was created expressed as the unix epoch
273
+ created = Time.now.to_i
274
+
275
+ # the Digest header is always included/overridden by
276
+ # this code. it is a hash of the body of the http message
277
+ # and is always present even if the body is empty
278
+ hash_sha512 = OpenSSL::Digest::SHA512.new
279
+ hash_sha512 << JSON.dump(query)
280
+ digest = 'SHA-512='+Base64.strict_encode64(hash_sha512.digest)
281
+
282
+ # Initialize the headers object to be returned via this method
283
+ all_headers = {}
284
+ # The content type of request
285
+ all_headers['content-type'] = 'application/json'
286
+ # The request target calculated above(reqt)
287
+ all_headers['(request-target)'] = reqt
288
+ # The date and time in GMT format
289
+ all_headers['date'] = get_date
290
+ # The host specified by the caller
291
+ all_headers['host'] = get_host(host)
292
+ all_headers['(created)'] = created
293
+ all_headers['digest'] = digest
294
+ headers = ['content-type', 'date', 'host', '(created)', '(request-target)', 'digest']
295
+
296
+ # include the specified headers in the hmac calculation. each
297
+ # header is of the form 'header_name: header value\n'
298
+ # included headers are also added to an ordered list of headers
299
+ # which is included in the message
300
+ hmac = OpenSSL::HMAC.new(sapi, OpenSSL::Digest::SHA512.new)
301
+ headers.each do |header|
302
+ if all_headers.key?(header)
303
+ hmac << "#{header}: #{all_headers[header]}\n"
304
+ end
305
+ end
306
+
307
+ all_headers.delete('(created)')
308
+ all_headers.delete('(request-target)')
309
+ all_headers.delete('host')
310
+
311
+ # Build the Signature header itself
312
+ all_headers['signature'] = 'keyId="' + papi + '"'
313
+ all_headers['signature'] += ', algorithm="hmac-sha512"'
314
+ all_headers['signature'] += ', created=' + created.to_s
315
+ all_headers['signature'] += ', headers="' + headers.join(" ") + '"'
316
+ all_headers['signature'] += ', signature="'
317
+ all_headers['signature'] += Base64.strict_encode64(hmac.digest)
318
+ all_headers['signature'] += '"'
319
+
320
+ return all_headers
321
+ end
322
+
323
+ def self.get_host(host)
324
+ uri = URI(host)
325
+ return "#{uri.hostname}:#{uri.port}"
326
+ end
327
+
328
+ def self.get_date
329
+ DateTime.now.in_time_zone('GMT').strftime("%a, %d %b %Y") + " " + DateTime.now.in_time_zone('GMT').strftime("%H:%M:%S") + " GMT"
330
+ end
331
+ end
332
+
333
+ class SecurityClient::Decryption
334
+ def initialize(creds)
335
+ # Initialize the decryption module object
336
+ # Set the credentials in instance varibales to be used among methods
337
+ # the server to which to make the request
338
+ raise RuntimeError, 'Some of your credentials are missing, please check!' if !validate_creds(creds)
339
+ @host = creds.host.blank? ? VOLTRON_HOST : creds.host
340
+
341
+ # The client's public API key (used to identify the client to the server
342
+ @papi = creds.access_key_id
343
+
344
+ # The client's secret API key (used to authenticate HTTP requests)
345
+ @sapi = creds.secret_signing_key
346
+
347
+ # 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.
348
+ @srsa = creds.secret_crypto_access_key
349
+
350
+ @decryption_ready = true
351
+ @decryption_started = false
352
+
353
+ end
354
+
355
+ def endpoint_base
356
+ @host + '/api/v0'
357
+ end
358
+
359
+ def endpoint
360
+ '/api/v0/decryption/key'
361
+ end
362
+
363
+ def begin
364
+ # Begin the decryption process
365
+
366
+ # This interface does not take any cipher text in its arguments
367
+ # in an attempt to maintain an API that corresponds to the
368
+ # encryption object. In doing so, the work that can take place
369
+ # in this function is limited. without any data, there is no
370
+ # way to determine which key is in use or decrypt any data.
371
+ #
372
+ # this function simply throws an error if starting an decryption
373
+ # while one is already in progress, and initializes the internal
374
+ # buffer
375
+
376
+ raise RuntimeError, 'Decryption is not ready' if !@decryption_ready
377
+
378
+ raise RuntimeError, 'Decryption Already Started' if @decryption_started
379
+
380
+ raise RuntimeError, 'Decryption already in progress' if @key.present? and @key.key?("dec")
381
+ @decryption_started = true
382
+ @data = ''
383
+ end
384
+
385
+ def update(data)
386
+ # Decryption of cipher text is performed here
387
+ # Cipher text must be passed to this function in the order in which it was output from the encryption.update function.
388
+
389
+ # Each encryption has a header on it that identifies the algorithm
390
+ # used and an encryption of the data key that was used to encrypt
391
+ # the original plain text. there is no guarantee how much of that
392
+ # data will be passed to this function or how many times this
393
+ # function will be called to process all of the data. to that end,
394
+ # this function buffers data internally, when it is unable to
395
+ # process it.
396
+ #
397
+ # The function buffers data internally until the entire header is
398
+ # received. once the header has been received, the encrypted data
399
+ # key is sent to the server for decryption. after the header has
400
+ # been successfully handled, this function always decrypts all of
401
+ # the data in its internal buffer *except* for however many bytes
402
+ # are specified by the algorithm's tag size. see the end() function
403
+ # for details.
404
+
405
+ raise RuntimeError, 'Decryption is not Started' if !@decryption_started
406
+
407
+ # Append the incoming data in the internal data buffer
408
+ @data = @data + data
409
+
410
+ # if there is no key or 'dec' member of key, then the code is still trying to build a complete header
411
+ if !@key.present? or !@key.key?("dec")
412
+ struct_length = [1,1,1,1,1].pack('CCCCn').length
413
+ packed_struct = @data[0...struct_length]
414
+
415
+ # Does the buffer contain enough of the header to
416
+ # determine the lengths of the initialization vector
417
+ # and the key?
418
+ if @data.length > struct_length
419
+ # Unpack the values packed in encryption
420
+ version, flag_for_later, algorithm_id, iv_length, key_length = packed_struct.unpack('CCCCn')
421
+
422
+ # verify flag and version are 0
423
+ raise RuntimeError, 'invalid encryption header' if version != 0 or flag_for_later != 0
424
+
425
+ # Does the buffer contain the entire header?
426
+ if @data.length > struct_length + iv_length + key_length
427
+ # Extract the initialization vector
428
+ iv = @data[struct_length...iv_length + struct_length]
429
+ # Extract the encryped key
430
+ encrypted_key = @data[struct_length + iv_length...key_length + struct_length + iv_length]
431
+ # Remove the header from the buffer
432
+ @data = @data[struct_length + iv_length + key_length..-1]
433
+
434
+ # generate a local identifier for the key
435
+ hash_sha512 = OpenSSL::Digest::SHA512.new
436
+ hash_sha512 << encrypted_key
437
+ client_id = hash_sha512.digest
438
+
439
+ if @key.present?
440
+ if @key['client_id'] != client_id
441
+ close()
442
+ end
443
+ end
444
+
445
+ # IF key object not exists, request a new one from the server
446
+ if !@key.present?
447
+ url = endpoint_base + "/decryption/key"
448
+ query = {encrypted_data_key: Base64.strict_encode64(encrypted_key)}
449
+ headers = Auth.build_headers(@papi, @sapi, endpoint, query, @host, 'post')
450
+
451
+ response = HTTParty.post(
452
+ url,
453
+ body: query.to_json,
454
+ headers: headers
455
+ )
456
+
457
+ # Response status is 200 OK
458
+ if response.code == WEBrick::HTTPStatus::RC_OK
459
+ @key = {}
460
+ @key['finger_print'] = response['key_fingerprint']
461
+ @key['client_id'] = client_id
462
+ @key['session'] = response['encryption_session']
463
+
464
+ @key['algorithm'] = 'aes-256-gcm'
465
+
466
+ encrypted_private_key = response['encrypted_private_key']
467
+ # Decrypt the encryped private key using SRSA
468
+ private_key = OpenSSL::PKey::RSA.new(encrypted_private_key,@srsa)
469
+
470
+ wrapped_data_key = response['wrapped_data_key']
471
+ # Decode WDK from base64 format
472
+ wdk = Base64.strict_decode64(wrapped_data_key)
473
+ # Use private key to decrypt the wrapped data key
474
+ dk = private_key.private_decrypt(wdk,OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
475
+
476
+ @key['raw'] = dk
477
+ @key['uses'] = 0
478
+ else
479
+ # Raise the error if response is not 200
480
+ raise RuntimeError, "HTTPError Response: Expected 201, got #{response.code}"
481
+ end
482
+ end
483
+
484
+ # If the key object exists, create a new decryptor
485
+ # with the initialization vector from the header and
486
+ # the decrypted key (which is either new from the
487
+ # server or cached from the previous decryption). in
488
+ # either case, increment the key usage
489
+
490
+ if @key.present?
491
+ @algo = Algo.new.get_algo(@key['algorithm'])
492
+ @key['dec'] = Algo.new.decryptor(@algo, @key['raw'], iv)
493
+ @key['uses'] += 1
494
+ end
495
+ end
496
+ end
497
+ end
498
+
499
+ # if the object has a key and a decryptor, then decrypt whatever
500
+ # data is in the buffer, less any data that needs to be saved to
501
+ # serve as the tag.
502
+ plain_text = ''
503
+ if @key.present? and @key.key?("dec")
504
+ size = @data.length - @algo[:tag_length]
505
+ if size > 0
506
+ puts @data[0..size-1]
507
+
508
+ plain_text = @key['dec'].update(@data[0..size-1])
509
+ @data = @data[size..-1]
510
+ end
511
+ return plain_text
512
+ end
513
+
514
+ end
515
+
516
+ def end
517
+ raise RuntimeError, 'Decryption is not Started' if !@decryption_started
518
+ # The update function always maintains tag-size bytes in
519
+ # the buffer because this function provides no data parameter.
520
+ # by the time the caller calls this function, all data must
521
+ # have already been input to the decryption object.
522
+
523
+ sz = @data.length - @algo[:tag_length]
524
+
525
+ raise RuntimeError, 'Invalid Tag!' if sz < 0
526
+ if sz == 0
527
+ @key['dec'].auth_tag = @data
528
+ begin
529
+ pt = @key['dec'].final
530
+ # Delete the decryptor context
531
+ @key.delete('dec')
532
+ # Return the decrypted plain data
533
+ @decryption_started = false
534
+ return pt
535
+ rescue Exception => e
536
+ print 'Invalid cipher data or tag!'
537
+ return ''
538
+ end
539
+ end
540
+ end
541
+
542
+ def close
543
+ raise RuntimeError, 'Decryption currently running' if @decryption_started
544
+ # Reset the internal state of the decryption object
545
+ if @key.present?
546
+ if @key['uses'] > 0
547
+ query_url = "#{endpoint}/#{@key['finger_print']}/#{@key['session']}"
548
+ url = "#{endpoint_base}/decryption/key/#{@key['finger_print']}/#{@key['session']}"
549
+ query = {uses: @key['uses']}
550
+ headers = Auth.build_headers(@papi, @sapi, query_url, query, @host, 'patch')
551
+ response = HTTParty.patch(
552
+ url,
553
+ body: query.to_json,
554
+ headers: headers
555
+ )
556
+ remove_instance_variable(:@data)
557
+ remove_instance_variable(:@key)
558
+ end
559
+ end
560
+ end
561
+
562
+ end
@@ -0,0 +1,3 @@
1
+ module SecurityClient
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "security_client/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "security_client"
7
+ spec.version = SecurityClient::VERSION
8
+ spec.authors = ["vinaymehta"]
9
+ spec.email = ["vinay.ymca@gmail.com"]
10
+
11
+ spec.summary = %q{Ubiq Security ruby client}
12
+
13
+ # Specify which files should be added to the gem when it is released.
14
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 2.0"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency 'httparty', '~> 0.13.7'
25
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: security_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - vinaymehta
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: httparty
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.13.7
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.13.7
55
+ description:
56
+ email:
57
+ - vinay.ymca@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".DS_Store"
63
+ - ".gitignore"
64
+ - CODE_OF_CONDUCT.md
65
+ - Gemfile
66
+ - README.md
67
+ - Rakefile
68
+ - bin/console
69
+ - bin/setup
70
+ - lib/security_client.rb
71
+ - lib/security_client/version.rb
72
+ - security_client.gemspec
73
+ homepage:
74
+ licenses: []
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubygems_version: 3.0.4
92
+ signing_key:
93
+ specification_version: 4
94
+ summary: Ubiq Security ruby client
95
+ test_files: []