Skip to content

Commit 77f5f1d

Browse files
committed
CLDSRV-863: add ChecksumTransform to calculate and verify stream checksums
1 parent 4026749 commit 77f5f1d

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const { errors } = require('arsenal');
2+
const { algorithms, ChecksumError } = require('../../api/apiUtils/integrity/validateChecksums');
3+
const { Transform } = require('stream');
4+
5+
class ChecksumTransform extends Transform {
6+
constructor(algoName, expectedDigest, isTrailer, log) {
7+
super({});
8+
this.log = log;
9+
this.algoName = algoName;
10+
this.algo = algorithms[algoName];
11+
this.hash = this.algo.createHash();
12+
this.digest = undefined;
13+
this.expectedDigest = expectedDigest;
14+
this.isTrailer = isTrailer;
15+
this.trailerChecksumName = undefined;
16+
this.trailerChecksumValue = undefined;
17+
}
18+
19+
setExpectedChecksum(name, value) {
20+
this.trailerChecksumName = name;
21+
this.trailerChecksumValue = value;
22+
}
23+
24+
validateChecksum() {
25+
if (this.isTrailer) {
26+
// x-amz-trailer in headers but no trailer in body.
27+
if (this.trailerChecksumValue === undefined) {
28+
return {
29+
error: ChecksumError.TrailerMissing,
30+
details: { expectedTrailer: `x-amz-checksum-${this.algoName}` },
31+
};
32+
}
33+
34+
if (this.trailerChecksumName !== `x-amz-checksum-${this.algoName}`) {
35+
return { error: ChecksumError.TrailerAlgoMismatch, details: { algorithm: this.algoName } };
36+
}
37+
38+
const expected = this.trailerChecksumValue;
39+
if (!this.algo.isValidDigest(expected)) {
40+
return {
41+
error: ChecksumError.TrailerChecksumMalformed,
42+
details: { algorithm: this.algoName, expected },
43+
};
44+
}
45+
46+
if (this.digest !== this.trailerChecksumValue) {
47+
return {
48+
error: ChecksumError.XAmzMismatch,
49+
details: { algorithm: this.algoName, calculated: this.digest, expected },
50+
};
51+
}
52+
53+
return null;
54+
}
55+
56+
if (this.trailerChecksumValue) {
57+
// Trailer found in the body but no x-amz-trailer in the headers.
58+
return {
59+
error: ChecksumError.TrailerUnexpected,
60+
details: { name: this.trailerChecksumName, val: this.trailerChecksumValue },
61+
};
62+
}
63+
64+
if (this.expectedDigest) {
65+
if (this.digest !== this.expectedDigest) {
66+
return {
67+
error: ChecksumError.XAmzMismatch,
68+
details: { algorithm: this.algoName, calculated: this.digest, expected: this.expectedDigest },
69+
};
70+
}
71+
}
72+
73+
return null;
74+
}
75+
76+
_flush(callback) {
77+
Promise.resolve(this.algo.digestFromHash(this.hash))
78+
.then(digest => { this.digest = digest; })
79+
.then(() => callback(), err => {
80+
this.log.error('failed to compute checksum digest', { error: err, algorithm: this.algoName });
81+
callback(errors.InternalError);
82+
});
83+
}
84+
85+
_transform(chunk, encoding, callback) {
86+
const input = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
87+
this.hash.update(input, encoding);
88+
callback(null, input);
89+
}
90+
}
91+
92+
module.exports = ChecksumTransform;

0 commit comments

Comments
 (0)