Last active
February 2, 2024 22:45
-
-
Save JanisErdmanis/0877382cc44f81a9e326ae0be0ecf4ea to your computer and use it in GitHub Desktop.
Test vector extraction for Azure HMAC implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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