Skip to content

Instantly share code, notes, and snippets.

@JanisErdmanis
Last active February 2, 2024 22:45
Show Gist options
  • Save JanisErdmanis/0877382cc44f81a9e326ae0be0ecf4ea to your computer and use it in GitHub Desktop.
Save JanisErdmanis/0877382cc44f81a9e326ae0be0ecf4ea to your computer and use it in GitHub Desktop.
Test vector extraction for Azure HMAC implementation
using JSON3
using Base64
using Nettle
using Dates
sha256(data) = Nettle.digest("sha256", data)
sha256(data, key) = Nettle.digest("sha256", key, data)
function verifyRequest(request, secretBase64::String)
method = request["method"]
url = request["url"]
headers = request["headers"]
body = get(request, "body", "")
# Decode Base64 secret
secret = base64decode(secretBase64)
# Compute content hash
#contentHash = isempty(body) ? "" : bytes2hex(sha256(body))
# Checking whether a body is empty may be unnecessary. Need to check compatability with JS here
contentHash = isempty(body) ? "" : Base64.base64encode(sha256(body))
# Extract received content hash and signature
contentHashReceived = headers["x-ms-content-sha256"]
authorizationHeader = headers["Authorization"]
signatureReceived = split(authorizationHeader, "&Signature=")[end]
# Verify content hash
if contentHash != contentHashReceived
return false
end
# Recreate string to sign
utcNow = headers["x-ms-date"]
host = headers["host"]
signedHeaders = "x-ms-date;host;x-ms-content-sha256"
stringToSign = join([method, url, join([utcNow, host, contentHashReceived], ";")], '\n')
# Recreate the HMAC signature
hmac = sha256(stringToSign, secret)
signature = base64encode(hmac)
# Verify the signature
return signature == signatureReceived
end
function signRequest(host, method, url, body, credential, secretBase64)
# Decode Base64 secret
secret = base64decode(secretBase64)
contentHash = Base64.base64encode(sha256(body))
utcNow = Dates.format(Dates.now(), "E, dd u yyyy HH:MM:SS") * " GMT"
#utcNow = "Fri, 02 Feb 2024 17:47:47 GMT" #Dates.now()
signedHeaders = "x-ms-date;host;x-ms-content-sha256"
stringToSign = join([method, url, join([utcNow, host, contentHash], ";")], '\n')
hmac = sha256(stringToSign, secret)
signature = base64encode(hmac)
return Dict(
"x-ms-date" => utcNow,
"x-ms-content-sha256" => contentHash,
"Authorization" => "HMAC-SHA256 Credential=" * credential * "&SignedHeaders=" * signedHeaders * "&Signature=" * signature
)
end
function timestamp(date_str)
date_format = DateFormat("dd u yyyy HH:MM:SS")
relevant_part = join(split(date_str)[2:end-1], " ")
parsed_date = DateTime(relevant_part, date_format)
return parsed_date
end
function credential(authorization_string)
regex = r"Credential=([^&]+)"
m = match(regex, authorization_string)
credential = m !== nothing ? m.captures[1] : "No match found"
return credential
end
secret = "mySecretKey"
secret_base64 = Base64.base64encode(secret)
request = read("requestObject.json") |> JSON3.read
verifyRequest(request, secret_base64)
# Testing signRequest method
host = "peacefounder.org"
method = "POST"
url = "/register"
body = "Hello World!"
credential_str = "exampleCredential"
authHeaders = signRequest(host, method, url, body, credential_str, secret_base64)
authHeaders["host"] = host
request = Dict(
"method" => method,
"url" => url,
"headers" => authHeaders,
"body" => body
)
verifyRequest(request, secret_base64)
const CryptoJS = require("crypto-js");
function signRequest(host,
method, // GET, PUT, POST, DELETE
url, // path+query
body, // request body (undefined of none)
credential, // access key id
secret) // access key value (base64 encoded)
{
var verb = method.toUpperCase();
var utcNow = new Date().toUTCString();
var contentHash = CryptoJS.SHA256(body).toString(CryptoJS.enc.Base64);
//
// SignedHeaders
var signedHeaders = "x-ms-date;host;x-ms-content-sha256"; // Semicolon separated header names
//
// String-To-Sign
var stringToSign =
verb + '\n' + // VERB
url + '\n' + // path_and_query
utcNow + ';' + host + ';' + contentHash; // Semicolon separated SignedHeaders values
//
// Signature
var signature = CryptoJS.HmacSHA256(stringToSign, CryptoJS.enc.Base64.parse(secret)).toString(CryptoJS.enc.Base64);
//
// Result request headers
return [
{ name: "x-ms-date", value: utcNow },
{ name: "x-ms-content-sha256", value: contentHash },
{ name: "Authorization", value: "HMAC-SHA256 Credential=" + credential + "&SignedHeaders=" + signedHeaders + "&Signature=" + signature }
];
}
function verifyRequest(request, secretBase64) {
const { method, url, headers, body } = request;
// Extract the necessary headers
const utcNow = headers['x-ms-date'];
const contentHashReceived = headers['x-ms-content-sha256'];
const authorizationHeader = headers['Authorization'];
// Recreate the content hash for verification
const contentHash = body ? CryptoJS.SHA256(body).toString(CryptoJS.enc.Base64) : CryptoJS.enc.Base64.stringify(CryptoJS.SHA256(''));
// Verify content hash
if (contentHash !== contentHashReceived) {
return false; // Hash mismatch indicates the body was altered
}
// Extract signature from the Authorization header
// Assuming the Authorization header format is "HMAC-SHA256 Credential=credential&SignedHeaders=signedHeaders&Signature=signature"
const signatureReceived = authorizationHeader.split('&Signature=')[1];
if (!signatureReceived) {
return false; // Signature not found in the Authorization header
}
// Recreate string to sign
const signedHeaders = "x-ms-date;host;x-ms-content-sha256";
const stringToSign = method.toUpperCase() + '\n' + url + '\n' + utcNow + ';' + headers['host'] + ';' + contentHash;
// Decode the base64 secret for signing
const secret = CryptoJS.enc.Base64.parse(secretBase64);
// Recreate the signature using the secret
const signature = CryptoJS.HmacSHA256(stringToSign, secret).toString(CryptoJS.enc.Base64);
// Verify the recreated signature against the received signature
return signature === signatureReceived;
}
// Can ChatGPT write a coresponding verify function for the headers!
// Simulate encoding a secret to base64
let secretPlainText = "mySecretKey";
let secretBase64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(secretPlainText));
console.log("Base64 Encoded Secret:", secretBase64);
// Example call to signRequest
let host = "peacefounder.org";
let method = "POST";
let url = "/register";
let body = "Hello World!" //JSON.stringify({ username: "testUser", password: "testPass" }); // Example body
let credential = "exampleCredential";
// Assuming secretBase64 is obtained as shown above
let authHeaders = signRequest(host, method, url, body, credential, secretBase64);
authHeaders.forEach(header => {
console.log(`${header.name}: ${header.value}`);
});
let headersObject = authHeaders.reduce((acc, header) => {
acc[header.name] = header.value;
return acc;
}, {host: host});
let requestObject = {
method: method,
url: url,
headers: headersObject,
body: body
}
console.log(requestObject);
//requestObject['body'] = "Hello World!";
console.log(verifyRequest(requestObject, secretBase64));
// Storing the request to a file
const fs = require('fs');
const path = require('path');
const jsonString = JSON.stringify(requestObject, null, 2); // Beautify the JSON string
const filePath = path.join(__dirname, 'requestObject.json');
try {
fs.writeFileSync(filePath, jsonString);
console.log('File has been written');
} catch (error) {
console.error('An error occurred:', error);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment