11const 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' ) ;
25const { errors : ArsenalErrors } = require ( 'arsenal' ) ;
36const { config } = require ( '../../../Config' ) ;
47
58const 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+ // What to do about the security vuln package?
32+ // Should we push only to 9.3???
33+
34+ const algorithms = {
35+ 'crc64nvme' : async data => {
36+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
37+ const crc = new CrtCrc64Nvme ( ) ;
38+ crc . update ( input ) ;
39+ const result = await crc . digest ( ) ;
40+ return Buffer . from ( result ) . toString ( 'base64' ) ;
41+ } ,
42+ 'crc32' : data => {
43+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
44+ return uint32ToBase64 ( new Crc32 ( ) . update ( input ) . digest ( ) >>> 0 ) ;
45+ } ,
46+ 'crc32c' : data => {
47+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
48+ return uint32ToBase64 ( new Crc32c ( ) . update ( input ) . digest ( ) >>> 0 ) ;
49+ } ,
50+ 'sha1' : data => {
51+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
52+ return crypto . createHash ( 'sha1' ) . update ( input ) . digest ( 'base64' ) ;
53+ } ,
54+ 'sha256' : data => {
55+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
56+ return crypto . createHash ( 'sha256' ) . update ( input ) . digest ( 'base64' ) ;
57+ }
58+ } ;
59+
60+ function uint32ToBase64 ( num ) {
61+ const buf = Buffer . alloc ( 4 ) ;
62+ buf . writeUInt32BE ( num , 0 ) ;
63+ return buf . toString ( 'base64' ) ;
64+ }
65+
66+ async function validateXAmzChecksums ( headers , body ) {
67+ const checksumHeaders = Object . keys ( headers ) . filter ( header => header . startsWith ( 'x-amz-checksum-' ) ) ;
68+ const xAmzCheckumCnt = checksumHeaders . length ;
69+ if ( xAmzCheckumCnt > 1 ) {
70+ return { error : ChecksumError . MultipleChecksumTypes , details : { algorithms : checksumHeaders } } ;
71+ }
72+
73+ if ( 'x-amz-sdk-checksum-algorithm' in headers ) {
74+ const algo = headers [ 'x-amz-sdk-checksum-algorithm' ] ;
75+ if ( typeof algo !== 'string' ) {
76+ return { error : ChecksumError . AlgoNotSupported , details : { algorithm : algo } } ;
77+ }
78+
79+ const lowerAlgo = algo . toLowerCase ( ) ;
80+ if ( lowerAlgo in algorithms === false ) {
81+ return { error : ChecksumError . AlgoNotSupported , details : { algorithm : algo } } ;
82+ }
83+
84+ if ( `x-amz-checksum-${ lowerAlgo } ` in headers === false ) {
85+ return { error : ChecksumError . MissingCorresponding , details : { expected : `x-amz-checksum-${ lowerAlgo } ` } } ;
86+ }
87+
88+ const expected = headers [ `x-amz-checksum-${ lowerAlgo } ` ] ;
89+ const calculated = await algorithms [ lowerAlgo ] ( body ) ;
90+ // console.log('EXPECTED:', expected, calculated);
91+ // console.log('==========', body, calculated, expected, expected !== calculated)
92+ if ( expected !== calculated ) {
93+ return { error : ChecksumError . XAmzMismatch , details : { calculated, expected } } ;
94+ }
95+
96+ return null ;
97+ }
98+
99+ if ( xAmzCheckumCnt === 0 ) {
100+ return { error : ChecksumError . MissingChecksum , details : null } ;
101+ }
102+
103+ // No x-amz-sdk-checksum-algorithm we expect one x-amz-checksum-[crc64nvme, crc32, crc32C, sha1, sha256].
104+ const algo = checksumHeaders [ 0 ] . split ( '-' ) [ 3 ] ;
105+ if ( typeof algo !== 'string' ) {
106+ return { error : ChecksumError . AlgoNotSupported , details : { algorithm : algo } } ;
107+ }
108+
109+ const lowerAlgo = algo . toLowerCase ( ) ;
110+ if ( lowerAlgo in algorithms === false ) {
111+ return { error : ChecksumError . AlgoNotSupported , details : { algorithm : algo } } ; ;
112+ }
113+
114+ const expected = headers [ `x-amz-checksum-${ lowerAlgo } ` ] ;
115+ const calculated = await algorithms [ lowerAlgo ] ( body ) ;
116+ // console.log('==========', body, calculated, expected, expected !== calculated )
117+ if ( expected !== calculated ) {
118+ return { error : ChecksumError . XAmzMismatch , details : { calculated, expected } } ;
119+ }
120+
121+ return null ;
122+ }
123+
10124/**
11125 * validateChecksumsNoChunking - Validate the checksums of a request.
12126 * @param {object } headers - http headers
13127 * @param {Buffer } body - http request body
14128 * @return {object } - error
15129 */
16- function validateChecksumsNoChunking ( headers , body ) {
17- if ( headers && 'content-md5' in headers ) {
130+ async function validateChecksumsNoChunking ( headers , body ) {
131+ if ( ! headers ) {
132+ return { error : ChecksumError . MissingChecksum , details : null } ;
133+ }
134+
135+ let md5Present = false ;
136+ if ( 'content-md5' in headers ) {
18137 const md5 = crypto . createHash ( 'md5' ) . update ( body ) . digest ( 'base64' ) ;
19138 if ( md5 !== headers [ 'content-md5' ] ) {
20139 return { error : ChecksumError . MD5Mismatch , details : { calculated : md5 , expected : headers [ 'content-md5' ] } } ;
21140 }
22141
142+ md5Present = true ;
143+ }
144+
145+ const err = await validateXAmzChecksums ( headers , body ) ;
146+ if ( err && err . error === ChecksumError . MissingChecksum && md5Present ) {
147+ // Don't return MissingChecksum if MD5 is present.
23148 return null ;
24149 }
25150
26- return { error : ChecksumError . MissingChecksum , details : null } ;
151+ return err ;
27152}
28153
29- function defaultValidationFunc ( request , body , log ) {
30- const err = validateChecksumsNoChunking ( request . headers , body ) ;
154+ // async function defaultValidationFunc2(request, body, log) { // Rename
155+ // const err = await validateChecksumsNoChunking(request.headers, body);
156+ // if (err) {
157+ // log.debug('failed checksum validation', { method: request.apiMethod, error: err });
158+ // return ArsenalErrors.BadDigest; // FIXME: InvalidDigest vs BadDigest
159+ // }
160+
161+ // return null;
162+ // }
163+
164+ async function defaultValidationFunc ( request , body , log ) { // Rename
165+ const err = await validateChecksumsNoChunking ( request . headers , body ) ;
31166 if ( err && err . error !== ChecksumError . MissingChecksum ) {
32167 log . debug ( 'failed checksum validation' , { method : request . apiMethod } , err ) ;
33- return ArsenalErrors . BadDigest ;
168+ return ArsenalErrors . BadDigest ; // FIXME: InvalidDigest vs BadDigest
34169 }
35170
36171 return null ;
@@ -47,6 +182,7 @@ const methodValidationFunc = Object.freeze({
47182 'bucketPutReplication' : defaultValidationFunc ,
48183 'bucketPutVersioning' : defaultValidationFunc ,
49184 'bucketPutWebsite' : defaultValidationFunc ,
185+ 'bucketPutLogging' : defaultValidationFunc ,
50186 // TODO: DeleteObjects requires a checksum. Should return an error if ChecksumError.MissingChecksum.
51187 'multiObjectDelete' : defaultValidationFunc ,
52188 'objectPutACL' : defaultValidationFunc ,
@@ -62,14 +198,13 @@ const methodValidationFunc = Object.freeze({
62198 * @param {object } log - logger
63199 * @return {object } - error
64200 */
65- function validateMethodChecksumNoChunking ( request , body , log ) {
201+ async function validateMethodChecksumNoChunking ( request , body , log ) {
66202 if ( config . integrityChecks [ request . apiMethod ] ) {
67203 const validationFunc = methodValidationFunc [ request . apiMethod ] ;
68204 if ( ! validationFunc ) {
69- return null ;
205+ return null ; //await defaultValidationFunc2(request, body, log);
70206 }
71-
72- return validationFunc ( request , body , log ) ;
207+ return await validationFunc ( request , body , log ) ;
73208 }
74209
75210 return null ;
0 commit comments