Real-Time

Signing & Verifying OnRequest PubNub Functions

Signing & Verifying OnRequest PubNub Functions Feature

PubNub OnRequest Functions allow developers to create hosted REST API endpoints that process incoming HTTP requests. This feature is incredibly useful for real-time communication services where scale and reliability are needed quickly. However, it may be necessary to sign these requests to ensure the integrity and authenticity of the data being sent. In this blog post, we'll cover how to sign requests and verify them in PubNub OnRequest Functions and why this process is essential for secure communication.

How Does Signing Work?

Signing a request involves generating a digital signature uniquely representing the data within the request payload and request headers using a private key. When the server receives the request, it can be verified by comparing the provided signature with one generated using the corresponding public key. This process ensures that the request has not been tampered with during transmission. 

In essence, signing a request helps authenticate both the sender and the integrity of the data. If the request is altered at any point during transit, the request signature will not match the expected value, and the request can be rejected.

Why Sign Requests? The Advantages

  1. Data Integrity: Signing ensures that the payload has not been altered during transmission. If any modification is made, the signature will no longer match, flagging the request as compromised.

  2. Authenticity: Using a public/private key pair, you can verify that the request came from a trusted source with the correct permissions. This eliminates the risk of spoofed requests.

  3. Security: Encrypted signatures prevent unauthorized access or manipulation of your data, especially when sensitive information is transmitted.

  4. Replay Attack Prevention: Using unique signing for each request helps guard against replay attacks, where malicious actors attempt to resend previously intercepted valid requests.

What Are PubNub Functions and Why Use Them?

PubNub Functions are serverless environments that allow developers to run custom code directly within PubNub’s platform in response to messages, events, or, in this case, HTTP requests. They offer several key benefits:

  1. Global Deployment: Cloud functions are deployed globally within seconds, ensuring low-latency execution near users, no matter their location.
  2. Auto-Scaling: PubNub handles automatic scaling, ensuring functions can handle any load—from small to large traffic—without manual intervention.
  3. No Server Management: With PubNub Functions, developers focus on code while PubNub manages infrastructure, eliminating the need for server provisioning or maintenance.

PubNub Functions are delivered as a Node.js environment, which provides access to several helper functions to save you development effort, including cryptography and the PubNub SDK. For more information on PubNub functions, including dependencies, please see our Functions help documentation.

Verifying Signatures in PubNub Functions

PubNub OnRequest Functions are designed to process incoming HTTP requests, and by using the crypto module, it's possible to verify signatures using public keys. In this blog, we’ll show you how to:

  • Generate key pairs
  • Sign payloads
  • Create an OnRequest Function that verifies signed requests using ECDSA (Elliptic Curve Digital Signature Algorithm).

Step 1: Generate a PEM Key Pair

You need a public/private key pair to sign and verify requests. There are multiple ways to generate these keys; one common method is using OpenSSL, and since we are using ECDSA, the ec param is used. If you have Git Bash installed, you can use the following command to generate a private key in PEM format:

1 openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem

Step 2: Convert PEM to JWK Format

Next, we must parse and transform the private key from PEM format into JWK (JSON Web Key) format. Specifically, we want to extract and initialize the following fields:

  • privateKey.d: The private key material.
  • publicKey.x: The X-coordinate of the public key.
  • publicKey.y: The Y-coordinate of the public key.

You can use a Python script to handle this conversion and ensure the fields are properly initialized. Below is an example script to generate the JWKs:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import json import base64 from ecdsa import SigningKey, VerifyingKey, NIST256p # Path to the private key PEM file PRIVATE_KEY_PEM = "private-key.pem" # Load the private key from the PEM file def load_private_key_from_pem(pem_file):     with open(pem_file, 'rb') as f:         private_key_data = f.read()         private_key = SigningKey.from_pem(private_key_data)         return private_key # Convert public key and private key to JWK format def convert_to_jwk(private_key):     public_key = private_key.get_verifying_key()     public_key_bytes = public_key.to_string()     # Split public key into x and y coordinates     x, y = public_key_bytes[:len(public_key_bytes) // 2], public_key_bytes[len(public_key_bytes) // 2:]     # Convert to base64url encoding     x_base64 = base64.urlsafe_b64encode(x).decode().rstrip("=")     y_base64 = base64.urlsafe_b64encode(y).decode().rstrip("=")     d_base64 = base64.urlsafe_b64encode(private_key.to_string()).decode().rstrip("=")     # Create the public and private JWKs     public_jwk = {         "kty": "EC",         "crv": "P-256",         "x": x_base64,         "y": y_base64,         "use": "sig"     }     private_jwk = {         "kty": "EC",         "crv": "P-256",         "d": d_base64,         "use": "sig"     }     return public_jwk, private_jwk # Simplified script execution # Load private key from PEM private_key = load_private_key_from_pem(PRIVATE_KEY_PEM) # Convert to JWK format public_jwk, private_jwk = convert_to_jwk(private_key) # Output the JWKs to the console print("Public JWK:\n", json.dumps(public_jwk, indent=4)) print("Private JWK:\n", json.dumps(private_jwk, indent=4))

