evervault 0.1.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e666a2b2b7d130e4221e2c25f963fdd6a3e7e986f08127be6f3937ab7f94e52
4
- data.tar.gz: dbd8034681606769295e1a2678b019c02f7600b96918c6bb737a5ad0bb9f0616
3
+ metadata.gz: 2b06c4fd79f260d853fa0fd738ae225c9670c6b7561bc6cb66ffac99850f0576
4
+ data.tar.gz: 6f39d076798f8b52dc557c1ff3f3834f67abe21874ad530ff399f6404ea46842
5
5
  SHA512:
6
- metadata.gz: 4830d141e96f9b47a65a581bd214a0cd4d86d440e6f2fdc6f70cd9abd7371f86ef331982d65d3850a19edbc56833994aec8463bc5d55fb0c4ea73c66b6d23abe
7
- data.tar.gz: 6d22b0c92ea74e1679513b53431ac7ebe78ba79b215eaea9ebf84062cb2ec45ba4a6e99a1075e66c93a39f9d8c7b1305f0e717c2f9408f4270a7a74d34ab5ca3
6
+ metadata.gz: 862d87c3a14651f46d6efef35c9d679d0e42a79fc90adb619ff4501ac8e304fa3c55650875e6c9e24bf7cf95c8d9bfef304f6af5157b0eab726c93f35d4e3c89
7
+ data.tar.gz: f7074851247c1794960daa610c0360423a41631be458752ea24f7e09230c1e20a3b7d862b23e3fc85cfd4a6815f8aceef60e517d5e30229fd0a008e0b10eae6e
@@ -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, jruby, jruby-head, truffleruby]
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, jruby, jruby-head, truffleruby]
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
@@ -9,3 +9,4 @@ gem "pry"
9
9
  gem "webmock"
10
10
  gem "gem-release"
11
11
  gem "faraday"
