Skip to content

Commit 7498887

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

1 file changed

Lines changed: 98 additions & 4 deletions

File tree

lib/api/apiUtils/integrity/validateChecksums.js

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,130 @@
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+
if (xAmzCheckumCnt === 0) {
75+
return { error: ChecksumError.MissingChecksum, details: null };
76+
}
77+
78+
// No x-amz-sdk-checksum-algorithm we expect one x-amz-checksum-[crc64nvme, crc32, crc32C, sha1, sha256].
79+
const algo = checksumHeaders[0].split('-')[3];
80+
if (algo in algorithms === false) {
81+
return { error: ChecksumError.AlgoNotSupported, details: null };;
82+
}
83+
84+
const expected = headers[`x-amz-checksum-${algo}`];
85+
const calculated = algorithms[algo](body);
86+
if (expected !== calculated) {
87+
return { error: ChecksumError.XAmzMismatch, details: null };
88+
}
89+
90+
return null;
91+
}
92+
1093
/**
1194
* validateChecksumsNoChunking - Validate the checksums of a request.
1295
* @param {object} headers - http headers
1396
* @param {Buffer} body - http request body
1497
* @return {object} - error
1598
*/
1699
function validateChecksumsNoChunking(headers, body) {
17-
if (headers && 'content-md5' in headers) {
100+
if (!headers) {
101+
return { error: ChecksumError.MissingChecksum, details: null };
102+
}
103+
104+
let md5Present = false;
105+
if ('content-md5' in headers) {
18106
const md5 = crypto.createHash('md5').update(body).digest('base64');
19107
if (md5 !== headers['content-md5']) {
20108
return { error: ChecksumError.MD5Mismatch, details: { calculated: md5, expected: headers['content-md5'] } };
21109
}
22110

23-
return null;
111+
md5Present = true;
112+
}
113+
114+
const err = validateXAmzChecksums(headers, body);
115+
if (err && err.error === ChecksumError.MissingChecksum && !md5Present) {
116+
// Return MissingChecksum only if no MD5 and no x-amz-checksum-.
117+
return { error: ChecksumError.MissingChecksum, details: null };
24118
}
25119

26-
return { error: ChecksumError.MissingChecksum, details: null };
120+
return err;
27121
}
28122

29123
function defaultValidationFunc(request, body, log) {
30124
const err = validateChecksumsNoChunking(request.headers, body);
31125
if (err && err.error !== ChecksumError.MissingChecksum) {
32126
log.debug('failed checksum validation', { method: request.apiMethod }, err);
33-
return ArsenalErrors.BadDigest;
127+
return ArsenalErrors.BadDigest; // FIXME: InvalidDigest vs BadDigest
34128
}
35129

36130
return null;

0 commit comments

Comments
 (0)