ubiq-security 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/README.md +4 -6
- data/lib/ubiq-security.rb +5 -6
- data/lib/ubiq/algo.rb +44 -40
- data/lib/ubiq/auth.rb +72 -71
- data/lib/ubiq/credentials.rb +75 -65
- data/lib/ubiq/decrypt.rb +220 -210
- data/lib/ubiq/encrypt.rb +156 -146
- data/lib/ubiq/host.rb +1 -1
- data/lib/ubiq/version.rb +1 -2
- metadata +1 -1
data/lib/ubiq/encrypt.rb
CHANGED
@@ -13,154 +13,161 @@
|
|
13
13
|
#
|
14
14
|
# https://ubiqsecurity.com/legal
|
15
15
|
#
|
16
|
+
|
17
|
+
# frozen_string_literal: true
|
18
|
+
|
16
19
|
require 'rb-readline'
|
17
20
|
require 'byebug'
|
18
21
|
require 'httparty'
|
19
|
-
require
|
22
|
+
require 'active_support/all'
|
20
23
|
require_relative './auth.rb'
|
21
24
|
require_relative './algo.rb'
|
22
25
|
require 'webrick'
|
23
26
|
|
27
|
+
# Ubiq Security Modules for encrypting / decrypting data
|
24
28
|
module Ubiq
|
29
|
+
# Ubiq Encryption object
|
30
|
+
# This object represents a single data encryption key and can be used to
|
31
|
+
# encrypt several separate plain texts using the same key
|
32
|
+
class Encryption
|
33
|
+
def initialize(creds, uses)
|
34
|
+
raise 'Some of your credentials are missing, please check!' unless validate_creds(creds)
|
25
35
|
|
26
|
-
#
|
27
|
-
|
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
|
36
|
+
# Set host, either the default or the one given by caller
|
37
|
+
@host = creds.host.blank? ? UBIQ_HOST : creds.host
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
# Set the credentials in instance varibales to be used among methods
|
40
|
+
# The client's public API key (used to identify the client to the server
|
41
|
+
@papi = creds.access_key_id
|
39
42
|
|
40
|
-
|
41
|
-
|
43
|
+
# The client's secret API key (used to authenticate HTTP requests)
|
44
|
+
@sapi = creds.secret_signing_key
|
42
45
|
|
43
|
-
|
44
|
-
|
46
|
+
# The client's secret RSA encryption key/password (used to decrypt the
|
47
|
+
# client's RSA key from the server). This key is not retained by this object.
|
48
|
+
@srsa = creds.secret_crypto_access_key
|
45
49
|
|
46
|
-
|
47
|
-
|
50
|
+
# Build the endpoint URL
|
51
|
+
url = endpoint_base + '/encryption/key'
|
48
52
|
|
49
|
-
|
50
|
-
|
53
|
+
# Build the Request Body with the number of uses of key
|
54
|
+
query = { uses: uses }
|
51
55
|
|
52
|
-
|
53
|
-
|
56
|
+
# Retrieve the necessary headers to make the request using Auth Object
|
57
|
+
headers = Auth.build_headers(@papi, @sapi, endpoint, query, @host, 'post')
|
54
58
|
|
55
|
-
|
56
|
-
|
59
|
+
@encryption_started = false
|
60
|
+
@encryption_ready = true
|
57
61
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
+
# Request a new encryption key from the server. if the request
|
63
|
+
# fails, the function raises a HTTPError indicating
|
64
|
+
# the status code returned by the server. this exception is
|
65
|
+
# propagated back to the caller
|
62
66
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
begin
|
68
|
+
response = HTTParty.post(
|
69
|
+
url,
|
70
|
+
body: query.to_json,
|
71
|
+
headers: headers
|
72
|
+
)
|
73
|
+
rescue HTTParty::Error
|
74
|
+
raise 'Cant reach server'
|
75
|
+
end
|
76
|
+
|
77
|
+
# Response status is 201 Created
|
78
|
+
if response.code == WEBrick::HTTPStatus::RC_CREATED
|
79
|
+
# The code below largely assumes that the server returns
|
80
|
+
# a json object that contains the members and is formatted
|
81
|
+
# according to the Ubiq REST specification.
|
82
|
+
|
83
|
+
# Build the key object
|
84
|
+
@key = {}
|
85
|
+
@key['id'] = response['key_fingerprint']
|
86
|
+
@key['session'] = response['encryption_session']
|
87
|
+
@key['security_model'] = response['security_model']
|
88
|
+
@key['algorithm'] = response['security_model']['algorithm'].downcase
|
89
|
+
@key['max_uses'] = response['max_uses']
|
90
|
+
@key['uses'] = 0
|
91
|
+
@key['encrypted'] = Base64.strict_decode64(response['encrypted_data_key'])
|
92
|
+
|
93
|
+
# Get encrypted private key from response body
|
94
|
+
encrypted_private_key = response['encrypted_private_key']
|
95
|
+
# Get wrapped data key from response body
|
96
|
+
wrapped_data_key = response['wrapped_data_key']
|
97
|
+
# Decrypt the encryped private key using @srsa supplied
|
98
|
+
private_key = OpenSSL::PKey::RSA.new(encrypted_private_key, @srsa)
|
99
|
+
# Decode WDK from base64 format
|
100
|
+
wdk = Base64.strict_decode64(wrapped_data_key)
|
101
|
+
# Use private key to decrypt the wrapped data key
|
102
|
+
dk = private_key.private_decrypt(wdk, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
103
|
+
@key['raw'] = dk
|
104
|
+
# Build the algorithm object
|
105
|
+
@algo = Algo.new.get_algo(@key['algorithm'])
|
106
|
+
else
|
107
|
+
# Raise the error if response is not 201
|
108
|
+
raise "HTTPError Response: Expected 201, got #{response.code}"
|
109
|
+
end
|
71
110
|
end
|
72
111
|
|
73
|
-
|
74
|
-
|
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
|
112
|
+
def begin
|
113
|
+
# Begin the encryption process
|
106
114
|
|
107
|
-
|
115
|
+
# When this function is called, the encryption object increments
|
116
|
+
# the number of uses of the key and creates a new internal context
|
117
|
+
# to be used to encrypt the data.
|
118
|
+
# If the encryption object is not yet ready to be used, throw an error
|
119
|
+
raise 'Encryption not ready' unless @encryption_ready
|
108
120
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
121
|
+
# if Encryption cipher context already exists
|
122
|
+
raise 'Encryption already in progress' if @encryption_started
|
123
|
+
# If max uses > uses
|
124
|
+
raise 'Maximum key uses exceeded' if @key['uses'] >= @key['max_uses']
|
131
125
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
# Any cipher text produced by the operation is returned
|
136
|
-
@enc.update(data)
|
137
|
-
end
|
126
|
+
@key['uses'] += 1
|
127
|
+
# create a new Encryption context and initialization vector
|
128
|
+
@enc, @iv = Algo.new.encryptor(@algo, @key['raw'])
|
138
129
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
#
|
150
|
-
|
130
|
+
# Pack the result into bytes to get a byte string
|
131
|
+
struct = [0, 0, @algo[:id], @iv.length, @key['encrypted'].length].pack('CCCCn')
|
132
|
+
@encryption_started = true
|
133
|
+
return struct + @iv + @key['encrypted']
|
134
|
+
end
|
135
|
+
|
136
|
+
def update(data)
|
137
|
+
raise 'Encryption is not Started' unless @encryption_started
|
138
|
+
|
139
|
+
# Encryption of some plain text is perfomed here
|
140
|
+
# Any cipher text produced by the operation is returned
|
141
|
+
@enc.update(data)
|
151
142
|
end
|
152
|
-
@encryption_started = false
|
153
|
-
# Return the encrypted result
|
154
|
-
return res
|
155
|
-
end
|
156
143
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
144
|
+
def end
|
145
|
+
raise 'Encryption is not Started' unless @encryption_started
|
146
|
+
|
147
|
+
# This function finalizes the encryption (producing the final
|
148
|
+
# cipher text for the encryption, if necessary) and adds any
|
149
|
+
# authentication information (if required by the algorithm).
|
150
|
+
# Any data produced is returned by the function.
|
151
|
+
|
152
|
+
# Finalize an encryption
|
153
|
+
res = @enc.final
|
154
|
+
if @algo[:tag_length] != 0
|
155
|
+
# Add the tag to the cipher text
|
156
|
+
res += @enc.auth_tag
|
157
|
+
end
|
158
|
+
@encryption_started = false
|
159
|
+
# Return the encrypted result
|
160
|
+
return res
|
161
|
+
end
|
162
|
+
|
163
|
+
def close
|
164
|
+
raise 'Encryption currently running' if @encryption_started
|
165
|
+
|
166
|
+
# If the key was used less times than was requested, send an update to the server
|
167
|
+
if @key['uses'] < @key['max_uses']
|
168
|
+
query_url = "#{endpoint}/#{@key['id']}/#{@key['session']}"
|
162
169
|
url = "#{endpoint_base}/encryption/key/#{@key['id']}/#{@key['session']}"
|
163
|
-
query = {actual: @key['uses'], requested: @key['max_uses']}
|
170
|
+
query = { actual: @key['uses'], requested: @key['max_uses'] }
|
164
171
|
headers = Auth.build_headers(@papi, @sapi, query_url, query, @host, 'patch')
|
165
172
|
response = HTTParty.patch(
|
166
173
|
url,
|
@@ -168,40 +175,43 @@ class Encryption
|
|
168
175
|
headers: headers
|
169
176
|
)
|
170
177
|
remove_instance_variable(:@key)
|
171
|
-
|
178
|
+
@encryption_ready = false
|
179
|
+
end
|
172
180
|
end
|
173
|
-
end
|
174
181
|
|
175
|
-
|
176
|
-
|
177
|
-
|
182
|
+
def endpoint_base
|
183
|
+
@host + '/api/v0'
|
184
|
+
end
|
185
|
+
|
186
|
+
def endpoint
|
187
|
+
'/api/v0/encryption/key'
|
188
|
+
end
|
178
189
|
|
179
|
-
|
180
|
-
|
190
|
+
def validate_creds(credentials)
|
191
|
+
# This method checks for the presence of the credentials
|
192
|
+
!credentials.access_key_id.blank? &&
|
193
|
+
!credentials.secret_signing_key.blank? &&
|
194
|
+
!credentials.secret_crypto_access_key.blank?
|
195
|
+
end
|
181
196
|
end
|
182
197
|
|
183
198
|
def validate_creds(credentials)
|
184
199
|
# This method checks for the presence of the credentials
|
185
|
-
!credentials.access_key_id.blank?
|
200
|
+
!credentials.access_key_id.blank? &&
|
201
|
+
!credentials.secret_signing_key.blank? &&
|
202
|
+
!credentials.secret_crypto_access_key.blank?
|
186
203
|
end
|
187
204
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
res = enc.begin() + enc.update(data) + enc.end()
|
199
|
-
enc.close()
|
200
|
-
rescue
|
201
|
-
enc.close() if enc
|
202
|
-
raise
|
205
|
+
def encrypt(creds, data)
|
206
|
+
begin
|
207
|
+
enc = Encryption.new(creds, 1)
|
208
|
+
res = enc.begin + enc.update(data) + enc.end
|
209
|
+
enc.close
|
210
|
+
rescue StandardError
|
211
|
+
enc&.close
|
212
|
+
raise
|
213
|
+
end
|
214
|
+
return res
|
203
215
|
end
|
204
|
-
return res
|
205
216
|
end
|
206
217
|
|
207
|
-
end
|
data/lib/ubiq/host.rb
CHANGED
data/lib/ubiq/version.rb
CHANGED