Skip to content
This repository has been archived by the owner on Feb 28, 2024. It is now read-only.

Library for Mastercard API compliant payload encryption/decryption.

License

Notifications You must be signed in to change notification settings

Mastercard/client-encryption-ruby

Repository files navigation

client-encryption-ruby

maintenance-status

Table of Contents

Overview

Ruby library for Mastercard API compliant payload encryption/decryption.

Compatibility

  • Ruby 2.4.4+
  • Truffle Ruby 1.0.0+

References

Versioning and Deprecation Policy

Usage

Prerequisites

Before using this library, you will need to set up a project in the Mastercard Developers Portal.

As part of this set up, you'll receive:

  • A public request encryption certificate (aka Client Encryption Keys)
  • A private response decryption key (aka Mastercard Encryption Keys)

Loading the Decryption Key

By default, the decryption key will be given in as a PKCS#12 password-protected file. The key can be loaded using either of the 2 methods below.

  1. The following code shows how to load the decryption key using OpenSSL:
require 'openssl'

is = File.binread("<insert PKCS#12 key file path>");
signing_key = OpenSSL::PKCS12.new(is, "<insert key password>").key;
  1. Follow our guide on Exporting Your Signing Key

Installation

If you want to use mastercard-client-encryption with Ruby, it is available as Gem:

Adding the library to your project

Add this line to your application's Gemfile:

gem 'mastercard-client-encryption'

And then execute:

$ bundle

Or install it yourself as:

$ gem install mastercard-client-encryption

Import the library:

require 'mcapi/encryption/openapi_interceptor' # to add the interceptor
# or
require 'mcapi/encryption/field_level_encryption' # to perform ad-hoc Mastercard encryption/decryption
require 'mcapi/encryption/jwe_encryption' # to perform ad-hoc JWE encryption/decryption

Performing Payload Encryption and Decryption

Introduction

This library supports two types of encryption/decryption, both of which support field level and entire payload encryption: JWE encryption and what the library refers to as Field Level Encryption (Mastercard encryption), a scheme used by many services hosted on Mastercard Developers before the library added support for JWE.

JWE Encryption and Decryption

• Introduction

This library uses JWE compact serialization for the encryption of sensitive data. The core methods responsible for payload encryption and decryption are encrypt and decrypt in the JweEncryption class.

  • encrypt() usage:
jwe = McAPI::Encryption::JweEncryption.new(@config)
encrypted_request_payload = jwe.encrypt(endpoint, body)
  • decrypt() usage:
jwe = McAPI::Encryption::JweEncryption.new(@config)
decrypted_response_payload = jwe.decrypt(encrypted_response_payload)

Configuring the JWE Encryption

JweEncryption needs a config object to instruct how to decrypt/decrypt the payloads. Example:

{
  "paths": [
    {
      "path": "/resource",
      "toEncrypt": [
        {
          "element": "path.to.foo",
          "obj": "path.to.encryptedFoo"
        }
      ],
      "toDecrypt": [
        {
          "element": "path.to.encryptedFoo",
          "obj": "path.to.foo"
        }
      ]
    }
  ],
  "encryptedValueFieldName": "encryptedData",
  "encryptionCertificate": "./path/to/public.cert",
  "privateKey": "./path/to/your/private.key",
}

For all config options, please see:

We have a predefined set of configurations to use with Mastercard services:

Performing JWE Encryption

Call JweEncryption.encrypt() with a JSON request payload.

Example using the configuration above:

payload = JSON.generate({
  path: {
    to: {
      foo: {
        sensitiveField1: 'sensitiveValue1',
        sensitiveField2: 'sensitiveValue2'
      }
    }
  }
})
jwe = McAPI::Encryption::JweEncryption.new(@config)
request_payload = jwe.encrypt("/resource", header, payload)

Output:

{
  "path": {
    "to": {
      "encryptedFoo": {
        "encryptedValue": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw"
      }
    }
  }
}

Performing JWE Decryption

Call JweEncryption.decrypt() with an (encrypted) response object with the following fields:

  • body: json payload
  • request.url: requesting url

Example using the configuration above:

response = {}
response[:request] = { url: '/resource1' }
response[:body] = 
{
  "path": {
    "to": {
      "encryptedFoo": {
        "encryptedValue": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM….Y+oPYKZEMTKyYcSIVEgtQw"
      }
    }
  }
}
jwe = McAPI::Encryption::JweEncryption.new(@config)
response_payload = jwe.decrypt(response)

Output:

{
  "path": {
    "to": {
      "foo": {
        "sensitiveField1": "sensitiveValue1",
        "sensitiveField2": "sensitiveValue2"
      }
    }
  }
}

Mastercard Encryption and Decryption

Introduction

The core methods responsible for payload encryption and decryption are encrypt and decrypt in the FieldLevelEncryption class.

  • encrypt() usage:
fle = McAPI::Encryption::FieldLevelEncryption.new(@config)
encrypted_request_payload = fle.encrypt(endpoint, header, body)
  • decrypt() usage:
fle = McAPI::Encryption::FieldLevelEncryption.new(@config)
decrypted_response_payload = fle.decrypt(encrypted_response_payload)

Configuring the Mastercard Encryption

FieldLevelEncryption needs a config object to instruct how to decrypt/decrypt the payloads. Example:

