ubiq-security 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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