Security
All API requests must be made over HTTPS (TLS 1.2).
API authentication
Every request to the API must be authenticated using the JWS detached token
- JSON Web Signature
(RFC7515) - with an
asymmetric signature. The algorithm used for the signature must be
ES256
(EC NIST P-256 DSA key with SHA-256 hashing).
Elliptic-curve cryptography (ECC) is an approach to public-key cryptography based on the algebraic structure of elliptic curves over finite fields. ECC, an alternative technique to RSA, is a powerful cryptography approach.
As the JWS detached token is defined, it does not contain the payload and
the format used is {header}..{signature}
.
The signature is only about the request body in JSON format using UTF-8 encoding. Ensure that the JWT library used to sign the payload does not add any other field to it in the process.
The token header must contain:
alg
: The cypher algorithm ID. Must beES256
.aud
: Target endpoint. It must be specified as{httpMethod} {urlPath}
. The{urlPath}
includes the path with the query string but not the domain. e.g.,POST /v1/payments
.exp
: Expiration timestamp in JSON format (5 minutes max). Make sure your system clock is in sync to avoid unexpected unauthorized responses.kid
: The ID to identify the private key used to sign the request. Thekid
is aKSUID
ID provided by the support team when the public key is registered in the platform.
JWS detached
X-Signature
header using the JSON Web Signature
token: {header}..{signature}
.
Each API credential can be assigned selected scopes to segregate permissions to different endpoints. In each endpoint specification, it is indicated the scope required.
Example: "X-Signature": "eyJhbGciOiJ..ICjcwYzA5Ny01ZjQzLT"
.
- Security Scheme Type: API Key
- Header parameter name: X-Signature
Generate a private EC key
Generate a private EC key, of size 256, and output it to a file named
key.pem
.
openssl ecparam -name prime256v1 -genkey -noout -out key.pem
Extract the public key from the key pair, which can be used in a certificate
openssl ec -in key.pem -pubout -out public.pem
After running these two commands you end up with two files: key.pem
and
public.pem
. The private key key.pem
must not be shared. Share the
public key public.pem
with developer support by emailing to
support@nomupay.com. NomuPay´s developer
support team will provide the key identifier kid
which must be included
in every request, and the shared HMAC key to verify the response signature.
Revoke/rotate keys
If your private key has been stolen, anyone can impersonate you. If the shared HMAC key has been stolen, you can not ensure the origins of responses or webhook messages.
If you need to regenerate your keys or revoke the existing ones, generate
new keys and contact support@nomupay.com to
indicate the key identifier kid
of the keys to revoke.
It is possible to have multiple keys at the same time. When you generate a new set of keys, the previous keys should be kept active for a period of several days. This ensures you're able to process with two signatures (as NomuPay's APIs operate using an asynchronous system we are not able to able to apply only the new signature to the following responses or sent webhooks notifications). During this time, your endpoint has multiple active secrets and it generates one signature for each secret.
When the old keys are revoked only the current X-Signature
will exist in
the header.
Code snippets
- C#
- Java
- Node.js
- PHP
- Python
For this example, we use a commonly recommended package jose-jwt
which will
help you create and validate JWT tokens with ease.
You can find it on GitHub.
using Jose;
using System.Security.Cryptography;
public async Task<string> GetJwsDetachedToken(string payload, string method, Uri path)
{
const string KeyId = "YOUR_KEY_ID";
const string PrivateKeyFilePath = "private-ecdsa.key";
const int ExpirationInMinutes = 3;
string privateKeyContent = await GetPrivateKeyFromFile(PrivateKeyFilePath);
byte[] blocks = Convert.FromBase64String(privateKeyContent);
var privateKeyEcDsa = ECDsa.Create();
privateKeyEcDsa.ImportECPrivateKey(blocks, out _);
TimeSpan exp = DateTime.UtcNow.AddMinutes(ExpirationInMinutes) - new DateTime(1970, 1, 1);
Dictionary<string, object> headers = new()
{
["aud"] = $"{method} {path}",
["kid"] = KeyId,
["exp"] = (int)exp.TotalSeconds,
};
return JWT.Encode(payload, privateKeyEcDsa, JwsAlgorithm.ES256, extraHeaders: headers, options: new JwtOptions { DetachPayload = true});
}
private static async Task<string> GetPrivateKeyFromFile(string fileName)
{
string privateKey = await File.ReadAllTextAsync(fileName, System.Text.Encoding.UTF8);
return privateKey
.Replace("-----BEGIN PRIVATE KEY-----", string.Empty)
.Replace("-----BEGIN EC PRIVATE KEY-----", string.Empty)
.Replace("-----END PRIVATE KEY-----", string.Empty)
.Replace("-----END EC PRIVATE KEY-----", string.Empty);
}
For this example, we use a commonly recommended package jose4j
which will help
you create and validate JWT tokens with ease. You can find it on
Bitbucket.
private static String getJwsDetachedToken(String payload, String method, String path) throws NoSuchAlgorithmException, InvalidKeySpecException, JoseException {
final String keyId = "YOUR_KEY_ID";
final String privateKeyFilePath = "private-ecdsa.key";
final int expirationInMinutes = 3;
String privateKeyContent = getPrivateKeyFromFile(privateKeyFilePath);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyContent));
PrivateKey privateKey = keyFactory.generatePrivate(keySpecPKCS8);
long exp = LocalDateTime.now().plusMinutes(expirationInMinutes).toEpochSecond(ZoneOffset.UTC);
JsonWebSignature jws = new JsonWebSignature();
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256);
jws.setKey(privateKey);
jws.setPayload(payload);
jws.setHeader("aud", method + " " + path);
jws.setHeader("exp", (int)exp);
jws.setKeyIdHeaderValue(keyId);
String jwsToken = jws.getCompactSerialization();
String[] jwsBlocks = jwsToken.split("[.]");
return jwsBlocks[0] + ".." + jwsBlocks[2];
}
private static String getPrivateKeyFromFile(String filename)
{
String privateKey = "";
try {
URL url = RequestExample.class.getClassLoader().getResource(filename);
File file = new File(url.getFile());
privateKey = new String(Files.readAllBytes(file.toPath()));
privateKey = privateKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----BEGIN EC PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("-----END EC PRIVATE KEY-----", "")
.replace("\n", "")
.replace("\r", "");
}
catch (IOException e) {
e.printStackTrace();
}
return privateKey;
}
For this example, we use a commonly recommended package jsonwebtoken
which
will help you to create and validate JWT tokens easily.
You can find it on GitHub.
const jwt = require('jsonwebtoken'),
fs = require('fs');
const getJwsDetachedToken = (payload, method, path) => {
const keyId = 'YOUR_KID',
privateKeyFilePath = 'private-ecdsa.key',
expirationInMinutes = 3;
const SECS_PER_MIN = 60,
MILLIS = 1000;
const now = new Date(),
exp = new Date(now.getTime() + expirationInMinutes * SECS_PER_MIN * MILLIS),
expiresIn = parseInt(exp.getTime() / 1000),
privateKey = fs.readFileSync(privateKeyFilePath, 'utf8');
const options = {
algorithm: 'ES256',
header: {
aud: `${method} ${path}`,
kid: keyId,
exp: expiresIn,
},
noTimestamp: true,
};
const jws = jwt.sign(payload, privateKey, options),
jwsBlocks = jws.split('.'),
jwsDetached = `${jwsBlocks[0]}..${jwsBlocks[2]}`;
return jwsDetached;
};
For this example, we use a commonly recommended package firebase/php-jwt
which
will help you create and validate JWT tokens with ease.
You can find it on GitHub.
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
function getJwsDetachedToken($payload, $method, $path)
{
$keyId = "YOUR_KEY_ID";
$privateKeyFilePath = "private-ecdsa.key";
$expirationInMinutes = 3;
$privateKey = file_get_contents($privateKeyFilePath);
$expireIn = time() + ($expirationInMinutes * 60);
$header = array(
"aud" => "$method $path",
"exp" => $expireIn
);
$jws = JWT::encode($payload, $privateKey, "ES256", $keyId, $header);
$jwsBlocks = explode(".", $jws);
return $jwsBlocks[0] . ".." . $jwsBlocks[2];
}
For this example, we use a commonly recommended package python-jose
which will help
you create and validate JWT tokens with ease. You can find it on
PyPI.
import base64
import json
import time
from jose import jwt
import requests
from cryptography.hazmat.primitives import serialization
def get_jws_detached_token(payload, method, path):
"""Get JWS detached token function."""
kid = "YOUR_KEY_ID"
private_key_file_path = "private-key.pem"
expiration_in_minutes = 3
with open(private_key_file_path, "rb") as private_key_file:
private_key_data = private_key_file.read()
private_key = serialization.load_pem_private_key(
private_key_data, password=None)
exp_seconds = int(time.time() + expiration_in_minutes * 60)
headers = {
"alg": "ES256",
"aud": f"{method} {path}",
"kid": kid,
"exp": exp_seconds,
}
# Create a JWS token with the payload and headers.
token = jwt.encode(payload, private_key,
algorithm="ES256", headers=headers)
encoded_parts = token.split(".")
detached_token = encoded_parts[0]+".." + encoded_parts[2]
return detached_token
def json_encode(json_payload):
"""Encode json payload function."""
# Serialize the JSON payload to a string. The separators parameter
# is used to remove all spaces and new lines from the JSON string.
json_data = json.dumps(json_payload, separators=(
',', ':')).encode('utf-8')
request_token = get_jws_detached_token(payload, method, path)
# All spaces and new lines must be removed from the request body.
request_body = json_encode(payload)
response = requests.post(url + path, data=request_body, headers={
'X-Signature': request_token,
'Content-type': 'application/json',
'Accept': 'application/json', }, timeout=10
)