evervault 0.1.1 → 1.0.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: e94804544d442e809f5a381a6e650091afd686aea080f54edf50c10ed0c3751b
4
- data.tar.gz: 2aa82fb3c03cdd1424b13392165c135d3607401c9f8b2532c4fa1d12044f4046
3
+ metadata.gz: 7382a19b6a9fa82b2d37caa8f3e440f7bfce74bc18a237f13bdbd598dbb0d855
4
+ data.tar.gz: 78d8c04663caeca5ec0ff9336f403ec496b69fa61b25f134c9fae66eff91ab78
5
5
  SHA512:
6
- metadata.gz: b84446fc0f2e3a2e0f9b5505934060d4483bf1e0452b0e0b14cb1befc871a43ba1835d4fe985e7144bda8be5162914e334e2fc425e7b1d77a80a3b7c98e7b725
7
- data.tar.gz: 4891c7eb1bd66ff1677fa56a403b60763bfce49c7e4e10e5bae56a5828132d831ac51972c0e4a43877bbca4c483c674e4ac0456ee2449b04429218666acde9da
6
+ metadata.gz: f0154d3c18eaa8685e6f1d26069c286609eaeb0bf8cc56049f53870ea3d374d0c7f45aeb855e47094dfc30b8a55d54d1122fac2ffc008e8a252ca843e711081e
7
+ data.tar.gz: 3915f0582de2f85ca71e9769f1cb4bf2bdd3013f50903beb8ae1946a39d9dd87733e340159aaf5c5192872ab1685ab39d6decd43d45487bd56dc8a4ea1914b4c
@@ -0,0 +1,6 @@
1
+ # This file is used to automatically request reviews on PRs.
2
+
3
+ # These owners will be the default owners for everything in
4
+ # the repo, unless a match on a later line takes precedence.
5
+ # "@evervault/product-engineering" below requests review from all members of Product Engineering.
6
+ * @evervault/product-engineering
@@ -0,0 +1,6 @@
1
+ # Why
2
+ Add a short description about this PR.
3
+ Add links to issues, tech plans etc.
4
+
5
+ # How
6
+ Describe how you've approached the problem
@@ -1,19 +1,38 @@
1
1
  name: Publish Gem
2
2
 
3
- on: [push]
4
-
3
+ on:
4
+ push:
5
+ tags:
6
+ - v*
5
7
 
6
8
  jobs:
7
-
9
+ test:
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ os: [ubuntu, macos]
14
+ ruby: [2.5, 2.6, 2.7, 3.1, truffleruby]
15
+ runs-on: ${{ matrix.os }}-latest
16
+ continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby }}
22
+ - run: bundle install
23
+ - run: bundle exec rake
24
+
8
25
  build:
9
26
  runs-on: ubuntu-latest
10
-
27
+ needs: test
28
+
11
29
  steps:
12
30
  - uses: actions/checkout@v2
13
31
  - uses: ruby/setup-ruby@v1
14
32
  with:
15
33
  ruby-version: 2.7
16
34
  - name: Release Gem
35
+ if: contains(github.ref, 'refs/tags/v')
17
36
  run: ./scripts/release.sh
18
37
  env:
19
38
  GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
@@ -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
@@ -6,6 +6,7 @@ gemspec
6
6
  gem "rake", "~> 12.0"
7
7
  gem "rspec", "~> 3.0"
8
8
  gem "pry"
9
- gem "typhoeus"
10
9
  gem "webmock"
11
10
  gem "gem-release"
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).
14
+
15
+ ## Documentation
16
16
 
