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
<?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();
php keyGen.php
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');
});
node keyGen.js
- Install required libraries
pip install requests cryptography
- 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()
- Run the script:
python keyGen.py
- Generate RSA Private Key
openssl genrsa -out rsa.private 4096
- 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
- Login to CorEMR as a user with Administrator permissions.
- Navigate to
Administration > Public Keys
and click the icon in the bottom-right corner. - Enter a username that you will remember (This will be used when sending requests to CWS).
- Copy the contents of the public key file and paste them in the "Contents" field.
- Check "Read Only" if you only want this public key to make GET requests to CWS.
- Click "Save" to save the public key.
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
- 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(); }
- Place private.key in
keys
folder in root directory. - Run the script:
php sendRequest.php
- Install required libraries
npm install axios
- 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); })();
- Place private.key in
keys
folder in root directory. - Run the script:
node sendRequest
- Install required libraries
pip install requests cryptography
- 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)
- Place private.key in
keys
folder in root directory. - Run the script:
python sendRequest.py
- 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).
- 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
- 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
- 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.
- 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:
- Create a new text file called response_sign.txt, copy and paste the exact value of BASE64_SIGNATURE into this without changing anything.
- Use the following command to decrypt the base64 signature into a machine readable format:
This will create a new file called response_sign.sha256 that we will use momentarily.
openssl enc -d -A -base64 -in response_sign.txt -out response_sign.sha256
- 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.
- 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
- 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
- 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.