evervault 0.1.3 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +1 -1
- data/.github/workflows/run-tests.yml +1 -1
- data/Gemfile +1 -0
- data/README.md +50 -34
- data/lib/evervault/client.rb +22 -14
- data/lib/evervault/crypto/client.rb +74 -47
- data/lib/evervault/crypto/curves/base.rb +38 -0
- data/lib/evervault/crypto/curves/p256.rb +25 -0
- data/lib/evervault/errors/error_map.rb +6 -2
- data/lib/evervault/errors/errors.rb +6 -0
- data/lib/evervault/http/request.rb +12 -47
- data/lib/evervault/http/request_handler.rb +68 -0
- data/lib/evervault/http/request_intercept.rb +135 -0
- data/lib/evervault/version.rb +2 -1
- metadata +7 -5
- data/Gemfile.lock +0 -58
- data/lib/evervault/crypto/key.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7382a19b6a9fa82b2d37caa8f3e440f7bfce74bc18a237f13bdbd598dbb0d855
|
4
|
+
data.tar.gz: 78d8c04663caeca5ec0ff9336f403ec496b69fa61b25f134c9fae66eff91ab78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0154d3c18eaa8685e6f1d26069c286609eaeb0bf8cc56049f53870ea3d374d0c7f45aeb855e47094dfc30b8a55d54d1122fac2ffc008e8a252ca843e711081e
|
7
|
+
data.tar.gz: 3915f0582de2f85ca71e9769f1cb4bf2bdd3013f50903beb8ae1946a39d9dd87733e340159aaf5c5192872ab1685ab39d6decd43d45487bd56dc8a4ea1914b4c
|
@@ -11,7 +11,7 @@ jobs:
|
|
11
11
|
fail-fast: false
|
12
12
|
matrix:
|
13
13
|
os: [ubuntu, macos]
|
14
|
-
ruby: [2.5, 2.6, 2.7,
|
14
|
+
ruby: [2.5, 2.6, 2.7, 3.1, truffleruby]
|
15
15
|
runs-on: ${{ matrix.os }}-latest
|
16
16
|
continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
|
17
17
|
steps:
|
@@ -7,7 +7,7 @@ jobs:
|
|
7
7
|
fail-fast: false
|
8
8
|
matrix:
|
9
9
|
os: [ubuntu, macos]
|
10
|
-
ruby: [2.5, 2.6, 2.7,
|
10
|
+
ruby: [2.5, 2.6, 2.7, 3.1, truffleruby]
|
11
11
|
runs-on: ${{ matrix.os }}-latest
|
12
12
|
continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
|
13
13
|
steps:
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,81 +1,94 @@
|
|
1
|
-
|
2
|
-
<p align="center">
|
3
|
-
<img src="res/logo.svg">
|
4
|
-
</p>
|
1
|
+
[](https://evervault.com/)
|
5
2
|
|
6
|
-
|
7
|
-
<a href="https://github.com/evervault/evervault-ruby/actions?query=workflow%3Aevervault-unit-tests"><img alt="Evervault unit tests status" src="https://github.com/evervault/evervault-ruby/workflows/evervault-unit-tests/badge.svg"></a>
|
8
|
-
</p>
|
3
|
+
[](https://github.com/evervault/evervault-ruby/actions?query=workflow%3Aevervault-unit-tests)
|
9
4
|
|
5
|
+
# Evervault Ruby SDK
|
6
|
+
|
7
|
+
The [Evervault](https://evervault.com) Ruby SDK is a toolkit for encrypting data as it enters your server, working with Cages, and proxying your outbound API requests to specific domains through [Outbound Relay](https://docs.evervault.com/concepts/relay/outbound-interception) to allow them to be decrypted before reaching their target.
|
10
8
|
|
11
9
|
## Getting Started
|
12
|
-
Ruby SDK for [Evervault](https://evervault.com)
|
13
|
-
### Prerequisites
|
14
10
|
|
15
|
-
|
11
|
+
Before starting with the Evervault Ruby SDK, you will need to [create an account](https://app.evervault.com/register) and a team.
|
12
|
+
|
13
|
+
For full installation support, [book time here](https://calendly.com/evervault/cages-onboarding).
|
14
|
+
|
15
|
+
## Documentation
|
16
16
|
|
17
|
-
|
17
|
+
See the Evervault [Ruby SDK documentation](https://docs.evervault.com/ruby).
|
18
18
|
|
19
19
|
## Installation
|
20
20
|
|
21
|
-
|
21
|
+
There are two ways to install the Ruby SDK.
|
22
|
+
|
23
|
+
#### 1. With Gemfile
|
24
|
+
|
25
|
+
Add this line to your application's `Gemfile`:
|
22
26
|
|
23
27
|
```ruby
|
24
28
|
gem 'evervault'
|
25
29
|
```
|
26
30
|
|
27
|
-
|
31
|
+
Then, run:
|
32
|
+
|
28
33
|
```sh
|
29
|
-
|
34
|
+
bundle install
|
30
35
|
```
|
31
|
-
|
36
|
+
#### 2. By yourself
|
37
|
+
|
38
|
+
Just run:
|
39
|
+
|
32
40
|
```sh
|
33
|
-
|
41
|
+
gem install evervault
|
34
42
|
```
|
35
43
|
|
36
44
|
## Setup
|
37
45
|
|
38
|
-
|
46
|
+
To make Evervault available for use in your app:
|
47
|
+
|
39
48
|
```ruby
|
40
49
|
require "evervault"
|
41
50
|
|
42
|
-
# Initialize the client with your team's
|
51
|
+
# Initialize the client with your team's API key
|
43
52
|
Evervault.api_key = <YOUR-API-KEY>
|
44
53
|
|
45
54
|
# Encrypt your data and run a cage
|
46
|
-
|
55
|
+
encrypted_data = Evervault.encrypt({ hello: 'World!' })
|
56
|
+
|
57
|
+
# Process the encrypted data in a Cage
|
58
|
+
result = Evervault.run(<CAGE-NAME>, encrypted_data)
|
47
59
|
```
|
48
60
|
|
49
|
-
|
61
|
+
## Reference
|
50
62
|
|
51
|
-
|
52
|
-
```ruby
|
53
|
-
require "evervault"
|
63
|
+
The Evervault Ruby SDK exposes eight methods.
|
54
64
|
|
55
|
-
|
56
|
-
evervault = Evervault::Client.new(api_key: <YOUR-API-KEY>)
|
65
|
+
### Evervault.encrypt
|
57
66
|
|
58
|
-
|
59
|
-
|
67
|
+
`Evervault.encrypt` encrypts data for use in your [Evervault Cages](https://docs.evervault.com/tutorial). To encrypt data on your server, simply pass a supported value into the `Evervault.encrypt` method and then you can store the encrypted data in your database as normal.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
Evervault.encrypt(data = String | Number | Boolean | Hash | Array)
|
60
71
|
```
|
61
72
|
|
62
|
-
|
73
|
+
| Parameter | Type | Description |
|
74
|
+
| --------- | ---- | ----------- |
|
75
|
+
| data | `String`, `Number`, `Boolean`, `Hash`, `Array` | Data to be encrypted |
|
63
76
|
|
64
|
-
### Evervault.
|
77
|
+
### Evervault.relay
|
65
78
|
|
66
|
-
|
79
|
+
`Evervault.relay` specifies which domains should be proxied through outbound relay. See [Outbound Relay](https://docs.evervault.com/concepts/relay/outbound-interception) to learn more.
|
67
80
|
|
68
81
|
```ruby
|
69
|
-
Evervault.
|
82
|
+
Evervault.relay(decryption_domains = Array)
|
70
83
|
```
|
71
84
|
|
72
85
|
| Parameter | Type | Description |
|
73
86
|
| --------- | ---- | ----------- |
|
74
|
-
|
|
87
|
+
| decryption_domains | `Array` | Requests sent to any of the domains listed will be proxied through outbound relay |
|
75
88
|
|
76
89
|
### Evervault.run
|
77
90
|
|
78
|
-
|
91
|
+
`Evervault.run` invokes a Cage with a given payload.
|
79
92
|
|
80
93
|
```ruby
|
81
94
|
Evervault.run(cage_name = String, data = Hash[, options = Hash])
|
@@ -220,7 +233,7 @@ Evervault.cage_list.to_hash
|
|
220
233
|
|
221
234
|
Each Cage model exposes a `run` method, which allows you to run that particular Cage.
|
222
235
|
|
223
|
-
*Note*: this does not encrypt data before running the Cage
|
236
|
+
*Note*: this does not encrypt data before running the Cage.
|
224
237
|
```ruby
|
225
238
|
cage = Evervault.cage_list.cages[0]
|
226
239
|
cage.run({'name': 'testing'})
|
@@ -242,6 +255,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
242
255
|
|
243
256
|
Bug reports and pull requests are welcome on GitHub at https://github.com/evervault/evervault-ruby.
|
244
257
|
|
258
|
+
## Feedback
|
259
|
+
|
260
|
+
Questions or feedback? [Let us know](mailto:support@evervault.com).
|
245
261
|
|
246
262
|
## License
|
247
263
|
|
data/lib/evervault/client.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require_relative "http/request"
|
2
|
+
require_relative "http/request_handler"
|
3
|
+
require_relative "http/request_intercept"
|
2
4
|
require_relative "crypto/client"
|
3
5
|
require_relative "models/cage_list"
|
4
6
|
|
@@ -9,20 +11,22 @@ module Evervault
|
|
9
11
|
def initialize(
|
10
12
|
api_key:,
|
11
13
|
base_url: "https://api.evervault.com/",
|
12
|
-
cage_run_url: "https://
|
13
|
-
|
14
|
+
cage_run_url: "https://run.evervault.com/",
|
15
|
+
relay_url: "https://relay.evervault.com:8443",
|
16
|
+
ca_host: "https://ca.evervault.com",
|
17
|
+
request_timeout: 30,
|
18
|
+
curve: 'prime256v1'
|
14
19
|
)
|
15
|
-
@
|
16
|
-
@
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
base_url: base_url,
|
23
|
-
cage_run_url: cage_run_url
|
20
|
+
@request = Evervault::Http::Request.new(timeout: request_timeout, api_key: api_key)
|
21
|
+
@intercept = Evervault::Http::RequestIntercept.new(
|
22
|
+
request: @request, ca_host: ca_host, api_key: api_key, relay_url: relay_url
|
23
|
+
)
|
24
|
+
@request_handler =
|
25
|
+
Evervault::Http::RequestHandler.new(
|
26
|
+
request: @request, base_url: base_url, cage_run_url: cage_run_url, cert: @intercept
|
24
27
|
)
|
25
|
-
@crypto_client = Evervault::Crypto::Client.new(
|
28
|
+
@crypto_client = Evervault::Crypto::Client.new(request_handler: @request_handler, curve: curve)
|
29
|
+
@intercept.setup()
|
26
30
|
end
|
27
31
|
|
28
32
|
def encrypt(data)
|
@@ -30,7 +34,7 @@ module Evervault
|
|
30
34
|
end
|
31
35
|
|
32
36
|
def run(cage_name, encrypted_data, options = {})
|
33
|
-
@
|
37
|
+
@request_handler.post(cage_name, encrypted_data, options: options, cage_run: true)
|
34
38
|
end
|
35
39
|
|
36
40
|
def encrypt_and_run(cage_name, data, options = {})
|
@@ -43,8 +47,12 @@ module Evervault
|
|
43
47
|
end
|
44
48
|
|
45
49
|
def cage_list
|
46
|
-
cages = @
|
50
|
+
cages = @request_handler.get("cages")
|
47
51
|
@cage_list ||= Evervault::Models::CageList.new(cages: cages["cages"], request: @request)
|
48
52
|
end
|
53
|
+
|
54
|
+
def relay(decryption_domains=[])
|
55
|
+
@intercept.setup_domains(decryption_domains)
|
56
|
+
end
|
49
57
|
end
|
50
58
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative "../errors/errors"
|
2
|
-
require_relative "
|
2
|
+
require_relative "curves/p256"
|
3
|
+
require_relative "../version"
|
3
4
|
require "openssl"
|
4
5
|
require "base64"
|
5
6
|
require "json"
|
@@ -8,80 +9,106 @@ require "securerandom"
|
|
8
9
|
module Evervault
|
9
10
|
module Crypto
|
10
11
|
class Client
|
11
|
-
attr_reader :
|
12
|
-
def initialize(
|
13
|
-
@
|
12
|
+
attr_reader :request_handler
|
13
|
+
def initialize(request_handler:, curve:)
|
14
|
+
@curve = curve
|
15
|
+
@p256 = Evervault::Crypto::Curves::P256.new()
|
16
|
+
@ev_version = base_64_remove_padding(
|
17
|
+
Base64.strict_encode64(EV_VERSION[curve])
|
18
|
+
)
|
19
|
+
response = request_handler.get("cages/key")
|
20
|
+
key = @curve == 'secp256k1' ? 'ecdhKey' : 'ecdhP256Key'
|
21
|
+
@team_key = response[key]
|
14
22
|
end
|
15
23
|
|
16
24
|
def encrypt(data)
|
17
25
|
raise Evervault::Errors::UndefinedDataError.new(
|
18
26
|
"Data is required for encryption"
|
19
|
-
) if data.nil? || data.empty?
|
27
|
+
) if data.nil? || (data.instance_of?(String) && data.empty?)
|
28
|
+
|
29
|
+
raise Evervault::Errors::UnsupportedEncryptType.new(
|
30
|
+
"Encryption is not supported for #{data.class}"
|
31
|
+
) if !(encryptable_data?(data) || data.instance_of?(Hash) || data.instance_of?(Array))
|
20
32
|
|
21
|
-
|
22
|
-
encrypt_hash(data)
|
23
|
-
elsif encryptable_data?(data)
|
24
|
-
encrypt_string(data)
|
25
|
-
end
|
33
|
+
traverse_and_encrypt(data)
|
26
34
|
end
|
27
35
|
|
28
|
-
private def encrypt_string(
|
29
|
-
cipher = OpenSSL::Cipher
|
36
|
+
private def encrypt_string(data_to_encrypt)
|
37
|
+
cipher = OpenSSL::Cipher.new('aes-256-gcm').encrypt
|
38
|
+
|
39
|
+
shared_key = generate_shared_key()
|
40
|
+
cipher.key = shared_key
|
41
|
+
|
30
42
|
iv = cipher.random_iv
|
31
|
-
root_key = cipher.random_key
|
32
|
-
cipher.key = root_key
|
33
43
|
cipher.iv = iv
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
44
|
+
|
45
|
+
if (@curve == 'prime256v1')
|
46
|
+
cipher.auth_data = Base64.strict_decode64(@team_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
encrypted_data = cipher.update(data_to_encrypt.to_s) + cipher.final + cipher.auth_tag
|
50
|
+
|
51
|
+
ephemeral_key_compressed_string = @ephemeral_public_key.to_octet_string(:compressed)
|
52
|
+
|
53
|
+
format(header_type(data_to_encrypt), Base64.strict_encode64(iv), Base64.strict_encode64(ephemeral_key_compressed_string), Base64.strict_encode64(encrypted_data))
|
43
54
|
end
|
44
55
|
|
45
|
-
private def
|
56
|
+
private def traverse_and_encrypt(data)
|
46
57
|
if encryptable_data?(data)
|
47
58
|
return encrypt_string(data)
|
48
59
|
elsif data.instance_of?(Hash)
|
49
60
|
encrypted_data = {}
|
50
|
-
data.each { |key, value| encrypted_data[key] =
|
61
|
+
data.each { |key, value| encrypted_data[key] = traverse_and_encrypt(value) }
|
62
|
+
return encrypted_data
|
63
|
+
elsif data.instance_of?(Array)
|
64
|
+
encrypted_data = data.map { |value| traverse_and_encrypt(value) }
|
51
65
|
return encrypted_data
|
66
|
+
else
|
67
|
+
raise Evervault::Errors::UnsupportedEncryptType.new(
|
68
|
+
"Encryption is not supported for #{data.class}"
|
69
|
+
)
|
52
70
|
end
|
53
71
|
data
|
54
72
|
end
|
55
73
|
|
56
74
|
private def encryptable_data?(data)
|
57
|
-
data.instance_of?(String) ||
|
58
|
-
|
59
|
-
data.instance_of?(Float)
|
75
|
+
data.instance_of?(String) || [true, false].include?(data) ||
|
76
|
+
data.instance_of?(Integer) || data.instance_of?(Float)
|
60
77
|
end
|
61
78
|
|
62
|
-
private def
|
63
|
-
|
79
|
+
private def generate_shared_key()
|
80
|
+
ec = OpenSSL::PKey::EC.new(@curve)
|
81
|
+
ec.generate_key
|
82
|
+
@ephemeral_public_key = ec.public_key
|
83
|
+
|
84
|
+
decoded_team_key = OpenSSL::BN.new(Base64.strict_decode64(@team_key), 2)
|
85
|
+
group_for_team_key = OpenSSL::PKey::EC::Group.new(@curve)
|
86
|
+
team_key_point = OpenSSL::PKey::EC::Point.new(group_for_team_key, decoded_team_key)
|
87
|
+
|
88
|
+
shared_key = ec.dh_compute_key(team_key_point)
|
89
|
+
|
90
|
+
if @curve == 'prime256v1'
|
91
|
+
# Perform KDF
|
92
|
+
encoded_ephemeral_key = @p256.encode(decompressed_key: @ephemeral_public_key.to_bn(:uncompressed).to_s(16)).to_der
|
93
|
+
hash_input = shared_key + [00, 00, 00, 01].pack('C*') + encoded_ephemeral_key
|
94
|
+
hash = OpenSSL::Digest::SHA256.new()
|
95
|
+
digest = hash.digest(hash_input)
|
96
|
+
return digest
|
97
|
+
end
|
98
|
+
|
99
|
+
shared_key
|
64
100
|
end
|
65
101
|
|
66
|
-
private def format(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
utf8_to_base_64_url(
|
73
|
-
{
|
74
|
-
cageData: encrypted_key,
|
75
|
-
keyIv: iv,
|
76
|
-
sharedEncryptedData: encrypted_data
|
77
|
-
}.to_json
|
78
|
-
)
|
79
|
-
"#{header}.#{payload}.#{SecureRandom.uuid}"
|
102
|
+
private def format(datatype, iv, team_key, encrypted_data)
|
103
|
+
"ev:#{@ev_version}#{
|
104
|
+
datatype != 'string' ? ':' + datatype : ''
|
105
|
+
}:#{base_64_remove_padding(iv)}:#{base_64_remove_padding(
|
106
|
+
team_key
|
107
|
+
)}:#{base_64_remove_padding(encrypted_data)}:$"
|
80
108
|
end
|
81
109
|
|
82
|
-
private def
|
83
|
-
|
84
|
-
b64_string.gsub("+", "-").gsub("/", "_")
|
110
|
+
private def base_64_remove_padding(str)
|
111
|
+
str.gsub(/={1,2}$/, '');
|
85
112
|
end
|
86
113
|
|
87
114
|
private def header_type(data)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "openssl"
|
2
|
+
|
3
|
+
module Evervault
|
4
|
+
module Crypto
|
5
|
+
class CurveBase
|
6
|
+
|
7
|
+
def initialize(curve_name:, curve_values:)
|
8
|
+
@asn1Encoder = buildEncoder(curve_values: curve_values)
|
9
|
+
end
|
10
|
+
|
11
|
+
def encode(decompressed_key:)
|
12
|
+
@asn1Encoder.call(decompressed_key)
|
13
|
+
end
|
14
|
+
|
15
|
+
private def buildEncoder(curve_values:)
|
16
|
+
a = OpenSSL::ASN1::OctetString.new([curve_values::A].pack('H*'))
|
17
|
+
b = OpenSSL::ASN1::OctetString.new([curve_values::B].pack('H*'))
|
18
|
+
seed = OpenSSL::ASN1::BitString.new([curve_values::SEED].pack('H*'))
|
19
|
+
curve = OpenSSL::ASN1::Sequence.new([a, b, seed])
|
20
|
+
|
21
|
+
field_type = OpenSSL::ASN1::ObjectId.new("1.2.840.10045.1.1")
|
22
|
+
parameters = OpenSSL::ASN1::Integer.new(curve_values::P.to_i(16))
|
23
|
+
field_id = OpenSSL::ASN1::Sequence.new([field_type, parameters])
|
24
|
+
|
25
|
+
version = OpenSSL::ASN1::Integer.new(1)
|
26
|
+
base = OpenSSL::ASN1::OctetString.new([curve_values::GENERATOR].pack('H*'))
|
27
|
+
order = OpenSSL::ASN1::Integer.new(curve_values::N.to_i(16))
|
28
|
+
cofactor = OpenSSL::ASN1::Integer.new(curve_values::H.to_i(16))
|
29
|
+
ec_parameters = OpenSSL::ASN1::Sequence.new([version, field_id, curve, base, order, cofactor])
|
30
|
+
|
31
|
+
algorithm = OpenSSL::ASN1::ObjectId.new("1.2.840.10045.2.1")
|
32
|
+
algorithm_identifier = OpenSSL::ASN1::Sequence.new([algorithm, ec_parameters])
|
33
|
+
|
34
|
+
return lambda {|public_key| OpenSSL::ASN1::Sequence.new([algorithm_identifier, OpenSSL::ASN1::BitString.new([public_key].pack('H*'))])}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
# https://neuromancer.sk/std/x962/prime256v1
|
4
|
+
|
5
|
+
module P256_CONSTANTS
|
6
|
+
A = "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC"
|
7
|
+
B = "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B"
|
8
|
+
SEED = "C49D360886E704936A6678E1139D26B7819F7E90"
|
9
|
+
P = "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF"
|
10
|
+
GENERATOR = "046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5"
|
11
|
+
N = "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
|
12
|
+
H = "01"
|
13
|
+
end
|
14
|
+
|
15
|
+
module Evervault
|
16
|
+
module Crypto
|
17
|
+
module Curves
|
18
|
+
class P256 < CurveBase
|
19
|
+
def initialize()
|
20
|
+
super(curve_name: 'prime256v1', curve_values: P256_CONSTANTS)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -3,7 +3,7 @@ require_relative "errors"
|
|
3
3
|
module Evervault
|
4
4
|
module Errors
|
5
5
|
class ErrorMap
|
6
|
-
def self.raise_errors_on_failure(status_code, body)
|
6
|
+
def self.raise_errors_on_failure(status_code, body, headers)
|
7
7
|
return if status_code < 400
|
8
8
|
case status_code
|
9
9
|
when 404
|
@@ -13,7 +13,11 @@ module Evervault
|
|
13
13
|
when 401
|
14
14
|
raise AuthenticationError.new("Unauthorized")
|
15
15
|
when 403
|
16
|
-
|
16
|
+
if (headers.include? "x-evervault-error-code") && (headers["x-evervault-error-code"] == "forbidden-ip-error")
|
17
|
+
raise ForbiddenIPError.new("IP is not present in Cage whitelist")
|
18
|
+
else
|
19
|
+
raise AuthenticationError.new("Forbidden")
|
20
|
+
end
|
17
21
|
when 500
|
18
22
|
raise ServerError.new("Server Error")
|
19
23
|
when 502
|
@@ -23,5 +23,11 @@ module Evervault
|
|
23
23
|
class InvalidPublicKeyError < EvervaultError; end
|
24
24
|
|
25
25
|
class UnexpectedError < EvervaultError; end
|
26
|
+
|
27
|
+
class CertDownloadError < EvervaultError; end
|
28
|
+
|
29
|
+
class UnsupportedEncryptType < EvervaultError; end
|
30
|
+
|
31
|
+
class ForbiddenIPError < EvervaultError; end
|
26
32
|
end
|
27
33
|
end
|
@@ -6,41 +6,24 @@ require_relative "../errors/error_map"
|
|
6
6
|
module Evervault
|
7
7
|
module Http
|
8
8
|
class Request
|
9
|
-
def initialize(
|
10
|
-
@api_key = api_key
|
9
|
+
def initialize(timeout:, api_key:)
|
11
10
|
@timeout = timeout
|
12
|
-
@
|
13
|
-
@cage_run_url = cage_run_url
|
14
|
-
end
|
15
|
-
|
16
|
-
def get(path, params = nil)
|
17
|
-
execute(:get, build_url(path), params)
|
18
|
-
end
|
19
|
-
|
20
|
-
def put(path, params)
|
21
|
-
execute(:put, build_url(path), params)
|
22
|
-
end
|
23
|
-
|
24
|
-
def delete(path, params)
|
25
|
-
execute(:delete, build_url(path), params)
|
26
|
-
end
|
27
|
-
|
28
|
-
def post(path, params, options: {}, cage_run: false)
|
29
|
-
execute(:post, build_url(path, cage_run), params, build_cage_run_headers(options, cage_run))
|
30
|
-
end
|
31
|
-
|
32
|
-
private def build_url(path, cage_run = false)
|
33
|
-
return "#{@base_url}#{path}" unless cage_run
|
34
|
-
"#{@cage_run_url}#{path}"
|
11
|
+
@api_key = api_key
|
35
12
|
end
|
36
13
|
|
37
|
-
def execute(method, url, params, optional_headers = {})
|
14
|
+
def execute(method, url, params, optional_headers = {}, is_ca = false)
|
38
15
|
resp = Faraday.send(method, url) do |req|
|
39
16
|
req.body = params.nil? || params.empty? ? nil : params.to_json
|
40
17
|
req.headers = build_headers(optional_headers)
|
18
|
+
req.options.timeout = @timeout
|
41
19
|
end
|
42
|
-
|
43
|
-
|
20
|
+
if resp.status >= 200 && resp.status <= 300
|
21
|
+
if is_ca
|
22
|
+
return resp.body
|
23
|
+
end
|
24
|
+
return JSON.parse(resp.body)
|
25
|
+
end
|
26
|
+
Evervault::Errors::ErrorMap.raise_errors_on_failure(resp.status, resp.body, resp.headers)
|
44
27
|
end
|
45
28
|
|
46
29
|
private def build_headers(optional_headers)
|
@@ -49,27 +32,9 @@ module Evervault
|
|
49
32
|
"Accept": "application/json",
|
50
33
|
"Content-Type": "application/json",
|
51
34
|
"User-Agent": "evervault-ruby/#{VERSION}",
|
52
|
-
"Api-Key": @api_key
|
35
|
+
"Api-Key": @api_key,
|
53
36
|
})
|
54
37
|
end
|
55
|
-
|
56
|
-
private def build_cage_run_headers(options, cage_run = false)
|
57
|
-
optional_headers = {}
|
58
|
-
return optional_headers unless cage_run
|
59
|
-
if options.key?(:async)
|
60
|
-
if options[:async]
|
61
|
-
optional_headers["x-async"] = "true"
|
62
|
-
end
|
63
|
-
options.delete(:async)
|
64
|
-
end
|
65
|
-
if options.key?(:version)
|
66
|
-
if options[:version].is_a? Integer
|
67
|
-
optional_headers["x-version-id"] = options[:version].to_s
|
68
|
-
end
|
69
|
-
options.delete(:version)
|
70
|
-
end
|
71
|
-
optional_headers.merge(options)
|
72
|
-
end
|
73
38
|
end
|
74
39
|
end
|
75
40
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "json"
|
3
|
+
require_relative "../version"
|
4
|
+
require_relative "../errors/error_map"
|
5
|
+
|
6
|
+
module Evervault
|
7
|
+
module Http
|
8
|
+
class RequestHandler
|
9
|
+
def initialize(request:, base_url:, cage_run_url:, cert:)
|
10
|
+
@request = request
|
11
|
+
@base_url = base_url
|
12
|
+
@cage_run_url = cage_run_url
|
13
|
+
@cert = cert
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(path, params = nil)
|
17
|
+
if @cert.is_certificate_expired()
|
18
|
+
@cert.setup()
|
19
|
+
end
|
20
|
+
@request.execute(:get, build_url(path), params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def put(path, params)
|
24
|
+
if @cert.is_certificate_expired()
|
25
|
+
@cert.setup()
|
26
|
+
end
|
27
|
+
@request.execute(:put, build_url(path), params)
|
28
|
+
end
|
29
|
+
|
30
|
+
def delete(path, params)
|
31
|
+
if @cert.is_certificate_expired()
|
32
|
+
@cert.setup()
|
33
|
+
end
|
34
|
+
@request.execute(:delete, build_url(path), params)
|
35
|
+
end
|
36
|
+
|
37
|
+
def post(path, params, options: {}, cage_run: false)
|
38
|
+
if @cert.is_certificate_expired()
|
39
|
+
@cert.setup()
|
40
|
+
end
|
41
|
+
@request.execute(:post, build_url(path, cage_run), params, build_cage_run_headers(options, cage_run))
|
42
|
+
end
|
43
|
+
|
44
|
+
private def build_url(path, cage_run = false)
|
45
|
+
return "#{@base_url}#{path}" unless cage_run
|
46
|
+
"#{@cage_run_url}#{path}"
|
47
|
+
end
|
48
|
+
|
49
|
+
private def build_cage_run_headers(options, cage_run = false)
|
50
|
+
optional_headers = {}
|
51
|
+
return optional_headers unless cage_run
|
52
|
+
if options.key?(:async)
|
53
|
+
if options[:async]
|
54
|
+
optional_headers["x-async"] = "true"
|
55
|
+
end
|
56
|
+
options.delete(:async)
|
57
|
+
end
|
58
|
+
if options.key?(:version)
|
59
|
+
if options[:version].is_a? Integer
|
60
|
+
optional_headers["x-version-id"] = options[:version].to_s
|
61
|
+
end
|
62
|
+
options.delete(:version)
|
63
|
+
end
|
64
|
+
optional_headers.merge(options)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require "faraday"
|
2
|
+
require "json"
|
3
|
+
require "tempfile"
|
4
|
+
require "openssl"
|
5
|
+
require 'net/http'
|
6
|
+
require_relative "../version"
|
7
|
+
require_relative "../errors/errors"
|
8
|
+
|
9
|
+
module NetHTTPOverride
|
10
|
+
@@api_key = nil
|
11
|
+
@@relay_url = nil
|
12
|
+
@@relay_port = nil
|
13
|
+
@@cert = nil
|
14
|
+
@@decrypt_if_exact = []
|
15
|
+
@@decrypt_if_ends_with = []
|
16
|
+
|
17
|
+
def self.set_api_key(value)
|
18
|
+
@@api_key = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.set_relay_url(value)
|
22
|
+
relay_address_and_port = value.gsub(/(^\w+:|^)\/\//, '').split(":")
|
23
|
+
@@relay_url = relay_address_and_port[0]
|
24
|
+
@@relay_port = relay_address_and_port[1]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.set_cert(value)
|
28
|
+
@@cert = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.add_to_decrypt_if_exact(value)
|
32
|
+
@@decrypt_if_exact.append(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.add_to_decrypt_if_ends_with(value)
|
36
|
+
@@decrypt_if_ends_with.append(value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def should_decrypt(domain)
|
40
|
+
return (@@decrypt_if_exact.include? domain) || (@@decrypt_if_ends_with.any? { |suffix| domain.end_with? suffix })
|
41
|
+
end
|
42
|
+
|
43
|
+
def connect
|
44
|
+
if should_decrypt(conn_address)
|
45
|
+
@cert_store = OpenSSL::X509::Store.new
|
46
|
+
@cert_store.add_cert(@@cert)
|
47
|
+
@proxy_from_env = false
|
48
|
+
@proxy_address = @@relay_url
|
49
|
+
@proxy_port = @@relay_port
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def request(req, body = nil, &block)
|
55
|
+
should_decrypt = should_decrypt(@address)
|
56
|
+
if should_decrypt
|
57
|
+
req["Proxy-Authorization"] = @@api_key
|
58
|
+
end
|
59
|
+
super
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
Net::HTTP.send :prepend, NetHTTPOverride
|
64
|
+
|
65
|
+
module Evervault
|
66
|
+
module Http
|
67
|
+
class RequestIntercept
|
68
|
+
def initialize(request:, ca_host:, api_key:, relay_url:)
|
69
|
+
NetHTTPOverride.set_api_key(api_key)
|
70
|
+
NetHTTPOverride.set_relay_url(relay_url)
|
71
|
+
|
72
|
+
@request = request
|
73
|
+
@ca_host = ca_host
|
74
|
+
@expire_date = nil
|
75
|
+
@initial_date = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def is_certificate_expired()
|
79
|
+
if @expire_date
|
80
|
+
now = Time.now
|
81
|
+
if now > @expire_date || now < @initial_date
|
82
|
+
return true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
return false
|
86
|
+
end
|
87
|
+
|
88
|
+
def setup_domains(decrypt_domains=[])
|
89
|
+
for domain in decrypt_domains
|
90
|
+
if domain.start_with?("www.")
|
91
|
+
domain = domain[4..-1]
|
92
|
+
end
|
93
|
+
NetHTTPOverride.add_to_decrypt_if_exact(domain)
|
94
|
+
NetHTTPOverride.add_to_decrypt_if_ends_with("." + domain)
|
95
|
+
NetHTTPOverride.add_to_decrypt_if_ends_with("@" + domain)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup
|
100
|
+
get_cert()
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_cert()
|
104
|
+
ca_content = nil
|
105
|
+
i = 0
|
106
|
+
|
107
|
+
while !ca_content && i < 1
|
108
|
+
i += 1
|
109
|
+
begin
|
110
|
+
ca_content = @request.execute("get", @ca_host, nil, {}, is_ca: true)
|
111
|
+
rescue;
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
if !ca_content || ca_content == ""
|
116
|
+
raise Evervault::Errors::CertDownloadError.new("Unable to install the Evervault root certificate from #{@ca_host}")
|
117
|
+
end
|
118
|
+
|
119
|
+
cert = OpenSSL::X509::Certificate.new ca_content
|
120
|
+
set_cert_expire_date(cert)
|
121
|
+
NetHTTPOverride.set_cert(cert)
|
122
|
+
end
|
123
|
+
|
124
|
+
def set_cert_expire_date(cert)
|
125
|
+
begin
|
126
|
+
@expire_date = cert.not_after
|
127
|
+
@initial_date = cert.not_before
|
128
|
+
rescue => exception
|
129
|
+
@expire_date = nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
data/lib/evervault/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evervault
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonny O'Mahony
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-08 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -24,7 +24,6 @@ files:
|
|
24
24
|
- ".gitignore"
|
25
25
|
- ".rspec"
|
26
26
|
- Gemfile
|
27
|
-
- Gemfile.lock
|
28
27
|
- LICENSE.txt
|
29
28
|
- README.md
|
30
29
|
- Rakefile
|
@@ -34,10 +33,13 @@ files:
|
|
34
33
|
- lib/evervault.rb
|
35
34
|
- lib/evervault/client.rb
|
36
35
|
- lib/evervault/crypto/client.rb
|
37
|
-
- lib/evervault/crypto/
|
36
|
+
- lib/evervault/crypto/curves/base.rb
|
37
|
+
- lib/evervault/crypto/curves/p256.rb
|
38
38
|
- lib/evervault/errors/error_map.rb
|
39
39
|
- lib/evervault/errors/errors.rb
|
40
40
|
- lib/evervault/http/request.rb
|
41
|
+
- lib/evervault/http/request_handler.rb
|
42
|
+
- lib/evervault/http/request_intercept.rb
|
41
43
|
- lib/evervault/models/cage.rb
|
42
44
|
- lib/evervault/models/cage_list.rb
|
43
45
|
- lib/evervault/version.rb
|
@@ -65,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
67
|
- !ruby/object:Gem::Version
|
66
68
|
version: '0'
|
67
69
|
requirements: []
|
68
|
-
rubygems_version: 3.1.
|
70
|
+
rubygems_version: 3.1.6
|
69
71
|
signing_key:
|
70
72
|
specification_version: 4
|
71
73
|
summary: Ruby SDK to run Evervault Cages
|
data/Gemfile.lock
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
evervault (0.1.3)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
addressable (2.7.0)
|
10
|
-
public_suffix (>= 2.0.2, < 5.0)
|
11
|
-
coderay (1.1.3)
|
12
|
-
crack (0.4.4)
|
13
|
-
diff-lcs (1.4.4)
|
14
|
-
faraday (1.1.0)
|
15
|
-
multipart-post (>= 1.2, < 3)
|
16
|
-
ruby2_keywords
|
17
|
-
gem-release (2.2.0)
|
18
|
-
hashdiff (1.0.1)
|
19
|
-
method_source (1.0.0)
|
20
|
-
multipart-post (2.1.1)
|
21
|
-
pry (0.13.1)
|
22
|
-
coderay (~> 1.1)
|
23
|
-
method_source (~> 1.0)
|
24
|
-
public_suffix (4.0.6)
|
25
|
-
rake (12.3.3)
|
26
|
-
rspec (3.9.0)
|
27
|
-
rspec-core (~> 3.9.0)
|
28
|
-
rspec-expectations (~> 3.9.0)
|
29
|
-
rspec-mocks (~> 3.9.0)
|
30
|
-
rspec-core (3.9.3)
|
31
|
-
rspec-support (~> 3.9.3)
|
32
|
-
rspec-expectations (3.9.2)
|
33
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
34
|
-
rspec-support (~> 3.9.0)
|
35
|
-
rspec-mocks (3.9.1)
|
36
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
-
rspec-support (~> 3.9.0)
|
38
|
-
rspec-support (3.9.3)
|
39
|
-
ruby2_keywords (0.0.2)
|
40
|
-
webmock (3.9.3)
|
41
|
-
addressable (>= 2.3.6)
|
42
|
-
crack (>= 0.3.2)
|
43
|
-
hashdiff (>= 0.4.0, < 2.0.0)
|
44
|
-
|
45
|
-
PLATFORMS
|
46
|
-
ruby
|
47
|
-
|
48
|
-
DEPENDENCIES
|
49
|
-
evervault!
|
50
|
-
faraday
|
51
|
-
gem-release
|
52
|
-
pry
|
53
|
-
rake (~> 12.0)
|
54
|
-
rspec (~> 3.0)
|
55
|
-
webmock
|
56
|
-
|
57
|
-
BUNDLED WITH
|
58
|
-
2.1.4
|
data/lib/evervault/crypto/key.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
require "openssl"
|
2
|
-
|
3
|
-
module Evervault
|
4
|
-
module Crypto
|
5
|
-
class Key
|
6
|
-
attr_reader :public_key
|
7
|
-
def initialize(public_key:)
|
8
|
-
@public_key = OpenSSL::PKey::RSA.new(format_key(public_key))
|
9
|
-
end
|
10
|
-
|
11
|
-
private def format_key(key)
|
12
|
-
key_header = "-----BEGIN PUBLIC KEY-----\n"
|
13
|
-
key_footer = "-----END PUBLIC KEY-----"
|
14
|
-
return key if key.include?(key_header) && key.include?(key_footer)
|
15
|
-
"#{key_header}#{key.scan(/.{0,64}/).join("\n")}#{key_footer}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|