Skip to content

Security Walkthrough Information

Note

This page is specifically written as a step by step guide to using CWS. For technical information, see Security.

Overview

CWS employs public-key cryptography (asymmetric encryption) for secure authentication. This ensures that messages sent to CorEMR, as well as responses returned to clients, can be verified by the receiver as genuinely coming from the claimed sender. To achieve this, both parties generate and manage their own private and public key pairs. Private keys are used to "sign" message contents, allowing the recipient to validate the authenticity of the signature upon receipt.

For almost all actions in this regard, CorEMR recommends using OpenSSL. Other software may work, but all further documentation will keep OpenSSL in mind and refer to its commands. For OpenSSL installation help, follow the guides on their website or contact CorEMR support.

With this information in mind, see the corresponding sections below for walkthroughs on how to accomplish common goals.

Security Setup

CorEMR will handle most parts of security creation and initialization. However, clients will have to create their own public and private keys.

Generate Public-Private Key Pair

Code Examples

keyGen.php
<?php

function generateKeyPair()
{
    // Generate private key.
    $private_key = openssl_pkey_new(['private_key_bits' => 4096]);
    if ($private_key === false) {
        die('Error generating key pair: ' . openssl_error_string());
    }

    // Save the private key
    openssl_pkey_export_to_file($private_key, 'private.key');
    echo "Private key saved to private.key\n";

    // Extract public key.
    $key_details = openssl_pkey_get_details($private_key);
    $public_key = $key_details['key'];

    // Save the public key
    file_put_contents('public.key', $public_key);
    echo "Public key saved to public.key\n";
}

generateKeyPair();
Example Usage:
php keyGen.php

keyGen.js
const { generateKeyPair } = require('crypto');
const fs = require('fs');

// Generate an RSA key pair
generateKeyPair('rsa', {
    modulusLength: 4096,
    publicKeyEncoding: {
        type: 'spki',
        format: 'pem'
    },
    privateKeyEncoding: {
        type: 'pkcs8',
        format: 'pem'
    }
}, (err, publicKey, privateKey) => {
    if (err) {
        console.error('Error generating key pair:', err);
        return;
    }

    // Save the public key
    fs.writeFileSync('public.key', publicKey);
    console.log('Public key saved to public.key');

    // Save the private key
    fs.writeFileSync('private.key', privateKey);
    console.log('Private key saved to private.key');
});
Example Usage:
node keyGen.js

  1. Install required libraries
    pip install requests cryptography
    
  2. Example code
    keyGen.py
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.backends import default_backend
    
    # Function to generate and save RSA key pair
    def generate_key_pair():
        # Generate private key
        private_key = rsa.generate_private_key(
            public_exponent=65537,  # Standard public exponent value
            key_size=4096,  # Key size in bits (same as Node.js example)
            backend=default_backend()
        )
    
        # Serialize private key to PEM format and save to file
        with open("private.key", "wb") as private_file:
            private_file.write(
                private_key.private_bytes(
                    encoding=serialization.Encoding.PEM,
                    format=serialization.PrivateFormat.PKCS8,  # Same format as Node.js example
                    encryption_algorithm=serialization.NoEncryption()
                )
            )
        print("Private key saved to private.key")
    
        # Serialize public key to PEM format and save to file
        public_key = private_key.public_key()
        with open("public.key", "wb") as public_file:
            public_file.write(
                public_key.public_bytes(
                    encoding=serialization.Encoding.PEM,
                    format=serialization.PublicFormat.SubjectPublicKeyInfo  # Equivalent to 'spki' in Node.js
                )
            )
        print("Public key saved to public.key")
    
    # Generate the RSA key pair
    generate_key_pair()
    
  3. Run the script:
    python keyGen.py
    
  1. Generate RSA Private Key
    openssl genrsa -out rsa.private 4096
    
  2. Generate RSA Public Key
    openssl rsa -in rsa.private -out rsa.public -pubout -outform PEM
    

This will create two new files: rsa.private and rsa.public. Note that by using '4096' as the value in the command you are creating a longer than average key that will be more secure. This number can be decreased, but 4096 is CorEMR's recommended configuration. These two new files will be crucial moving forward, but the largest idea to take away is that clients are responsible not to lose their individual rsa.private key. If the private key is lost or an unknown third party gains access to it, inform CorEMR immediately. This could result in a data breach and quick removal of access to CWS is crucial to avoid it.

