In this “How-to” guide, we will show you how to test MLE (Message Level Encryption) enabled APIs using Ruby.
Important Links:
Encryption Guide https ://developer.visa.com/pages/encryption_guide
Working with Two Way SSL https://developer.visa.com/pages/working-with-visa-apis/two-way-ssl
Login to your Visa Developer Dashboard and go to your project, you should see something like this:
Enable the APIs for which MLE needs to be active in VDP by toggling the API for which MLE needs to be enforced.
In this example, we will enable MLE for Funds Transfer API And Query API as below:
You can obtain your project credentials by browsing the left side navigation menu of your project and click on “Credentials”.
Next step we will create a Key-ID by clicking on the Generate Key-ID button.
After you have clicked the button, you will get Key-ID. Copy the Key-ID for your reference.
The Key-ID will look like this: 41d9f2a1-xxxx-4xxx-b40c-a0480c2xxxxx
The next step is to add a CSR (Certificate Signing Request). Click on the link "Add CSR" .
You will be prompt to submit a Certificate Signing Request.
We have option to Generate a CSR for me (default) or submit your own. In this example we will use the Generate a CSR for me (default) and Click Confirm button.
After submitting the request, you will be prompt to download the Certificate/Copy Private Key.
After you have downloaded the private key, check the box "I confirm that I've downloaded my certificate key" and click continue. You will see the Status change to "Active".
Expand the Key-ID and you will see the Server Encryption Certificate and Client Encryption Certificate.
Download both certificates and save it.
To be able to make an API call with MLE, you need to have the following
Launch RubyMine, on the RubyMine Welcome Screen, click Create New Project
Please refer to How to run Ruby Sample Code using the Hello World API and Mutual SSL for how to create a project with RubyMine and testing VISA APIs using Mutual SSL
@user_id = 'YOUR USER ID HERE'
@password = 'YOUR PASSWORD HERE'
@key_path = 'PATH TO PRIVATE KEY HERE'
@cert_path = 'PATH TO PROJECT CERTIFICATE HERE'
@key_id = 'YOUR MLE KEY ID HERE'
@mle_private_key = 'PATH TO MLE PRIVATE KEY'
@mle_public_key = 'PATH TO MLE SERVER CERTIFICATE'
# *(c) Copyright 2018 - 2020 Visa. All Rights Reserved.**
#
# *NOTICE: The software and accompanying information and documentation (together, the “Software”) remain the property of and are proprietary to Visa and its suppliers and affiliates. The Software remains protected by intellectual property rights and may be covered by U.S. and foreign patents or patent applications. The Software is licensed and not sold.*
#
# * By accessing the Software you are agreeing to Visa's terms of use (developer.visa.com/terms) and privacy policy (developer.visa.com/privacy).In addition, all permissible uses of the Software must be in support of Visa products, programs and services provided through the Visa Developer Program (VDP) platform only (developer.visa.com). **THE SOFTWARE AND ANY ASSOCIATED INFORMATION OR DOCUMENTATION IS PROVIDED ON AN “AS IS,” “AS AVAILABLE,” “WITH ALL FAULTS” BASIS WITHOUT WARRANTY OR CONDITION OF ANY KIND. YOUR USE IS AT YOUR OWN RISK.** All brand names are the property of their respective owners, used for identification purposes only, and do not imply product endorsement or affiliation with Visa. Any links to third party sites are for your information only and equally do not constitute a Visa endorsement. Visa has no insight into and control over third party content and code and disclaims all liability for any such components, including continued availability and functionality. Benefits depend on implementation details and business factors and coding steps shown are exemplary only and do not reflect all necessary elements for the described capabilities. Capabilities and features are subject to Visa’s terms and conditions and may require development,implementation and resources by you based on your business and operational details. Please refer to the specific API documentation for details on the requirements, eligibility and geographic availability.*
#
# *This Software includes programs, concepts and details under continuing development by Visa. Any Visa features,functionality, implementation, branding, and schedules may be amended, updated or canceled at Visa’s discretion.The timing of widespread availability of programs and functionality is also subject to a number of factors outside Visa’s control,including but not limited to deployment of necessary infrastructure by issuers, acquirers, merchants and mobile device manufacturers.*
#
require 'rest-client'
require 'yaml'
require 'sysrandom'
require 'json'
require 'jose'
require 'openssl'
@visa_url = 'https://sandbox.api.visa.com/'
# THIS IS EXAMPLE ONLY how will user_id and password look like
# user_id = '1WM2TT4IHPXC8DQ5I3CH21n1rEBGK-Eyv_oLdzE2VZpDqRn_U'
# password = '19JRVdej9'
@user_id = 'YOUR USER ID HERE'
@password = 'YOUR PASSWORD HERE'
# THIS IS EXAMPLE ONLY how will cert and key look like
# key_path = 'key_83d11ea6-a22d-4e52-b310-e0558816727d.pem'
# cert_path = 'cert.pem'
@key_path = 'PATH TO PRIVATE KEY HERE'
@cert_path = 'PATH TO PROJECT CERTIFICATE HERE'
# MLE Key ID, private key corresponding to MLE key ID as well as server certificate need to be added here
@key_id = 'YOUR MLE KEY ID HERE'
@mle_private_key = 'PATH TO MLE PRIVATE KEY'
@mle_public_key = 'PATH TO MLE SERVER CERTIFICATE'
def logRequest(test_info, url, request_body)
puts test_info
puts "URL : #{url}"
if (request_body != nil && request_body != '')
puts "Request Body : "
puts request_body
end
end
def logResponse(response)
puts "Response Status : #{response.code.to_s}"
puts "Response Headers : "
for header, value in response.headers
puts "#{header.to_s} : #{value.to_s}"
end
puts "Response Body : " + JSON.pretty_generate(JSON.parse(response.body))
end
def get_encrypted_payload(payload)
jwk_rsa_pk = JOSE::JWK.from_key(OpenSSL::X509::Certificate.new(File.read(@mle_public_key)).public_key)
iat = (Time.now.to_f * 1000).floor
encrypted_string = JOSE::JWE.block_encrypt(jwk_rsa_pk, payload, {"alg" => "RSA-OAEP-256", "enc" => "A128GCM", "kid" => @key_id, "iat" => iat}).compact
"{\"encData\" : \"#{encrypted_string}\"}"
end
def get_decrypted_response(response_body, private_key)
parsed_response = JSON.parse(response_body)
data = parsed_response['encData']
jwk_rsa_private_key = JOSE::JWK.from_pem_file(private_key)
JOSE::JWE.block_decrypt(jwk_rsa_private_key, data).first
end
def doMutualAuthRequest(path, test_info, method_type, request_body, headers = {})
url = _url + "#{path}"
logRequest(test_info, url, request_body)
if method_type == 'post' || method_type == 'put'
headers['Content-type'] = 'application/json'
end
headers['accept'] = 'application/json'
begin
response = RestClient::Request.execute(
:method => method_type,
:url => url,
:headers => headers,
:payload => request_body,
:user => @user_id,
:password => @password,
:ssl_client_key => OpenSSL::PKey::RSA.new(File.read(@key_path)),
:ssl_client_cert => OpenSSL::X509::Certificate.new(File.read(@cert_path))
)
logResponse(response)
if headers.has_key?("keyId") then
puts "Decrypted Response Body : " + JSON.pretty_generate(JSON.parse(get_decrypted_response(response.body, @mle_private_key)))
end
return response
rescue RestClient::ExceptionWithResponse => e
logResponse(e.response)
return e.response
end
end
base_uri = 'visadirect/'
resource_path = 'fundstransfer/v1/pushfundstransactions'
@strDate = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
rrn = '412770451018'
stan = '451018'
acquiring_bin = '408999'
@pushFundsRequest = '{
"acquirerCountryCode": "840",
"acquiringBin": "' '' + acquiring_bin + '' '",
"amount": "124.05",
"businessApplicationId": "AA",
"cardAcceptor": {
"address": {
"country": "USA",
"county": "San Mateo",
"state": "CA",
"zipCode": "94404"
},
"idCode": "CA-IDCode-77765",
"name": "Visa Inc. USA-Foster City",
"terminalId": "TID-9999"
},
"localTransactionDateTime": "' '' + @strDate + '' '",
"merchantCategoryCode": "6012",
"pointOfServiceData": {
"motoECIIndicator": "0",
"panEntryMode": "90",
"posConditionCode": "00"
},
"recipientName": "rohan",
"recipientPrimaryAccountNumber": "4957030420210462",
"retrievalReferenceNumber": "' '' + rrn + '' '",
"senderAccountNumber": "4957030420210454",
"senderAddress": "901 Metro Center Blvd",
"senderCity": "Foster City",
"senderCountryCode": "124",
"senderName": "Mohammed Qasim",
"senderReference": "",
"senderStateCode": "CA",
"sourceOfFundsCode": "05",
"systemsTraceAuditNumber": "' '' + stan + '' '",
"transactionCurrencyCode": "USD",
"transactionIdentifier": "381228649430015"
}'
response = doMutualAuthRequest("#{base_uri}#{resource_path}", "", "post", get_encrypted_payload(@pushFundsRequest), {'keyId' => @key_id})
decrypted_response = get_decrypted_response(response, @mle_private_key)
transaction_identifier = JSON.parse(decrypted_response)['transactionIdentifier']
query_resource_path = base_uri + 'v1/transactionquery?transactionIdentifier=' + transaction_identifier.to_s + "&rrn=" + rrn + "&stan=" + stan + "&acquiringBIN=" + acquiring_bin
query_response = doMutualAuthRequest(query_resource_path, "", "get", nil, {'keyId' => @key_id})
print get_decrypted_response(query_response, @mle_private_key)
fail 'MLE OCT test failed' if "200" != response.code.to_s
Want more? Join the Visa Developer Community to get alerts on the latest tutorials, guides and new developer resources. Stay tuned for more in the series.
Hi!
I've gone through the article and got t
his error while decrypting response payload:
jose/jwk/pem.rb:6:in `read': Could not parse PKey: no start line (OpenSSL::PKey::PKeyError)
the complete trace is:
6: from /Development/myver-api/app/services/visa.rb:112:in `<main>'
5: from /Development/myver-api/app/services/visa.rb:91:in `doMutualAuthRequest'
4: from /Development/myver-api/app/services/visa.rb:64:in `get_decrypted_response'
3: from /.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/jose-1.1.3/lib/jose/jwk.rb:247:in `from_pem_file'
2: from /.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/jose-1.1.3/lib/jose/jwk.rb:237:in `from_pem'
1: from /.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/jose-1.1.3/lib/jose/jwk/pem.rb:6:in `from_binary'
/.rbenv/versions/2.7.4/lib/ruby/gems/2.7.0/gems/jose-1.1.3/lib/jose/jwk/pem.rb:6:in `read': Could not parse PKey: no start line (OpenSSL::PKey::PKeyError)
I've tested this endpoint using the Visa Developer Playground and the request was successfully decrypted using the same certificate that seems to be failing now.
Maybe the gem is too old and doesn't know how to process this new certificate? Could you help shadding some light here?
My stack is:
ruby 2.7
rails 6
jose 1.1.3
openssl 2.2.1
Hi @myver ,
Thank you for trying out the "How to run Ruby Sample Code for MLE".
As per the error message "Could not parse PKey", this is an error from OpenSSL while loading the private key.
It seems you haven't provided a private key file.
Kindly double check if you have provided a private key file (PEM format).
You should be able to identify the private key to be used with the MLE Key ID.
@key_path = '<PATH>/key_<MLE_KEY_ID>.pem'
@cert_path = '<PATH>/server_cert_<MLE_KEY_ID>.pem'
Let us know if you have any further questions.
Thank you
hi @shameem
Thanks for your response, I did provide the certs in the inputs, even in the same path. Here are the MLE keys:
# MLE Key ID, private key corresponding to MLE key ID as well as server certificate need to be added here
@key_id = '0adff726-9a03-4962-9375-xxxxxxxxxxxx'
@mle_private_key = 'client_cert_0adff726-9a03-4962-9375-xxxxxxxxxxxx.pem'
@mle_public_key = 'server_cert_0adff726-9a03-4962-9375-xxxxxxxxxxxx.pem'
I would say there is an issue with the gem and the certificate, what do you think?
Hi @myver ,
You are setting a certificate (public key) in the mle_private_key variable.
Please replace the variable value from
@mle_private_key = 'client_cert_0adff726-9a03-4962-9375-xxxxxxxxxxxx.pem'
to
@mle_private_key = 'key_0adff726-9a03-4962-9375-xxxxxxxxxxxx.pem'
And it should work.
Thank you
Hello @shameem, thank you for posting this.
I am troubleshooting my sandbox connection for an API requiring MLE. I have successfully tested my credentials using the HelloWorld endpoint.
When I use the above code to make requests to the API sandbox I receive a 401 response with the following body:
{"responseStatus"=>{"status"=>401, "code"=>"9207", "severity"=>"ERROR", "message"=>"Token validation failed", "info"=>""}}
One example request is correlatnId: b48dbab13afbcd89fb64950e65f3165f
I have checked and double-checked that I am loading the correct credentials. Unfortunately 'Token validation failed' doesn't provide many clues. Am I correct in understanding that this error means that the MLE has not been performed correctly? Do you have any suggestions for how I might troubleshoot this error which is blocking my integration?
Hi @dsmcclain ,
Could you please check if you are sending the correct request payload. Since the API requires MLE, your json request payload should be as follows.
{"encData": "<REPLACE_WITH_ENCRYPTED_DATA>"}
Please replace the placeholder (<REPLACE_WITH_ENCRYPTED_DATA>) with the encrypted string (JWE) which is generated.
Ensure the same request payload is used when generating the x-pay-token (no formatting, extra space, etc..).
I hope that helps.
Thank you