mastercard-client-encryption 1.0.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ee1474b23b7bec011ba28254f5771a3ed89dd5995e4eed541d8382833d97429
4
- data.tar.gz: 6fd5ef2ca4c9094744175dcdf4e297ea4f4cfd9ff4b22f32c6dffda8ebd46c37
3
+ metadata.gz: 498fe9a62ff880357316d0dd97d78d27dae85bca253702d905e2769087579ba2
4
+ data.tar.gz: 22a35a9afebde5f8f746067154c339ced42edd4b0d4ec0d6fa920963f1de5abd
5
5
  SHA512:
6
- metadata.gz: 32064f9cf37511cc6d25e9922f25683ed522f472830ba1570212cc42c1e1cfb72c97f405114937b124d83ad8717eca828d4933089ab2ec266b411e6ba9816a71
7
- data.tar.gz: aba8ffdc24a32e4af8cef5c9c3417b94e72922ce10a290cc026e4e9275a7e7ea92ed3787279cda55e966f956d1e1e157559f7142bdf4c5f6b5592986b1367254
6
+ metadata.gz: 3fe63908d6249e94fc6db683aefe97a82622ee4c8c59eca8e8a7cd21f7d79a96c0476f6efb6bc9362d2903eed8afc0a0d04a37b2b8a44ed438989560c5b47034
7
+ data.tar.gz: 879b24f693402374293b543d7a5bbacd4b05b12b23f4586a85ee2c078a3631fa16159051cc2acac2f07820fa48ddfdf6f1205dcd3419cf9cd2d6851af0ebb383
@@ -20,11 +20,11 @@ module McAPI
20
20
  def initialize(config)
21
21
  valid_config?(config)
22
22
  @encoding = config['dataEncoding']
23
- @cert = OpenSSL::X509::Certificate.new(File.read(config['encryptionCertificate']))
23
+ @cert = OpenSSL::X509::Certificate.new(IO.binread(config['encryptionCertificate']))
24
24
  if config['privateKey']
25
- @private_key = OpenSSL::PKey.read(File.new(config['privateKey']))
25
+ @private_key = OpenSSL::PKey.read(IO.binread(config['privateKey']))
26
26
  elsif config['keyStore']
27
- @private_key = OpenSSL::PKCS12.new(File.read(config['keyStore']), config['keyStorePassword']).key
27
+ @private_key = OpenSSL::PKCS12.new(IO.binread(config['keyStore']), config['keyStorePassword']).key
28
28
  end
29
29
  @oaep_hashing_alg = config['oaepPaddingDigestAlgorithm']
30
30
  @encrypted_value_field_name = config['encryptedValueFieldName']
@@ -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(@oaep_hashing_alg)
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 [String] type: +certificate+ or +publickey+
125
+ # @param [Hash] type: +certificate+ or +publickey+
125
126
  #
126
127
  # @return [String] the computed fingerprint encoded using the configured encoding
127
128
  #
@@ -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
@@ -13,7 +14,7 @@ module McAPI
13
14
  #
14
15
  # Create a new instance with the provided configuration
15
16
  #
16
- # @param [Object] config Configuration object
17
+ # @param [Hash] config Configuration object
17
18
  #
18
19
  def initialize(config)
19
20
  @config = config
@@ -27,7 +28,7 @@ module McAPI
27
28
  # Encrypt parts of a HTTP request using the given config
28
29
  #
29
30
  # @param [String] endpoint HTTP URL for the current call
30
- # @param [Object] header HTTP header
31
+ # @param [Object|nil] header HTTP header
31
32
  # @param [String,Hash] body HTTP body
32
33
  #
33
34
  # @return [Hash] Hash with two keys:
@@ -36,20 +37,21 @@ 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)
41
+ body_map = body
40
42
  if config
41
43
  if !@is_with_header
42
- config['toEncrypt'].each do |v|
44
+ body_map = config['toEncrypt'].map do |v|
43
45
  encrypt_with_body(v, body)
44
46
  end
45
47
  else
46
48
  enc_params = @crypto.new_encryption_params
47
- config['toEncrypt'].each do |v|
49
+ body_map = config['toEncrypt'].map do |v|
48
50
  body = encrypt_with_header(v, enc_params, header, body)
49
51
  end
50
52
  end
51
53
  end
52
- { header: header, body: body.json }
54
+ { header: header, body: config ? McAPI::Utils.compute_body(config['toEncrypt'], body_map) { body.json } : body.json }
53
55
  end
54
56
 
55
57
  #
@@ -61,35 +63,42 @@ module McAPI
61
63
  #
62
64
  def decrypt(response)
63
65
  response = JSON.parse(response)
64
- config = config?(response['request']['url'])
66
+ config = McAPI::Utils.config?(response['request']['url'], @config)
67
+ body_map = response
65
68
  if config
66
69
  if !@is_with_header
67
- config['toDecrypt'].each do |v|
70
+ body_map = config['toDecrypt'].map do |v|
68
71
  decrypt_with_body(v, response['body'])
69
72
  end
70
73
  else
71
74
  config['toDecrypt'].each do |v|
72
- elem = elem_from_path(v['obj'], response['body'])
75
+ elem = McAPI::Utils.elem_from_path(v['obj'], response['body'])
73
76
  decrypt_with_header(v, elem, response) if elem[:node][v['element']]
74
77
  end
75
78
  end
76
79
  end
80
+ response['body'] = McAPI::Utils.compute_body(config['toDecrypt'], body_map) { response['body'] } unless config.nil?
77
81
  JSON.generate(response)
