mastercard-client-encryption 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mcapi/encryption/crypto/jwe-crypto.rb +175 -0
- data/lib/mcapi/encryption/field_level_encryption.rb +10 -43
- data/lib/mcapi/encryption/jwe_encryption.rb +95 -0
- data/lib/mcapi/encryption/openapi_interceptor.rb +36 -8
- data/lib/mcapi/encryption/utils/utils.rb +40 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 498fe9a62ff880357316d0dd97d78d27dae85bca253702d905e2769087579ba2
|
4
|
+
data.tar.gz: 22a35a9afebde5f8f746067154c339ced42edd4b0d4ec0d6fa920963f1de5abd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3fe63908d6249e94fc6db683aefe97a82622ee4c8c59eca8e8a7cd21f7d79a96c0476f6efb6bc9362d2903eed8afc0a0d04a37b2b8a44ed438989560c5b47034
|
7
|
+
data.tar.gz: 879b24f693402374293b543d7a5bbacd4b05b12b23f4586a85ee2c078a3631fa16159051cc2acac2f07820fa48ddfdf6f1205dcd3419cf9cd2d6851af0ebb383
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'openssl'
|
5
|
+
require 'base64'
|
6
|
+
require 'securerandom'
|
7
|
+
require_relative '../utils/utils'
|
8
|
+
require_relative '../utils/openssl_rsa_oaep'
|
9
|
+
|
10
|
+
module McAPI
|
11
|
+
module Encryption
|
12
|
+
#
|
13
|
+
# JWE Crypto class provide RSA/AES encrypt/decrypt methods
|
14
|
+
#
|
15
|
+
class JweCrypto
|
16
|
+
#
|
17
|
+
# Create a new instance with the provided config
|
18
|
+
#
|
19
|
+
# @param [Hash] config configuration object
|
20
|
+
#
|
21
|
+
def initialize(config)
|
22
|
+
@encoding = config['dataEncoding']
|
23
|
+
@cert = OpenSSL::X509::Certificate.new(IO.binread(config['encryptionCertificate']))
|
24
|
+
if config['privateKey']
|
25
|
+
@private_key = OpenSSL::PKey.read(IO.binread(config['privateKey']))
|
26
|
+
elsif config['keyStore']
|
27
|
+
@private_key = OpenSSL::PKCS12.new(IO.binread(config['keyStore']), config['keyStorePassword']).key
|
28
|
+
end
|
29
|
+
@encrypted_value_field_name = config['encryptedValueFieldName'] || 'encryptedData'
|
30
|
+
@public_key_fingerprint = compute_public_fingerprint
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Perform data encryption:
|
35
|
+
#
|
36
|
+
# @param [String] data json string to encrypt
|
37
|
+
#
|
38
|
+
# @return [Hash] encrypted data
|
39
|
+
#
|
40
|
+
def encrypt_data(data:)
|
41
|
+
cek = SecureRandom.random_bytes(32)
|
42
|
+
iv = SecureRandom.random_bytes(12)
|
43
|
+
|
44
|
+
md = OpenSSL::Digest::SHA256
|
45
|
+
encrypted_key = @cert.public_key.public_encrypt_oaep(cek, '', md, md)
|
46
|
+
|
47
|
+
header = generate_header('RSA-OAEP-256', 'A256GCM')
|
48
|
+
json_hdr = header.to_json
|
49
|
+
auth_data = jwe_encode(json_hdr)
|
50
|
+
|
51
|
+
cipher = OpenSSL::Cipher.new('aes-256-gcm')
|
52
|
+
cipher.encrypt
|
53
|
+
cipher.key = cek
|
54
|
+
cipher.iv = iv
|
55
|
+
cipher.padding = 0
|
56
|
+
cipher.auth_data = auth_data
|
57
|
+
cipher_text = cipher.update(data) + cipher.final
|
58
|
+
|
59
|
+
payload = generate_serialization(json_hdr, encrypted_key, cipher_text, iv, cipher.auth_tag)
|
60
|
+
{
|
61
|
+
@encrypted_value_field_name => payload
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Perform data decryption
|
67
|
+
#
|
68
|
+
# @param [String] encrypted_data encrypted data to decrypt
|
69
|
+
#
|
70
|
+
# @return [String] Decrypted JSON object
|
71
|
+
#
|
72
|
+
def decrypt_data(encrypted_data:)
|
73
|
+
parts = encrypted_data.split('.')
|
74
|
+
encrypted_header, encrypted_key, initialization_vector, cipher_text, authentication_tag = parts
|
75
|
+
|
76
|
+
jwe_header = jwe_decode(encrypted_header)
|
77
|
+
encrypted_key = jwe_decode(encrypted_key)
|
78
|
+
iv = jwe_decode(initialization_vector)
|
79
|
+
cipher_text = jwe_decode(cipher_text)
|
80
|
+
cipher_tag = jwe_decode(authentication_tag)
|
81
|
+
|
82
|
+
md = OpenSSL::Digest::SHA256
|
83
|
+
cek = @private_key.private_decrypt_oaep(encrypted_key, '', md, md)
|
84
|
+
|
85
|
+
enc_method = JSON.parse(jwe_header)['enc']
|
86
|
+
|
87
|
+
if enc_method == "A256GCM"
|
88
|
+
enc_string = "aes-256-gcm"
|
89
|
+
elsif enc_method == "A128CBC-HS256"
|
90
|
+
cek = cek.byteslice(16, cek.length)
|
91
|
+
enc_string = "aes-128-cbc"
|
92
|
+
else
|
93
|
+
raise Exception, "Encryption method '#{enc_method}' not supported."
|
94
|
+
end
|
95
|
+
|
96
|
+
cipher = OpenSSL::Cipher.new(enc_string)
|
97
|
+
cipher.decrypt
|
98
|
+
cipher.key = cek
|
99
|
+
cipher.iv = iv
|
100
|
+
cipher.padding = 0
|
101
|
+
if enc_method == "A256GCM"
|
102
|
+
cipher.auth_data = encrypted_header
|
103
|
+
cipher.auth_tag = cipher_tag
|
104
|
+
end
|
105
|
+
|
106
|
+
cipher.update(cipher_text) + cipher.final
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
#
|
112
|
+
# Compute the fingerprint for the provided public key
|
113
|
+
#
|
114
|
+
# @return [String] the computed fingerprint encoded using the configured encoding
|
115
|
+
#
|
116
|
+
def compute_public_fingerprint
|
117
|
+
OpenSSL::Digest::SHA256.new(@cert.public_key.to_der).to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Generate the JWE header for the provided encryption algorithm and encryption method
|
122
|
+
#
|
123
|
+
# @param [String] alg the cryptographic algorithm used to encrypt the value of the CEK
|
124
|
+
# @param [String] enc the content encryption algorithm used to perform authenticated encryption on the plaintext
|
125
|
+
#
|
126
|
+
# @return [Hash] the JWE header
|
127
|
+
#
|
128
|
+
def generate_header(alg, enc)
|
129
|
+
{ alg: alg, enc: enc, kid: @public_key_fingerprint, cty: 'application/json' }
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# URL safe Base64 encode the provided value
|
134
|
+
#
|
135
|
+
# @param [String] value to be encoded
|
136
|
+
#
|
137
|
+
# @return [String] URL safe Base64 encoded value
|
138
|
+
#
|
139
|
+
def jwe_encode(value)
|
140
|
+
::Base64.urlsafe_encode64(value).delete('=')
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# URL safe Base64 decode the provided value
|
145
|
+
#
|
146
|
+
# @param [String] value to be decoded
|
147
|
+
#
|
148
|
+
# @return [String] URL safe Base64 decoded value
|
149
|
+
#
|
150
|
+
def jwe_decode(value)
|
151
|
+
padlen = 4 - (value.length % 4)
|
152
|
+
if padlen < 4
|
153
|
+
pad = '=' * padlen
|
154
|
+
value += pad
|
155
|
+
end
|
156
|
+
::Base64.urlsafe_decode64(value)
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Generate JWE compact payload from the provided values
|
161
|
+
#
|
162
|
+
# @param [String] hdr JWE header
|
163
|
+
# @param [String] cek content encryption key
|
164
|
+
# @param [String] content cipher text
|
165
|
+
# @param [String] iv initialization vector
|
166
|
+
# @param [String] tag cipher auth tag
|
167
|
+
#
|
168
|
+
# @return [String] URL safe Base64 decoded value
|
169
|
+
#
|
170
|
+
def generate_serialization(hdr, cek, content, iv, tag)
|
171
|
+
[hdr, cek, iv, content, tag].map { |piece| jwe_encode(piece) }.join '.'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'crypto/crypto'
|
4
4
|
require_relative 'utils/hash.ext'
|
5
|
+
require_relative 'utils/utils'
|
5
6
|
require 'json'
|
6
7
|
|
7
8
|
module McAPI
|
@@ -36,7 +37,7 @@ module McAPI
|
|
36
37
|
#
|
37
38
|
def encrypt(endpoint, header, body)
|
38
39
|
body = JSON.parse(body) if body.is_a?(String)
|
39
|
-
config = config?(endpoint)
|
40
|
+
config = McAPI::Utils.config?(endpoint, @config)
|
40
41
|
body_map = body
|
41
42
|
if config
|
42
43
|
if !@is_with_header
|
@@ -50,7 +51,7 @@ module McAPI
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
53
|
-
{ header: header, body: config ? compute_body(config['toEncrypt'], body_map) { body.json } : body.json }
|
54
|
+
{ header: header, body: config ? McAPI::Utils.compute_body(config['toEncrypt'], body_map) { body.json } : body.json }
|
54
55
|
end
|
55
56
|
|
56
57
|
#
|
@@ -62,7 +63,7 @@ module McAPI
|
|
62
63
|
#
|
63
64
|
def decrypt(response)
|
64
65
|
response = JSON.parse(response)
|
65
|
-
config = config?(response['request']['url'])
|
66
|
+
config = McAPI::Utils.config?(response['request']['url'], @config)
|
66
67
|
body_map = response
|
67
68
|
if config
|
68
69
|
if !@is_with_header
|
@@ -71,31 +72,31 @@ module McAPI
|
|
71
72
|
end
|
72
73
|
else
|
73
74
|
config['toDecrypt'].each do |v|
|
74
|
-
elem = elem_from_path(v['obj'], response['body'])
|
75
|
+
elem = McAPI::Utils.elem_from_path(v['obj'], response['body'])
|
75
76
|
decrypt_with_header(v, elem, response) if elem[:node][v['element']]
|
76
77
|
end
|
77
78
|
end
|
78
79
|
end
|
79
|
-
response['body'] = compute_body(config['toDecrypt'], body_map) { response['body'] } unless config.nil?
|
80
|
+
response['body'] = McAPI::Utils.compute_body(config['toDecrypt'], body_map) { response['body'] } unless config.nil?
|
80
81
|
JSON.generate(response)
|
81
82
|
end
|
82
83
|
|
83
84
|
private
|
84
85
|
|
85
86
|
def encrypt_with_body(path, body)
|
86
|
-
elem = elem_from_path(path['element'], body)
|
87
|
+
elem = McAPI::Utils.elem_from_path(path['element'], body)
|
87
88
|
return unless elem && elem[:node]
|
88
89
|
|
89
90
|
encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]))
|
90
91
|
body = McAPI::Utils.mutate_obj_prop(path['obj'], encrypted_data, body)
|
91
|
-
unless json_root?(path['obj']) || path['element'] == "#{path['obj']}.#{@config['encryptedValueFieldName']}"
|
92
|
+
unless McAPI::Utils.json_root?(path['obj']) || path['element'] == "#{path['obj']}.#{@config['encryptedValueFieldName']}"
|
92
93
|
McAPI::Utils.delete_node(path['element'], body)
|
93
94
|
end
|
94
95
|
body
|
95
96
|
end
|
96
97
|
|
97
98
|
def encrypt_with_header(path, enc_params, header, body)
|
98
|
-
elem = elem_from_path(path['element'], body)
|
99
|
+
elem = McAPI::Utils.elem_from_path(path['element'], body)
|
99
100
|
return unless elem && elem[:node]
|
100
101
|
|
101
102
|
encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]), encryption_params: enc_params)
|
@@ -105,7 +106,7 @@ module McAPI
|
|
105
106
|
end
|
106
107
|
|
107
108
|
def decrypt_with_body(path, body)
|
108
|
-
elem = elem_from_path(path['element'], body)
|
109
|
+
elem = McAPI::Utils.elem_from_path(path['element'], body)
|
109
110
|
return unless elem && elem[:node]
|
110
111
|
|
111
112
|
decrypted = @crypto.decrypt_data(elem[:node][@config['encryptedValueFieldName']],
|
@@ -130,46 +131,12 @@ module McAPI
|
|
130
131
|
response['headers'][@config['oaepHashingAlgorithmHeaderName']][0]))
|
131
132
|
end
|
132
133
|
|
133
|
-
def elem_from_path(path, obj)
|
134
|
-
parent = nil
|
135
|
-
paths = path.split('.')
|
136
|
-
if path && !paths.empty?
|
137
|
-
paths.each do |e|
|
138
|
-
parent = obj
|
139
|
-
obj = json_root?(e) ? obj : obj[e]
|
140
|
-
end
|
141
|
-
end
|
142
|
-
{ node: obj, parent: parent }
|
143
|
-
rescue StandardError
|
144
|
-
nil
|
145
|
-
end
|
146
|
-
|
147
|
-
def config?(endpoint)
|
148
|
-
return unless endpoint
|
149
|
-
|
150
|
-
endpoint = endpoint.split('?').shift
|
151
|
-
conf = @config['paths'].select { |e| endpoint.match(e['path']) }
|
152
|
-
conf.empty? ? nil : conf[0]
|
153
|
-
end
|
154
|
-
|
155
134
|
def set_header(header, params)
|
156
135
|
header[@config['encryptedKeyHeaderName']] = params[:encoded][:encryptedKey]
|
157
136
|
header[@config['ivHeaderName']] = params[:encoded][:iv]
|
158
137
|
header[@config['oaepHashingAlgorithmHeaderName']] = params[:oaepHashingAlgorithm].sub('-', '')
|
159
138
|
header[@config['publicKeyFingerprintHeaderName']] = params[:publicKeyFingerprint]
|
160
139
|
end
|
161
|
-
|
162
|
-
def json_root?(elem)
|
163
|
-
elem == '$'
|
164
|
-
end
|
165
|
-
|
166
|
-
def compute_body(config_param, body_map)
|
167
|
-
encryption_param?(config_param, body_map) ? body_map[0] : yield
|
168
|
-
end
|
169
|
-
|
170
|
-
def encryption_param?(enc_param, body_map)
|
171
|
-
enc_param.length == 1 && body_map.length == 1
|
172
|
-
end
|
173
140
|
end
|
174
141
|
end
|
175
142
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'crypto/jwe-crypto'
|
4
|
+
require_relative 'utils/hash.ext'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module McAPI
|
8
|
+
module Encryption
|
9
|
+
#
|
10
|
+
# Performs JWE encryption on HTTP payloads.
|
11
|
+
#
|
12
|
+
class JweEncryption
|
13
|
+
#
|
14
|
+
# Create a new instance with the provided configuration
|
15
|
+
#
|
16
|
+
# @param [Hash] config Configuration object
|
17
|
+
#
|
18
|
+
def initialize(config)
|
19
|
+
@config = config
|
20
|
+
@crypto = McAPI::Encryption::JweCrypto.new(config)
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Encrypt parts of a HTTP request using the given config
|
25
|
+
#
|
26
|
+
# @param [String] endpoint HTTP URL for the current call
|
27
|
+
# @param [Object|nil] header HTTP header
|
28
|
+
# @param [String,Hash] body HTTP body
|
29
|
+
#
|
30
|
+
# @return [Hash] Hash with two keys:
|
31
|
+
# * :header header with encrypted value (if configured with header)
|
32
|
+
# * :body encrypted body
|
33
|
+
#
|
34
|
+
def encrypt(endpoint, body)
|
35
|
+
body = JSON.parse(body) if body.is_a?(String)
|
36
|
+
config = McAPI::Utils.config?(endpoint, @config)
|
37
|
+
body_map = body
|
38
|
+
if config
|
39
|
+
body_map = config['toEncrypt'].map do |v|
|
40
|
+
encrypt_with_body(v, body)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
{ body: config ? McAPI::Utils.compute_body(config['toEncrypt'], body_map) { body.json } : body.json }
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Decrypt part of the HTTP response using the given config
|
48
|
+
#
|
49
|
+
# @param [Object] response object as obtained from the http client
|
50
|
+
#
|
51
|
+
# @return [Object] response object with decrypted fields
|
52
|
+
#
|
53
|
+
def decrypt(response)
|
54
|
+
response = JSON.parse(response)
|
55
|
+
config = McAPI::Utils.config?(response['request']['url'], @config)
|
56
|
+
body_map = response
|
57
|
+
if config
|
58
|
+
body_map = config['toDecrypt'].map do |v|
|
59
|
+
decrypt_with_body(v, response['body'])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
response['body'] = McAPI::Utils.compute_body(config['toDecrypt'], body_map) { response['body'] } unless config.nil?
|
63
|
+
JSON.generate(response)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def encrypt_with_body(path, body)
|
69
|
+
elem = McAPI::Utils.elem_from_path(path['element'], body)
|
70
|
+
return unless elem && elem[:node]
|
71
|
+
|
72
|
+
encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]))
|
73
|
+
body = McAPI::Utils.mutate_obj_prop(path['obj'], encrypted_data, body)
|
74
|
+
unless McAPI::Utils.json_root?(path['obj']) || path['element'] == "#{path['obj']}.#{@config['encryptedValueFieldName']}"
|
75
|
+
McAPI::Utils.delete_node(path['element'], body)
|
76
|
+
end
|
77
|
+
body
|
78
|
+
end
|
79
|
+
|
80
|
+
def decrypt_with_body(path, body)
|
81
|
+
elem = McAPI::Utils.elem_from_path(path['element'], body)
|
82
|
+
return unless elem && elem[:node]
|
83
|
+
|
84
|
+
decrypted = @crypto.decrypt_data(encrypted_data: elem[:node][@config['encryptedValueFieldName']])
|
85
|
+
begin
|
86
|
+
decrypted = JSON.parse(decrypted)
|
87
|
+
rescue JSON::ParserError
|
88
|
+
# ignored
|
89
|
+
end
|
90
|
+
|
91
|
+
McAPI::Utils.mutate_obj_prop(path['obj'], decrypted, body, path['element'], @encryption_response_properties)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'field_level_encryption'
|
4
|
+
require_relative 'jwe_encryption'
|
4
5
|
require_relative 'utils/utils'
|
5
6
|
|
6
7
|
module McAPI
|
@@ -27,14 +28,36 @@ module McAPI
|
|
27
28
|
McAPI::Encryption::OpenAPIInterceptor.init_deserialize swagger_client
|
28
29
|
end
|
29
30
|
|
31
|
+
#
|
32
|
+
# Install the JWE encryption in the OpenAPI HTTP client
|
33
|
+
# adding encryption/decryption capabilities for the request/response payload.
|
34
|
+
#
|
35
|
+
# @param [Object] swagger_client OpenAPI service client (it can be generated by the swagger code generator)
|
36
|
+
# @param [Hash] config configuration object describing which field to enable encryption/decryption
|
37
|
+
#
|
38
|
+
def install_jwe_encryption(swagger_client, config)
|
39
|
+
jwe = McAPI::Encryption::JweEncryption.new(config)
|
40
|
+
# Hooking ApiClient#call_api
|
41
|
+
hook_call_api jwe
|
42
|
+
# Hooking ApiClient#deserialize
|
43
|
+
hook_deserialize jwe
|
44
|
+
McAPI::Encryption::OpenAPIInterceptor.init_call_api swagger_client
|
45
|
+
McAPI::Encryption::OpenAPIInterceptor.init_deserialize swagger_client
|
46
|
+
end
|
47
|
+
|
30
48
|
private
|
31
49
|
|
32
|
-
def hook_call_api(
|
50
|
+
def hook_call_api(enc)
|
33
51
|
self.class.send :define_method, :init_call_api do |client|
|
34
52
|
client.define_singleton_method(:call_api) do |http_method, path, opts|
|
35
53
|
if opts && opts[:body]
|
36
|
-
|
37
|
-
|
54
|
+
if enc.instance_of? McAPI::Encryption::FieldLevelEncryption
|
55
|
+
encrypted = enc.encrypt(path, opts[:header_params], opts[:body])
|
56
|
+
opts[:body] = JSON.generate(encrypted[:body])
|
57
|
+
else
|
58
|
+
encrypted = enc.encrypt(path, opts[:body])
|
59
|
+
opts[:body] = JSON.generate(encrypted[:body])
|
60
|
+
end
|
38
61
|
end
|
39
62
|
# noinspection RubySuperCallWithoutSuperclassInspection
|
40
63
|
super(http_method, path, opts)
|
@@ -42,15 +65,20 @@ module McAPI
|
|
42
65
|
end
|
43
66
|
end
|
44
67
|
|
45
|
-
def hook_deserialize(
|
68
|
+
def hook_deserialize(enc)
|
46
69
|
self.class.send :define_method, :init_deserialize do |client|
|
47
70
|
client.define_singleton_method(:deserialize) do |response, return_type|
|
48
71
|
if response&.body
|
49
72
|
endpoint = response.request.base_url.sub client.config.base_url, ''
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
73
|
+
if enc.instance_of? McAPI::Encryption::FieldLevelEncryption
|
74
|
+
to_decrypt = { headers: McAPI::Utils.parse_header(response.options[:response_headers]),
|
75
|
+
request: { url: endpoint },
|
76
|
+
body: JSON.parse(response.body) }
|
77
|
+
else
|
78
|
+
to_decrypt = { request: { url: endpoint },
|
79
|
+
body: JSON.parse(response.body) }
|
80
|
+
end
|
81
|
+
decrypted = enc.decrypt(JSON.generate(to_decrypt, symbolize_names: false))
|
54
82
|
body = JSON.generate(JSON.parse(decrypted)['body'])
|
55
83
|
response.options[:response_body] = JSON.generate(JSON.parse(body))
|
56
84
|
end
|
@@ -146,5 +146,45 @@ module McAPI
|
|
146
146
|
end
|
147
147
|
header
|
148
148
|
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Get an element from the JSON path
|
152
|
+
#
|
153
|
+
def self.elem_from_path(path, obj)
|
154
|
+
parent = nil
|
155
|
+
paths = path.split('.')
|
156
|
+
if path && !paths.empty?
|
157
|
+
paths.each do |e|
|
158
|
+
parent = obj
|
159
|
+
obj = json_root?(e) ? obj : obj[e]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
{ node: obj, parent: parent }
|
163
|
+
rescue StandardError
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# Check whether the encryption/decryption path refers to the root element
|
169
|
+
#
|
170
|
+
def self.json_root?(elem)
|
171
|
+
elem == '$'
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.config?(endpoint, config)
|
175
|
+
return unless endpoint
|
176
|
+
|
177
|
+
endpoint = endpoint.split('?').shift
|
178
|
+
conf = config['paths'].select { |e| endpoint.match(e['path']) }
|
179
|
+
conf.empty? ? nil : conf[0]
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.compute_body(config_param, body_map)
|
183
|
+
encryption_param?(config_param, body_map) ? body_map[0] : yield
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.encryption_param?(enc_param, body_map)
|
187
|
+
enc_param.length == 1 && body_map.length == 1
|
188
|
+
end
|
149
189
|
end
|
150
190
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mastercard-client-encryption
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mastercard
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hamster
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -73,7 +87,9 @@ extensions: []
|
|
73
87
|
extra_rdoc_files: []
|
74
88
|
files:
|
75
89
|
- lib/mcapi/encryption/crypto/crypto.rb
|
90
|
+
- lib/mcapi/encryption/crypto/jwe-crypto.rb
|
76
91
|
- lib/mcapi/encryption/field_level_encryption.rb
|
92
|
+
- lib/mcapi/encryption/jwe_encryption.rb
|
77
93
|
- lib/mcapi/encryption/openapi_interceptor.rb
|
78
94
|
- lib/mcapi/encryption/utils/hash.ext.rb
|
79
95
|
- lib/mcapi/encryption/utils/openssl_rsa_oaep.rb
|