mastercard-client-encryption 1.0.3 → 1.1.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/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
|