78
82
  end
79
83
 
80
84
  private
81
85
 
82
86
  def encrypt_with_body(path, body)
83
- elem = elem_from_path(path['element'], body)
87
+ elem = McAPI::Utils.elem_from_path(path['element'], body)
84
88
  return unless elem && elem[:node]
89
+
85
90
  encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]))
86
- McAPI::Utils.mutate_obj_prop(path['obj'], encrypted_data, body)
87
- McAPI::Utils.delete_node(path['element'], body) if path['element'] != "#{path['obj']}.#{@config['encryptedValueFieldName']}"
91
+ body = McAPI::Utils.mutate_obj_prop(path['obj'], encrypted_data, body)
92
+ unless McAPI::Utils.json_root?(path['obj']) || path['element'] == "#{path['obj']}.#{@config['encryptedValueFieldName']}"
93
+ McAPI::Utils.delete_node(path['element'], body)
94
+ end
95
+ body
88
96
  end
89
97
 
90
98
  def encrypt_with_header(path, enc_params, header, body)
91
- elem = elem_from_path(path['element'], body)
99
+ elem = McAPI::Utils.elem_from_path(path['element'], body)
92
100
  return unless elem && elem[:node]
101
+
93
102
  encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]), encryption_params: enc_params)
94
103
  body = { path['obj'] => { @config['encryptedValueFieldName'] => encrypted_data[@config['encryptedValueFieldName']] } }
95
104
  set_header(header, enc_params)
@@ -97,11 +106,13 @@ module McAPI
97
106
  end
98
107
 
99
108
  def decrypt_with_body(path, body)
100
- elem = elem_from_path(path['element'], body)
109
+ elem = McAPI::Utils.elem_from_path(path['element'], body)
101
110
  return unless elem && elem[:node]
111
+
102
112
  decrypted = @crypto.decrypt_data(elem[:node][@config['encryptedValueFieldName']],
103
- elem[:node][@config['ivFieldName']],
104
- elem[:node][@config['encryptedKeyFieldName']])
113
+ elem[:node][@config['ivFieldName']],
114
+ elem[:node][@config['encryptedKeyFieldName']],
115
+ elem[:node][@config['oaepHashingAlgorithmFieldName']])
105
116
  begin
106
117
  decrypted = JSON.parse(decrypted)
107
118
  rescue JSON::ParserError
@@ -116,29 +127,8 @@ module McAPI
116
127
  response['body'].clear
117
128
  response['body'] = JSON.parse(@crypto.decrypt_data(encrypted_data,
118
129
  response['headers'][@config['ivHeaderName']][0],
119
- response['headers'][@config['encryptedKeyHeaderName']][0]))
120
- end
121
-
122
- def elem_from_path(path, obj)
123
- parent = nil
124
- paths = path.split('.')
125
- if path && !paths.empty?
126
- paths.each do |e|
127
- parent = obj
128
- obj = obj[e]
129
- end
130
- end
131
- { node: obj, parent: parent }
132
- rescue StandardError
133
- nil
134
- end
135
-
136
- def config?(endpoint)
137
- return unless endpoint
138
-
139
- endpoint = endpoint.split('?').shift
140
- conf = @config['paths'].select { |e| endpoint.match(e['path']) }
141
- conf.empty? ? nil : conf[0]
130
+ response['headers'][@config['encryptedKeyHeaderName']][0],
131
+ response['headers'][@config['oaepHashingAlgorithmHeaderName']][0]))
142
132
  end
143
133
 
144
134
  def set_header(header, params)
@@ -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
@@ -15,7 +16,7 @@ module McAPI
15
16
  # adding encryption/decryption capabilities for the request/response payload.
16
17
  #
17
18
  # @param [Object] swagger_client OpenAPI service client (it can be generated by the swagger code generator)
18
- # @param [Object] config configuration object describing which field to enable encryption/decryption
19
+ # @param [Hash] config configuration object describing which field to enable encryption/decryption
19
20
  #
20
21
  def install_field_level_encryption(swagger_client, config)
21
22
  fle = McAPI::Encryption::FieldLevelEncryption.new(config)
@@ -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(fle)
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
- encrypted = fle.encrypt(path, opts[:header_params], opts[:body])
37
- opts[:body] = JSON.generate(encrypted[:body])
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(fle)
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
- if response && response.body
71
+ if response&.body
49
72
  endpoint = response.request.base_url.sub client.config.base_url, ''
50
- to_decrypt = { headers: McAPI::Utils.parse_header(response.options[:response_headers]),
51
- request: { url: endpoint },
52
- body: JSON.parse(response.body) }
53
- decrypted = fle.decrypt(JSON.generate(to_decrypt, symbolize_names: false))
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
@@ -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 invaid in order to avoid attacks.
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
- paths.each do |e|
74
- tmp[e] = {} unless tmp[e]
75
- prev = tmp
76
- tmp = tmp[e]
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 value.is_a?(Hash) && !value.is_a?(Array)
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
 
@@ -140,5 +146,45 @@ module McAPI
140
146
  end
141
147
  header
142
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
143
189
  end
144
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.0.2
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: 2019-08-13 00:00:00.000000000 Z
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
@@ -42,16 +56,16 @@ dependencies:
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ">="
46
60
  - !ruby/object:Gem::Version
47
- version: '10.0'
61
+ version: 12.3.3
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: '10.0'
68
+ version: 12.3.3
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: simplecov
57
71
  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