mastercard-client-encryption 1.0.3 → 1.1.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/crypto.rb +8 -7
- data/lib/mcapi/encryption/field_level_encryption.rb +35 -12
- data/lib/mcapi/encryption/openapi_interceptor.rb +2 -2
- data/lib/mcapi/encryption/utils/openssl_rsa_oaep.rb +1 -1
- data/lib/mcapi/encryption/utils/utils.rb +12 -6
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b77cd2d8a1ded5e2456a290e91add5537e3de8ce397e02c049b58a23c4d93f79
|
4
|
+
data.tar.gz: 82350cf1e7aafc602f7efccdf94b271ef218b77fa26b90be3bd7baac18855c27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a56bec98e3a1ed10af314ecf1c318abbf88b36a39dab24f3f888ed2a05d8767befde6fc807022a57ae1d33d6a50c163903bb436f52de480683be9d59f2044c43
|
7
|
+
data.tar.gz: cf09bf8da562269a9f481bea48662cf44c0381cdbfed88945436d627bea23cc8bb3b4fb63247042f7c3fbffc87ece128109ec128b832a868588f7db478a52012
|
@@ -37,8 +37,8 @@ module McAPI
|
|
37
37
|
#
|
38
38
|
# Generate encryption parameters.
|
39
39
|
#
|
40
|
-
# @param [String] iv IV to use instead to generate a random IV
|
41
|
-
# @param [String] secret_key Secret Key to use instead to generate a random key
|
40
|
+
# @param [String,nil] iv IV to use instead to generate a random IV
|
41
|
+
# @param [String,nil] secret_key Secret Key to use instead to generate a random key
|
42
42
|
#
|
43
43
|
# @return [Hash] hash with the generated encryption parameters
|
44
44
|
#
|
@@ -70,8 +70,8 @@ module McAPI
|
|
70
70
|
# If +iv+, +secret_key+, +encryption_params+ and +encoding+ are not provided, randoms will be generated.
|
71
71
|
#
|
72
72
|
# @param [String] data json string to encrypt
|
73
|
-
# @param [String] (optional) iv Initialization vector to use to create the cipher, if not provided generate a random one
|
74
|
-
# @param [String] (optional) encryption_params encryption parameters
|
73
|
+
# @param [String,nil] (optional) iv Initialization vector to use to create the cipher, if not provided generate a random one
|
74
|
+
# @param [String,nil] (optional) encryption_params encryption parameters
|
75
75
|
# @param [String] encoding encoding to use for the encrypted bytes (hex or base64)
|
76
76
|
#
|
77
77
|
# @return [String] encrypted data
|
@@ -103,11 +103,12 @@ module McAPI
|
|
103
103
|
# @param [String] iv Initialization vector to use to create the Decipher
|
104
104
|
# @param [String] encrypted_key Encrypted key to use to decrypt the data
|
105
105
|
# (the key is the decrypted using the provided PrivateKey)
|
106
|
+
# @param [String] oaep_hashing_alg OAEP Algorithm to use
|
106
107
|
#
|
107
108
|
# @return [String] Decrypted JSON object
|
108
109
|
#
|
109
|
-
def decrypt_data(encrypted_data, iv, encrypted_key)
|
110
|
-
md = Utils.create_message_digest(
|
110
|
+
def decrypt_data(encrypted_data, iv, encrypted_key, oaep_hashing_alg)
|
111
|
+
md = Utils.create_message_digest(oaep_hashing_alg)
|
111
112
|
decrypted_key = @private_key.private_decrypt_oaep(Utils.decode(encrypted_key, @encoding), '', md, md)
|
112
113
|
aes = OpenSSL::Cipher::AES.new(decrypted_key.size * 8, :CBC)
|
113
114
|
aes.decrypt
|
@@ -121,7 +122,7 @@ module McAPI
|
|
121
122
|
#
|
122
123
|
# Compute the fingerprint for the provided public key
|
123
124
|
#
|
124
|
-
# @param [
|
125
|
+
# @param [Hash] type: +certificate+ or +publickey+
|
125
126
|
#
|
126
127
|
# @return [String] the computed fingerprint encoded using the configured encoding
|
127
128
|
#
|
@@ -13,7 +13,7 @@ module McAPI
|
|
13
13
|
#
|
14
14
|
# Create a new instance with the provided configuration
|
15
15
|
#
|
16
|
-
# @param [
|
16
|
+
# @param [Hash] config Configuration object
|
17
17
|
#
|
18
18
|
def initialize(config)
|
19
19
|
@config = config
|
@@ -27,7 +27,7 @@ module McAPI
|
|
27
27
|
# Encrypt parts of a HTTP request using the given config
|
28
28
|
#
|
29
29
|
# @param [String] endpoint HTTP URL for the current call
|
30
|
-
# @param [Object] header HTTP header
|
30
|
+
# @param [Object|nil] header HTTP header
|
31
31
|
# @param [String,Hash] body HTTP body
|
32
32
|
#
|
33
33
|
# @return [Hash] Hash with two keys:
|
@@ -37,19 +37,20 @@ module McAPI
|
|
37
37
|
def encrypt(endpoint, header, body)
|
38
38
|
body = JSON.parse(body) if body.is_a?(String)
|
39
39
|
config = config?(endpoint)
|
40
|
+
body_map = body
|
40
41
|
if config
|
41
42
|
if !@is_with_header
|
42
|
-
config['toEncrypt'].
|
43
|
+
body_map = config['toEncrypt'].map do |v|
|
43
44
|
encrypt_with_body(v, body)
|
44
45
|
end
|
45
46
|
else
|
46
47
|
enc_params = @crypto.new_encryption_params
|
47
|
-
config['toEncrypt'].
|
48
|
+
body_map = config['toEncrypt'].map do |v|
|
48
49
|
body = encrypt_with_header(v, enc_params, header, body)
|
49
50
|
end
|
50
51
|
end
|
51
52
|
end
|
52
|
-
{ header: header, body: body.json }
|
53
|
+
{ header: header, body: config ? compute_body(config['toEncrypt'], body_map) { body.json } : body.json }
|
53
54
|
end
|
54
55
|
|
55
56
|
#
|
@@ -62,9 +63,10 @@ module McAPI
|
|
62
63
|
def decrypt(response)
|
63
64
|
response = JSON.parse(response)
|
64
65
|
config = config?(response['request']['url'])
|
66
|
+
body_map = response
|
65
67
|
if config
|
66
68
|
if !@is_with_header
|
67
|
-
config['toDecrypt'].
|
69
|
+
body_map = config['toDecrypt'].map do |v|
|
68
70
|
decrypt_with_body(v, response['body'])
|
69
71
|
end
|
70
72
|
else
|
@@ -74,6 +76,7 @@ module McAPI
|
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
79
|
+
response['body'] = compute_body(config['toDecrypt'], body_map) { response['body'] } unless config.nil?
|
77
80
|
JSON.generate(response)
|
78
81
|
end
|
79
82
|
|
@@ -82,14 +85,19 @@ module McAPI
|
|
82
85
|
def encrypt_with_body(path, body)
|
83
86
|
elem = elem_from_path(path['element'], body)
|
84
87
|
return unless elem && elem[:node]
|
88
|
+
|
85
89
|
encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]))
|
86
|
-
McAPI::Utils.mutate_obj_prop(path['obj'], encrypted_data, body)
|
87
|
-
|
90
|
+
body = McAPI::Utils.mutate_obj_prop(path['obj'], encrypted_data, body)
|
91
|
+
unless json_root?(path['obj']) || path['element'] == "#{path['obj']}.#{@config['encryptedValueFieldName']}"
|
92
|
+
McAPI::Utils.delete_node(path['element'], body)
|
93
|
+
end
|
94
|
+
body
|
88
95
|
end
|
89
96
|
|
90
97
|
def encrypt_with_header(path, enc_params, header, body)
|
91
98
|
elem = elem_from_path(path['element'], body)
|
92
99
|
return unless elem && elem[:node]
|
100
|
+
|
93
101
|
encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]), encryption_params: enc_params)
|
94
102
|
body = { path['obj'] => { @config['encryptedValueFieldName'] => encrypted_data[@config['encryptedValueFieldName']] } }
|
95
103
|
set_header(header, enc_params)
|
@@ -99,9 +107,11 @@ module McAPI
|
|
99
107
|
def decrypt_with_body(path, body)
|
100
108
|
elem = elem_from_path(path['element'], body)
|
101
109
|
return unless elem && elem[:node]
|
110
|
+
|
102
111
|
decrypted = @crypto.decrypt_data(elem[:node][@config['encryptedValueFieldName']],
|
103
|
-
|
104
|
-
|
112
|
+
elem[:node][@config['ivFieldName']],
|
113
|
+
elem[:node][@config['encryptedKeyFieldName']],
|
114
|
+
elem[:node][@config['oaepHashingAlgorithmFieldName']])
|
105
115
|
begin
|
106
116
|
decrypted = JSON.parse(decrypted)
|
107
117
|
rescue JSON::ParserError
|
@@ -116,7 +126,8 @@ module McAPI
|
|
116
126
|
response['body'].clear
|
117
127
|
response['body'] = JSON.parse(@crypto.decrypt_data(encrypted_data,
|
118
128
|
response['headers'][@config['ivHeaderName']][0],
|
119
|
-
response['headers'][@config['encryptedKeyHeaderName']][0]
|
129
|
+
response['headers'][@config['encryptedKeyHeaderName']][0],
|
130
|
+
response['headers'][@config['oaepHashingAlgorithmHeaderName']][0]))
|
120
131
|
end
|
121
132
|
|
122
133
|
def elem_from_path(path, obj)
|
@@ -125,7 +136,7 @@ module McAPI
|
|
125
136
|
if path && !paths.empty?
|
126
137
|
paths.each do |e|
|
127
138
|
parent = obj
|
128
|
-
obj = obj[e]
|
139
|
+
obj = json_root?(e) ? obj : obj[e]
|
129
140
|
end
|
130
141
|
end
|
131
142
|
{ node: obj, parent: parent }
|
@@ -147,6 +158,18 @@ module McAPI
|
|
147
158
|
header[@config['oaepHashingAlgorithmHeaderName']] = params[:oaepHashingAlgorithm].sub('-', '')
|
148
159
|
header[@config['publicKeyFingerprintHeaderName']] = params[:publicKeyFingerprint]
|
149
160
|
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
|
150
173
|
end
|
151
174
|
end
|
152
175
|
end
|
@@ -15,7 +15,7 @@ module McAPI
|
|
15
15
|
# adding encryption/decryption capabilities for the request/response payload.
|
16
16
|
#
|
17
17
|
# @param [Object] swagger_client OpenAPI service client (it can be generated by the swagger code generator)
|
18
|
-
# @param [
|
18
|
+
# @param [Hash] config configuration object describing which field to enable encryption/decryption
|
19
19
|
#
|
20
20
|
def install_field_level_encryption(swagger_client, config)
|
21
21
|
fle = McAPI::Encryption::FieldLevelEncryption.new(config)
|
@@ -45,7 +45,7 @@ module McAPI
|
|
45
45
|
def hook_deserialize(fle)
|
46
46
|
self.class.send :define_method, :init_deserialize do |client|
|
47
47
|
client.define_singleton_method(:deserialize) do |response, return_type|
|
48
|
-
if response
|
48
|
+
if response&.body
|
49
49
|
endpoint = response.request.base_url.sub client.config.base_url, ''
|
50
50
|
to_decrypt = { headers: McAPI::Utils.parse_header(response.options[:response_headers]),
|
51
51
|
request: { url: endpoint },
|
@@ -51,7 +51,7 @@ module OpenSSL
|
|
51
51
|
em = str.bytes
|
52
52
|
raise OpenSSL::PKey::RSAError if em.size < 2 * mdlen + 2
|
53
53
|
|
54
|
-
# Keep constant calculation even if the text is
|
54
|
+
# Keep constant calculation even if the text is invalid in order to avoid attacks.
|
55
55
|
good = secure_byte_is_zero(em[0])
|
56
56
|
masked_seed = em[1...1 + mdlen].pack('C*')
|
57
57
|
masked_db = em[1 + mdlen...em.size].pack('C*')
|
@@ -66,22 +66,27 @@ module McAPI
|
|
66
66
|
def self.mutate_obj_prop(path, value, obj, src_path = nil, properties = [])
|
67
67
|
tmp = obj
|
68
68
|
prev = nil
|
69
|
-
return unless path
|
69
|
+
return obj unless path
|
70
70
|
|
71
71
|
delete_node(src_path, obj, properties) if src_path
|
72
72
|
paths = path.split('.')
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
unless path == '$'
|
74
|
+
paths.each do |e|
|
75
|
+
tmp[e] = {} unless tmp[e]
|
76
|
+
prev = tmp
|
77
|
+
tmp = tmp[e]
|
78
|
+
end
|
77
79
|
end
|
78
80
|
elem = path.split('.').pop
|
79
|
-
if
|
81
|
+
if elem == '$'
|
82
|
+
obj = value # replace root
|
83
|
+
elsif value.is_a?(Hash) && !value.is_a?(Array)
|
80
84
|
prev[elem] = {} unless prev[elem].is_a?(Hash)
|
81
85
|
override_props(prev[elem], value)
|
82
86
|
else
|
83
87
|
prev[elem] = value
|
84
88
|
end
|
89
|
+
obj
|
85
90
|
end
|
86
91
|
|
87
92
|
def self.override_props(target, obj)
|
@@ -105,6 +110,7 @@ module McAPI
|
|
105
110
|
obj = obj[e]
|
106
111
|
prev.delete(to_delete) if obj && index == paths.size - 1
|
107
112
|
end
|
113
|
+
obj.keys.each { |e| obj.delete(e) } if paths.length == 1 && paths[0] == '$'
|
108
114
|
properties.each { |e| obj.delete(e) } if paths.empty?
|
109
115
|
end
|
110
116
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mastercard-client-encryption
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.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: 2021-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|