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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3b50c9b63dbb48205dd12e7f25a7df7bde49ccee2945a4a40f999d635d14ca4
4
- data.tar.gz: d5582b0e9be47496c09d01581a00e6e3cfd6abf824d8bb57275f3fcd7800ac1d
3
+ metadata.gz: 7382a19b6a9fa82b2d37caa8f3e440f7bfce74bc18a237f13bdbd598dbb0d855
4
+ data.tar.gz: 78d8c04663caeca5ec0ff9336f403ec496b69fa61b25f134c9fae66eff91ab78
5
5
  SHA512:
6
- metadata.gz: c01d3201c76b86f8924c8ee8f90feb77da2d6bca98cebb724c10a75c181f6ab8ac7847686044cdc7e3f57ea84918e7f15d4eb66b6e2d792cffb9b362d5da8988
7
- data.tar.gz: e0b570280d96839cf6df0cb8c595bc3e9b23b9f60fcb763f75513af4961bd97ffadfae01de40be5c023cc7390d7cead0f8e3c0b8ce6c67bda36e2d62e12b3b7e
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, 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,81 +1,94 @@
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
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
 
@@ -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://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)
@@ -30,7 +34,7 @@ module Evervault
30
34
  end
31
35
 
32
36
  def run(cage_name, encrypted_data, options = {})
33
- @request.post(cage_name, encrypted_data, options: options, cage_run: true)
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 = @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
@@ -6,41 +6,24 @@ 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, 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
- return JSON.parse(resp.body) if resp.status >= 200 && resp.status <= 300
43
- Evervault::Errors::ErrorMap.raise_errors_on_failure(resp.status, resp.body)
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
+
@@ -1,3 +1,4 @@
1
1
  module Evervault
2
- VERSION = "0.1.3"
2
+ VERSION = "1.0.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.3
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-11-10 00:00:00.000000000 Z
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/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.4
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
@@ -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