Step 3: Sign the Payload Using the Private Key

Once the JWKs have been generated, the next step is to sign the message payload using the private key. This will produce a unique signature that corresponds to the content of the payload. The private key ensures that the signature is cryptographically tied to the request data.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import base64 from ecdsa import SigningKey, NIST256p import hashlib import json # Private JWK constant (manually copied from the output of the first script) PRIVATE_JWK = {     "kty": "EC",     "crv": "P-256",     "d": "PASTE_YOUR_D_VALUE_HERE",     "use": "sig" } # Payload constant PAYLOAD = "SOME_PAYLOAD"  # Replace with your actual payload # Convert JWK to private key def private_jwk_to_signing_key(jwk):     d = base64.urlsafe_b64decode(jwk['d'] + '==')  # Add padding back for base64 decoding     private_key = SigningKey.from_string(d, curve=NIST256p, hashfunc=hashlib.sha256)     return private_key # Sign the payload def sign_payload(private_key, payload):     signature = private_key.sign_deterministic(payload.encode(), hashfunc=hashlib.sha256)     signature_base64 = base64.urlsafe_b64encode(signature).decode()     return signature_base64 # Convert JWK to private key private_key = private_jwk_to_signing_key(PRIVATE_JWK) # Sign the payload signature = sign_payload(private_key, PAYLOAD) # Format the req payload request_payload = {     "payload": PAYLOAD,     "signature": signature } # Output the formatted request payload as JSON print(json.dumps(request_payload, indent=4))

Step 4: Verify the Signature in PubNub Functions

Now that we have a signed payload, we can set up an OnRequest Function in PubNub to verify the request. Using the public JWK generated in step 2 and the PubNub crypto module, we can verify whether the signature matches and whether the request is valid.

Here’s an outline of how the OnRequest Function works:

  1. The payload and signature are sent as part of the HTTP request.
  2. The Function extracts these values and uses the public key (JWK format) to verify the integrity of the payload by checking the signature.
  3. If the signature is valid, the request is processed. If not, the request is rejected.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 const crypto = require("crypto"); // async method, callable only when invoked export default (request, response) => {   try {     return request.json().then((requestBody) => {       const payload = requestBody.payload;       const signature = requestBody.signature;       console.log('Payload: ' + payload);       console.log('Signature: ' + signature);       // Public key in JWK format       const publicKey = {         "kty": "EC",         "crv": "P-256",         "x": "<PUBLIC_KEY_X_VALUE>",         "y": "<PUBLIC_KEY_Y_VALUE>",         "use": "sig"       };       // Verify the signature using PubNub's crypto.verify with JWK directly       return crypto.verify(signature, publicKey, payload, crypto.ALGORITHM.ECDSA_P256_SHA256).then((isSignatureValid) => {         console.log("Signature verified:", isSignatureValid);         if (isSignatureValid) {           response.status = 200;           return response.send("Ok");         } else {           response.status = 401;           return response.send("Signature verification failed.");         }       }).catch((error) => {         console.log("Unable to verify signature:", error);         response.status = 400;         return response.send("Unable to verify signature");       });     });   } catch (error) {     console.log("Unable to verify signature:", error);     response.status = 400;     return response.send("Unable to verify signature");   } };

Step 5: Testing the Function with Postman

Once the Function is set up, you can test it using tools like Postman. By sending POST requests with the valid payload and signature provided by the script in step 3, you can verify that the request is accepted and processed correctly. Conversely, altering the payload or sending an invalid signature will result in the request being rejected.

Successful Request

Below is an example screenshot of a successful request where signature validation has succeeded.

Signing & Verifying OnRequest PubNub Functions 2

Failed Request

Here is an example of a failed request due to a tampered payload. In this case the signature verification fails.

Signing & Verifying OnRequest PubNub Functions 1

Summary

Signing requests when using PubNub OnRequest Functions is a crucial step to ensure the security, authenticity, and integrity of your data. By leveraging the crypto module and using public/private key pairs, developers can safeguard their applications from malicious attacks or data tampering. Whether using RSA, DSA, or ECDSA, signing and verifying signatures should be a fundamental part of your application security strategy.

Once you have a working OnRequest Function that verifies signatures, your system is much more secure against unauthorized or compromised requests!

Get Started with PubNub by signing up for a free account right now.