{
  "paths": [
    {
      "path": "/resource",
      "toEncrypt": [
        {
          "element": "path.to.foo",
          "obj": "path.to.encryptedFoo"
        }
      ],
      "toDecrypt": [
        {
          "element": "path.to.encryptedFoo",
          "obj": "path.to.foo"
        }
      ]
    }
  ],
  "ivFieldName": "iv",
  "encryptedKeyFieldName": "encryptedKey",
  "encryptedValueFieldName": "encryptedData",
  "dataEncoding": "hex",
  "encryptionCertificate": "./path/to/public.cert",
  "privateKey": "./path/to/your/private.key",
  "oaepPaddingDigestAlgorithm": "SHA-256"
}

For all config options, please see:

We have a predefined set of configurations to use with Mastercard services:

Performing Mastercard Encryption

Call FieldLevelEncryption.encrypt() with a JSON request payload, and optional header object.

Example using the configuration above:

payload = JSON.generate({
  path: {
    to: {
      foo: {
        sensitiveField1: 'sensitiveValue1',
        sensitiveField2: 'sensitiveValue2'
      }
    }
  }
})
fle = McAPI::Encryption::FieldLevelEncryption.new(@config)
request_payload = fle.encrypt("/resource", header, payload)

Output:

{
    "path": {
        "to": {
            "encryptedFoo": {
                "iv": "7f1105fb0c684864a189fb3709ce3d28",
                "encryptedKey": "67f467d1b653d98411a0c6d3c…ffd4c09dd42f713a51bff2b48f937c8",
                "encryptedData": "b73aabd267517fc09ed72455c2…dffb5fa04bf6e6ce9ade1ff514ed6141",
                "publicKeyFingerprint": "80810fc13a8319fcf0e2e…82cc3ce671176343cfe8160c2279",
                "oaepHashingAlgorithm": "SHA256"
            }
        }
    }
}

Performing Mastercard Decryption

Call FieldLevelEncryption.decrypt() with an (encrypted) response object with the following fields:

  • body: json payload
  • request.url: requesting url
  • header: optional, header object

Example using the configuration above:

response = {}
response[:request] = { url: '/resource1' }
response[:body] = 
{
  path: {
    to: {
      encryptedFoo: {
        iv: 'e5d313c056c411170bf07ac82ede78c9',
        encryptedKey: 'e3a56746c0f9109d18b3a2652b76…f16d8afeff36b2479652f5c24ae7bd',
        encryptedData: '809a09d78257af5379df0c454dcdf…353ed59fe72fd4a7735c69da4080e74f',
        oaepHashingAlgorithm: 'SHA256',
        publicKeyFingerprint: '80810fc13a8319fcf0e2e…3ce671176343cfe8160c2279'
      }
    }
  }
}
fle = McAPI::Encryption::FieldLevelEncryption.new(@config)
response_payload = fle.decrypt(response)

Output:

{
  "path": {
    "to": {
      "foo": {
        "sensitiveField1": "sensitiveValue1",
        "sensitiveField2": "sensitiveValue2"
      }
    }
  }
}

Integrating with OpenAPI Generator API Client Libraries

OpenAPI Generator generates API client libraries from OpenAPI Specs. It provides generators and library templates for supporting multiple languages and frameworks.

The client-encryption-ruby library provides a method you can use to integrate the OpenAPI generated client with this library:

# JWE Encryption
McAPI::Encryption::OpenAPIInterceptor.install_jwe_encryption(open_api_client, config)

# Mastercard Encryption
McAPI::Encryption::OpenAPIInterceptor.install_field_level_encryption(open_api_client, config)

The above methods will handle the encryption in the generated OpenApi client, taking care of encrypting request and decrypting response payloads, but also of updating HTTP headers when needed, automatically, without manually calling encrypt()/decrypt() functions for each API request or response.

OpenAPI Generator

OpenAPI client can be generated, starting from your OpenAPI Spec / Swagger using the following command:

openapi-generator-cli generate -i openapi-spec.yaml -l ruby -o out

Client library will be generated in the out folder.

See also:

Usage of the McAPI::Encryption::OpenAPIInterceptor.install_field_level_encryption:

To use it:

  1. Generate the OpenAPI client, as above

  2. Import the mastercard-client-encryption OpenAPI Interceptor and the generated OpenApi client

    require 'mcapi/encryption/openapi_interceptor'
    require_relative './out/generated_open_apiclient' #import generated OpenAPI client
  3. Install the Mastercard/JWE encryption in the generated client:

    # Read the service configuration obj
    @config = File.read('./config.json')   
    # Create a new instance of the generated client
    @api_client = client::ApiClient.new
    
    # Use 1 of the below 2 methods (depending on the encryption type) to enable encryption
    # Enable Mastercard encryption
    McAPI::Encryption::OpenAPIInterceptor.install_field_level_encryption(@api_client, @config)
    
    # Enable JWE encryption
    McAPI::Encryption::OpenAPIInterceptor.install_jwe_encryption(@api_client, @config)
  4. Use the api_client object with the Field Level Encryption enabled:

    Example:

    api_instance = OpenApiService::ServiceApi.new @api_client
    body = # … #
    response = api_instance.create_merchants(body)
    # requests and responses will be automatically encrypted and decrypted
    # accordingly with the configuration object used
    
    # … use the (decrypted) response object here …