Full Example
API Signed Request Examples
const buffer = require('buffer');
const crypto = require('crypto');
/* Message Signing Helpers */
/**
* Convert a string to an array buffer
* @param {string} str
* @returns {string}
*/
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
/**
* Convert a string to base64-url safe encoding
* @param {string} str
* @returns {string}
*/
function base64UrlEncode(str) {
return Buffer.from(str).toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
/**
* Import a private key and return a CryptoKey
* @param {string} privateKeyDer base64 encoded private key der
* @returns {Promise<CryptoKey>}
*/
async function getPrivateKeyFromDer(privKeyDer) {
const decoded = Buffer.from(privKeyDer, 'base64').toString('binary');
const buf = str2ab(decoded);
return crypto.subtle.importKey('pkcs8', buf, {
name: 'ECDSA',
namedCurve: 'P-256',
}, true, ['sign']);
}
/**
* Sign a message with a private CryptoKey
* @param {CryptoKey} privateKey
* @param {string} message The message to sign
* @returns {string} The signed message
*/
async function signMessage(privateKey, message) {
return crypto.subtle.sign(
{
name: 'ECDSA',
hash: 'SHA-256'
},
privateKey,
new TextEncoder().encode(message)
);
}
/**
* Create a request signature
* @param {CryptoKey} privateKey
* @param {string} date HTTP header date value
* @param {string} method HTTP method value
* @param {string} path HTTP path
* @param {string} [queryString] HTTP path query string (? not included)
*/
async function createRequestSignature({
privateKey,
date,
method,
path,
queryString = undefined
}) {
const nonce = Date.now().toString();
const sigParts = [
method.toUpperCase(),
path,
queryString,
date,
nonce
].filter(str => str && str.length !== 0);
const signature = await signMessage(privateKey, sigParts.join('\n'));
return base64UrlEncode(signature) + '.' + base64UrlEncode(nonce);
}
/* HTTP Request */
const API_URL = 'https://int.quadrata.com';
const API_KEY = '...';
/**
* Send a signed request
* @param {CryptoKey} privateKey
* @param {string} method HTTP method value
* @param {string} path HTTP path
* @param {string} [queryString] HTTP path query string (? not included)
* @param {object} [body] HTTP body payload
* @param {object} [extraHeaders] Optional headers to send with the request
* @returns {Promise<Response|void>}
*/
async function makeRequest({
privateKey,
method,
path,
queryString = undefined,
body = undefined,
extraHeaders = undefined
}) {
const date = (new Date()).toUTCString();
const signature = await createRequestSignature({
privateKey,
method,
path,
queryString,
date
});
const headers = {
...extraHeaders,
'Authorization': `Basic ${Buffer.from(API_KEY).toString('base64')}`,
'Date': date,
'Signature': signature
};
let urlPath = `${API_URL}${path}`;
if (queryString && queryString.length !== 0) {
urlPath += `?${queryString}`;
}
const response = await fetch(urlPath, {
method,
headers,
body: body ? JSON.stringify(body) : undefined
});
if (!response.ok) {
console.error(response);
return;
}
return response.json();
}
/**
* Base64 Encoded Private Key DER
* @type {string}
*/
const privateKeyDer = '...';
/**
* @type {CryptoKey}
*/
const privateKey = await getPrivateKeyFromDer(privateKeyDer);
/**
* @type {string}
*/
const walletAddress = '...';
// make the request
const json = await makeRequest({
method: 'GET',
privateKey: privateKey,
path: `/api/v1/wallets/${encodeURIComponent(walletAddress)}/screening`,
});
import base64
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey, ECDSA
from cryptography.hazmat.primitives import hashes
from datetime import datetime, timezone
from secrets import token_hex
from typing import Any, Dict, Optional, Union
API_URL = 'http://int.quadrata.com'
API_KEY = b'...'
PRIVATE_KEY_DER = b'...' # base64 encoded private key der
def b64_url_encode(bstr: bytes) -> str:
return base64.urlsafe_b64encode(bstr).rstrip(b'=').decode('utf-8')
def private_key_from_der(
der: bytes,
password: Optional[bytes] = None
) -> EllipticCurvePrivateKey:
return serialization.load_der_private_key(base64.b64decode(der), password)
def create_sig(
priv_key: EllipticCurvePrivateKey,
method: str,
path: str,
date: str,
query_string: Optional[str] = None
) -> str:
nonce = token_hex(10)
return b64_url_encode(
priv_key.sign(
'\n'.join(
list(
filter(
lambda s: s and len(s) > 0,
[
method.upper(),
path,
query_string if query_string else None,
date,
nonce,
],
)
)
).encode('utf-8'),
ECDSA(hashes.SHA256()),
)
) + (f'.{b64_url_encode(bytes(nonce, "utf-8"))}')
def make_request(
path: str,
method: str = 'get',
query_string: Optional[str] = None,
extra_headers: Optional[Dict[str, Any]] = None
) -> str:
request_action = getattr(requests, method.lower(), None)
if request_action is None:
raise ValueError(f'Invalid method provided: {method}')
date = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
sig = create_sig(
priv_key=private_key_from_der(PRIVATE_KEY_DER),
method=method,
path=path,
date=date,
query_string=query_string
)
url_path = f'{API_URL}{path}'
if query_string is not None and len(query_string) != 0:
url_path += f'?{query_string}'
response = request_action(
url_path,
headers={
'Authorization': f'Basic {base64.b64encode(API_KEY).decode()}',
'Signature': sig,
'Date': date
} | (extra_headers if extra_headers is not None else {})
)
return response.json()
wallet_address = '...'
api_response = make_request(f'/api/v1/wallets/{wallet_address}/screening')
Last updated