mastercard-client-encryption 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1a98e0ce57b2212e9f27e9d09c2aa3586afa57c8f351549b1566607525608c2e
4
+ data.tar.gz: 4f054c956f369efc28dc7f288d39c0105769232084806379d1b4a07ff0200094
5
+ SHA512:
6
+ metadata.gz: c2efcd434d514d9404a583c22bf3417cdb988a407cd29c1ce84b85e577a11c6582c7b4c51eff4c09db21e104d08bcf79517838ac4d639b88ee4c9242b9e283d0
7
+ data.tar.gz: d3399d4cec5349ffc560e1f2de941f04bb9cdf5a75e2c897afaeefad648e415fcd22b1127a33fef3c0423715229061d9d3a260552b8ba51b852c090965f35a19
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'openssl'
5
+ require 'base64'
6
+ require_relative '../utils/utils'
7
+ require_relative '../utils/openssl_rsa_oaep'
8
+
9
+ module McAPI
10
+ module Encryption
11
+ #
12
+ # Crypto class provide RSA/AES encrypt/decrypt methods
13
+ #
14
+ class Crypto
15
+ #
16
+ # Create a new instance with the provided config
17
+ #
18
+ # @param [Hash] config configuration object
19
+ #
20
+ def initialize(config)
21
+ valid_config?(config)
22
+ @encoding = config['dataEncoding']
23
+ @cert = OpenSSL::X509::Certificate.new(File.read(config['encryptionCertificate']))
24
+ if config['privateKey']
25
+ @private_key = OpenSSL::PKey.read(File.new(config['privateKey']))
26
+ elsif config['keyStore']
27
+ @private_key = OpenSSL::PKCS12.new(File.read(config['keyStore']), config['keyStorePassword']).key
28
+ end
29
+ @oaep_hashing_alg = config['oaepPaddingDigestAlgorithm']
30
+ @encrypted_value_field_name = config['encryptedValueFieldName']
31
+ @encrypted_key_field_name = config['encryptedKeyFieldName']
32
+ @public_key_fingerprint = compute_public_fingerprint(config['publicKeyFingerprintType'])
33
+ @public_key_fingerprint_field_name = config['publicKeyFingerprintFieldName']
34
+ @oaep_hashing_alg_field_name = config['oaepHashingAlgorithmFieldName']
35
+ end
36
+
37
+ #
38
+ # Generate encryption parameters.
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
42
+ #
43
+ # @return [Hash] hash with the generated encryption parameters
44
+ #
45
+ def new_encryption_params(iv = nil, secret_key = nil)
46
+ # Generate a secret key (should be 128 (or 256) bits)
47
+ secret_key ||= OpenSSL::Random.random_bytes(16)
48
+ # Generate a random initialization vector (IV)
49
+ iv ||= OpenSSL::Random.random_bytes(16)
50
+ md = Utils.create_message_digest(@oaep_hashing_alg)
51
+ # Encrypt secret key with issuer key
52
+ encrypted_key = @cert.public_key.public_encrypt_oaep(secret_key, '', md, md)
53
+
54
+ {
55
+ iv: iv,
56
+ secretKey: secret_key,
57
+ encryptedKey: encrypted_key,
58
+ oaepHashingAlgorithm: @oaep_hashing_alg,
59
+ publicKeyFingerprint: @public_key_fingerprint,
60
+ encoded: {
61
+ iv: Utils.encode(iv, @encoding),
62
+ secretKey: Utils.encode(secret_key, @encoding),
63
+ encryptedKey: Utils.encode(encrypted_key, @encoding)
64
+ }
65
+ }
66
+ end
67
+
68
+ #
69
+ # Perform data encryption:
70
+ # If +iv+, +secret_key+, +encryption_params+ and +encoding+ are not provided, randoms will be generated.
71
+ #
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
75
+ # @param [String] encoding encoding to use for the encrypted bytes (hex or base64)
76
+ #
77
+ # @return [String] encrypted data
78
+ #
79
+ def encrypt_data(data:, iv: nil, secret_key: nil, encryption_params: nil, encoding: nil)
80
+ encoding ||= @encoding
81
+ encryption_params ||= new_encryption_params(iv, secret_key)
82
+ # Create Symmetric Cipher: AES 128-bit
83
+ aes = OpenSSL::Cipher::AES.new(128, :CBC)
84
+ # Initialize for encryption mode
85
+ aes.encrypt
86
+ aes.iv = encryption_params[:iv]
87
+ aes.key = encryption_params[:secretKey]
88
+ encrypted = aes.update(data) + aes.final
89
+ data = {
90
+ @encrypted_value_field_name => Utils.encode(encrypted, encoding),
91
+ 'iv' => Utils.encode(encryption_params[:iv], encoding)
92
+ }
93
+ data[@encrypted_key_field_name] = Utils.encode(encryption_params[:encryptedKey], encoding) if @encrypted_key_field_name
94
+ data[@public_key_fingerprint_field_name] = @public_key_fingerprint if @public_key_fingerprint
95
+ data[@oaep_hashing_alg_field_name] = @oaep_hashing_alg.sub('-', '') if @oaep_hashing_alg_field_name
96
+ data
97
+ end
98
+
99
+ #
100
+ # Perform data decryption
101
+ #
102
+ # @param [String] encrypted_data encrypted data to decrypt
103
+ # @param [String] iv Initialization vector to use to create the Decipher
104
+ # @param [String] encrypted_key Encrypted key to use to decrypt the data
105
+ # (the key is the decrypted using the provided PrivateKey)
106
+ #
107
+ # @return [String] Decrypted JSON object
108
+ #
109
+ def decrypt_data(encrypted_data, iv, encrypted_key)
110
+ md = Utils.create_message_digest(@oaep_hashing_alg)
111
+ decrypted_key = @private_key.private_decrypt_oaep(Utils.decode(encrypted_key, @encoding), '', md, md)
112
+ aes = OpenSSL::Cipher::AES.new(decrypted_key.size * 8, :CBC)
113
+ aes.decrypt
114
+ aes.key = decrypted_key
115
+ aes.iv = Utils.decode(iv, @encoding)
116
+ aes.update(Utils.decode(encrypted_data, @encoding)) + aes.final
117
+ end
118
+
119
+ private
120
+
121
+ #
122
+ # Compute the fingerprint for the provided public key
123
+ #
124
+ # @param [String] type: +certificate+ or +publickey+
125
+ #
126
+ # @return [String] the computed fingerprint encoded using the configured encoding
127
+ #
128
+ def compute_public_fingerprint(type)
129
+ return unless type
130
+
131
+ case type.downcase
132
+ when 'certificate'
133
+ if @encoding == 'hex'
134
+ OpenSSL::Digest::SHA256.new(@cert.to_der).to_s
135
+ else
136
+ Digest::SHA256.base64digest(@cert.to_der)
137
+ end
138
+ when 'publickey'
139
+ OpenSSL::Digest::SHA256.new(@cert.public_key.to_der).to_s
140
+ else
141
+ raise 'Selected public fingerprint not supported'
142
+ end
143
+ end
144
+
145
+ #
146
+ # Check if the passed configuration is valid
147
+ #
148
+ def valid_config?(config)
149
+ props_basic = %w[oaepPaddingDigestAlgorithm paths dataEncoding encryptionCertificate encryptedValueFieldName]
150
+ props_field = %w[ivFieldName encryptedKeyFieldName]
151
+ props_header = %w[ivHeaderName encryptedKeyHeaderName oaepHashingAlgorithmHeaderName]
152
+ props_fingerprint = %w[publicKeyFingerprintType publicKeyFingerprintFieldName publicKeyFingerprintHeaderName]
153
+ props_opt_fingerprint = %w[publicKeyFingerprint]
154
+
155
+ raise 'Config not valid: config should be an Hash.' unless config.is_a?(Hash)
156
+ raise 'Config not valid: paths should be an array of path element.' unless config['paths'] && config['paths'].is_a?(Array)
157
+
158
+ check_props = !Utils.contains(config, props_basic) ||
159
+ (!Utils.contains(config, props_field) && !Utils.contains(config, props_header))
160
+ raise 'Config not valid: please check that all the properties are defined.' if check_props
161
+
162
+ raise 'Config not valid: paths should be not empty.' if config['paths'].length.zero?
163
+ raise "Config not valid: dataEncoding should be 'hex' or 'base64'" if config['dataEncoding'] != 'hex' &&
164
+ config['dataEncoding'] != 'base64'
165
+
166
+ check_finger = !Utils.contains(config, props_opt_fingerprint) &&
167
+ (config[props_fingerprint[1]] || config[props_fingerprint[2]]) &&
168
+ config[props_fingerprint[0]] != 'certificate' &&
169
+ config[props_fingerprint[0]] != 'publicKey'
170
+ raise "Config not valid: propertiesFingerprint should be: 'certificate' or 'publicKey'" if check_finger
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'crypto/crypto'
4
+ require_relative 'utils/hash.ext'
5
+ require 'json'
6
+
7
+ module McAPI
8
+ module Encryption
9
+ #
10
+ # Performs field level encryption on HTTP payloads.
11
+ #
12
+ class FieldLevelEncryption
13
+ #
14
+ # Create a new instance with the provided configuration
15
+ #
16
+ # @param [Object] config Configuration object
17
+ #
18
+ def initialize(config)
19
+ @config = config
20
+ @crypto = McAPI::Encryption::Crypto.new(config)
21
+ @is_with_header = config['ivHeaderName'] && config['encryptedKeyHeaderName']
22
+ @encryption_response_properties = [@config['ivFieldName'], @config['encryptedKeyFieldName'],
23
+ @config['publicKeyFingerprintFieldName'], @config['oaepHashingAlgorithmFieldName']]
24
+ end
25
+
26
+ #
27
+ # Encrypt parts of a HTTP request using the given config
28
+ #
29
+ # @param [String] endpoint HTTP URL for the current call
30
+ # @param [Object] header HTTP header
31
+ # @param [String,Hash] body HTTP body
32
+ #
33
+ # @return [Hash] Hash with two keys:
34
+ # * :header header with encrypted value (if configured with header)
35
+ # * :body encrypted body
36
+ #
37
+ def encrypt(endpoint, header, body)
38
+ body = JSON.parse(body) if body.is_a?(String)
39
+ config = config?(endpoint)
40
+ if config
41
+ if !@is_with_header
42
+ config['toEncrypt'].each do |v|
43
+ encrypt_with_body(v, body)
44
+ end
45
+ else
46
+ enc_params = @crypto.new_encryption_params
47
+ config['toEncrypt'].each do |v|
48
+ body = encrypt_with_header(v, enc_params, header, body)
49
+ end
50
+ end
51
+ end
52
+ { header: header, body: body.json }
53
+ end
54
+
55
+ #
56
+ # Decrypt part of the HTTP response using the given config
57
+ #
58
+ # @param [Object] response object as obtained from the http client
59
+ #
60
+ # @return [Object] response object with decrypted fields
61
+ #
62
+ def decrypt(response)
63
+ response = JSON.parse(response)
64
+ config = config?(response['request']['url'])
65
+ if config
66
+ if !@is_with_header
67
+ config['toDecrypt'].each do |v|
68
+ decrypt_with_body(v, response['body'])
69
+ end
70
+ else
71
+ config['toDecrypt'].each do |v|
72
+ elem = elem_from_path(v['obj'], response['body'])
73
+ decrypt_with_header(v, elem, response) if elem[:node][v['element']]
74
+ end
75
+ end
76
+ end
77
+ JSON.generate(response)
78
+ end
79
+
80
+ private
81
+
82
+ def encrypt_with_body(path, body)
83
+ elem = elem_from_path(path['element'], body)
84
+ encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]))
85
+ McAPI::Utils.mutate_obj_prop(path['obj'], encrypted_data, body)
86
+ McAPI::Utils.delete_node(path['element'], body) if path['element'] != "#{path['obj']}.#{@config['encryptedValueFieldName']}"
87
+ end
88
+
89
+ def encrypt_with_header(path, enc_params, header, body)
90
+ elem = elem_from_path(path['element'], body)
91
+ encrypted_data = @crypto.encrypt_data(data: JSON.generate(elem[:node]), encryption_params: enc_params)
92
+ body = { path['obj'] => { @config['encryptedValueFieldName'] => encrypted_data[@config['encryptedValueFieldName']] } }
93
+ set_header(header, enc_params)
94
+ body
95
+ end
96
+
97
+ def decrypt_with_body(path, body)
98
+ elem = elem_from_path(path['element'], body)
99
+ return unless elem && elem[:node]
100
+
101
+ decrypted = JSON.parse(@crypto.decrypt_data(elem[:node][@config['encryptedValueFieldName']],
102
+ elem[:node][@config['ivFieldName']],
103
+ elem[:node][@config['encryptedKeyFieldName']]))
104
+ McAPI::Utils.mutate_obj_prop(path['obj'], decrypted, body, path['element'], @encryption_response_properties)
105
+ end
106
+
107
+ def decrypt_with_header(path, elem, response)
108
+ encrypted_data = elem[:node][path['element']][@config['encryptedValueFieldName']]
109
+ response['body'].clear
110
+ response['body'] = JSON.parse(@crypto.decrypt_data(encrypted_data,
111
+ response['headers'][@config['ivHeaderName']][0],
112
+ response['headers'][@config['encryptedKeyHeaderName']][0]))
113
+ end
114
+
115
+ def elem_from_path(path, obj)
116
+ parent = nil
117
+ paths = path.split('.')
118
+ if path && !paths.empty?
119
+ paths.each do |e|
120
+ parent = obj
121
+ obj = obj[e]
122
+ end
123
+ end
124
+ { node: obj, parent: parent }
125
+ rescue StandardError
126
+ nil
127
+ end
128
+
129
+ def config?(endpoint)
130
+ return unless endpoint
131
+
132
+ endpoint = endpoint.split('?').shift
133
+ conf = @config['paths'].select { |e| endpoint.match(e['path']) }
134
+ conf.empty? ? nil : conf[0]
135
+ end
136
+
137
+ def set_header(header, params)
138
+ header[@config['encryptedKeyHeaderName']] = params[:encoded][:encryptedKey]
139
+ header[@config['ivHeaderName']] = params[:encoded][:iv]
140
+ header[@config['oaepHashingAlgorithmHeaderName']] = params[:oaepHashingAlgorithm].sub('-', '')
141
+ header[@config['publicKeyFingerprintHeaderName']] = params[:publicKeyFingerprint]
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'field_level_encryption'
4
+ require_relative 'utils/utils'
5
+
6
+ module McAPI
7
+ module Encryption
8
+ ##
9
+ # Service class that provide interceptor facilities for OpenApi swagger client
10
+ #
11
+ class OpenAPIInterceptor
12
+ class << self
13
+ #
14
+ # Install the field level encryption in the OpenAPI HTTP client
15
+ # adding encryption/decryption capabilities for the request/response payload.
16
+ #
17
+ # @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
+ #
20
+ def install_field_level_encryption(swagger_client, config)
21
+ fle = McAPI::Encryption::FieldLevelEncryption.new(config)
22
+ # Hooking ApiClient#call_api
23
+ hook_call_api fle
24
+ # Hooking ApiClient#deserialize
25
+ hook_deserialize fle
26
+ McAPI::Encryption::OpenAPIInterceptor.init_call_api swagger_client
27
+ McAPI::Encryption::OpenAPIInterceptor.init_deserialize swagger_client
28
+ end
29
+
30
+ private
31
+
32
+ def hook_call_api(fle)
33
+ self.class.send :define_method, :init_call_api do |client|
34
+ client.define_singleton_method(:call_api) do |http_method, path, opts|
35
+ if opts && opts[:body]
36
+ encrypted = fle.encrypt(path, opts[:header_params], opts[:body])
37
+ opts[:body] = JSON.generate(encrypted[:body])
38
+ end
39
+ # noinspection RubySuperCallWithoutSuperclassInspection
40
+ super(http_method, path, opts)
41
+ end
42
+ end
43
+ end
44
+
45
+ def hook_deserialize(fle)
46
+ self.class.send :define_method, :init_deserialize do |client|
47
+ client.define_singleton_method(:deserialize) do |response, return_type|
48
+ if response && response.body
49
+ 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))
54
+ body = JSON.generate(JSON.parse(decrypted)['body'])
55
+ response.options[:response_body] = JSON.generate(JSON.parse(body))
56
+ end
57
+ # noinspection RubySuperCallWithoutSuperclassInspection
58
+ super(response, return_type)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Hash extension
5
+ #
6
+ class Hash
7
+ #
8
+ # Parse the current hash as json
9
+ #
10
+ def json
11
+ JSON.parse(to_json)
12
+ end
13
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ #
6
+ # Extends the OpenSSL library with RSA OAEP MGF1 padding
7
+ #
8
+
9
+ module OpenSSL
10
+ module PKey
11
+ class RSA
12
+ def public_encrypt_oaep(str, label = '', md = nil, mgf1md = nil)
13
+ padded = PKCS1.add_oaep_mgf1(str, n.num_bytes, label, md, mgf1md)
14
+ public_encrypt(padded, OpenSSL::PKey::RSA::NO_PADDING)
15
+ end
16
+
17
+ def private_decrypt_oaep(str, label = '', md = nil, mgf1md = nil)
18
+ padded = private_decrypt(str, OpenSSL::PKey::RSA::NO_PADDING)
19
+ PKCS1.check_oaep_mgf1(padded, label, md, mgf1md)
20
+ end
21
+ end
22
+ end
23
+
24
+ module PKCS1
25
+ def add_oaep_mgf1(str, len, label = '', md = nil, mgf1md = nil)
26
+ md ||= OpenSSL::Digest::SHA1
27
+ mgf1md ||= md
28
+
29
+ mdlen = md.new.digest_length
30
+ z_len = len - str.bytesize - 2 * mdlen - 2
31
+ raise OpenSSL::PKey::RSAError, 'key size too small' if len < 2 * mdlen + 1
32
+ raise OpenSSL::PKey::RSAError, 'data too large for key size' if z_len.negative?
33
+
34
+ l_hash = md.digest(label)
35
+ db = l_hash + ([0] * z_len + [1]).pack('C*') + [str].pack('a*')
36
+ seed = OpenSSL::Random.random_bytes(mdlen)
37
+
38
+ masked_db = mgf1_xor(db, seed, mgf1md)
39
+ masked_seed = mgf1_xor(seed, masked_db, mgf1md)
40
+
41
+ [0, masked_seed, masked_db].pack('Ca*a*')
42
+ end
43
+
44
+ module_function :add_oaep_mgf1
45
+
46
+ def check_oaep_mgf1(str, label = '', md = nil, mgf1md = nil)
47
+ md ||= OpenSSL::Digest::SHA1
48
+ mgf1md ||= md
49
+
50
+ mdlen = md.new.digest_length
51
+ em = str.bytes
52
+ raise OpenSSL::PKey::RSAError if em.size < 2 * mdlen + 2
53
+
54
+ # Keep constant calculation even if the text is invaid in order to avoid attacks.
55
+ good = secure_byte_is_zero(em[0])
56
+ masked_seed = em[1...1 + mdlen].pack('C*')
57
+ masked_db = em[1 + mdlen...em.size].pack('C*')
58
+
59
+ seed = mgf1_xor(masked_seed, masked_db, mgf1md)
60
+ db = mgf1_xor(masked_db, seed, mgf1md)
61
+ db_bytes = db.bytes
62
+
63
+ l_hash = md.digest(label)
64
+ good &= secure_hash_eq(l_hash.bytes, db_bytes[0...mdlen])
65
+
66
+ one_index = 0
67
+ found_one_byte = 0
68
+ (mdlen...db_bytes.size).each do |i|
69
+ equals1 = secure_byte_eq(db_bytes[i], 1)
70
+ equals0 = secure_byte_is_zero(db_bytes[i])
71
+ one_index = secure_select(~found_one_byte & equals1, i, one_index)
72
+ found_one_byte |= equals1
73
+ good &= (found_one_byte | equals0)
74
+ end
75
+
76
+ good &= found_one_byte
77
+
78
+ raise OpenSSL::PKey::RSAError if good.zero?
79
+
80
+ db_bytes[one_index + 1...db_bytes.size].pack('C*')
81
+ end
82
+
83
+ module_function :check_oaep_mgf1
84
+
85
+ def mgf1_xor(out, seed, md)
86
+ counter = 0
87
+ out_bytes = out.bytes
88
+ mask_bytes = []
89
+ while mask_bytes.size < out_bytes.size
90
+ mask_bytes += md.digest([seed, counter].pack('a*N')).bytes
91
+ counter += 1
92
+ end
93
+ out_bytes.size.times do |i|
94
+ out_bytes[i] ^= mask_bytes[i]
95
+ end
96
+ out_bytes.pack('C*')
97
+ end
98
+
99
+ module_function :mgf1_xor
100
+
101
+ # Constant time comparistion utilities.
102
+ def secure_byte_is_zero(v)
103
+ v - 1 >> 8
104
+ end
105
+
106
+ def secure_byte_eq(v1, v2)
107
+ secure_byte_is_zero(v1 ^ v2)
108
+ end
109
+
110
+ def secure_select(mask, eq, ne)
111
+ (mask & eq) | (~mask & ne)
112
+ end
113
+
114
+ def secure_hash_eq(vs1, vs2)
115
+ # Assumes the given hash values have the same size.
116
+ # This check is not constant time, but should not depends on the texts.
117
+ return 0 unless vs1.size == vs2.size
118
+
119
+ res = secure_byte_is_zero(0)
120
+ (0...vs1.size).each do |i|
121
+ res &= secure_byte_eq(vs1[i], vs2[i])
122
+ end
123
+ res
124
+ end
125
+
126
+ module_function :secure_byte_is_zero, :secure_byte_eq, :secure_select, :secure_hash_eq
127
+ end
128
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+
5
+ module McAPI
6
+ #
7
+ # Utils module
8
+ module Utils
9
+ #
10
+ # Data encoding
11
+ #
12
+ def self.encode(data, encoding)
13
+ return unless encoding
14
+
15
+ case encoding.downcase
16
+ when 'hex'
17
+ data.each_byte.map { |b| format('%02x', b.to_i) }.join
18
+ when 'base64'
19
+ Base64.encode64(data).delete("\n")
20
+ else
21
+ raise 'Encoding not supported'
22
+ end
23
+ end
24
+
25
+ #
26
+ # Data decoding
27
+ #
28
+ def self.decode(data, encoding)
29
+ return unless encoding
30
+
31
+ case encoding.downcase
32
+ when 'hex'
33
+ [data].pack('H*')
34
+ when 'base64'
35
+ Base64.decode64(data)
36
+ else
37
+ raise 'Encoding not supported'
38
+ end
39
+ end
40
+
41
+ #
42
+ # Create Digest object for the provided digest string
43
+ #
44
+ def self.create_message_digest(digest)
45
+ return unless digest
46
+
47
+ case digest.upcase
48
+ when 'SHA-256', 'SHA256'
49
+ OpenSSL::Digest::SHA256
50
+ when 'SHA-512', 'SHA512'
51
+ OpenSSL::Digest::SHA512
52
+ else
53
+ raise 'Digest algorithm not supported'
54
+ end
55
+ end
56
+
57
+ def self.contains(config, props)
58
+ props.any? do |i|
59
+ config.key? i
60
+ end
61
+ end
62
+
63
+ #
64
+ # Perform JSON object properties manipulations
65
+ #
66
+ def self.mutate_obj_prop(path, value, obj, src_path = nil, properties = [])
67
+ tmp = obj
68
+ prev = nil
69
+ return unless path
70
+
71
+ delete_node(src_path, obj, properties) if src_path
72
+ paths = path.split('.')
73
+ paths.each do |e|
74
+ tmp[e] = {} unless tmp[e]
75
+ prev = tmp
76
+ tmp = tmp[e]
77
+ end
78
+ elem = path.split('.').pop
79
+ if value.is_a?(Hash) && !value.is_a?(Array)
80
+ prev[elem] = {} unless prev[elem].is_a?(Hash)
81
+ override_props(prev[elem], value)
82
+ else
83
+ prev[elem] = value
84
+ end
85
+ end
86
+
87
+ def self.override_props(target, obj)
88
+ obj.each do |k, _|
89
+ target[k] = obj[k]
90
+ end
91
+ end
92
+
93
+ #
94
+ # Delete node from JSON object
95
+ #
96
+ def self.delete_node(path, obj, properties = [])
97
+ return unless path && obj
98
+
99
+ paths = path.split('.')
100
+ to_delete = paths[paths.size - 1]
101
+ paths.each_with_index do |e, index|
102
+ prev = obj
103
+ next unless obj[e]
104
+
105
+ obj = obj[e]
106
+ prev.delete(to_delete) if obj && index == paths.size - 1
107
+ end
108
+ properties.each { |e| obj.delete(e) } if paths.empty?
109
+ end
110
+
111
+ #
112
+ # Parse raw HTTP Header
113
+ #
114
+ def self.parse_header(raw)
115
+ raw = raw.partition("\n").last
116
+ header = Hash.new([].freeze)
117
+ field = nil
118
+ raw.each_line do |line|
119
+ case line
120
+ when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om
121
+ field = Regexp.last_match(1)
122
+ value = Regexp.last_match(2)
123
+ field.downcase!
124
+ header[field] = [] unless header.key?(field)
125
+ header[field] << value
126
+ when /^\s+(.*?)\s*\z/om
127
+ value = Regexp.last_match(1)
128
+ raise Exception, "bad header '#{line}'." unless field
129
+
130
+ header[field][-1] << ' ' << value
131
+ else
132
+ raise Exception, "bad header '#{line}'."
133
+ end
134
+ end
135
+ header.each do |_key, values|
136
+ values.each do |value|
137
+ value.strip!
138
+ value.gsub!(/\s+/, ' ')
139
+ end
140
+ end
141
+ header
142
+ end
143
+ end
144
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mastercard-client-encryption
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mastercard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.16.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.16.1
69
+ description: Library for Mastercard API compliant payload encryption/decryption.
70
+ email:
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/mcapi/encryption/crypto/crypto.rb
76
+ - lib/mcapi/encryption/field_level_encryption.rb
77
+ - lib/mcapi/encryption/openapi_interceptor.rb
78
+ - lib/mcapi/encryption/utils/hash.ext.rb
79
+ - lib/mcapi/encryption/utils/openssl_rsa_oaep.rb
80
+ - lib/mcapi/encryption/utils/utils.rb
81
+ homepage: https://github.com/Mastercard/client-encryption-ruby
82
+ licenses:
83
+ - MIT
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.7.7
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Mastercard encryption library
105
+ test_files: []