Skip to content

Commit 5c6e2cd

Browse files
committed
CLDSRV-848: check x-amz-checksum-[crc64nvme, crc32, crc32C, sha1, sha256] headers
1 parent 5889490 commit 5c6e2cd

File tree

1 file changed

+94
-4
lines changed

1 file changed

+94
-4
lines changed

lib/api/apiUtils/integrity/validateChecksums.js

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,126 @@
11
const crypto = require('crypto');
2+
const { Crc32 } = require('@aws-crypto/crc32');
3+
const { Crc32c } = require('@aws-crypto/crc32c');
4+
const { CrtCrc64Nvme } = require('@aws-sdk/crc64-nvme-crt');
25
const { errors: ArsenalErrors } = require('arsenal');
36
const { config } = require('../../../Config');
47

58
const ChecksumError = Object.freeze({
69
MD5Mismatch: 'MD5Mismatch',
10+
XAmzMismatch: 'XAmzMismatch',
711
MissingChecksum: 'MissingChecksum',
12+
AlgoNotSupported: 'AlgoNotSupported',
13+
MultipleChecksumTypes: 'MultipleChecksumTypes',
14+
MissingCorresponding: 'MissingCorresponding'
815
});
916

17+
// TEMP
18+
// https://github.com/aws/aws-sdk-js-v3/issues/6744
19+
// https://stackoverflow.com/questions/77663519/
20+
// does-aws-s3-allow-specifying-multiple-checksum-values-crc32-crc32c-sha1-and-s
21+
// Expecting a single x-amz-checksum- header. Multiple checksum Types are not allowed.
22+
// XAmzContentChecksumMismatch: The provided 'x-amz-checksum' header does not match what was computed.
23+
24+
// TODO:
25+
// x-amz-checksum-algorithm x2 different
26+
// x-amz-checksum-algorithm x2 equal
27+
// x-amz-checksum-algorithm x2 + x-amz-checksum- valid x2
28+
// x-amz-checksum-algorithm x2 + x-amz-checksum- invalid x2
29+
// x-amz-checksum-algorithm x2 + x-amz-checksum- mismatch x2
30+
31+
const algorithms = {
32+
'crc64nvme': data => uint64ToBase64(new CrtCrc64Nvme().update(data).digest()),
33+
'crc32': data => uint32ToBase64(new Crc32().update(data).digest() >>> 0),
34+
'crc32c': data => uint32ToBase64(new Crc32c().update(data).digest() >>> 0),
35+
'sha1': data => crypto.createHash('sha1').update(data).digest('base64'),
36+
'sha256': data => crypto.createHash('sha256').update(data).digest('base64'),
37+
};
38+
39+
function uint32ToBase64(num) {
40+
return Buffer.alloc(4).writeUInt32BE(num, 0).toString('base64');
41+
}
42+
43+
function uint64ToBase64(num) {
44+
return Buffer.alloc(8).writeBigUint64BE(num, 0).toString('base64');
45+
}
46+
47+
48+
function validateXAmzChecksums(headers, body) {
49+
const checksumHeaders = Object.keys(headers).filter(header => header.startsWith('x-amz-checksum-'));
50+
const xAmzCheckumCnt = checksumHeaders.length;
51+
if (xAmzCheckumCnt > 1) {
52+
return { error: ChecksumError.MultipleChecksumTypes, details: null };
53+
}
54+
55+
if ('x-amz-sdk-checksum-algorithm' in headers) {
56+
const algo = headers['x-amz-sdk-checksum-algorithm'];
57+
if (algo in algorithms === false) {
58+
return { error: ChecksumError.AlgoNotSupported, details: null };
59+
}
60+
61+
if (`x-amz-checksum-${algo}` in headers === false) {
62+
return { error: ChecksumError.MissingCorresponding, details: null };
63+
}
64+
65+
const expected = headers[`x-amz-checksum-${algo}`];
66+
const calculated = algorithms[algo](body);
67+
if (expected !== calculated) {
68+
return { error: ChecksumError.XAmzMismatch, details: null };
69+
}
70+
71+
return null;
72+
}
73+
74+
// No x-amz-sdk-checksum-algorithm we expect one x-amz-checksum-[crc64nvme, crc32, crc32C, sha1, sha256].
75+
const algo = checksumHeaders[0].split('-')[3];
76+
if (algo in algorithms === false) {
77+
return { error: ChecksumError.AlgoNotSupported, details: null };;
78+
}
79+
80+
const expected = headers[`x-amz-checksum-${algo}`];
81+
const calculated = algorithms[algo](body);
82+
if (expected !== calculated) {
83+
return { error: ChecksumError.XAmzMismatch, details: null };
84+
}
85+
86+
return null;
87+
}
88+
1089
/**
1190
* validateChecksumsNoChunking - Validate the checksums of a request.
1291
* @param {object} headers - http headers
1392
* @param {Buffer} body - http request body
1493
* @return {object} - error
1594
*/
1695
function validateChecksumsNoChunking(headers, body) {
17-
if (headers && 'content-md5' in headers) {
96+
if (!headers) {
97+
return { error: ChecksumError.MissingChecksum, details: null };
98+
}
99+
100+
let md5Present = false;
101+
if ('content-md5' in headers) {
18102
const md5 = crypto.createHash('md5').update(body).digest('base64');
19103
if (md5 !== headers['content-md5']) {
20104
return { error: ChecksumError.MD5Mismatch, details: { calculated: md5, expected: headers['content-md5'] } };
21105
}
22106

23-
return null;
107+
md5Present = true;
108+
}
109+
110+
const err = validateXAmzChecksums(headers, body);
111+
if (err && err.error === ChecksumError.MissingChecksum && !md5Present) {
112+
// Return MissingChecksum only if no MD5 and no x-amz-checksum-.
113+
return { error: ChecksumError.MissingChecksum, details: null };
24114
}
25115

26-
return { error: ChecksumError.MissingChecksum, details: null };
116+
return err;
27117
}
28118

29119
function defaultValidationFunc(request, body, log) {
30120
const err = validateChecksumsNoChunking(request.headers, body);
31121
if (err && err.error !== ChecksumError.MissingChecksum) {
32122
log.debug('failed checksum validation', { method: request.apiMethod }, err);
33-
return ArsenalErrors.BadDigest;
123+
return ArsenalErrors.BadDigest; // FIXME: InvalidDigest vs BadDigest
34124
}
35125

36126
return null;

0 commit comments

Comments
 (0)