Skip to content

Commit b9c19c2

Browse files
feat: capture raw body bytes for v4 hmac calculation
TICKET: CAAS-659
1 parent 8b4a279 commit b9c19c2

File tree

6 files changed

+557
-24
lines changed

6 files changed

+557
-24
lines changed

modules/express/src/clientRoutes.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,37 @@ async function handleNetworkV1EnterpriseClientConnections(
11921192
return handleProxyReq(req, res, next);
11931193
}
11941194

1195+
/**
1196+
* Helper to send request body, using raw bytes when available.
1197+
*
1198+
* For v4 HMAC authentication, we need to send the exact bytes that were
1199+
* received from the client to ensure the HMAC signature matches.
1200+
* The rawBodyBuffer is captured by body-parser's verify callback before
1201+
* JSON parsing, preserving exact whitespace, key ordering, etc.
1202+
*
1203+
* For v2/v3, sending the raw string also works because serializeRequestData
1204+
* now properly returns strings as-is for HMAC calculation.
1205+
*
1206+
* @param request - The superagent request object
1207+
* @param req - The Express request containing body and rawBodyBuffer
1208+
* @returns The request with body attached
1209+
*/
1210+
function sendRequestBody(request: ReturnType<BitGo['post']>, req: express.Request) {
1211+
if (req.rawBodyBuffer) {
1212+
// Preserve original Content-Type header from client
1213+
const contentTypeHeader = req.headers['content-type'];
1214+
if (contentTypeHeader) {
1215+
request.set('Content-Type', Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : contentTypeHeader);
1216+
}
1217+
// Send raw body as UTF-8 string to preserve exact bytes for HMAC.
1218+
// JSON is always UTF-8 (RFC 8259), so this is lossless for JSON bodies.
1219+
// serializeRequestData will return this string as-is for HMAC calculation.
1220+
return request.send(req.rawBodyBuffer.toString('utf8'));
1221+
}
1222+
// Fall back to parsed body for backward compatibility (e.g., non-JSON bodies)
1223+
return request.send(req.body);
1224+
}
1225+
11951226
/**
11961227
* Redirect a request using the bitgo request functions.
11971228
* @param bitgo
@@ -1214,19 +1245,19 @@ export function redirectRequest(
12141245
request = bitgo.get(url);
12151246
break;
12161247
case 'POST':
1217-
request = bitgo.post(url).send(req.body);
1248+
request = sendRequestBody(bitgo.post(url), req);
12181249
break;
12191250
case 'PUT':
1220-
request = bitgo.put(url).send(req.body);
1251+
request = sendRequestBody(bitgo.put(url), req);
12211252
break;
12221253
case 'PATCH':
1223-
request = bitgo.patch(url).send(req.body);
1254+
request = sendRequestBody(bitgo.patch(url), req);
12241255
break;
12251256
case 'OPTIONS':
1226-
request = bitgo.options(url).send(req.body);
1257+
request = sendRequestBody(bitgo.options(url), req);
12271258
break;
12281259
case 'DELETE':
1229-
request = bitgo.del(url).send(req.body);
1260+
request = sendRequestBody(bitgo.del(url), req);
12301261
break;
12311262
}
12321263

@@ -1267,7 +1298,12 @@ function apiResponse(status: number, result: any, message?: string): ApiResponse
12671298
return new ApiResponseError(message, status, result);
12681299
}
12691300

1270-
const expressJSONParser = bodyParser.json({ limit: '20mb' });
1301+
const expressJSONParser = bodyParser.json({
1302+
limit: '20mb',
1303+
verify: (req, res, buf) => {
1304+
(req as express.Request).rawBodyBuffer = buf;
1305+
},
1306+
});
12711307

12721308
/**
12731309
* Perform body parsing here only on routes we want

modules/express/src/expressApp.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,18 @@ export function app(cfg: Config): express.Application {
302302
checkPreconditions(cfg);
303303
debug('preconditions satisfied');
304304

305-
app.use(bodyParser.json({ limit: '20mb' }));
305+
app.use(
306+
bodyParser.json({
307+
limit: '20mb',
308+
verify: (req, res, buf) => {
309+
// Store the raw body buffer on the request object.
310+
// This preserves the exact bytes before JSON parsing,
311+
// which may alter whitespace, key ordering, etc.
312+
// Required for v4 HMAC authentication.
313+
(req as express.Request).rawBodyBuffer = buf;
314+
},
315+
})
316+
);
306317

307318
// Be more robust about accepting URLs with double slashes
308319
app.use(function replaceUrlSlashes(req, res, next) {

0 commit comments

Comments
 (0)