mastercard-client-encryption 1.1.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +32 -5
- data/lib/mcapi/encryption/utils/utils.rb +49 -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: '08ad02a73785dc39ed291c030224d1379854d643b90495b92e767d8acbe288df'
|
4
|
+
data.tar.gz: 5e480d366d9f64ea262d2939acd76651f933d3c830a3985f7ce9cd3850c72856
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a38444c27685c44adb3256716a5ee818e79ba0b2c54c717f28b537c43928686e16c29d24001cfff997c7a993cbfb8158587b4d83ff5143aca51091cd85dd8eb6
|
7
|
+
data.tar.gz: c27f71c809642ff8222aa78ca3325f2f2d638165eeabf59ea4871e36bbc0ba508a953528d279eb9a1f6d5cf90e168f93450e0f96db8a084e554e9c3ac900b753
|
@@ -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,7 +65,7 @@ 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
|
@@ -50,8 +73,12 @@ module McAPI
|
|
50
73
|
to_decrypt = { headers: McAPI::Utils.parse_header(response.options[:response_headers]),
|
51
74
|
request: { url: endpoint },
|
52
75
|
body: JSON.parse(response.body) }
|
53
|
-
|
76
|
+
|
77
|
+
decrypted = enc.decrypt(JSON.generate(to_decrypt, symbolize_names: false))
|
54
78
|
body = JSON.generate(JSON.parse(decrypted)['body'])
|
79
|
+
if enc.instance_of? McAPI::Encryption::JweEncryption
|
80
|
+
body = McAPI::Utils.parse_decrypted_payload(body)
|
81
|
+
end
|
55
82
|
response.options[:response_body] = JSON.generate(JSON.parse(body))
|
56
83
|
end
|
57
84
|
# noinspection RubySuperCallWithoutSuperclassInspection
|
@@ -146,5 +146,54 @@ 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
|
189
|
+
|
190
|
+
def self.parse_decrypted_payload(payload)
|
191
|
+
parsed_payload = payload.gsub("\\u000f", "")
|
192
|
+
parsed_payload = parsed_payload.gsub("\\", "")
|
193
|
+
if parsed_payload[0] == "\""
|
194
|
+
parsed_payload = parsed_payload.delete_prefix('"').delete_suffix('"')
|
195
|
+
end
|
196
|
+
parsed_payload
|
197
|
+
end
|
149
198
|
end
|
150
199
|
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.3.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
|