Skip to content

Commit ec2d446

Browse files
committed
CLDSRV-848: return InvalidDigest if Content-MD5 is invalid
1 parent 3c2175e commit ec2d446

File tree

4 files changed

+73
-17
lines changed

4 files changed

+73
-17
lines changed

lib/api/api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ const api = {
312312
return next(null, userInfo, authorizationResults, streamingV4Params, infos);
313313
})
314314
.catch(error => {
315+
log.error('error validating checksums', { error });
315316
next(error);
316317
});
317318
});

lib/api/apiUtils/integrity/validateChecksums.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { config } = require('../../../Config');
77

88
const ChecksumError = Object.freeze({
99
MD5Mismatch: 'MD5Mismatch',
10+
MD5Invalid: 'MD5Invalid',
1011
XAmzMismatch: 'XAmzMismatch',
1112
MissingChecksum: 'MissingChecksum',
1213
AlgoNotSupported: 'AlgoNotSupported',
@@ -158,7 +159,14 @@ async function validateChecksumsNoChunking(headers, body) {
158159

159160
let md5Present = false;
160161
if ('content-md5' in headers) {
161-
// TODO: check if the content-md5 is valid base64
162+
if (typeof headers['content-md5'] !== 'string') {
163+
return { error: ChecksumError.MD5Invalid, details: { expected: headers['content-md5'] } };
164+
}
165+
166+
if (headers['content-md5'].length !== 24) {
167+
return { error: ChecksumError.MD5Invalid, details: { expected: headers['content-md5'] } };
168+
}
169+
162170
const md5 = crypto.createHash('md5').update(body).digest('base64');
163171
if (md5 !== headers['content-md5']) {
164172
return { error: ChecksumError.MD5Mismatch, details: { calculated: md5, expected: headers['content-md5'] } };
@@ -182,7 +190,9 @@ async function defaultValidationFunc(request, body, log) {
182190
return null;
183191
}
184192

185-
log.debug('failed checksum validation', { method: request.apiMethod }, err);
193+
if (err.error !== ChecksumError.MissingChecksum) {
194+
log.debug('failed checksum validation', { method: request.apiMethod }, err);
195+
}
186196

187197
switch (err.error) {
188198
case ChecksumError.MissingChecksum:
@@ -214,6 +224,8 @@ async function defaultValidationFunc(request, body, log) {
214224
return ArsenalErrors.InvalidRequest.customizeDescription(
215225
`Value for x-amz-checksum-${err.details.algorithm} header is invalid.`
216226
);
227+
case ChecksumError.MD5Invalid:
228+
return ArsenalErrors.InvalidDigest;
217229
default:
218230
return ArsenalErrors.BadDigest;
219231
}

tests/unit/api/api.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ describe('api.callApiMethod', () => {
138138
it(`should return BadDigest for ${method} when bad MD5 checksum is provided`, done => {
139139
const body = '<xml></xml>';
140140
const headers = {
141-
'content-md5': 'badchecksum123=', // Invalid MD5
141+
'content-md5': '1B2M2Y8AsgTpgAmY7PhCfg==', // Wrong MD5
142142
'content-length': body.length.toString()
143143
};
144144

@@ -160,6 +160,32 @@ describe('api.callApiMethod', () => {
160160
});
161161
});
162162

163+
methodsWithChecksumValidation.forEach(method => {
164+
it(`should return InvalidDigest for ${method} when invalid MD5 checksum is provided`, done => {
165+
const body = '<xml></xml>';
166+
const headers = {
167+
'content-md5': 'x', // Invalid MD5
168+
'content-length': body.length.toString()
169+
};
170+
171+
const requestWithBody = new DummyRequest({
172+
headers,
173+
query: {},
174+
socket: { remoteAddress: '127.0.0.1', destroy: sandbox.stub() }
175+
}, body);
176+
177+
sandbox.stub(api, method).callsFake(() => {
178+
done(new Error(`${method} was called despite bad checksum`));
179+
});
180+
181+
api.callApiMethod(method, requestWithBody, response, log, err => {
182+
assert(err, `Expected error for ${method} with bad checksum`);
183+
assert(err.is.InvalidDigest, `Expected InvalidDigest error for ${method}, got: ${err.code}`);
184+
done();
185+
});
186+
});
187+
});
188+
163189
methodsWithChecksumValidation.forEach(method => {
164190
it(`should succeed for ${method} when correct MD5 checksum is provided`, done => {
165191
const body = '<xml></xml>';

tests/unit/api/apiUtils/integrity/validateChecksums.js

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('validateChecksumsNoChunking MD5', () => {
2424
describe('with MD5 mismatch', () => {
2525
it('should return MD5Mismatch error when checksums do not match', async () => {
2626
const body = 'Hello, World!';
27-
const wrongMd5 = 'wrongchecksum123=';
27+
const wrongMd5 = '1B2M2Y8AsgTpgAmY7PhCfg==';
2828
const expectedMd5 = crypto.createHash('md5').update(body, 'utf8').digest('base64');
2929
const headers = {
3030
'content-md5': wrongMd5
@@ -56,45 +56,39 @@ describe('validateChecksumsNoChunking MD5', () => {
5656
assert.strictEqual(result.details, null);
5757
});
5858

59-
it('should return MD5Mismatch error when content-md5 header is undefined', async () => {
59+
it('should return MD5Invalid error when content-md5 header is undefined', async () => {
6060
const body = 'Hello, World!';
6161
const headers = {
6262
'content-type': 'application/json',
6363
'content-md5': undefined
6464
};
65-
const calculatedMD5 = crypto.createHash('md5').update(body, 'utf8').digest('base64');
6665

6766
const result = await validateChecksumsNoChunking(headers, body);
68-
assert.strictEqual(result.error, ChecksumError.MD5Mismatch);
69-
assert.strictEqual(result.details.calculated, calculatedMD5);
67+
assert.strictEqual(result.error, ChecksumError.MD5Invalid);
7068
assert.strictEqual(result.details.expected, undefined);
7169
});
7270

73-
it('should return MD5Mismatch error when content-md5 header is null', async () => {
71+
it('should return MD5Invalid error when content-md5 header is null', async () => {
7472
const body = 'Hello, World!';
7573
const headers = {
7674
'content-type': 'application/json',
7775
'content-md5': null
7876
};
79-
const calculatedMD5 = crypto.createHash('md5').update(body, 'utf8').digest('base64');
8077

8178
const result = await validateChecksumsNoChunking(headers, body);
82-
assert.strictEqual(result.error, ChecksumError.MD5Mismatch);
83-
assert.strictEqual(result.details.calculated, calculatedMD5);
79+
assert.strictEqual(result.error, ChecksumError.MD5Invalid);
8480
assert.strictEqual(result.details.expected, null);
8581
});
8682

87-
it('should return MD5Mismatch error when content-md5 header is empty string', async () => {
83+
it('should return MD5Invalid error when content-md5 header is empty string', async () => {
8884
const body = 'Hello, World!';
8985
const headers = {
9086
'content-type': 'application/json',
9187
'content-md5': ''
9288
};
93-
const calculatedMD5 = crypto.createHash('md5').update(body, 'utf8').digest('base64');
9489

9590
const result = await validateChecksumsNoChunking(headers, body);
96-
assert.strictEqual(result.error, ChecksumError.MD5Mismatch);
97-
assert.strictEqual(result.details.calculated, calculatedMD5);
91+
assert.strictEqual(result.error, ChecksumError.MD5Invalid);
9892
assert.strictEqual(result.details.expected, '');
9993
});
10094
});
@@ -318,7 +312,7 @@ describe('validateMethodChecksumNoChunking', () => {
318312
config.integrityChecks[method] = true;
319313

320314
const body = 'Hello, World!';
321-
const wrongMd5 = 'wrongchecksum123=';
315+
const wrongMd5 = '1B2M2Y8AsgTpgAmY7PhCfg==';
322316
const request = {
323317
apiMethod: method,
324318
headers: {
@@ -335,6 +329,29 @@ describe('validateMethodChecksumNoChunking', () => {
335329
});
336330
});
337331

332+
describe('when checksum mismatches', () => {
333+
supportedMethods.forEach(method => {
334+
it(`should return InvalidDigest error for ${method} when checksum mismatch`, async () => {
335+
config.integrityChecks[method] = true;
336+
337+
const body = 'Hello, World!';
338+
const wrongMd5 = 'wrongchecksum123=';
339+
const request = {
340+
apiMethod: method,
341+
headers: {
342+
'content-md5': wrongMd5
343+
}
344+
};
345+
const log = { debug: sandbox.stub() };
346+
347+
const result = await validateMethodChecksumNoChunking(request, body, log);
348+
349+
assert.deepStrictEqual(result, ArsenalErrors.InvalidDigest, 'Expected BadDigest error');
350+
assert(log.debug.calledOnce);
351+
});
352+
});
353+
});
354+
338355
describe('when no checksum is provided', () => {
339356
supportedMethods.forEach(method => {
340357
it(`should return null for ${method} when no checksum is provided`, async () => {

0 commit comments

Comments
 (0)