12
+ gem "rexml"
data/README.md CHANGED
@@ -1,94 +1,115 @@
1
- # Evervault
2
- <p align="center">
3
- <img src="res/logo.svg">
4
- </p>
1
+ [![Evervault](https://evervault.com/evervault.svg)](https://evervault.com/)
5
2
 
6
- <p align="center">
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
+ [![Unit Tests Status](https://github.com/evervault/evervault-ruby/workflows/evervault-unit-tests/badge.svg)](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
- To get started with the Evervault Ruby SDK, you will need to have created a team on the evervault dashboard.
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).
16
14
 
17
- We are currently in invite-only early access. You can apply for early access [here](https://evervault.com).
15
+ ## Documentation
16
+
17
+ See the Evervault [Ruby SDK documentation](https://docs.evervault.com/ruby).
18
18
 
19
19
  ## Installation
20
20
 
21
- Add this line to your application's Gemfile:
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
- And then execute:
31
+ Then, run:
32
+
28
33
  ```sh
29
- bundle install
34
+ bundle install
30
35
  ```
31
- Or install it yourself as:
36
+ #### 2. By yourself
37
+
38
+ Just run:
39
+
32
40
  ```sh
33
- gem install evervault
41
+ gem install evervault
34
42
  ```
35
43
 
36
44
  ## Setup
37
45
 
38
- Evervault can be initialized as a singleton throughout the lifecycle of your application.
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 api key
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
- result = Evervault.encrypt_and_run(<CAGE-NAME>, { hello: 'World!' })
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
- It's recommended to re-use your Evervault client, to prevent additional overhead of loading keys at runtime, so the singleton pattern should be the go-to pattern for most use-cases.
61
+ ## Reference
50
62
 
51
- However, if you'd prefer to initialize different clients at different times, for example, if you have multiple teams and need to switch context, you can simply create a client:
52
- ```ruby
53
- require "evervault"
63
+ The Evervault Ruby SDK exposes eight methods.
54
64
 
55
- # Initialize the client with your team's api key
56
- evervault = Evervault::Client.new(api_key: <YOUR-API-KEY>)
65
+ ### Evervault.encrypt
57
66
 
58
- # Encrypt your data and run a cage
59
- result = evervault.encrypt_and_run(<CAGE-NAME>, { hello: 'World!' })
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
- ## API Reference
73
+ | Parameter | Type | Description |
74
+ | --------- | ---- | ----------- |
75
+ | data | `String`, `Number`, `Boolean`, `Hash`, `Array` | Data to be encrypted |
63
76
 
64
- ### Evervault.encrypt
77
+ ### Evervault.relay
65
78
 
66
- Encrypt lets you encrypt data for use in any of your evervault cages. You can use it to store encrypted data to be used in a cage at another time.
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.encrypt(data = Hash | String)
82
+ Evervault.relay(decryption_domains = Array)
70
83
  ```
71
84
 
72
85
  | Parameter | Type | Description |
73
86
  | --------- | ---- | ----------- |
74
- | data | Hash or String | Data to be encrypted |
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
- Run lets you invoke your evervault cages with a given payload.
91
+ `Evervault.run` invokes a Cage with a given payload.
79
92
 
80
93
  ```ruby
81
- Evervault.run(cage_name = String, data = Hash)
94
+ Evervault.run(cage_name = String, data = Hash[, options = Hash])
82
95
  ```
83
96
 
84
97
  | Parameter | Type | Description |
85
98
  | --------- | ---- | ----------- |
86
- | cageName | String | Name of the cage to be run |
87
- | data | Hash | Payload for the cage |
99
+ | cage_name | String | Name of the Cage to be run |
100
+ | data | Hash | Payload for the Cage |
101
+ | options | Hash | [Options for the Cage run](#Cage-Run-Options) |
102
+
103
+ #### Cage Run Options
104
+
105
+ | Option | Type | Default | Description |
106
+ | ------ | ---- | ------- | ----------- |
107
+ | `async` | `Boolean` | `false` | Run your Cage in async mode. Async Cage runs will be queued for processing. |
108
+ | `version` | `Integer` | `nil` | Specify the version of your Cage to run. By default, the latest version will be run. |
88
109
 
89
110
  ### Evervault.encrypt_and_run
90
111
 
91
- Encrypt your data and use it as the payload to invoke the cage.
112
+ Encrypt your data and use it as the payload to invoke the Cage.
92
113
 
93
114
  ```ruby
94
115
  Evervault.encrypt_and_run(cage_name = String, data = Hash)
@@ -96,9 +117,22 @@ Evervault.encrypt_and_run(cage_name = String, data = Hash)
96
117
 
97
118
  | Parameter | Type | Description |
98
119
  | --------- | ---- | ----------- |
99
- | cageName | String | Name of the cage to be run |
120
+ | cage_name | String | Name of the Cage to be run |
100
121
  | data | dict | Data to be encrypted |
101
122
 
123
+ ### Evervault.create_run_token
124
+
125
+ `Evervault.create_run_token` creates a single use, time bound token for invoking a cage.
126
+
127
+ ```ruby
128
+ Evervault.create_run_token(cage_name = String, data = Hash)
129
+ ```
130
+
131
+ | Parameter | Type | Description |
132
+ | --------- | ------ | ---------------------------------------------------- |
133
+ | cage_name | String | Name of the Cage the run token should be created for |
134
+ | data | Hash | Payload that the token can be used with |
135
+
102
136
  ### Evervault.cages
103
137
 
104
138
  Return a hash of your team's Cage objects in hash format, with cage-name as keys
@@ -129,7 +163,7 @@ Evervault.cages
129
163
 
130
164
  ### Evervault.cage_list
131
165
 
132
- Return a `CageList` object, containing a list of your team's cages
166
+ Return a `CageList` object, containing a list of your team's Cages
133
167
 
134
168
  ```ruby
135
169
  Evervault.cage_list
@@ -167,7 +201,7 @@ Evervault.cage_list
167
201
 
168
202
  #### CageList.to_hash
169
203
 
170
- Converts a list of cages to a hash with keys of CageName => Cage Model
204
+ Converts a list of Cages to a hash with keys of CageName => Cage Model
171
205
 
172
206
  ```ruby
173
207
  Evervault.cage_list.to_hash
@@ -210,9 +244,9 @@ Evervault.cage_list.to_hash
210
244
 
211
245
  ### Evervault::Models::Cage.run
212
246
 
213
- Each Cage model exposes a `run` method, which allows you to run that particular cage.
247
+ Each Cage model exposes a `run` method, which allows you to run that particular Cage.
214
248
 
215
- *Note*: this does not encrypt data before running the cage
249
+ *Note*: this does not encrypt data before running the Cage.
216
250
  ```ruby
217
251
  cage = Evervault.cage_list.cages[0]
218
252
  cage.run({'name': 'testing'})
@@ -221,7 +255,8 @@ cage.run({'name': 'testing'})
221
255
 
222
256
  | Parameter | Type | Description |
223
257
  | --------- | ---- | ----------- |
224
- | data | Hash | Payload for the cage |
258
+ | data | Hash | Payload for the Cage |
259
+ | options | Hash | [Options for the Cage run](#Cage-Run-Options) |
225
260
 
226
261
  ## Development
227
262
 
@@ -233,6 +268,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
233
268
 
234
269
  Bug reports and pull requests are welcome on GitHub at https://github.com/evervault/evervault-ruby.
235
270
 
271
+ ## Feedback
272
+
273
+ Questions or feedback? [Let us know](mailto:support@evervault.com).
236
274
 
237
275
  ## License
238
276
 
@@ -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,33 +11,35 @@ module Evervault
9
11
  def initialize(
10
12
  api_key:,
11
13
  base_url: "https://api.evervault.com/",
12
- cage_run_url: "https://cage.run/",
13
- request_timeout: 30
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
- @api_key = api_key
16
- @base_url = base_url
17
- @cage_run_url = cage_run_url
18
- @request =
19
- Evervault::Http::Request.new(
20
- api_key: api_key,
21
- timeout: request_timeout,
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(request: @request)
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)
29
33
  @crypto_client.encrypt(data)
30
34
  end
31
35
 
32
- def run(cage_name, encrypted_data)
33
- @request.post(cage_name, encrypted_data, cage_run: true)
36
+ def run(cage_name, encrypted_data, options = {})
37
+ @request_handler.post(cage_name, encrypted_data, options: options, cage_run: true)
34
38
  end
35
39
 
36
- def encrypt_and_run(cage_name, data)
40
+ def encrypt_and_run(cage_name, data, options = {})
37
41
  encrypted_data = encrypt(data)
38
- run(cage_name, encrypted_data)
42
+ run(cage_name, encrypted_data, options)
39
43
  end
40
44
 
41
45
  def cages
@@ -43,8 +47,16 @@ module Evervault
43
47
  end
44
48
 
45
49
  def cage_list
46
- cages = @request.get("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
57
+
58
+ def create_run_token(cage_name, data)
59
+ @request_handler.post("v2/functions/#{cage_name}/run-token", data)
60
+ end
49
61
  end
50
62
  end
@@ -1,5 +1,6 @@
1
1
  require_relative "../errors/errors"
2
- require_relative "key"
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 :request
12
- def initialize(request:)
13
- @request = request
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
- if data.instance_of? Hash
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(data)
29
- cipher = OpenSSL::Cipher::AES256.new(:GCM).encrypt
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
- encrypted_data = cipher.update(data) + cipher.final
35
- encrypted_buffer = encrypted_data + cipher.auth_tag
36
- encrypted_key =
37
- team_key.public_key.public_encrypt(
38
- root_key,
39
- OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
40
- )
41
- data = [encrypted_key, encrypted_buffer, iv].map { |val| Base64.strict_encode64(val) }
42
- format(header_type(data), *data)
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 encrypt_hash(data)
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] = encrypt_hash(value) }
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) || data.instance_of?(Array) ||
58
- [true, false].include?(data) || data.instance_of?(Integer) ||
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 team_key
63
- @team_key ||= Key.new(public_key: @request.get("cages/key")["key"])
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(header, encrypted_key, encrypted_data, iv)
67
- header =
68
- utf8_to_base_64_url(
69
- { iss: "evervault", version: 1, datatype: header }.to_json
70
- )
71
- payload =
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 utf8_to_base_64_url(data)
83
- b64_string = Base64.strict_encode64(data)
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
- raise AuthenticationError.new("Forbidden")
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,51 +6,34 @@ require_relative "../errors/error_map"
6
6
  module Evervault
7
7
  module Http
8
8
  class Request
9
- def initialize(api_key:, base_url:, cage_run_url:, timeout:)
10
- @api_key = api_key
9
+ def initialize(timeout:, api_key:)
11
10
  @timeout = timeout
12
- @base_url = base_url
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, cage_run: false)
29
- execute(:post, build_url(path, cage_run), params)
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)
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
- req.headers = build_headers
17
+ req.headers = build_headers(optional_headers)
18
+ req.options.timeout = @timeout
19
+ end
20
+ if resp.status >= 200 && resp.status <= 300
21
+ if is_ca
22
+ return resp.body
23
+ end
24
+ return JSON.parse(resp.body)
41
25
  end
42
- return JSON.parse(resp.body) if resp.status >= 200 && resp.status <= 300
43
- Evervault::Errors::ErrorMap.raise_errors_on_failure(resp.status, resp.body)
26
+ Evervault::Errors::ErrorMap.raise_errors_on_failure(resp.status, resp.body, resp.headers)
44
27
  end
45
28
 
46
- private def build_headers
47
- {
29
+ private def build_headers(optional_headers)
30
+ optional_headers.merge({
48
31
  "AcceptEncoding": "gzip, deflate",
49
32
  "Accept": "application/json",
50
33
  "Content-Type": "application/json",
51
34
  "User-Agent": "evervault-ruby/#{VERSION}",
52
- "Api-Key": @api_key
53
- }
35
+ "Api-Key": @api_key,
36
+ })
54
37
  end
55
38
  end
56
39
  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
+
@@ -9,8 +9,8 @@ module Evervault
9
9
  @request = request
10
10
  end
11
11
 
12
- def run(params)
13
- @request.post(self.name, params, cage_run: true)
12
+ def run(params, options = {})
13
+ @request.post(self.name, params, options: options, cage_run: true)
14
14
  end
15
15
 
16
16
  end
@@ -1,3 +1,4 @@
1
1
  module Evervault
2
- VERSION = "0.1.2"
2
+ VERSION = "1.1.0"
3
+ EV_VERSION = {"prime256v1" => "NOC", "secp256k1" => "DUB"}
3
4
  end
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.1.2
4
+ version: 1.1.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: 2020-11-04 00:00:00.000000000 Z
11
+ date: 2022-09-05 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/key.rb
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.2
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.1)
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
@@ -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