17
- We are currently in invite-only early access. You can apply for early access [here](https://evervault.com).
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
+ | cageName | 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,7 +117,7 @@ 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
+ | cageName | String | Name of the Cage to be run |
100
121
  | data | dict | Data to be encrypted |
101
122
 
102
123
  ### Evervault.cages
@@ -129,7 +150,7 @@ Evervault.cages
129
150
 
130
151
  ### Evervault.cage_list
131
152
 
132
- Return a `CageList` object, containing a list of your team's cages
153
+ Return a `CageList` object, containing a list of your team's Cages
133
154
 
134
155
  ```ruby
135
156
  Evervault.cage_list
@@ -167,7 +188,7 @@ Evervault.cage_list
167
188
 
168
189
  #### CageList.to_hash
169
190
 
170
- Converts a list of cages to a hash with keys of CageName => Cage Model
191
+ Converts a list of Cages to a hash with keys of CageName => Cage Model
171
192
 
172
193
  ```ruby
173
194
  Evervault.cage_list.to_hash
@@ -210,9 +231,9 @@ Evervault.cage_list.to_hash
210
231
 
211
232
  ### Evervault::Models::Cage.run
212
233
 
213
- Each Cage model exposes a `run` method, which allows you to run that particular cage.
234
+ Each Cage model exposes a `run` method, which allows you to run that particular Cage.
214
235
 
215
- *Note*: this does not encrypt data before running the cage
236
+ *Note*: this does not encrypt data before running the Cage.
216
237
  ```ruby
217
238
  cage = Evervault.cage_list.cages[0]
218
239
  cage.run({'name': 'testing'})
@@ -221,7 +242,8 @@ cage.run({'name': 'testing'})
221
242
 
222
243
  | Parameter | Type | Description |
223
244
  | --------- | ---- | ----------- |
224
- | data | Hash | Payload for the cage |
245
+ | data | Hash | Payload for the Cage |
246
+ | options | Hash | [Options for the Cage run](#Cage-Run-Options) |
225
247
 
226
248
  ## Development
227
249
 
@@ -233,6 +255,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
233
255
 
234
256
  Bug reports and pull requests are welcome on GitHub at https://github.com/evervault/evervault-ruby.
235
257
 
258
+ ## Feedback
259
+
260
+ Questions or feedback? [Let us know](mailto:support@evervault.com).
236
261
 
237
262
  ## License
238
263
 
@@ -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,12 @@ 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
49
57
  end
50
58
  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
@@ -1,4 +1,4 @@
1
- require "typhoeus"
1
+ require "faraday"
2
2
  require "json"
3
3
  require_relative "../version"
4
4
  require_relative "../errors/error_map"
@@ -6,63 +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)
38
- Typhoeus::Config.user_agent = "evervault-ruby/#{VERSION}"
39
- req =
40
- Typhoeus::Request.new(
41
- url,
42
- method: method,
43
- params: params,
44
- headers: build_headers
45
- )
46
- req.on_complete do |response|
47
- if response.success?
48
- return JSON.parse(response.body)
49
- else
50
- Evervault::Errors::ErrorMap.raise_errors_on_failure(
51
- response.code,
52
- response.body
53
- )
14
+ def execute(method, url, params, optional_headers = {}, is_ca = false)
15
+ resp = Faraday.send(method, url) do |req|
16
+ req.body = params.nil? || params.empty? ? nil : params.to_json
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
54
23
  end
24
+ return JSON.parse(resp.body)
55
25
  end
56
- resp = req.run
26
+ Evervault::Errors::ErrorMap.raise_errors_on_failure(resp.status, resp.body, resp.headers)
57
27
  end
58
28
 
59
- private def build_headers
60
- {
29
+ private def build_headers(optional_headers)
30
+ optional_headers.merge({
61
31
  "AcceptEncoding": "gzip, deflate",
62
32
  "Accept": "application/json",
63
33
  "Content-Type": "application/json",
64
- "Api-Key": @api_key
65
- }
34
+ "User-Agent": "evervault-ruby/#{VERSION}",
35
+ "Api-Key": @api_key,
36
+ })
66
37
  end
67
38
  end
68
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.1"
2
+ VERSION = "1.0.0"
3
+ EV_VERSION = {"prime256v1" => "NOC", "secp256k1" => "DUB"}
3
4
  end
data/lib/evervault.rb CHANGED
@@ -6,24 +6,8 @@ module Evervault
6
6
  class << self
7
7
  attr_accessor :api_key
8
8
 
9
- def run(cage_name, encrypted_data)
10
- client.run(cage_name, encrypted_data)
11
- end
12
-
13
- def encrypt_and_run(cage_name, data)
14
- client.encrypt_and_run(cage_name, data)
15
- end
16
-
17
- def encrypt(data)
18
- client.encrypt(data)
19
- end
20
-
21
- def cages
22
- client.cages
23
- end
24
-
25
- def cage_list
26
- client.cage_list
9
+ def method_missing(method, *args, &block)
10
+ client.send(method, *args, &block)
27
11
  end
28
12
 
29
13
  private def client
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.1
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: 2020-10-23 00:00:00.000000000 Z
11
+ date: 2022-07-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -17,12 +17,13 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - ".github/CODEOWNERS"
21
+ - ".github/pull_request_template.md"
20
22
  - ".github/workflows/release.yml"
21
23
  - ".github/workflows/run-tests.yml"
22
24
  - ".gitignore"
23
25
  - ".rspec"
24
26
  - Gemfile
25
- - Gemfile.lock
26
27
  - LICENSE.txt
27
28
  - README.md
28
29
  - Rakefile
@@ -32,10 +33,13 @@ files:
32
33
  - lib/evervault.rb
33
34
  - lib/evervault/client.rb
34
35
  - lib/evervault/crypto/client.rb
35
- - lib/evervault/crypto/key.rb
36
+ - lib/evervault/crypto/curves/base.rb
37
+ - lib/evervault/crypto/curves/p256.rb
36
38
  - lib/evervault/errors/error_map.rb
37
39
  - lib/evervault/errors/errors.rb
38
40
  - lib/evervault/http/request.rb
41
+ - lib/evervault/http/request_handler.rb
42
+ - lib/evervault/http/request_intercept.rb
39
43
  - lib/evervault/models/cage.rb
40
44
  - lib/evervault/models/cage_list.rb
41
45
  - lib/evervault/version.rb
@@ -63,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
67
  - !ruby/object:Gem::Version
64
68
  version: '0'
65
69
  requirements: []
66
- rubygems_version: 3.1.4
70
+ rubygems_version: 3.1.6
67
71
  signing_key:
68
72
  specification_version: 4
69
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
- ethon (0.12.0)
15
- ffi (>= 1.3.0)
16
- ffi (1.13.1)
17
- gem-release (2.2.0)
18
- hashdiff (1.0.1)
19
- method_source (1.0.0)
20
- pry (0.13.1)
21
- coderay (~> 1.1)
22
- method_source (~> 1.0)
23
- public_suffix (4.0.6)
24
- rake (12.3.3)
25
- rspec (3.9.0)
26
- rspec-core (~> 3.9.0)
27
- rspec-expectations (~> 3.9.0)
28
- rspec-mocks (~> 3.9.0)
29
- rspec-core (3.9.3)
30
- rspec-support (~> 3.9.3)
31
- rspec-expectations (3.9.2)
32
- diff-lcs (>= 1.2.0, < 2.0)
33
- rspec-support (~> 3.9.0)
34
- rspec-mocks (3.9.1)
35
- diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.9.0)
37
- rspec-support (3.9.3)
38
- typhoeus (1.4.0)
39
- ethon (>= 0.9.0)
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
- gem-release
51
- pry
52
- rake (~> 12.0)
53
- rspec (~> 3.0)
54
- typhoeus
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