cryptograpi_ruby 0.0.1 → 0.0.1.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/Gemfile +4 -1
- data/Rakefile +13 -1
- data/cryptograpi_ruby.gemspec +15 -0
- data/lib/cryptograpi_ruby/cipher.rb +71 -0
- data/lib/cryptograpi_ruby/credentials.rb +87 -0
- data/lib/cryptograpi_ruby/decrypt.rb +200 -0
- data/lib/cryptograpi_ruby/encrypt.rb +165 -0
- data/lib/cryptograpi_ruby/host.rb +3 -0
- data/lib/cryptograpi_ruby/signature.rb +66 -0
- data/lib/cryptograpi_ruby/version.rb +1 -1
- data/lib/cryptograpi_ruby.rb +17 -1
- metadata +149 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba7b04c4eda2c473ba5f06e06780b132d253e2306102e0a6e06d7f13d3731a4c
|
4
|
+
data.tar.gz: 99bbff7d24a71953d31348b21f41fb58378d64aaebf73cbbb35f4531d250eac7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 183e28010a0cd233dda749e7998470a89b0707fdfc58115d26ee9a9377ddc4c5b611aa5a6df27ff02a3f05146687254cf35a142c3f7e61173e845e1872ab00e7
|
7
|
+
data.tar.gz: '0971e1bd891126004b3233a972152f94ea86cf4020ee79451d44ceca174b42ad619db7f3126a6f0d2c425dba145a5f957d3401bd22ebceaed31cd42bbe39d6bf'
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,8 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rake'
|
4
|
-
require 'rubocop/rake_task'
|
5
4
|
|
5
|
+
begin
|
6
|
+
require 'bundler/setup'
|
7
|
+
Bundler::GemHelper.install_tasks
|
8
|
+
rescue LoadError
|
9
|
+
puts 'it is recommended to run bundler for running the tests'
|
10
|
+
end
|
11
|
+
|
12
|
+
task default: :spec
|
13
|
+
|
14
|
+
require 'rspec/core/rake_task'
|
15
|
+
RSpec::Core::RakeTask.new(:spec)
|
16
|
+
|
17
|
+
require 'rubocop/rake_task'
|
6
18
|
RuboCop::RakeTask.new do |task|
|
7
19
|
task.requires << 'rubocop-performance'
|
8
20
|
task.requires << 'rubocop-rspec'
|
data/cryptograpi_ruby.gemspec
CHANGED
@@ -22,8 +22,23 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency 'rubocop', '~> 1.18'
|
23
23
|
spec.add_development_dependency 'rubocop-performance', '~> 1.11'
|
24
24
|
spec.add_development_dependency 'rubocop-rspec', '~> 2.4'
|
25
|
+
spec.add_development_dependency 'codecov', '~> 0.1'
|
26
|
+
spec.add_development_dependency 'dotenv', '~> 2.5'
|
27
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.6'
|
29
|
+
spec.add_development_dependency 'simplecov', '~> 0.16'
|
30
|
+
spec.add_development_dependency 'vcr', '~> 6.0'
|
31
|
+
|
32
|
+
# Use an embedded rails app for testing
|
33
|
+
spec.add_development_dependency 'rails', '~> 6.1'
|
34
|
+
spec.add_development_dependency 'rspec-rails', '~> 4.0'
|
35
|
+
|
36
|
+
|
37
|
+
|
25
38
|
spec.add_runtime_dependency 'activesupport', '~> 6.1'
|
39
|
+
spec.add_runtime_dependency 'configparser', '>=0.1'
|
26
40
|
spec.add_runtime_dependency 'httparty', '~> 0.18'
|
27
41
|
spec.add_runtime_dependency 'rb-readline', '~> 0.5'
|
42
|
+
spec.add_runtime_dependency 'tzinfo-data', '>= 1'
|
28
43
|
spec.add_runtime_dependency 'webrick', '~> 1.7'
|
29
44
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module Cryptograpi
|
5
|
+
class Cipher
|
6
|
+
|
7
|
+
CRYPTOFLAG = 0b0000001
|
8
|
+
|
9
|
+
def set_algorithm
|
10
|
+
@algorithm = {
|
11
|
+
'aes-128-gcm' => {
|
12
|
+
id: 0,
|
13
|
+
algorithm: OpenSSL::Cipher::AES128,
|
14
|
+
mode: OpenSSL::Cipher.new('aes-128-gcm'),
|
15
|
+
key_length: 16,
|
16
|
+
iv_length: 12,
|
17
|
+
tag_length: 16
|
18
|
+
},
|
19
|
+
'aes-256-gcm' => {
|
20
|
+
id: 1,
|
21
|
+
algorithm: OpenSSL::Cipher::AES256,
|
22
|
+
mode: OpenSSL::Cipher.new('aes-256-gcm'),
|
23
|
+
key_length: 32,
|
24
|
+
iv_length: 12,
|
25
|
+
tag_length: 16
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_algorithm(id)
|
31
|
+
set_algorithm.each do |k, v|
|
32
|
+
return k if v[:id] == id
|
33
|
+
end
|
34
|
+
'unknown'
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_algorithm(name)
|
38
|
+
set_algorithm[name]
|
39
|
+
end
|
40
|
+
|
41
|
+
def encryptor(obj, key, init_vector = nil)
|
42
|
+
# The 'key' parameter is a byte string that contains the key
|
43
|
+
# for this encryption operation.
|
44
|
+
# If there is an, correct length, initialization vector (init_vector)
|
45
|
+
# then it is used. If not, the function generates one.
|
46
|
+
raise 'Invalid key length' if key.length != obj[:key_length]
|
47
|
+
|
48
|
+
raise 'Invalid initialization vector length' if !init_vector.nil? && init_vector.length != obj[:init_vector_length]
|
49
|
+
|
50
|
+
cipher = obj[:mode]
|
51
|
+
cipher.encrypt
|
52
|
+
cipher.key = key
|
53
|
+
init_vector = cipher.random_init_vector
|
54
|
+
|
55
|
+
[cipher, init_vector]
|
56
|
+
end
|
57
|
+
|
58
|
+
def decryptor(obj, key, init_vector)
|
59
|
+
raise 'Invalid key length' if key.length != obj[:key_length]
|
60
|
+
|
61
|
+
raise 'Invalid initialization vector length' if !init_vector.nil? && init_vector.length != obj[:init_vector_length]
|
62
|
+
|
63
|
+
cipher = obj[:mode]
|
64
|
+
cipher.decrypt
|
65
|
+
cipher.key = key
|
66
|
+
cipher.init_vector = init_vector
|
67
|
+
|
68
|
+
cipher
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'configparser'
|
4
|
+
require 'rb-readline'
|
5
|
+
require_relative './host'
|
6
|
+
|
7
|
+
module Cryptograpi
|
8
|
+
class Info
|
9
|
+
def initialize(access_key_id, secret_access_key, signing_key, host)
|
10
|
+
@access_key_id = access_key_id
|
11
|
+
@secret_access_key = secret_access_key
|
12
|
+
@signing_key = signing_key
|
13
|
+
@host = host
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_attrs
|
17
|
+
OpenStruct.new(
|
18
|
+
access_key_id: @access_key_id,
|
19
|
+
secret_access_key: @secret_access_key,
|
20
|
+
signing_key: @signing_key,
|
21
|
+
host: @host
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Loads and reads a credential file or uses default info
|
27
|
+
class ConfigCredentials < Info
|
28
|
+
def initialize(configuration_file, profile)
|
29
|
+
# Check if the file exists
|
30
|
+
raise "There is an error finding or reading the #{configuration_file} file" if !configuration_file.nil? && !File.exist?(configuration_file)
|
31
|
+
|
32
|
+
configuration_file = '~/.cryptograpi/credentials' if configuration_file.nil?
|
33
|
+
|
34
|
+
@credentials = load_configuration_file(configuration_file, profile) if File.exist?(File.expand_path(configuration_file))
|
35
|
+
end
|
36
|
+
|
37
|
+
def attrs
|
38
|
+
@credentials
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_configuration_file(file, profile)
|
42
|
+
config = ConfigParser.new(File.expand_path(file))
|
43
|
+
|
44
|
+
# Dict for profiles
|
45
|
+
p = {}
|
46
|
+
d = {}
|
47
|
+
|
48
|
+
# If there is a default profile, get it
|
49
|
+
d = config['default'] if config['default'].present?
|
50
|
+
|
51
|
+
d['SERVER'] = Cryptograpi::CRYPTOGRAPI_HOST unless d.key?('SERVER')
|
52
|
+
|
53
|
+
# If there is a supplied profile, get it
|
54
|
+
p = config[profile] if config[profile].present?
|
55
|
+
|
56
|
+
# Use the supplied profile. Otherwise use default
|
57
|
+
access_key_id = p.key?('ACCESS_KEY_ID') ? p['ACCESS_KEY_ID'] : d['ACCESS_KEY_ID']
|
58
|
+
secret_access_key = p.key?('SECRET_ACCESS_KEY') ? p['SECRET_ACCESS_KEY'] : d['SECRET_ACCESS_KEY']
|
59
|
+
signing_key = p.key?('SIGNING_KEY') ? p['SIGNING_KEY'] : d['SIGNING_KEY']
|
60
|
+
host = p.key?('SERVER') ? p['SERVER'] : d['SERVER']
|
61
|
+
|
62
|
+
# Sanitizing the host variable to always include https
|
63
|
+
host = "https://#{host}" if !host.include?('http://') && !host.include?('https://')
|
64
|
+
|
65
|
+
Info.new(access_key_id, secret_access_key, signing_key, host).set_attrs
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Credentials can be explicitly set or
|
70
|
+
# use info from ENV Variables
|
71
|
+
class Credentials < Info
|
72
|
+
def initialize(papi, sapi, srsa, host)
|
73
|
+
super
|
74
|
+
@access_key_id = papi.present? ? papi : ENV['CRYPTOGRAPI_ACCESS_KEY_ID']
|
75
|
+
@secret_access_key = sapi.present? ? sapi : ENV['CRYPTOGRAPI_SECRET_ACCESS_KEY']
|
76
|
+
@signing_key = srsa.present? ? srsa : ENV['CRYPTOGRAPI_SIGNING_KEY']
|
77
|
+
@host = host.present? ? host : ENV['CRYPTOGRAPI_SERVER']
|
78
|
+
end
|
79
|
+
|
80
|
+
@creds = Info.new(@access_key_id, @secret_access_key, @signing_key, @host).set_attrs
|
81
|
+
|
82
|
+
def attrs
|
83
|
+
@creds
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'httparty'
|
3
|
+
require 'rb-readline'
|
4
|
+
require 'webrick'
|
5
|
+
require_relative './cipher'
|
6
|
+
require_relative './encrypt'
|
7
|
+
require_relative './signature'
|
8
|
+
|
9
|
+
module Cryptograpi
|
10
|
+
class Decryption
|
11
|
+
def initialize(creds)
|
12
|
+
raise 'Some credentials are missing' unless validate_credentials(creds)
|
13
|
+
|
14
|
+
@papi = creds.access_key_id
|
15
|
+
@sapi = creds.signing_key
|
16
|
+
@srsa = creds.secret_access_key
|
17
|
+
@host = creds.host.blank? ? CRYPTOGRAPI_HOST : creds.host
|
18
|
+
|
19
|
+
@decryption_started = false
|
20
|
+
@decryption_ready = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def begin_decryption
|
24
|
+
raise ' Decryption is not ready' unless @decryption_ready
|
25
|
+
|
26
|
+
raise ' Decryption already started' if @decryption_started
|
27
|
+
|
28
|
+
raise 'Decryption already in progress' if @key.present? && @key.key?('dec')
|
29
|
+
|
30
|
+
@decryption_started = true
|
31
|
+
@data = ''
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_decryption(data)
|
35
|
+
raise ' Decryption is not started' unless @decryption_started
|
36
|
+
|
37
|
+
# Act as a buffer for data
|
38
|
+
@data += data
|
39
|
+
|
40
|
+
# If there is no key or dec member of key, a header is still being built
|
41
|
+
if !@key.present? || !@key.key?('dec')
|
42
|
+
struct_length = [1, 1, 1, 1, 1].pack('CCCCn').length
|
43
|
+
packed_struct = @data[0...struct_length]
|
44
|
+
|
45
|
+
# Does the buffer contain enough of the header to determine
|
46
|
+
# the lengths of the initialization vector and the key?
|
47
|
+
if @data.length > struct_length
|
48
|
+
version, flags, algorithm_id, iv_length, key_length = packed_struct.unpack('CCCCn')
|
49
|
+
|
50
|
+
raise 'Invalid encryption header' if (version != 0) || ((flags & ~Cipher::CRYPTOFLAG) != 0)
|
51
|
+
|
52
|
+
if @data.length > struct_length + iv_length + key_length
|
53
|
+
# Extract the initialization vector
|
54
|
+
iv = @data[struct_length...iv_length + struct_length]
|
55
|
+
# Extract the encrypted key
|
56
|
+
enc_key = @data[struct_length + iv_length..key_length + struct_length + iv_length]
|
57
|
+
# Remove the header from the buffer
|
58
|
+
@data = @data[struct_length + iv_length + key_length..-1]
|
59
|
+
|
60
|
+
# Generate a key identifier
|
61
|
+
hash_sha512 = OpenSSL::Digest.new('SHA512')
|
62
|
+
hash_sha512 << enc_key
|
63
|
+
client_id = hash_sha512.digest
|
64
|
+
|
65
|
+
if @key.present?
|
66
|
+
close if @key['client_id'] != client_id
|
67
|
+
end
|
68
|
+
|
69
|
+
unless @key.present?
|
70
|
+
url = endpoint_base + '/decrypt/key'
|
71
|
+
query = { encrypted_data_key: Base64.strict_encode64(enc_key) }
|
72
|
+
headers = Signature.headers(endpoint, @host, 'post', @papi, query, @sapi)
|
73
|
+
|
74
|
+
response = HTTParty.post(
|
75
|
+
url,
|
76
|
+
body: query.to_json,
|
77
|
+
headers: headers
|
78
|
+
)
|
79
|
+
|
80
|
+
if response.code == WEBrick::HTTPStatus::RC_OK
|
81
|
+
@key = {}
|
82
|
+
@key['finger_print'] = response[key_fingerprint]
|
83
|
+
@key['client_id'] = client_id
|
84
|
+
@key['session'] = response['encryption_session']
|
85
|
+
|
86
|
+
# Get the cipher name
|
87
|
+
@key['algorithm'] = Cipher.new.find_algorithm(algorithm_id)
|
88
|
+
|
89
|
+
encrypted_private_key = response['encrypted_private_key']
|
90
|
+
# Decrypt WDK from base64
|
91
|
+
wdk = Base64.strict_decode64(wrapped_data_key)
|
92
|
+
# Use private key to decrypt the wdk
|
93
|
+
dk = private_key.private_decrypt(wdk, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
94
|
+
|
95
|
+
@key['raw'] = dk
|
96
|
+
@key['uses'] = 0
|
97
|
+
else
|
98
|
+
raise "HTTPError response: Expected 201 got #{response.code}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# If the key object exists, create a new decryptor.
|
103
|
+
# Increment the key usage
|
104
|
+
if @key.present?
|
105
|
+
@cipher = Cipher.new.get_algorithm(@key['algorithm'])
|
106
|
+
@key['dec'] = Cipher.new.decryptor(@cipher, @key['raw'], iv)
|
107
|
+
|
108
|
+
if (flags & Cipher::CRYPTOFLAG) != 0
|
109
|
+
@key['dec'].auth_data = packed_struct + iv + enc_key
|
110
|
+
end
|
111
|
+
@key['uses'] += 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
plain_text = ''
|
118
|
+
if @key.present? && @key.key?('dec')
|
119
|
+
size = @data.length - @cipher[:tag_length]
|
120
|
+
if size.positive?
|
121
|
+
plain_text = @key['dec'].update(@data[0..size - 1])
|
122
|
+
@data = @data[size..-1]
|
123
|
+
end
|
124
|
+
plain_text
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def finish_decryption
|
129
|
+
raise 'Decryption is not started' unless @decryption_started
|
130
|
+
|
131
|
+
# Update maintains tag-size bytes in the buffer
|
132
|
+
# When this function is called, all data must already be
|
133
|
+
# in the decryption object
|
134
|
+
sz = @data.length - @cipher[:tag_length]
|
135
|
+
|
136
|
+
raise 'Invalid Tag!' if sz.negative?
|
137
|
+
|
138
|
+
if sz.zero?
|
139
|
+
@key['dec'].auth_tag = @data
|
140
|
+
begin
|
141
|
+
plain_text = @key['dec'].final
|
142
|
+
# Delete the context
|
143
|
+
@key.delete('dec')
|
144
|
+
# Return the plain text
|
145
|
+
@decryption_started = false
|
146
|
+
plain_text
|
147
|
+
rescue Exception
|
148
|
+
print 'Invalid cipher data or tag!'
|
149
|
+
''
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def close_decryption
|
155
|
+
raise 'Decryption currently running' if @decryption_started
|
156
|
+
|
157
|
+
# Reset the decryption object
|
158
|
+
if @key.present?
|
159
|
+
if @key['uses'].positive?
|
160
|
+
query_url = "#{endpoint}/#{@key['finger_print']}/#{@key['session']}"
|
161
|
+
url = "#{endpoint_base}/decryption/key/#{@key['finger_print']}/#{@key['session']}"
|
162
|
+
query = { uses: @key['uses'] }
|
163
|
+
headers = Signature.headers(query_url, @host, 'patch', @papi, query, @sapi)
|
164
|
+
|
165
|
+
response = HTTParty.patch(
|
166
|
+
url,
|
167
|
+
body: query.to_json,
|
168
|
+
headers: headers
|
169
|
+
)
|
170
|
+
|
171
|
+
remove_instance_variable(:@data)
|
172
|
+
remove_instance_variable(:@key)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def endpoint
|
178
|
+
'/api/v0/encryption/key'
|
179
|
+
end
|
180
|
+
|
181
|
+
def endpoint_base
|
182
|
+
"#{@host}/api/v0"
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
def decrypt(creds, data)
|
188
|
+
begin
|
189
|
+
dec = Decryption.new(creds)
|
190
|
+
res = dec.begin_decryption + dec.update_decryption(data) + dec.finish_decryption
|
191
|
+
dec.close_decryption
|
192
|
+
rescue StandardError
|
193
|
+
dec&.close_decryption
|
194
|
+
raise
|
195
|
+
end
|
196
|
+
|
197
|
+
res
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'httparty'
|
3
|
+
require 'rb-readline'
|
4
|
+
require 'webrick'
|
5
|
+
require_relative './cipher'
|
6
|
+
require_relative './signature'
|
7
|
+
|
8
|
+
module Cryptograpi
|
9
|
+
class Encryption
|
10
|
+
def initialize(creds, uses)
|
11
|
+
raise 'Some credentials are missing' unless validate_credentials(creds)
|
12
|
+
|
13
|
+
@papi = creds.access_key_id
|
14
|
+
@sapi = creds.signing_key
|
15
|
+
@srsa = creds.secret_access_key
|
16
|
+
@host = creds.host.blank? ? CRYPTOGRAPI_HOST : creds.host
|
17
|
+
url = "#{endpoint_base}/encryption/key"
|
18
|
+
query = { uses: uses }
|
19
|
+
headers = Signature.headers(endpoint, @host, 'post', @papi, query, @sapi)
|
20
|
+
|
21
|
+
@encryption_started = false
|
22
|
+
@encryption_ready = true
|
23
|
+
|
24
|
+
# First, ask for a key from the server.
|
25
|
+
# If the request fails, raise a HTTPError.
|
26
|
+
|
27
|
+
begin
|
28
|
+
response = HTTParty.post(
|
29
|
+
url,
|
30
|
+
body: query.to_json,
|
31
|
+
headers: headers
|
32
|
+
)
|
33
|
+
rescue HTTParty::Error
|
34
|
+
raise 'Server Unreachable'
|
35
|
+
end
|
36
|
+
|
37
|
+
if response.code == WEBrick::HTTPStatus::RC_CREATED
|
38
|
+
# Builds the key object
|
39
|
+
@key = {}
|
40
|
+
@key['id'] = response['key_fingerprint']
|
41
|
+
@key['session'] = response['encryption_session']
|
42
|
+
@key['security_model'] = response['security_model']
|
43
|
+
@key['algorithm'] = response['security_model']['algorithm'].downcase
|
44
|
+
@key['max_uses'] = response['max_uses']
|
45
|
+
@key['uses'] = 0
|
46
|
+
@key['encrypted'] = Base64.strict_decode64(response['encrypted_data_key'])
|
47
|
+
|
48
|
+
# get the encrypted private key from response body
|
49
|
+
encrypted_pk = response['encrypted_private_key']
|
50
|
+
# Data key from response body
|
51
|
+
wrapped_data_key = response['wrapped_data_key']
|
52
|
+
# decrypt the encrypted private key using @srsa
|
53
|
+
pk = OpenSSL::PKey::RSA.new(encrypted_pk, @srsa)
|
54
|
+
# Decode WDK from base64 format
|
55
|
+
wdk = Base64.strict_decode64(wrapped_data_key)
|
56
|
+
# Use private key to decrypt the wrapped data key
|
57
|
+
dk = pk.private_decrypt(wdk, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
58
|
+
@key['raw'] = dk
|
59
|
+
@cipher = Cipher.new.get_algorithm(@key['algorithm'])
|
60
|
+
else
|
61
|
+
raise "HTTPError Response: Expected 201, got #{response.code}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def begin_encryption
|
66
|
+
# Begins the encryption process
|
67
|
+
# Each time this function is called, uses increments by 1
|
68
|
+
raise 'Encryption not ready' unless @encryption_ready
|
69
|
+
# cipher already exists
|
70
|
+
raise 'Encryption in progress' if @encryption_started
|
71
|
+
# Check for max uses flag
|
72
|
+
raise 'Maximum usage exceeded' if @key['uses'] >= @key["max_uses"]
|
73
|
+
|
74
|
+
# Increase the uses counter
|
75
|
+
@key['uses'] += 1
|
76
|
+
# New context and initialization vector
|
77
|
+
@enc, @iv = Cipher.new.encryptor(@cipher, @key['raw'])
|
78
|
+
# Pack and create a byte string
|
79
|
+
struct = [0, Cipher::CRYPTOFLAG, @cipher[:id], @iv.length, @key['encrypted'].length].pack('CCCCn')
|
80
|
+
|
81
|
+
@enc.auth_data = struct + @iv + @key['encrypted']
|
82
|
+
@encryption_started = true
|
83
|
+
|
84
|
+
# Return the encrypted object
|
85
|
+
struct + @iv + @key['encrypted']
|
86
|
+
end
|
87
|
+
|
88
|
+
def update_encryption(data)
|
89
|
+
raise 'Encryption has not started yet' unless @encryption_started
|
90
|
+
|
91
|
+
@enc.update(data)
|
92
|
+
end
|
93
|
+
|
94
|
+
def finish_encryption
|
95
|
+
raise 'Encryption not started' unless @encryption_started
|
96
|
+
|
97
|
+
# Finalizes the encryption and adds any auth info required by the algorithm
|
98
|
+
res = @enc.final
|
99
|
+
if @cipher[:tag_length] != 0
|
100
|
+
# Add the tag to the cipher text
|
101
|
+
res += @enc.auth_tag
|
102
|
+
end
|
103
|
+
|
104
|
+
@encryption_started = false
|
105
|
+
# return the encrypted result
|
106
|
+
res
|
107
|
+
end
|
108
|
+
|
109
|
+
def close_encryption
|
110
|
+
raise 'Encryption currently running' if @encryption_started
|
111
|
+
|
112
|
+
if @key['uses'] < @key['max_uses']
|
113
|
+
query_url = "#{endpoint}/#{@key['id']}/#{@key['session']}"
|
114
|
+
url = "#{endpoint_base}/encryption/key/#{@key['id']}/#{@key['session']}"
|
115
|
+
query = { actual: @key['uses'], requested: @key['max_uses'] }
|
116
|
+
headers = Signature.headers(query_url, @host, 'patch', @papi, query, @sapi)
|
117
|
+
|
118
|
+
response = HTTParty.patch(
|
119
|
+
url,
|
120
|
+
body: query.to_json,
|
121
|
+
headers: headers
|
122
|
+
)
|
123
|
+
remove_instance_variable(:@key)
|
124
|
+
@encryption_ready = false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def endpoint
|
129
|
+
'/api/v0/encryption/key'
|
130
|
+
end
|
131
|
+
|
132
|
+
def endpoint_base
|
133
|
+
"#{@host}/api/v0"
|
134
|
+
end
|
135
|
+
|
136
|
+
def validate_credentials(credentials)
|
137
|
+
!credentials.access_key_id.blank? &&
|
138
|
+
!credentials.secret_access_key.blank? &&
|
139
|
+
!credentials.signing_key.blank?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Check credentials are present and valid
|
144
|
+
def validate_credentials(credentials)
|
145
|
+
!credentials.access_key_id.blank? &&
|
146
|
+
!credentials.secret_access_key.blank? &&
|
147
|
+
!credentials.signing_key.blank?
|
148
|
+
end
|
149
|
+
|
150
|
+
# Qui e!
|
151
|
+
def encrypt(credentials, data)
|
152
|
+
begin
|
153
|
+
enc = Encryption.new(credentials, 1)
|
154
|
+
res =
|
155
|
+
enc.begin_encryption +
|
156
|
+
enc.update_encryption(data) +
|
157
|
+
enc.finish_encryption
|
158
|
+
enc.close_encryption
|
159
|
+
rescue StandardError
|
160
|
+
enc&.close_encryption
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
res
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
|
5
|
+
module Cryptograpi
|
6
|
+
# HTTP authentication for our platform
|
7
|
+
class Signature
|
8
|
+
def self.headers(endpoint, host, http_method, papi, query, sapi)
|
9
|
+
|
10
|
+
# Request Target (http_method path?query)
|
11
|
+
req_target = "#{http_method} #{endpoint}"
|
12
|
+
|
13
|
+
# Unix time for signature creation
|
14
|
+
created_at = Time.now.to_i
|
15
|
+
|
16
|
+
# We hash the body of the HTTP message. Even if it's empty
|
17
|
+
ha_sha512 = OpenSSL::Digest.new('SHA512')
|
18
|
+
ha_sha512 << JSON.dump(query)
|
19
|
+
digest = "SHA-512=#{Base64.strict_encode64(ha_sha512.digest)}"
|
20
|
+
|
21
|
+
# Initialize headers
|
22
|
+
header_signature = {}
|
23
|
+
header_signature['user-agent'] = "cryptograpi_ruby/#{Cryptograpi::VERSION}"
|
24
|
+
header_signature['content-type'] = 'application/json'
|
25
|
+
header_signature['(request-target'] = req_target
|
26
|
+
header_signature['date'] = sign_date
|
27
|
+
header_signature['host'] = get_host(host)
|
28
|
+
header_signature['(created)'] = created_at
|
29
|
+
header_signature['digest'] = digest
|
30
|
+
headers = %w[content-type date host (created) (request-target) digest]
|
31
|
+
|
32
|
+
# Calculate HMAC including the headers
|
33
|
+
hmac = OpenSSL::HMAC.new(sapi, OpenSSL::Digest.new('SHA512'))
|
34
|
+
headers.each do |header|
|
35
|
+
hmac << "#{header}: #{header_signature[header]}\n" if header_signature.key?(header)
|
36
|
+
end
|
37
|
+
|
38
|
+
header_signature.delete('(created)')
|
39
|
+
header_signature.delete('(request-target)')
|
40
|
+
header_signature.delete('(host)')
|
41
|
+
|
42
|
+
# Build the final signature
|
43
|
+
header_signature['signature'] = "keyId=\"#{papi}\""
|
44
|
+
header_signature['signature'] += ', algorithm="hmac-sha512"'
|
45
|
+
header_signature['signature'] += ", created=#{created_at}"
|
46
|
+
header_signature['signature'] += ", headers=#{headers.join(' ')}\""
|
47
|
+
header_signature['signature'] += ', signature='
|
48
|
+
header_signature['signature'] += Base64.strict_encode64(hmac.digest)
|
49
|
+
header_signature['signature'] += '"'
|
50
|
+
|
51
|
+
header_signature
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.sign_date
|
55
|
+
"#{DateTime.now.in_time_zone('GMT').strftime('%a, %d %b %Y')} #{DateTime.now.in_time_zone('GMT').strftime('%H:%M:%S')} GMT"
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.get_host(host)
|
59
|
+
uri = URI(host)
|
60
|
+
ret = uri.hostname.to_s
|
61
|
+
ret += ":#{uri.port}" if /:[0-9]/.match?(host)
|
62
|
+
ret
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/cryptograpi_ruby.rb
CHANGED
@@ -1,2 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
module CryptograpiRuby
|
4
|
+
class << self
|
5
|
+
attr_accessor :api_token, :project_id
|
6
|
+
# attr_writer :locales_path
|
7
|
+
|
8
|
+
# Let's provide a straightforward way
|
9
|
+
# to provide options, like
|
10
|
+
# CryptograpiRuby.config do |c|
|
11
|
+
# c.api_token = '123'
|
12
|
+
# c.project_id = '345'
|
13
|
+
# end
|
14
|
+
def config
|
15
|
+
yield self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cryptograpi_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1
|
4
|
+
version: 0.0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cryptograpi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -66,6 +66,118 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '2.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: codecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: dotenv
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '13.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '13.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.6'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.6'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.16'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.16'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: vcr
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '6.0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '6.0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rails
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '6.1'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '6.1'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspec-rails
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '4.0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '4.0'
|
69
181
|
- !ruby/object:Gem::Dependency
|
70
182
|
name: activesupport
|
71
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +192,20 @@ dependencies:
|
|
80
192
|
- - "~>"
|
81
193
|
- !ruby/object:Gem::Version
|
82
194
|
version: '6.1'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: configparser
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0.1'
|
202
|
+
type: :runtime
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0.1'
|
83
209
|
- !ruby/object:Gem::Dependency
|
84
210
|
name: httparty
|
85
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +234,20 @@ dependencies:
|
|
108
234
|
- - "~>"
|
109
235
|
- !ruby/object:Gem::Version
|
110
236
|
version: '0.5'
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: tzinfo-data
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '1'
|
244
|
+
type: :runtime
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - ">="
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '1'
|
111
251
|
- !ruby/object:Gem::Dependency
|
112
252
|
name: webrick
|
113
253
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,6 +277,12 @@ files:
|
|
137
277
|
- Rakefile
|
138
278
|
- cryptograpi_ruby.gemspec
|
139
279
|
- lib/cryptograpi_ruby.rb
|
280
|
+
- lib/cryptograpi_ruby/cipher.rb
|
281
|
+
- lib/cryptograpi_ruby/credentials.rb
|
282
|
+
- lib/cryptograpi_ruby/decrypt.rb
|
283
|
+
- lib/cryptograpi_ruby/encrypt.rb
|
284
|
+
- lib/cryptograpi_ruby/host.rb
|
285
|
+
- lib/cryptograpi_ruby/signature.rb
|
140
286
|
- lib/cryptograpi_ruby/version.rb
|
141
287
|
homepage: https://cryptograpi.com
|
142
288
|
licenses:
|
@@ -157,7 +303,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
303
|
- !ruby/object:Gem::Version
|
158
304
|
version: '0'
|
159
305
|
requirements: []
|
160
|
-
rubygems_version: 3.2.
|
306
|
+
rubygems_version: 3.2.26
|
161
307
|
signing_key:
|
162
308
|
specification_version: 4
|
163
309
|
summary: Cryptograpi library for ruby apps
|