security_client 0.1.0

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