ubiq-security 1.0.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: 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: []