ubiq-security 1.0.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: 20d4dacbe17d8162ab7d308069b82e2b54b89cf7a7253763919742ef350597e3
4
+ data.tar.gz: 370015b19528bc6a81678e3b0f7f270b0524b6309f23dc6087d04256edad1e27
5
+ SHA512:
6
+ metadata.gz: 7ed8203d50dbe828d649af7decc40f300f1b2b5d74e912db9119e84444c840dc45a8fcc1c8e0b3fa49a8d7a6ebf73fbb9ce935e7244c1669d9d072af61674c52
7
+ data.tar.gz: ddd4e2690d2d47a737fda60d53524d9b57eec456410f14f5e4a56cb61e447e632f6c7a70bc0cc2dfc5126b95191df739dd6484089e07e6126f2d848989b28841
@@ -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 support@ubiqsecurity.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 [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in ubiq.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
8
+ gem "rb-readline"
9
+ gem "httparty"
10
+
@@ -0,0 +1,17 @@
1
+
2
+ Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
3
+
4
+ NOTICE: All information contained herein is, and remains the property
5
+ of Ubiq Security, Inc. The intellectual and technical concepts contained
6
+ herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
7
+ covered by U.S. and Foreign Patents, patents in process, and are
8
+ protected by trade secret or copyright law. Dissemination of this
9
+ information or reproduction of this material is strictly forbidden
10
+ unless prior written permission is obtained from Ubiq Security, Inc.
11
+
12
+ Your use of the software is expressly conditioned upon the terms
13
+ and conditions available at:
14
+
15
+ https://ubiqsecurity.com/legal
16
+
17
+
@@ -0,0 +1,188 @@
1
+ # Ubiq Security Ruby Library
2
+
3
+ The Ubiq Security Ruby library provides convenient interaction with the
4
+ Ubiq Security Platform API from applications written in the Ruby language.
5
+ It includes a pre-defined set of classes that will provide simple interfaces
6
+ to encrypt and decrypt data
7
+
8
+ ## Documentation
9
+
10
+ See the [Ruby API docs](https://ubiqsecurity.com/docs/api?lang=ruby).
11
+
12
+
13
+
14
+ ## Installation
15
+
16
+
17
+ To install using [Bundler][bundler] add the following to your project's Gemfile
18
+
19
+ ```ruby
20
+ gem ubiq-security
21
+ ```
22
+
23
+ To manually install `ubiq-security` via [Rubygems][rubygems] simply use gem to install it:
24
+
25
+ ```sh
26
+ gem install ubiq-security
27
+ ```
28
+
29
+ To build and install directly from a clone of the gitlab repository source:
30
+
31
+ ```sh
32
+ git clone https://gitlab.com/ubiqsecurity/ubiq-ruby.git
33
+ cd ubiq-ruby
34
+ rake install:local
35
+ ```
36
+
37
+
38
+ ## Usage
39
+
40
+ The library needs to be configured with your account credentials which is
41
+ available in your [Ubiq Dashboard][dashboard] [Credentials][credentials]. The credentials can be
42
+ explicitly set, set using environment variables, loaded from an explicit file
43
+ or read from the default location [~/.ubiq/credentials]
44
+
45
+ ```ruby
46
+ require 'ubiq-security'
47
+ include Ubiq
48
+ ```
49
+
50
+ ### Read credentials from a specific file and use a specific profile
51
+ ```ruby
52
+ credentials = ConfigCredentials.new( "some-credential-file", "some-profile").get_attributes
53
+ ```
54
+
55
+
56
+ ### Read credentials from ~/.ubiq/credentials and use the default profile
57
+ ```ruby
58
+ credentials = ConfigCredentials.new().get_attributes
59
+ ```
60
+
61
+
62
+ ### Use the following environment variables to set the credential values
63
+ UBIQ_ACCESS_KEY_ID
64
+ UBIQ_SECRET_SIGNING_KEY
65
+ UBIQ_SECRET_CRYPTO_ACCESS_KEY
66
+ ```ruby
67
+ credentials = Credentials()
68
+ ```
69
+
70
+
71
+ ### Explicitly set the credentials
72
+ ```ruby
73
+ credentials = Credentials(access_key_id = "...", secret_signing_key = "...", secret_crypto_access_key = "...")
74
+ ```
75
+
76
+
77
+
78
+
79
+ ### Encrypt a simple block of data
80
+
81
+ Pass credentials and data into the encryption function. The encrypted data
82
+ will be returned.
83
+
84
+
85
+ ```ruby
86
+ require "ubiq-security"
87
+ include Ubiq
88
+
89
+ encrypted_data = encrypt(credentials, plaintext_data)
90
+ ```
91
+
92
+
93
+ ### Decrypt a simple block of data
94
+
95
+ Pass credentials and encrypted data into the decryption function. The plaintext data
96
+ will be returned.
97
+
98
+ ```ruby
99
+ require "ubiq-security"
100
+ include Ubiq
101
+
102
+ plaintext_data = decrypt(credentials, encrypted_data)
103
+ ```
104
+
105
+
106
+ ### Encrypt a large data element where data is loaded in chunks
107
+
108
+ - Create an encryption object using the credentials.
109
+ - Call the encryption instance begin method
110
+ - Call the encryption instance update method repeatedly until all the data is processed
111
+ - Call the encryption instance end method
112
+ - Call the encryption instance close method
113
+
114
+
115
+ ```ruby
116
+ require "ubiq-security"
117
+ include Ubiq
118
+
119
+ # Process 1 MiB of plaintext data at a time
120
+ BLOCK_SIZE = 1024 * 1024
121
+
122
+ # Rest of the program
123
+ ....
124
+ encryption = Encryption.new(credentials, 1)
125
+
126
+ # Write out the header information
127
+ encrypted_data = encryption.begin()
128
+
129
+ # Loop until the end of the input file is reached
130
+ until infile.eof?
131
+ chunk = infile.read BLOCK_SIZE
132
+ encrypted_data += encryption.update(chunk))
133
+ end
134
+ # Make sure any additional encrypted data is retrieved from encryption instance
135
+ encrypted_data += encryption.end()
136
+
137
+ # Make sure to release any resources used during the encryption process
138
+ encryption.close()
139
+ ```
140
+
141
+ ### Decrypt a large data element where data is loaded in chunks
142
+
143
+ - Create an instance of the decryption object using the credentials.
144
+ - Call the decryption instance begin method
145
+ - Call the decryption instance update method repeatedly until all the data is processed
146
+ - Call the decryption instance end method
147
+ - Call the decryption instance close method
148
+
149
+
150
+ ```ruby
151
+ require "ubiq-security"
152
+ include Ubiq
153
+
154
+ # Process 1 MiB of encrypted data at a time
155
+ BLOCK_SIZE = 1024 * 1024
156
+
157
+ # Rest of the program
158
+ ....
159
+
160
+ decryption = Decryption(credentials)
161
+
162
+ # Start the decryption and get any header information
163
+ plaintext_data = decryption.begin())
164
+
165
+ # Loop until the end of the input file is reached
166
+ until infile.eof?
167
+ chunk = infile.read BLOCK_SIZE
168
+ plaintext_data += decryption.update(chunk)
169
+ end
170
+
171
+ # Make sure an additional plaintext data is retrieved from decryption instance
172
+ plaintext_data += decryption.end()
173
+
174
+ # Make sure to release any resources used during the decryption process
175
+ decryption.close()
176
+ ```
177
+
178
+
179
+
180
+
181
+ [bundler]: https://bundler.io
182
+ [rubygems]: https://rubygems.org
183
+ [gem]: https://rubygems.org/gems/uniq-security
184
+ [dashboard]:https://dev.ubiqsecurity.com/docs/dashboard
185
+ [credentials]:https://dev.ubiqsecurity.com/docs/how-to-create-api-keys
186
+
187
+
188
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,22 @@
1
+ # Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
2
+ #
3
+ # NOTICE: All information contained herein is, and remains the property
4
+ # of Ubiq Security, Inc. The intellectual and technical concepts contained
5
+ # herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
6
+ # covered by U.S. and Foreign Patents, patents in process, and are
7
+ # protected by trade secret or copyright law. Dissemination of this
8
+ # information or reproduction of this material is strictly forbidden
9
+ # unless prior written permission is obtained from Ubiq Security, Inc.
10
+ #
11
+ # Your use of the software is expressly conditioned upon the terms
12
+ # and conditions available at:
13
+ #
14
+ # https://ubiqsecurity.com/legal
15
+
16
+ require "ubiq/version"
17
+ require "ubiq/encrypt"
18
+ require "ubiq/decrypt"
19
+ require "ubiq/credentials"
20
+
21
+
22
+
@@ -0,0 +1,69 @@
1
+ # Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
2
+ #
3
+ # NOTICE: All information contained herein is, and remains the property
4
+ # of Ubiq Security, Inc. The intellectual and technical concepts contained
5
+ # herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
6
+ # covered by U.S. and Foreign Patents, patents in process, and are
7
+ # protected by trade secret or copyright law. Dissemination of this
8
+ # information or reproduction of this material is strictly forbidden
9
+ # unless prior written permission is obtained from Ubiq Security, Inc.
10
+ #
11
+ # Your use of the software is expressly conditioned upon the terms
12
+ # and conditions available at:
13
+ #
14
+ # https://ubiqsecurity.com/legal
15
+ #
16
+
17
+ require "active_support/all"
18
+ require 'openssl'
19
+
20
+ module Ubiq
21
+
22
+ class Algo
23
+
24
+ def set_algo
25
+ @algorithm = {
26
+ "aes-256-gcm"=>{
27
+ id:0,
28
+ algorithm: OpenSSL::Cipher::AES256,
29
+ mode: OpenSSL::Cipher::AES256.new(:GCM),
30
+ key_length: 32,
31
+ iv_length: 12,
32
+ tag_length: 16
33
+ },
34
+ }
35
+ end
36
+
37
+ def get_algo(name)
38
+ set_algo[name]
39
+ end
40
+
41
+ def encryptor(obj,key, iv=nil)
42
+ # key : A byte string containing the key to be used with this encryption
43
+ # If the caller specifies the initialization vector, it must be
44
+ # the correct length and, if so, will be used. If it is not
45
+ # specified, the function will generate a new one
46
+
47
+ cipher = obj[:mode]
48
+ raise RuntimeError, 'Invalid key length' if key.length != obj[:key_length]
49
+
50
+ raise RuntimeError, 'Invalid initialization vector length' if (iv!= nil and iv.length != obj[:iv_length])
51
+ cipher.encrypt
52
+ cipher.key = key
53
+ iv = cipher.random_iv
54
+ return cipher, iv
55
+ end
56
+
57
+ def decryptor(obj, key, iv)
58
+ cipher = obj[:mode]
59
+ raise RuntimeError, 'Invalid key length' if key.length != obj[:key_length]
60
+
61
+ raise RuntimeError, 'Invalid initialization vector length' if (iv!= nil and iv.length != obj[:iv_length])
62
+ cipher = obj[:mode]
63
+ cipher.decrypt
64
+ cipher.key = key
65
+ cipher.iv = iv
66
+ return cipher
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,99 @@
1
+ # Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
2
+ #
3
+ # NOTICE: All information contained herein is, and remains the property
4
+ # of Ubiq Security, Inc. The intellectual and technical concepts contained
5
+ # herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
6
+ # covered by U.S. and Foreign Patents, patents in process, and are
7
+ # protected by trade secret or copyright law. Dissemination of this
8
+ # information or reproduction of this material is strictly forbidden
9
+ # unless prior written permission is obtained from Ubiq Security, Inc.
10
+ #
11
+ # Your use of the software is expressly conditioned upon the terms
12
+ # and conditions available at:
13
+ #
14
+ # https://ubiqsecurity.com/legal
15
+ #
16
+ require "active_support/all"
17
+
18
+ module Ubiq
19
+
20
+ class Auth
21
+ # HTTP Authentication for the Ubiq Platform
22
+
23
+ # This module implements HTTP authentication for the Ubiq platform
24
+ # via message signing as described by the IETF httpbis-message-signatures
25
+ # draft specification.
26
+
27
+ def self.build_headers(papi, sapi, endpoint, query, host, http_method)
28
+
29
+ # This function calculates the signature for the message, adding the Signature header
30
+ # to contain the data. Certain HTTP headers are required for
31
+ # signature calculation and will be added by this code as
32
+ # necessary. The constructed headers object is returned
33
+
34
+ # the '(request-target)' is part of the signed data.
35
+ # it's value is 'http_method path?query'
36
+ reqt = "#{http_method} #{endpoint}"
37
+
38
+ # The time at which the signature was created expressed as the unix epoch
39
+ created = Time.now.to_i
40
+
41
+ # the Digest header is always included/overridden by
42
+ # this code. it is a hash of the body of the http message
43
+ # and is always present even if the body is empty
44
+ hash_sha512 = OpenSSL::Digest::SHA512.new
45
+ hash_sha512 << JSON.dump(query)
46
+ digest = 'SHA-512='+Base64.strict_encode64(hash_sha512.digest)
47
+
48
+ # Initialize the headers object to be returned via this method
49
+ all_headers = {}
50
+ # The content type of request
51
+ all_headers['content-type'] = 'application/json'
52
+ # The request target calculated above(reqt)
53
+ all_headers['(request-target)'] = reqt
54
+ # The date and time in GMT format
55
+ all_headers['date'] = get_date
56
+ # The host specified by the caller
57
+ all_headers['host'] = get_host(host)
58
+ all_headers['(created)'] = created
59
+ all_headers['digest'] = digest
60
+ headers = ['content-type', 'date', 'host', '(created)', '(request-target)', 'digest']
61
+
62
+ # include the specified headers in the hmac calculation. each
63
+ # header is of the form 'header_name: header value\n'
64
+ # included headers are also added to an ordered list of headers
65
+ # which is included in the message
66
+ hmac = OpenSSL::HMAC.new(sapi, OpenSSL::Digest::SHA512.new)
67
+ headers.each do |header|
68
+ if all_headers.key?(header)
69
+ hmac << "#{header}: #{all_headers[header]}\n"
70
+ end
71
+ end
72
+
73
+ all_headers.delete('(created)')
74
+ all_headers.delete('(request-target)')
75
+ all_headers.delete('host')
76
+
77
+ # Build the Signature header itself
78
+ all_headers['signature'] = 'keyId="' + papi + '"'
79
+ all_headers['signature'] += ', algorithm="hmac-sha512"'
80
+ all_headers['signature'] += ', created=' + created.to_s
81
+ all_headers['signature'] += ', headers="' + headers.join(" ") + '"'
82
+ all_headers['signature'] += ', signature="'
83
+ all_headers['signature'] += Base64.strict_encode64(hmac.digest)
84
+ all_headers['signature'] += '"'
85
+
86
+ return all_headers
87
+ end
88
+
89
+ def self.get_host(host)
90
+ uri = URI(host)
91
+ return "#{uri.hostname}:#{uri.port}"
92
+ end
93
+
94
+ def self.get_date
95
+ DateTime.now.in_time_zone('GMT').strftime("%a, %d %b %Y") + " " + DateTime.now.in_time_zone('GMT').strftime("%H:%M:%S") + " GMT"
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,105 @@
1
+ # Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
2
+ #
3
+ # NOTICE: All information contained herein is, and remains the property
4
+ # of Ubiq Security, Inc. The intellectual and technical concepts contained
5
+ # herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
6
+ # covered by U.S. and Foreign Patents, patents in process, and are
7
+ # protected by trade secret or copyright law. Dissemination of this
8
+ # information or reproduction of this material is strictly forbidden
9
+ # unless prior written permission is obtained from Ubiq Security, Inc.
10
+ #
11
+ # Your use of the software is expressly conditioned upon the terms
12
+ # and conditions available at:
13
+ #
14
+ # https://ubiqsecurity.com/legal
15
+ #
16
+ require 'configparser'
17
+ require 'rb-readline'
18
+ require 'byebug'
19
+
20
+ module Ubiq
21
+
22
+ class CredentialsInfo
23
+
24
+ def initialize(access_key_id, secret_signing_key, secret_crypto_access_key, host)
25
+ @access_key_id = access_key_id
26
+ @secret_signing_key = secret_signing_key
27
+ @secret_crypto_access_key = secret_crypto_access_key
28
+ @host = host
29
+ end
30
+
31
+ def set_attributes
32
+ 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)
33
+ end
34
+ end
35
+
36
+
37
+ class ConfigCredentials < CredentialsInfo
38
+ def initialize(config_file, profile)
39
+ # If config file is not found
40
+ if config_file != nil and !File.exists?(config_file)
41
+ raise RuntimeError, "Unable to open config file #{config_file} or contains missing values"
42
+ end
43
+
44
+ if config_file == nil
45
+ config_file = "~/.ubiq/credentials"
46
+ end
47
+
48
+ # If config file is found
49
+ if File.exists?(File.expand_path(config_file))
50
+ @creds = load_config_file(config_file, profile)
51
+ end
52
+ end
53
+
54
+ def get_attributes
55
+ return @creds
56
+ end
57
+
58
+ def load_config_file(file, profile)
59
+ config = ConfigParser.new(File.expand_path(file))
60
+
61
+ # Create empty dictionaries for the default and supplied profile
62
+ p = {}
63
+ d = {}
64
+
65
+ # get the default profile if there is one
66
+ if config['default'].present?
67
+ d = config['default']
68
+ end
69
+
70
+ # get the supplied profile if there is one
71
+ if config[profile].present?
72
+ p = config[profile]
73
+ end
74
+
75
+ # Use given profile if it is available, otherwise use default.
76
+ access_key_id = p.key?('ACCESS_KEY_ID') ? p['ACCESS_KEY_ID'] : d['ACCESS_KEY_ID']
77
+ secret_signing_key = p.key?('SECRET_SIGNING_KEY') ? p['SECRET_SIGNING_KEY'] : d['SECRET_SIGNING_KEY']
78
+ secret_crypto_access_key = p.key?('SECRET_CRYPTO_ACCESS_KEY') ? p['SECRET_CRYPTO_ACCESS_KEY'] : d['SECRET_CRYPTO_ACCESS_KEY']
79
+ host = p.key?('SERVER') ? p['SERVER'] : d['SERVER']
80
+
81
+ # If the provided host does not contain http protocol then add to it
82
+ if !host.include?('http://') and !host.include?('https://')
83
+ host = 'https://' + host
84
+ end
85
+
86
+ return CredentialsInfo.new(access_key_id, secret_signing_key, secret_crypto_access_key, host).set_attributes
87
+ end
88
+ end
89
+
90
+ class Credentials < CredentialsInfo
91
+
92
+ def initialize(papi, sapi, srsa, host)
93
+ @access_key_id = papi.present? ? papi : ENV['UBIQ_ACCESS_KEY_ID']
94
+ @secret_signing_key = sapi.present? ? sapi : ENV['UBIQ_SECRET_SIGNING_KEY']
95
+ @secret_crypto_access_key = srsa.present? ? srsa : ENV['UBIQ_SECRET_CRYPTO_ACCESS_KEY']
96
+ @host = host.present? ? host : ENV['UBIQ_SERVER']
97
+ end
98
+
99
+ @creds = CredentialsInfo.new(@access_key_id, @secret_signing_key, @secret_crypto_access_key, @host).set_attributes
100
+
101
+ def get_attributes
102
+ return @creds
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,268 @@
1
+ # Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
2
+ #
3
+ # NOTICE: All information contained herein is, and remains the property
4
+ # of Ubiq Security, Inc. The intellectual and technical concepts contained
5
+ # herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
6
+ # covered by U.S. and Foreign Patents, patents in process, and are
7
+ # protected by trade secret or copyright law. Dissemination of this
8
+ # information or reproduction of this material is strictly forbidden
9
+ # unless prior written permission is obtained from Ubiq Security, Inc.
10
+ #
11
+ # Your use of the software is expressly conditioned upon the terms
12
+ # and conditions available at:
13
+ #
14
+ # https://ubiqsecurity.com/legal
15
+ #
16
+ require 'rb-readline'
17
+ require 'byebug'
18
+ require 'httparty'
19
+ require "active_support/all"
20
+ require_relative './auth.rb'
21
+ require_relative './algo.rb'
22
+ require_relative './encrypt.rb'
23
+ require 'webrick'
24
+
25
+ module Ubiq
26
+
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
34
+
35
+ # The client's public API key (used to identify the client to the server
36
+ @papi = creds.access_key_id
37
+
38
+ # The client's secret API key (used to authenticate HTTP requests)
39
+ @sapi = creds.secret_signing_key
40
+
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
43
+
44
+ @decryption_ready = true
45
+ @decryption_started = false
46
+
47
+ end
48
+
49
+ def endpoint_base
50
+ @host + '/api/v0'
51
+ end
52
+
53
+ def endpoint
54
+ '/api/v0/decryption/key'
55
+ end
56
+
57
+ def begin
58
+ # Begin the decryption process
59
+
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
+
70
+ raise RuntimeError, 'Decryption is not ready' if !@decryption_ready
71
+
72
+ raise RuntimeError, 'Decryption Already Started' if @decryption_started
73
+
74
+ raise RuntimeError, 'Decryption already in progress' if @key.present? and @key.key?("dec")
75
+ @decryption_started = true
76
+ @data = ''
77
+ end
78
+
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
138
+
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}"
175
+ end
176
+ end
177
+
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
183
+
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
188
+ end
189
+ end
190
+ end
191
+ end
192
+
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]
202
+ end
203
+ return plain_text
204
+ end
205
+
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 ''
230
+ end
231
+ end
232
+ end
233
+
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)
250
+ end
251
+ end
252
+ end
253
+
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
264
+ end
265
+ return res
266
+
267
+ end
268
+ end
@@ -0,0 +1,207 @@
1
+ # Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
2
+ #
3
+ # NOTICE: All information contained herein is, and remains the property
4
+ # of Ubiq Security, Inc. The intellectual and technical concepts contained
5
+ # herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
6
+ # covered by U.S. and Foreign Patents, patents in process, and are
7
+ # protected by trade secret or copyright law. Dissemination of this
8
+ # information or reproduction of this material is strictly forbidden
9
+ # unless prior written permission is obtained from Ubiq Security, Inc.
10
+ #
11
+ # Your use of the software is expressly conditioned upon the terms
12
+ # and conditions available at:
13
+ #
14
+ # https://ubiqsecurity.com/legal
15
+ #
16
+ require 'rb-readline'
17
+ require 'byebug'
18
+ require 'httparty'
19
+ require "active_support/all"
20
+ require_relative './auth.rb'
21
+ require_relative './algo.rb'
22
+ require 'webrick'
23
+
24
+ module Ubiq
25
+
26
+ # Ubiq Encryption object
27
+ # This object represents a single data encryption key and can be used to encrypt several separate plain texts using the same key
28
+ class Encryption
29
+ def initialize(creds, uses)
30
+
31
+ raise RuntimeError, 'Some of your credentials are missing, please check!' if !validate_creds(creds)
32
+
33
+ # Set host, either the default or the one given by caller
34
+ @host = creds.host.blank? ? UBIQ_HOST : creds.host
35
+
36
+ # Set the credentials in instance varibales to be used among methods
37
+ # The client's public API key (used to identify the client to the server
38
+ @papi = creds.access_key_id
39
+
40
+ # The client's secret API key (used to authenticate HTTP requests)
41
+ @sapi = creds.secret_signing_key
42
+
43
+ # 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.
44
+ @srsa = creds.secret_crypto_access_key
45
+
46
+ # Build the endpoint URL
47
+ url = endpoint_base + '/encryption/key'
48
+
49
+ # Build the Request Body with the number of uses of key
50
+ query = {uses: uses}
51
+
52
+ # Retrieve the necessary headers to make the request using Auth Object
53
+ headers = Auth.build_headers(@papi, @sapi, endpoint, query, @host,'post')
54
+
55
+ @encryption_started = false
56
+ @encryption_ready = true
57
+
58
+ # Request a new encryption key from the server. if the request
59
+ # fails, the function raises a HTTPError indicating
60
+ # the status code returned by the server. this exception is
61
+ # propagated back to the caller
62
+
63
+ begin
64
+ response = HTTParty.post(
65
+ url,
66
+ body: query.to_json,
67
+ headers: headers
68
+ )
69
+ rescue HTTParty::Error
70
+ raise RuntimeError, 'Cant reach server'
71
+ end
72
+
73
+ # Response status is 201 Created
74
+ if response.code == WEBrick::HTTPStatus::RC_CREATED
75
+ # The code below largely assumes that the server returns
76
+ # a json object that contains the members and is formatted
77
+ # according to the Ubiq REST specification.
78
+
79
+ # Build the key object
80
+ @key = {}
81
+ @key['id'] = response['key_fingerprint']
82
+ @key['session'] = response['encryption_session']
83
+ @key['security_model'] = response['security_model']
84
+ @key['algorithm'] = response['security_model']['algorithm'].downcase
85
+ @key['max_uses'] = response['max_uses']
86
+ @key['uses'] = 0
87
+ @key['encrypted'] = Base64.strict_decode64(response['encrypted_data_key'])
88
+
89
+ # Get encrypted private key from response body
90
+ encrypted_private_key = response['encrypted_private_key']
91
+ # Get wrapped data key from response body
92
+ wrapped_data_key = response['wrapped_data_key']
93
+ # Decrypt the encryped private key using @srsa supplied
94
+ private_key = OpenSSL::PKey::RSA.new(encrypted_private_key,@srsa)
95
+ # Decode WDK from base64 format
96
+ wdk = Base64.strict_decode64(wrapped_data_key)
97
+ # Use private key to decrypt the wrapped data key
98
+ dk = private_key.private_decrypt(wdk,OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
99
+ @key['raw'] = dk
100
+ # Build the algorithm object
101
+ @algo = Algo.new.get_algo(@key['algorithm'])
102
+ else
103
+ # Raise the error if response is not 201
104
+ raise RuntimeError, "HTTPError Response: Expected 201, got #{response.code}"
105
+ end
106
+
107
+ end
108
+
109
+ def begin
110
+ # Begin the encryption process
111
+
112
+ # When this function is called, the encryption object increments
113
+ # the number of uses of the key and creates a new internal context
114
+ # to be used to encrypt the data.
115
+ # If the encryption object is not yet ready to be used, throw an error
116
+ raise RuntimeError, 'Encryption not ready' if !@encryption_ready
117
+
118
+ # if Encryption cipher context already exists
119
+ raise RuntimeError, 'Encryption already in progress' if @encryption_started
120
+ # If max uses > uses
121
+ raise RuntimeError, 'Maximum key uses exceeded' if @key['uses'] >= @key['max_uses']
122
+ @key['uses'] += 1
123
+ # create a new Encryption context and initialization vector
124
+ @enc , @iv = Algo.new.encryptor(@algo, @key['raw'])
125
+
126
+ # Pack the result into bytes to get a byte string
127
+ struct = [0, 0, @algo[:id], @iv.length, @key['encrypted'].length].pack('CCCCn')
128
+ @encryption_started = true
129
+ return struct + @iv + @key['encrypted']
130
+ end
131
+
132
+ def update(data)
133
+ raise RuntimeError, 'Encryption is not Started' if !@encryption_started
134
+ # Encryption of some plain text is perfomed here
135
+ # Any cipher text produced by the operation is returned
136
+ @enc.update(data)
137
+ end
138
+
139
+ def end
140
+ raise RuntimeError, 'Encryption is not Started' if !@encryption_started
141
+ # This function finalizes the encryption (producing the final
142
+ # cipher text for the encryption, if necessary) and adds any
143
+ # authentication information (if required by the algorithm).
144
+ # Any data produced is returned by the function.
145
+
146
+ # Finalize an encryption
147
+ res = @enc.final
148
+ if @algo[:tag_length] != 0
149
+ # Add the tag to the cipher text
150
+ res+= @enc.auth_tag
151
+ end
152
+ @encryption_started = false
153
+ # Return the encrypted result
154
+ return res
155
+ end
156
+
157
+ def close
158
+ raise RuntimeError, 'Encryption currently running' if @encryption_started
159
+ # If the key was used less times than was requested, send an update to the server
160
+ if @key['uses'] < @key['max_uses']
161
+ query_url = "#{endpoint}/#{@key['id']}/#{@key['session']}"
162
+ url = "#{endpoint_base}/encryption/key/#{@key['id']}/#{@key['session']}"
163
+ query = {actual: @key['uses'], requested: @key['max_uses']}
164
+ headers = Auth.build_headers(@papi, @sapi, query_url, query, @host, 'patch')
165
+ response = HTTParty.patch(
166
+ url,
167
+ body: query.to_json,
168
+ headers: headers
169
+ )
170
+ remove_instance_variable(:@key)
171
+ @encryption_ready = false;
172
+ end
173
+ end
174
+
175
+ def endpoint_base
176
+ @host + '/api/v0'
177
+ end
178
+
179
+ def endpoint
180
+ '/api/v0/encryption/key'
181
+ end
182
+
183
+ def validate_creds(credentials)
184
+ # This method checks for the presence of the credentials
185
+ !credentials.access_key_id.blank? and !credentials.secret_signing_key.blank? and !credentials.secret_crypto_access_key.blank?
186
+ end
187
+
188
+ end
189
+
190
+ def validate_creds(credentials)
191
+ # This method checks for the presence of the credentials
192
+ !credentials.access_key_id.blank? and !credentials.secret_signing_key.blank? and !credentials.secret_crypto_access_key.blank?
193
+ end
194
+
195
+ def encrypt(creds, data)
196
+ begin
197
+ enc = Encryption.new(creds, 1)
198
+ res = enc.begin() + enc.update(data) + enc.end()
199
+ enc.close()
200
+ rescue
201
+ enc.close() if enc
202
+ raise
203
+ end
204
+ return res
205
+ end
206
+
207
+ end
@@ -0,0 +1,18 @@
1
+ # Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
2
+ #
3
+ # NOTICE: All information contained herein is, and remains the property
4
+ # of Ubiq Security, Inc. The intellectual and technical concepts contained
5
+ # herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
6
+ # covered by U.S. and Foreign Patents, patents in process, and are
7
+ # protected by trade secret or copyright law. Dissemination of this
8
+ # information or reproduction of this material is strictly forbidden
9
+ # unless prior written permission is obtained from Ubiq Security, Inc.
10
+ #
11
+ # Your use of the software is expressly conditioned upon the terms
12
+ # and conditions available at:
13
+ #
14
+ # https://ubiqsecurity.com/legal
15
+ #
16
+ module Ubiq
17
+ UBIQ_HOST = 'api.ubiqsecurity.com:8811'
18
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright 2020 Ubiq Security, Inc., Proprietary and All Rights Reserved.
2
+ #
3
+ # NOTICE: All information contained herein is, and remains the property
4
+ # of Ubiq Security, Inc. The intellectual and technical concepts contained
5
+ # herein are proprietary to Ubiq Security, Inc. and its suppliers and may be
6
+ # covered by U.S. and Foreign Patents, patents in process, and are
7
+ # protected by trade secret or copyright law. Dissemination of this
8
+ # information or reproduction of this material is strictly forbidden
9
+ # unless prior written permission is obtained from Ubiq Security, Inc.
10
+ #
11
+ # Your use of the software is expressly conditioned upon the terms
12
+ # and conditions available at:
13
+ #
14
+ # https://ubiqsecurity.com/legal
15
+ #
16
+
17
+ # frozen_string_literal: true
18
+
19
+ module Ubiq
20
+ VERSION = "1.0.0"
21
+ end
22
+
@@ -0,0 +1,30 @@
1
+ require_relative 'lib/ubiq/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "ubiq-security"
5
+ spec.version = Ubiq::VERSION
6
+ spec.authors = ["Ubiq Security, Inc."]
7
+ spec.email = ["support@ubiqsecurity.com"]
8
+
9
+ spec.summary = %q{Ruby Client Library for accessing the Ubiq Platform}
10
+ spec.description = "Provide data encryption to any application with a couple of API calls. " \
11
+ "See https://www.ubiqsecurity.com for details."
12
+ spec.homepage = "https://dev.ubiqsecurity.com/docs/ruby-library"
13
+ spec.license = "Nonstandard"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://gitlab.com/ubiqsecurity/ubiq-ruby"
18
+ spec.metadata["changelog_uri"] = "https://gitlab.com/ubiqsecurity/ubiq-ruby/-/blob/master/README.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|example)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+ spec.add_runtime_dependency 'rb-readline', '~> 0.2', '>= 0.2'
29
+ spec.add_runtime_dependency 'httparty', '~> 0.15', '>= 0.15'
30
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ubiq-security
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ubiq Security, Inc.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rb-readline
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '0.2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '0.2'
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: httparty
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0.15'
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '0.15'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0.15'
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '0.15'
53
+ description: Provide data encryption to any application with a couple of API calls. See
54
+ https://www.ubiqsecurity.com for details.
55
+ email:
56
+ - support@ubiqsecurity.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - CODE_OF_CONDUCT.md
62
+ - Gemfile
63
+ - LICENSE.txt
64
+ - README.md
65
+ - Rakefile
66
+ - lib/ubiq-security.rb
67
+ - lib/ubiq/algo.rb
68
+ - lib/ubiq/auth.rb
69
+ - lib/ubiq/credentials.rb
70
+ - lib/ubiq/decrypt.rb
71
+ - lib/ubiq/encrypt.rb
72
+ - lib/ubiq/host.rb
73
+ - lib/ubiq/version.rb
74
+ - ubiq-security.gemspec
75
+ homepage: https://dev.ubiqsecurity.com/docs/ruby-library
76
+ licenses:
77
+ - Nonstandard
78
+ metadata:
79
+ homepage_uri: https://dev.ubiqsecurity.com/docs/ruby-library
80
+ source_code_uri: https://gitlab.com/ubiqsecurity/ubiq-ruby
81
+ changelog_uri: https://gitlab.com/ubiqsecurity/ubiq-ruby/-/blob/master/README.md
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 2.3.0
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.0.3
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Ruby Client Library for accessing the Ubiq Platform
101
+ test_files: []