@@ -37,6 +37,12 @@ const ChecksumError = Object.freeze({
3737 MultipleChecksumTypes : 'MultipleChecksumTypes' ,
3838 MissingCorresponding : 'MissingCorresponding' ,
3939 MalformedChecksum : 'MalformedChecksum' ,
40+ TrailerAlgoMismatch : 'TrailerAlgoMismatch' ,
41+ TrailerChecksumMalformed : 'TrailerChecksumMalformed' ,
42+ TrailerMissing : 'TrailerMissing' ,
43+ TrailerUnexpected : 'TrailerUnexpected' ,
44+ TrailerAndChecksum : 'TrailerAndChecksum' ,
45+ TrailerNotSupported : 'TrailerNotSupported' ,
4046} ) ;
4147
4248const base64Regex = / ^ [ A - Z a - z 0 - 9 + / ] * = { 0 , 2 } $ / ;
@@ -56,35 +62,51 @@ const algorithms = Object.freeze({
5662 const result = await crc . digest ( ) ;
5763 return Buffer . from ( result ) . toString ( 'base64' ) ;
5864 } ,
65+ digestFromHash : async hash => {
66+ const result = await hash . digest ( ) ;
67+ return Buffer . from ( result ) . toString ( 'base64' ) ;
68+ } ,
5969 isValidDigest : expected => typeof expected === 'string' && expected . length === 12 && base64Regex . test ( expected ) ,
70+ createHash : ( ) => new CrtCrc64Nvme ( )
6071 } ,
6172 crc32 : {
6273 digest : data => {
6374 const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
6475 return uint32ToBase64 ( new Crc32 ( ) . update ( input ) . digest ( ) >>> 0 ) ; // >>> 0 coerce number to uint32
6576 } ,
77+ digestFromHash : hash => {
78+ const result = hash . digest ( ) ;
79+ return uint32ToBase64 ( result >>> 0 ) ;
80+ } ,
6681 isValidDigest : expected => typeof expected === 'string' && expected . length === 8 && base64Regex . test ( expected ) ,
82+ createHash : ( ) => new Crc32 ( )
6783 } ,
6884 crc32c : {
6985 digest : data => {
7086 const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
7187 return uint32ToBase64 ( new Crc32c ( ) . update ( input ) . digest ( ) >>> 0 ) ; // >>> 0 coerce number to uint32
7288 } ,
89+ digestFromHash : hash => uint32ToBase64 ( hash . digest ( ) >>> 0 ) ,
7390 isValidDigest : expected => typeof expected === 'string' && expected . length === 8 && base64Regex . test ( expected ) ,
91+ createHash : ( ) => new Crc32c ( )
7492 } ,
7593 sha1 : {
7694 digest : data => {
7795 const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
7896 return crypto . createHash ( 'sha1' ) . update ( input ) . digest ( 'base64' ) ;
7997 } ,
98+ digestFromHash : hash => hash . digest ( 'base64' ) ,
8099 isValidDigest : expected => typeof expected === 'string' && expected . length === 28 && base64Regex . test ( expected ) ,
100+ createHash : ( ) => crypto . createHash ( 'sha1' )
81101 } ,
82102 sha256 : {
83103 digest : data => {
84104 const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
85105 return crypto . createHash ( 'sha256' ) . update ( input ) . digest ( 'base64' ) ;
86106 } ,
107+ digestFromHash : hash => hash . digest ( 'base64' ) ,
87108 isValidDigest : expected => typeof expected === 'string' && expected . length === 44 && base64Regex . test ( expected ) ,
109+ createHash : ( ) => crypto . createHash ( 'sha256' )
88110 }
89111} ) ;
90112
@@ -141,6 +163,94 @@ async function validateXAmzChecksums(headers, body) {
141163 return null ;
142164}
143165
166+ function getChecksumDataFromHeaders ( headers ) {
167+ const checkSdk = algo => {
168+ if ( ! ( 'x-amz-sdk-checksum-algorithm' in headers ) ) {
169+ return null ;
170+ }
171+
172+ const sdkAlgo = headers [ 'x-amz-sdk-checksum-algorithm' ] ;
173+ if ( typeof sdkAlgo !== 'string' ) {
174+ return { error : ChecksumError . AlgoNotSupportedSDK , details : { algorithm : sdkAlgo } } ;
175+ }
176+
177+ const sdkLowerAlgo = sdkAlgo . toLowerCase ( ) ;
178+ if ( ! ( sdkLowerAlgo in algorithms ) ) {
179+ return { error : ChecksumError . AlgoNotSupportedSDK , details : { algorithm : sdkAlgo } } ;
180+ }
181+
182+ // If AWS there is a mismatch, AWS returns the same error as if the algo was invalid.
183+ if ( sdkLowerAlgo !== algo ) {
184+ return { error : ChecksumError . AlgoNotSupportedSDK , details : { algorithm : sdkAlgo } } ;
185+ }
186+
187+ return null ;
188+ } ;
189+
190+ const checksumHeaders = Object . keys ( headers ) . filter ( header => header . startsWith ( 'x-amz-checksum-' ) ) ;
191+ const xAmzChecksumCnt = checksumHeaders . length ;
192+ if ( xAmzChecksumCnt > 1 ) {
193+ return { error : ChecksumError . MultipleChecksumTypes , details : { algorithms : checksumHeaders } } ;
194+ }
195+
196+ if ( xAmzChecksumCnt === 0 && ! ( 'x-amz-trailer' in headers ) && 'x-amz-sdk-checksum-algorithm' in headers ) {
197+ return {
198+ error : ChecksumError . MissingCorresponding ,
199+ details : { expected : headers [ 'x-amz-sdk-checksum-algorithm' ] }
200+ } ;
201+ }
202+
203+ if ( 'x-amz-trailer' in headers ) {
204+ if ( xAmzChecksumCnt !== 0 ) {
205+ return {
206+ error : ChecksumError . TrailerAndChecksum ,
207+ details : { trailer : headers [ 'x-amz-trailer' ] , checksum : checksumHeaders } ,
208+ } ;
209+ }
210+
211+ const trailer = headers [ 'x-amz-trailer' ] ;
212+ if ( ! trailer . startsWith ( 'x-amz-checksum-' ) ) {
213+ return { error : ChecksumError . TrailerNotSupported , details : { value : trailer } } ;
214+ }
215+
216+ const trailerAlgo = trailer . slice ( 'x-amz-checksum-' . length ) ;
217+ if ( ! ( trailerAlgo in algorithms ) ) {
218+ return { error : ChecksumError . TrailerNotSupported , details : { value : trailer } } ;
219+ }
220+
221+ const err = checkSdk ( trailerAlgo ) ;
222+ if ( err ) {
223+ return err ;
224+ }
225+
226+ return { algorithm : trailerAlgo , isTrailer : true , expected : undefined } ;
227+ }
228+
229+ if ( xAmzChecksumCnt === 0 ) {
230+ // There was no x-amz-checksum- or x-amz-trailer return crc64nvme.
231+ // The calculated crc64nvme will be stored in the object metadata.
232+ return { algorithm : 'crc64nvme' , isTrailer : false , expected : undefined } ;
233+ }
234+
235+ // No x-amz-sdk-checksum-algorithm we expect one x-amz-checksum-[crc64nvme, crc32, crc32C, sha1, sha256].
236+ const algo = checksumHeaders [ 0 ] . slice ( 'x-amz-checksum-' . length ) ;
237+ if ( ! ( algo in algorithms ) ) {
238+ return { error : ChecksumError . AlgoNotSupported , details : { algorithm : algo } } ;
239+ }
240+
241+ const expected = headers [ `x-amz-checksum-${ algo } ` ] ;
242+ if ( ! algorithms [ algo ] . isValidDigest ( expected ) ) {
243+ return { error : ChecksumError . MalformedChecksum , details : { algorithm : algo , expected } } ;
244+ }
245+
246+ const err = checkSdk ( algo ) ;
247+ if ( err ) {
248+ return err ;
249+ }
250+
251+ return { algorithm : algo , isTrailer : false , expected } ;
252+ }
253+
144254/**
145255 * validateChecksumsNoChunking - Validate the checksums of a request.
146256 * @param {object } headers - http headers
@@ -183,16 +293,7 @@ async function validateChecksumsNoChunking(headers, body) {
183293 return err ;
184294}
185295
186- async function defaultValidationFunc ( request , body , log ) {
187- const err = await validateChecksumsNoChunking ( request . headers , body ) ;
188- if ( ! err ) {
189- return null ;
190- }
191-
192- if ( err . error !== ChecksumError . MissingChecksum ) {
193- log . debug ( 'failed checksum validation' , { method : request . apiMethod } , err ) ;
194- }
195-
296+ function arsenalErrorFromChecksumError ( err ) {
196297 switch ( err . error ) {
197298 case ChecksumError . MissingChecksum :
198299 return null ;
@@ -225,11 +326,40 @@ async function defaultValidationFunc(request, body, log) {
225326 ) ;
226327 case ChecksumError . MD5Invalid :
227328 return ArsenalErrors . InvalidDigest ;
329+ case ChecksumError . TrailerAlgoMismatch :
330+ return ArsenalErrors . MalformedTrailerError ;
331+ case ChecksumError . TrailerMissing :
332+ return ArsenalErrors . MalformedTrailerError ;
333+ case ChecksumError . TrailerUnexpected :
334+ return ArsenalErrors . MalformedTrailerError ;
335+ case ChecksumError . TrailerChecksumMalformed :
336+ return ArsenalErrors . InvalidRequest . customizeDescription (
337+ `Value for x-amz-checksum-${ err . details . algorithm } trailing header is invalid.`
338+ ) ;
339+ case ChecksumError . TrailerAndChecksum :
340+ return ArsenalErrors . InvalidRequest . customizeDescription ( 'Expecting a single x-amz-checksum- header' ) ;
341+ case ChecksumError . TrailerNotSupported :
342+ return ArsenalErrors . InvalidRequest . customizeDescription (
343+ 'The value specified in the x-amz-trailer header is not supported'
344+ ) ;
228345 default :
229346 return ArsenalErrors . BadDigest ;
230347 }
231348}
232349
350+ async function defaultValidationFunc ( request , body , log ) {
351+ const err = await validateChecksumsNoChunking ( request . headers , body ) ;
352+ if ( ! err ) {
353+ return null ;
354+ }
355+
356+ if ( err . error !== ChecksumError . MissingChecksum ) {
357+ log . debug ( 'failed checksum validation' , { method : request . apiMethod } , err ) ;
358+ }
359+
360+ return arsenalErrorFromChecksumError ( err ) ;
361+ }
362+
233363/**
234364 * validateMethodChecksumsNoChunking - Validate the checksums of a request.
235365 * @param {object } request - http request
@@ -253,5 +383,8 @@ module.exports = {
253383 ChecksumError,
254384 validateChecksumsNoChunking,
255385 validateMethodChecksumNoChunking,
386+ getChecksumDataFromHeaders,
387+ arsenalErrorFromChecksumError,
388+ algorithms,
256389 checksumedMethods,
257390} ;
0 commit comments