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+ AlgoNotSupportedSDK : 'AlgoNotSupportedSDK' ,
14+ MultipleChecksumTypes : 'MultipleChecksumTypes' ,
15+ MissingCorresponding : 'MissingCorresponding' ,
16+ InvalidAlgoValue : 'InvalidAlgoValue' ,
817} ) ;
918
19+ const algorithms = {
20+ 'crc64nvme' : {
21+ 'digest' : async data => {
22+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
23+ const crc = new CrtCrc64Nvme ( ) ;
24+ crc . update ( input ) ;
25+ const result = await crc . digest ( ) ;
26+ return Buffer . from ( result ) . toString ( 'base64' ) ;
27+ } ,
28+ 'checkExpected' : expected => {
29+ if ( typeof expected !== 'string' ) {
30+ return false ;
31+ }
32+ return expected . length === 12 ;
33+ } ,
34+ } ,
35+ 'crc32' : {
36+ 'digest' : data => {
37+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
38+ return uint32ToBase64 ( new Crc32 ( ) . update ( input ) . digest ( ) >>> 0 ) ;
39+ } ,
40+ 'checkExpected' : expected => {
41+ if ( typeof expected !== 'string' ) {
42+ return false ;
43+ }
44+ return expected . length === 8 ;
45+ } ,
46+ } ,
47+ 'crc32c' : {
48+ 'digest' : data => {
49+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
50+ return uint32ToBase64 ( new Crc32c ( ) . update ( input ) . digest ( ) >>> 0 ) ;
51+ } ,
52+ 'checkExpected' : expected => {
53+ if ( typeof expected !== 'string' ) {
54+ return false ;
55+ }
56+ return expected . length === 8 ;
57+ } ,
58+ } ,
59+ 'sha1' : {
60+ 'digest' : data => {
61+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
62+ return crypto . createHash ( 'sha1' ) . update ( input ) . digest ( 'base64' ) ;
63+ } ,
64+ 'checkExpected' : expected => {
65+ if ( typeof expected !== 'string' ) {
66+ return false ;
67+ }
68+ return expected . length === 28 ;
69+ } ,
70+ } ,
71+ 'sha256' : {
72+ 'digest' : data => {
73+ const input = Buffer . isBuffer ( data ) ? data : Buffer . from ( data ) ;
74+ return crypto . createHash ( 'sha256' ) . update ( input ) . digest ( 'base64' ) ;
75+ } ,
76+ 'checkExpected' : expected => {
77+ if ( typeof expected !== 'string' ) {
78+ return false ;
79+ }
80+ return expected . length === 44 ;
81+ } ,
82+ }
83+ } ;
84+
85+ function uint32ToBase64 ( num ) {
86+ const buf = Buffer . alloc ( 4 ) ;
87+ buf . writeUInt32BE ( num , 0 ) ;
88+ return buf . toString ( 'base64' ) ;
89+ }
90+
91+ async function validateXAmzChecksums ( headers , body ) {
92+ const checksumHeaders = Object . keys ( headers ) . filter ( header => header . startsWith ( 'x-amz-checksum-' ) ) ;
93+ const xAmzCheckumCnt = checksumHeaders . length ;
94+ if ( xAmzCheckumCnt > 1 ) {
95+ return { error : ChecksumError . MultipleChecksumTypes , details : { algorithms : checksumHeaders } } ;
96+ }
97+
98+ if ( xAmzCheckumCnt === 0 && 'x-amz-sdk-checksum-algorithm' in headers ) {
99+ return {
100+ error : ChecksumError . MissingCorresponding ,
101+ details : { expected : headers [ 'x-amz-sdk-checksum-algorithm' ] }
102+ } ;
103+ } else if ( xAmzCheckumCnt === 0 ) {
104+ return { error : ChecksumError . MissingChecksum , details : null } ;
105+ }
106+
107+ // No x-amz-sdk-checksum-algorithm we expect one x-amz-checksum-[crc64nvme, crc32, crc32C, sha1, sha256].
108+ const algo = checksumHeaders [ 0 ] . split ( '-' ) [ 3 ] ;
109+ if ( typeof algo !== 'string' ) {
110+ return { error : ChecksumError . AlgoNotSupported , details : { algorithm : algo } } ;
111+ }
112+
113+ if ( algo in algorithms === false ) {
114+ return { error : ChecksumError . AlgoNotSupported , details : { algorithm : algo } } ; ;
115+ }
116+
117+ const expected = headers [ `x-amz-checksum-${ algo } ` ] ;
118+ if ( ! algorithms [ algo ] . checkExpected ( expected ) ) {
119+ return { error : ChecksumError . InvalidAlgoValue , details : { algorithm : algo , expected } } ;
120+ }
121+
122+ const calculated = await algorithms [ algo ] . digest ( body ) ;
123+ if ( expected !== calculated ) {
124+ return { error : ChecksumError . XAmzMismatch , details : { algorithm : algo , calculated, expected } } ;
125+ }
126+
127+ // AWS checks x-amz-checksum- first and then x-amz-sdk-checksum-algorithm
128+ if ( 'x-amz-sdk-checksum-algorithm' in headers ) {
129+ const sdkAlgo = headers [ 'x-amz-sdk-checksum-algorithm' ] ;
130+ if ( typeof sdkAlgo !== 'string' ) {
131+ return { error : ChecksumError . AlgoNotSupportedSDK , details : { algorithm : sdkAlgo } } ;
132+ }
133+
134+ const sdkLowerAlgo = sdkAlgo . toLowerCase ( ) ;
135+ if ( sdkLowerAlgo in algorithms === false ) {
136+ return { error : ChecksumError . AlgoNotSupportedSDK , details : { algorithm : sdkAlgo } } ;
137+ }
138+
139+ // If AWS there is a mismatch, AWS returns the same error as if the algo was invalid.
140+ if ( sdkLowerAlgo !== algo ) {
141+ return { error : ChecksumError . AlgoNotSupportedSDK , details : { algorithm : sdkAlgo } } ;
142+ }
143+ }
144+
145+ return null ;
146+ }
147+
10148/**
11149 * validateChecksumsNoChunking - Validate the checksums of a request.
12150 * @param {object } headers - http headers
13151 * @param {Buffer } body - http request body
14152 * @return {object } - error
15153 */
16- function validateChecksumsNoChunking ( headers , body ) {
17- if ( headers && 'content-md5' in headers ) {
154+ async function validateChecksumsNoChunking ( headers , body ) {
155+ if ( ! headers ) {
156+ return { error : ChecksumError . MissingChecksum , details : null } ;
157+ }
158+
159+ let md5Present = false ;
160+ if ( 'content-md5' in headers ) {
161+ // TODO: check if the content-md5 is valid base64
18162 const md5 = crypto . createHash ( 'md5' ) . update ( body ) . digest ( 'base64' ) ;
19163 if ( md5 !== headers [ 'content-md5' ] ) {
20164 return { error : ChecksumError . MD5Mismatch , details : { calculated : md5 , expected : headers [ 'content-md5' ] } } ;
21165 }
22166
167+ md5Present = true ;
168+ }
169+
170+ const err = await validateXAmzChecksums ( headers , body ) ;
171+ if ( err && err . error === ChecksumError . MissingChecksum && md5Present ) {
172+ // Don't return MissingChecksum if MD5 is present.
23173 return null ;
24174 }
25175
26- return { error : ChecksumError . MissingChecksum , details : null } ;
176+ return err ;
27177}
28178
29- function defaultValidationFunc ( request , body , log ) {
30- const err = validateChecksumsNoChunking ( request . headers , body ) ;
31- if ( err && err . error !== ChecksumError . MissingChecksum ) {
32- log . debug ( 'failed checksum validation' , { method : request . apiMethod } , err ) ;
33- return ArsenalErrors . BadDigest ;
179+ async function defaultValidationFunc ( request , body , log ) {
180+ const err = await validateChecksumsNoChunking ( request . headers , body ) ;
181+ if ( ! err ) {
182+ return null ;
34183 }
35184
36- return null ;
185+ log . debug ( 'failed checksum validation' , { method : request . apiMethod } , err ) ;
186+
187+ switch ( err . error ) {
188+ case ChecksumError . MissingChecksum :
189+ return null ;
190+ case ChecksumError . XAmzMismatch : {
191+ const algoUpper = err . details . algorithm . toUpperCase ( ) ;
192+ return ArsenalErrors . BadDigest . customizeDescription (
193+ `The ${ algoUpper } you specified did not match the calculated checksum.`
194+ ) ;
195+ }
196+ case ChecksumError . AlgoNotSupported :
197+ return ArsenalErrors . InvalidRequest . customizeDescription (
198+ 'The algorithm type you specified in x-amz-checksum- header is invalid.'
199+ ) ;
200+ case ChecksumError . AlgoNotSupportedSDK :
201+ return ArsenalErrors . InvalidRequest . customizeDescription (
202+ 'Value for x-amz-sdk-checksum-algorithm header is invalid.'
203+ ) ;
204+ case ChecksumError . MissingCorresponding :
205+ return ArsenalErrors . InvalidRequest . customizeDescription (
206+ 'x-amz-sdk-checksum-algorithm specified, but no corresponding x-amz-checksum-* ' +
207+ 'or x-amz-trailer headers were found.'
208+ ) ;
209+ case ChecksumError . MultipleChecksumTypes :
210+ return ArsenalErrors . InvalidRequest . customizeDescription (
211+ 'Expecting a single x-amz-checksum- header. Multiple checksum Types are not allowed.'
212+ ) ;
213+ case ChecksumError . InvalidAlgoValue :
214+ return ArsenalErrors . InvalidRequest . customizeDescription (
215+ `Value for x-amz-checksum-${ err . details . algorithm } header is invalid.`
216+ ) ;
217+ default :
218+ return ArsenalErrors . BadDigest ;
219+ }
37220}
38221
39222const methodValidationFunc = Object . freeze ( {
223+ 'completeMultipartUpload' : defaultValidationFunc ,
40224 'bucketPutACL' : defaultValidationFunc ,
41225 'bucketPutCors' : defaultValidationFunc ,
42226 'bucketPutEncryption' : defaultValidationFunc ,
@@ -47,12 +231,15 @@ const methodValidationFunc = Object.freeze({
47231 'bucketPutReplication' : defaultValidationFunc ,
48232 'bucketPutVersioning' : defaultValidationFunc ,
49233 'bucketPutWebsite' : defaultValidationFunc ,
234+ 'bucketPutLogging' : defaultValidationFunc ,
235+ 'bucketPutTagging' : defaultValidationFunc ,
50236 // TODO: DeleteObjects requires a checksum. Should return an error if ChecksumError.MissingChecksum.
51237 'multiObjectDelete' : defaultValidationFunc ,
52238 'objectPutACL' : defaultValidationFunc ,
53239 'objectPutLegalHold' : defaultValidationFunc ,
54240 'objectPutTagging' : defaultValidationFunc ,
55241 'objectPutRetention' : defaultValidationFunc ,
242+ 'objectRestore' : defaultValidationFunc ,
56243} ) ;
57244
58245/**
@@ -62,14 +249,13 @@ const methodValidationFunc = Object.freeze({
62249 * @param {object } log - logger
63250 * @return {object } - error
64251 */
65- function validateMethodChecksumNoChunking ( request , body , log ) {
252+ async function validateMethodChecksumNoChunking ( request , body , log ) {
66253 if ( config . integrityChecks [ request . apiMethod ] ) {
67254 const validationFunc = methodValidationFunc [ request . apiMethod ] ;
68255 if ( ! validationFunc ) {
69- return null ;
256+ return null ; //await defaultValidationFunc2(request, body, log);
70257 }
71-
72- return validationFunc ( request , body , log ) ;
258+ return await validationFunc ( request , body , log ) ;
73259 }
74260
75261 return null ;
0 commit comments