With all this in mind, we can open the rsa.public file (if your system doesn't know how to open either file, choose to open it with notepad) and copy all contents within it. We can then go into the CorEMR application, choose 'Administration', then 'Public Keys', and finally the plus sign in the bottom right hand corner. Paste the contents from the public key file into 'Contents' and choose a username that you will easily remember. This will be used often, so ensure it's something unique. With that, store the rsa.private file somewhere safe, and setup is complete.

Upload Public Key to CorEMR

Steps to upload public key
  1. Login to CorEMR as a user with Administrator permissions.
  2. Navigate to Administration > Public Keys and click the icon in the bottom-right corner.
  3. Enter a username that you will remember (This will be used when sending requests to CWS).
  4. Copy the contents of the public key file and paste them in the "Contents" field.
  5. Check "Read Only" if you only want this public key to make GET requests to CWS.
  6. Click "Save" to save the public key.

Image title Image title

Sending a Request

CorEMR Web Services requires every message body be signed by the sending party, even if that message body is empty. Therefore, when sending a request to hit any endpoint, follow these steps:

Code Examples
  1. Example code
    sendRequest.php
    <?php
    
    // Function to sign the request body
    function signRequestBody($privateKeyPath, $bodyContent)
    {
        $privateKey = file_get_contents($privateKeyPath);
    
        // Sign the body content with the private key
        $privateKeyId = openssl_pkey_get_private($privateKey);
    
        if (!$privateKeyId) {
            throw new Exception('Invalid private key.');
        }
    
        openssl_sign($bodyContent, $signature, $privateKeyId, OPENSSL_ALGO_SHA256);
    
        // Encode signature to base64
        $base64Signature = base64_encode($signature);
    
        return $base64Signature;
    }
    
    // Function to send a request to CorEMR
    function sendRequest($url, $method, $headers, $body, $privateKeyPath, $username)
    {
        // Sign the request body
        $signature = signRequestBody($privateKeyPath, $body);
    
        // Add Authorization header
        $headers[] = "Authorization: CWS-SHA256 Access={$username}, Signature={$signature}";
        $headers[] = "Content-Type: application/json";
    
        // Initialize cURL session
        $ch = curl_init($url);
    
        // Set cURL options based on the method
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    
        // Execute the request and get the response
        $response = curl_exec($ch);
    
        // Check for cURL errors
        if (curl_errno($ch)) {
            throw new Exception('Request Error: ' . curl_error($ch));
        }
    
        // Close the cURL session
        curl_close($ch);
    
        return $response;
    }
    
    // Example usage
    try {
        $host = 'http://localhost'; // Change to actual hostname
        $url = "{$host}/ws/records/facilities/";
        $method = 'GET';
        // For GET requests, sign empty body ({} as empty JSON object)
        $body = "{}";
        $privateKeyPath = './keys/private.key';
        $username = 'exampleuser'; // Change to username set in CorEMR Admin/Public Keys
    
        // Send the request and output the response
        $response = sendRequest($url, $method, [], $body, $privateKeyPath, $username);
        echo 'Response: ' . $response;
    } catch (Exception $e) {
        echo 'Error: ' . $e->getMessage();
    }
    
  2. Place private.key in keys folder in root directory.
  3. Run the script:
    php sendRequest.php
    
  1. Install required libraries
    npm install axios
    
  2. Example code
    sendRequest.js
    const crypto = require('crypto');
    const fs = require('fs');
    const axios = require('axios');
    
    // Helper function to read file content
    function readFileContent(filePath) {
        return fs.readFileSync(filePath, 'utf8');
    }
    
    // Function to sign the request body
    function signRequestBody(privateKeyPath, bodyContent) {
        const privateKey = readFileContent(privateKeyPath);
    
        // Sign the body content with the private key
        const sign = crypto.createSign('SHA256');
        sign.update(bodyContent);
        sign.end();
    
        const signature = sign.sign(privateKey);
        const base64Signature = signature.toString('base64');
    
        return base64Signature;
    }
    
    // Function to send a request to CorEMR
    async function sendRequest(url, method, headers, body, privateKeyPath, username) {
        const signature = signRequestBody(privateKeyPath, body);
        // Add Authorization header
        headers['Authorization'] = `CWS-SHA256 Access=${username}, Signature=${signature}`;
    
        try {
            const response = await axios({
                method: method,
                url: url,
                headers: headers,
                data: body
            });
    
            return response;
        } catch (error) {
            console.error('Error sending request:', error);
            throw error;
        }
    }
    
    // Example usage
    (async () => {
        const host = 'localhost'; // Change to actual hostname
        const url = `${host}/ws/records/facilities/`;
        const method = 'GET';
        const headers = {
            'Content-Type': 'application/json'
        };
        // For GET requests, sign empty body ({})
        const body = "{}";
        const privateKeyPath = './keys/private.key';
        const username = 'exampleuser'; // Change to username set in CorEMR Admin/Public Keys
    
        const response = await sendRequest(url, method, headers, body, privateKeyPath, username);
        console.log('Response:', response.data);
    })();
    
  3. Place private.key in keys folder in root directory.
  4. Run the script:
    node sendRequest
    
  1. Install required libraries
    pip install requests cryptography
    
  2. Example code
    sendRequest.py
    import requests
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.asymmetric import padding
    from cryptography.hazmat.primitives import serialization
    import base64
    
    # Helper function to read file content
    def read_file_content(file_path):
        with open(file_path, 'r') as file:
            return file.read()
    
    # Function to sign the request body
    def sign_request_body(private_key_path, body_content):
        private_key = read_file_content(private_key_path).encode('utf-8')
    
        # Load the private key
        private_key_obj = serialization.load_pem_private_key(private_key, password=None)
    
        # Sign the body content
        signature = private_key_obj.sign(
            body_content.encode('utf-8'),
            padding.PKCS1v15(),
            hashes.SHA256()
        )
    
        base64_signature = base64.b64encode(signature).decode('utf-8')
        return base64_signature
    
    # Function to send a request to CorEMR
    def send_request(url, method, headers, body, private_key_path, username):
        # Sign the request body
        signature = sign_request_body(private_key_path, body)
    
        # Add Authorization header
        headers['Authorization'] = f'CWS-SHA256 Access={username}, Signature={signature}'
    
        try:
            response = requests.request(method, url, headers=headers, data=body)
            return response
        except Exception as e:
            print(f'Error sending request: {e}')
            raise
    
    # Example usage
    if __name__ == "__main__":
        host = 'http://localhost'  # Change to actual hostname
        url = f'{host}/ws/records/facilities/'
        method = 'GET'
        headers = {
            'Content-Type': 'application/json'
        }
        # For GET requests, sign empty body ('{}')
        body = "{}"
        private_key_path = './keys/private.key'
        username = 'exampleuser' # Change to username set in CorEMR Admin/Public Keys
    
        response = send_request(url, method, headers, body, private_key_path, username)
        print('Response:', response.text)
    
  3. Place private.key in keys folder in root directory.
  4. Run the script:
    python sendRequest.py
    
  1. Copy the body of your request into a file called body.txt (if the request is a GET request or the body is otherwise empty, create an empty file called body.txt).
  2. Using your securely stored rsa.private key, use the following command to sign the body of the request:
    openssl dgst -sha256 -sign rsa.private -out sign.sha256 body.txt
    
  3. This will generate a new file called sign.sha256 which will contain a signature in an unreadable format, use the following command to encrypt it into base64:
    openssl enc -A -base64 -in sign.sha256 -out sign.txt
    
  4. A new file will now exist called sign.txt. Open it and copy the contents exactly, this is your signature which CorEMR will use to verify the request as real. With this, go back into your request and add a new header called 'Authorization' with the following contents: CWS-SHA256 Access=USERNAME, Signature=BASE64_SIGNATURE Where USERNAME is replaced with the username of the public key within CorEMR's application that correlates to your private key, and BASE64_SIGNATURE is everything copied from sign.txt. Note here that we used CWS-SHA256 since that's the algorithm we used to sign the body. CWS-SHA1 is also supported but must also be used to sign the request back in step 2 via the -sha1 command.
  5. With the header and body now connected to each other via the signature, the request can be sent. If any mistakes are made along the way, CorEMR will reject the request and inform the user of the problem. Contact CorEMR Support if issues persist or with any questions.

Verifying CorEMR's Response

All CWS responses include a header called 'Authorization' with the following contents:

Authorization Header

ALGORITHM Access=USERNAME, Signature=BASE64_SIGNATURE

Where ALGORITHM is typically replaced with either CWS-SHA256 or CWS-SHA1, USERNAME is replaced by a varient of coremr, and BASE64_SIGNATURE contains a human readable signature. Note that this will look very similar to the request header that was attached to your initial request. In order to verify that this reponse came from CorEMR, follow these steps:

  1. Create a new text file called response_sign.txt, copy and paste the exact value of BASE64_SIGNATURE into this without changing anything.
  2. Use the following command to decrypt the base64 signature into a machine readable format:
    openssl enc -d -A -base64 -in response_sign.txt -out response_sign.sha256
    
    This will create a new file called response_sign.sha256 that we will use momentarily.
  3. Go into the CorEMR application and choose 'Administration' then 'Public Keys'. There should be a key with a username that perfectly matches USERNAME located in the header. This is CorEMR's public key, and you will use it to verify the signature. Copy all of the contents of this key and place it into a new file called rsa.public.
  4. Next, copy the exact data from the body of the response, ensure that it is the raw data that has not been formatted in any way. Paste this into another new file called body.txt
  5. Run the following command to verify that the body of the response was signed by CorEMR:
    openssl dgst -sha256 -verify rsa.public -signature response_sign.sha256 body.txt
    
  6. If the response returned is "Verified OK" and all above steps have been followed exactly, the response is verified to have come from CorEMR. If any other response is returned and no mistake was made during the process of verification, the response could be fradulant and CorEMR should be informed immediately.

Example Request

Example Request

In this example, we will be creating a new patient request by hitting the /ws/patients/ endpoint. Our body will look something like this:

{
    "external_id": "FakeExternalID",
    "facility": 10000,
    "agency": 2,
    "billing_agency": 1, 
    "fname": "First",
    "lname": "Name",
    "dob": "2000-04-12 10:00:00.000",
    "sex": "M",
    "booking_no": "EthanBooking14",
    "booking_date": "2023-04-07 10:00:00.00"
}

Note that the body must be written in proper JSON format and that there could be more optional fields included here. Our header will contain a key called "Authorization" with a value that looks something like this:

CWS-SHA1 Access=fake-username, Signature=XXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
XXxxxXXxXXxxXXxxXXXXXxXXXxXXxXXXXxxxxxXxxxXxXXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxXXxXXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxXXX==

Where the X's will be replaced with seemingly random uppercase and lowercase letters, slashes, and numbers. If this format is followed, the request should work.