amzn / selling-partner-api-models

This repository contains OpenAPI models for developers to use when developing software to call Selling Partner APIs.
Apache License 2.0
612 stars 736 forks source link

Issue uploading an feed document #756

Closed girija45701 closed 3 years ago

girija45701 commented 4 years ago

I am getting an "Signature does not match" error while uploading the encrypted feed document. I get the response from createFeedDocument call and it returns me the url as well. Since the url is signed, i do not sign it any further and just use the url as is. But strangely enough the error comes. I am not sure if anyone has faced this error and how they solved this.

I am using C# .NET.

dsokolowski commented 4 years ago

Maybe you set wrong content type for your request?

rugved1991 commented 4 years ago

The Content-Type for uploading the encrypted feed should match the Content-Type mentioned in createFeedDocument request.

girija45701 commented 4 years ago

Hi,

Thanks for reply. I am using the content types as "text/tab-separated-values; charset=UTF-8" in both while calling the CreateFeedDocument and the same is set on the RestRequest.

girija45701 commented 4 years ago

Here is the full error response:

`<?xml version="1.0" encoding="UTF-8"?>

SignatureDoesNotMatchThe request signature we calculated does not match the signature you provided. Check your key and signing method.AKIAX6THGYUFBGL4IYJW4AWS4-HMAC-SHA256 20201107T074422Z 20201107/eu-west-1/s3/aws4_request b3f29e6dc3bd2853nfikge897934n 239ytr3inifb1e4dfb1d5d5a003590c842e309d5e12f04dngiokru95o34n16393174e7f070dcd3763df50aa90e41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 0a 32 30 32 30 31 31 30 37 54 30 37 34 34 32 32 5a 0a 32 30 32 30 31 31 30 37 2f 65 75 2d 63 65 73 74 2d 31 2f 73 33 2f 61 77 73 34 5f 72 65 71 75 65 73 74 0a 62 33 66 32 39 65 36 64 63 33 62 64 32 38 35 33 36 34 66 35 33 24 37 32 38 63 66 34 32 36 31 33 64 63 32 30 61 33 36 62 62 31 64 61 64 38 37 31 62 31 65 34 64 66 62 31 64 35 64 35 61 30 30 33POST //NinetyDays/amzn1.tortuga.3.7645842a-c27d-48d6-9977-89e1e1c212b2.T311RQKDJYIDKF X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAX2ZVONJKNDK4IYJW4%2F20201107%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20201107T074422Z&X-Amz-Expires=300&X-Amz-SignedHeaders=content-type%3Bhost content-type:multipart/form-data; boundary=-----------------------------28947758029299 host:tortuga-prod-eu.s3-eu-west-1.amazonaws.com content-type;host UNSIGNED-PAYLOAD50 4f 53 54 0a 2f 2f 4e 69 6e 65 74 79 44 61 79 73 2f 61 6d 7a 6e 31 2e 74 6f 72 74 75 67 61 2e 33 2e 37 36 34 35 38 34 32 61 2d 63 32 37 64 2d 34 38 64 36 2d 39 39 37 37 2d 38 39 65 31 65 31 63 32 31 32 62 32 2e 54 33 31 31 52 51 4b 44 4a 59 49 44 4b 46 0a 58 2d 41 6d 7a 2d 41 6c 67 6f 72 69 74 68 6d 3d 41 57 53 34 2d 48 4d 41 43 2d 53 48 41 32 35 36 26 58 2d 41 6d 7a 2d 43 72 65 64 65 6e 74 69 61 6c 3d 41 4b 49 41 58 32 5a 56 4f 5a 46 42 47 4c 34 49 59 4a 57 34 25 32 46 32 30 32 30 31 31 30 37 25 32 46 65 75 2d 77 65 73 74 2d 31 25 32 46 73 33 25 32 46 61 77 73 34 5f 72 65 71 75 65 73 74 26 58 2d 41 6d 7a 2d 44 61 74 65 3d 32 30 32 30 31 31 30 37 54 30 37 34 34 32 32 5a 26 58 2d 41 6d 7a 2d 45 78 70 69 72 65 73 3d 33 30 30 26 58 2d 41 6d 7a 2d 53 69 67 6e 65 64 48 65 61 64 65 72 73 3d 63 6f 6e 74 65 6e 74 2d 74 79 70 65 25 33 42 68 6f 73 74 0a 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3a 6d 75 6c 74 69 70 61 72 74 2f 66 6f 72 6d 2d 64 61 74 61 3b 20 62 6f 75 6e 64 61 72 79 3d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 2d 32 38 39 34 37 37 35 38 30 32 39 32 39 39 0a 68 6f 73 74 3a 74 6f 72 74 75 67 61 2d 70 72 6f 64 2d 65 75 2e 73 33 2d 65 75 2d 77 65 73 74 2d 31 2e 61 6d 61 7a 6f 6e 61 77 73 2e 63 6f 6d 0a 0a 63 6f 6e 74 65 6e 74 2d 74 79 70 65 3b 68 6f 73 74 0a 55 4e 53 49 47 4e 45 44 2d 50 41 59 4c 4f 41 44721D2ACE9AC0228BELNxyvMpEodDPSsqxgIl/0b+1xa2IoQ754fS9em4+ZuZE48Yw6SL+C6eg1/hZnHQzl0Z2UK/VMs=` The RestSharpClient and RestSharp Request are in below screenshots. I have tried to do PUT instead of POST as well, but the response is the same. Any pointers is helpful. - Shankar ![RestClient](https://user-images.githubusercontent.com/2811839/98435961-3d2b6180-20fd-11eb-9bd2-70ddf9e9393b.JPG) ![Rest Request](https://user-images.githubusercontent.com/2811839/98435964-40265200-20fd-11eb-8b39-6352354d7749.JPG)
roger-sanchez117 commented 4 years ago

I have the same problem but in the createFeedDocument request.

Besides this I am a bit lost, what data should I load in the url that the previous request will return to me?

girija45701 commented 4 years ago

Not sure the exact issue you are having in CreateFeeddocument but here is an thread to look at:

https://github.com/amzn/selling-partner-api-models/issues/699

JacksonJeans commented 3 years ago

Hello to all. I have probably found the solution to upload documents. The following things are important:

The ContentType for the request to the predefined URL must be the same as the one specified for the createFeedDocument method.

The file must be AES 256 encrypted with the padding "PKSC5". BTW for the PHP developers among us: In PHP PKSC5 for AES encryption is not possible using the openssl function. I can provide assistance on request.

The key and the IV from the createFeedDocument method response must be decoded base64 before it can be used for encryption.

After the content of the file has been encrypted I encode it back into base64 and temporarily save the file and retrieve it as a resource.

The request method must be "PUT", not "POST"!

I then add the "resource" as body to the request and execute the request (important with PUT!). I get a response with 0 content length, but with HTTP status 200.

Enclosed my implementation using PHP:

<?php

// get amazon feed object
$AmazonFeeds = new Feeds\AmazonFeeds($AmazonAuth);

// example values 
$tmpFileDirectory = dirname(__FILE__) .'/amazon/temp/request/';
$tmpFileprefix = 'amz_feeds';
$tmpFilename = 'random';
$tmpFilesuffix = 'tmp';

$contentType = 'application/pdf';

// do createFeedDocument - operation
$createFeedDocumentSpecifications = new Feeds\CreateFeedDocumentSpecification();
$createFeedDocumentSpecifications->contentType = $contentType;
$createFeedDocumentSpecifications = $AmazonFeeds->createFeedDocument($createFeedDocumentSpecifications);

// if createFeedDocument - operation Status 200 - OK
if (!is_null($createFeedDocumentSpecifications->Response)) {
    // get payload 
    $createFeedDocumentSpecifications = $createFeedDocumentSpecifications->Response;

    // get values
    $url = $createFeedDocumentSpecifications->url;

    // base64 decode
    $initializationVector = base64_decode($createFeedDocumentSpecifications->encryptionDetails->initializationVector, true);
    $key = base64_decode($createFeedDocumentSpecifications->encryptionDetails->key, true);

    // get file to upload 
    $file = file_get_contents('test/dummy.pdf');
    $fileResourceType = gettype($file);

    // resource or string ? make it to a string
    if ($fileResourceType == 'resource') {
        $file = stream_get_contents($file);
    } elseif ($fileResourceType == 'string') {
        $file = $file;
    }   

    // utf8 !
    $UTF8Handler = new UTF8($file,true);
    $file = $UTF8Handler->str;

    // encrypt string and get value as base64 encoded string
    $encryptedFile = xxxxxx\AESCryptoStreamFactory::encrypt($file, $key, $initializationVector);
    $encryptedFilename = $tmpFileDirectory.$tmpFileprefix.'_'.$tmpFilename.'_enc_'.time().'.'.$tmpFilesuffix;
    file_put_contents($encryptedFilename, $encryptedFile);
    $resourceFile = fopen($encryptedFilename,'r');

    // my http client
    $client = new GuzzleHttp\Client(['exceptions' => false]);

    $request = new Request(
        // PUT!
        'PUT',
        // predefined url
        $url,
        // content type equal to content type from response createFeedDocument-operation 
        array('Content-Type' => $contentType),
        // resource File
        $resourceFile
    );

    $response = $client->send($request);
    $HTTPStatusCode = $response->getStatusCode();

    if($HTTPStatusCode == 200){
        unlink($encryptedFilename);
    }else{
        $error = $response->getBody()->getContents();
    }
girija45701 commented 3 years ago

hi @JacksonJeans ,

Can you please share a sample template that you were able to successfully upload. The problem i am facing is while creating the file with the first line. When i download the template from Amazon Seller central the first line has a Template Signature and template identifier like below:

TemplateSignature=Qk9EWV9DQVJFX1BST0RVQ1QsU01BTExfSE9NRV9BUFBMSUFOQ0VT

settings=contentLanguageTag=en_IN&feedType=113&headerLanguageTag=en_IN&templateIdentifier=926f3561-686e-40b7-89a5-7d153dc199bf&timestamp=2020-11-17T15%3A20%3A04.970Z

My doubt is if i am generating the template at runtime, how the signature and identifier can be set.

Thanks in advance.

JacksonJeans commented 3 years ago

Hi @girija45701 ,

I suspect you are trying to upload FLAT_FILE. In my experience I did not have to generate values for TemplateSignatureor others, neither before nor during runtime. I use these templates on Amazon DE: Amazon DE Help

and the templates we have already successfully uploaded are these (Amazon Germany Templatefiles):

Which template are you trying to use and upload?

girija45701 commented 3 years ago

Hi @JacksonJeans ,

Yes, i am using Flat file.

I am using Category specific templates (Kategoriespezifische Lagerbestandsdateivorlagen). I went to my seller account and downloaded the templates and the header look like this.

Capture

Note : The version is different

JacksonJeans commented 3 years ago

Hey @girija45701 , There is a separate Flat_File for each category. The top line must not be changed. The data stored there in the header is generated by Amazon.

In my Flat_Files of the category-specific stocks the top line still contains the friendly hint from Amazon:

"The top three lines are for use by Amazon.de only. Do not change or delete the top three lines.".

girija45701 commented 3 years ago

Hi @JacksonJeans,

Yes, Just wondering what is the correct template for product. I see many different based on the region.

girija45701 commented 3 years ago

Hi all,

I just tried uploading through code and am getting Unauthorized exception. The createDocument returns fine. I then use to encrypt and upload the file. that works fine.

When i call the createFeed, it fails and throws me the exception. I use the same set of token that i use for createDocument (as i assume we will reuse the token as it is valid for 1 hour). The error i get is :

'Error calling CreateFeed: { "errors": [ { "code": "Unauthorized", "message": "Access to the resource is forbidden", "details": "" } ] }

Has anyone encountered this error or has any pointers.

JacksonJeans commented 3 years ago

Unfortunately I cannot tell you which product template you need. I just program the crap here,... our sales department is busy uploading the documents. ^^

But I can tell you that your POST request (createFeed) is broken. I had the same problem when my URI and canonical URI ended with "/".

So that I can say more about it, as well as others here, we need some more information. What does your request look like? Would you like to show us your canonical request and your HTTP body?

Please close the Issue if you now manage to upload documents. I will also reply to your new Issue at amzn/selling-partner-api-models#780 .

girija45701 commented 3 years ago

Closed the issue as the uploading is solved

NetrushAbhijit commented 3 years ago

Hi @girija45701 Could you please share the code that worked for you to upload the Feeds? I am getting the same error "Access to the resource is forbidden". Thank you

girija45701 commented 3 years ago

Hi @girija45701 Could you please share the code that worked for you to upload the Feeds? I am getting the same error "Access to the resource is forbidden". Thank you

Hi Netrush,

Still not able to work that out. Discussion is on another thread amzn/selling-partner-api-models#780

roger-sanchez117 commented 3 years ago

Hello to all. I have probably found the solution to upload documents. The following things are important:

The ContentType for the request to the predefined URL must be the same as the one specified for the createFeedDocument method.

The file must be AES 256 encrypted with the padding "PKSC5". BTW for the PHP developers among us: In PHP PKSC5 for AES encryption is not possible using the openssl function. I can provide assistance on request.

The key and the IV from the createFeedDocument method response must be decoded base64 before it can be used for encryption.

After the content of the file has been encrypted I encode it back into base64 and temporarily save the file and retrieve it as a resource.

The request method must be "PUT", not "POST"!

I then add the "resource" as body to the request and execute the request (important with PUT!). I get a response with 0 content length, but with HTTP status 200.

Enclosed my implementation using PHP:

<?php

// get amazon feed object
$AmazonFeeds = new Feeds\AmazonFeeds($AmazonAuth);

// example values 
$tmpFileDirectory = dirname(__FILE__) .'/amazon/temp/request/';
$tmpFileprefix = 'amz_feeds';
$tmpFilename = 'random';
$tmpFilesuffix = 'tmp';

$contentType = 'application/pdf';

// do createFeedDocument - operation
$createFeedDocumentSpecifications = new Feeds\CreateFeedDocumentSpecification();
$createFeedDocumentSpecifications->contentType = $contentType;
$createFeedDocumentSpecifications = $AmazonFeeds->createFeedDocument($createFeedDocumentSpecifications);

// if createFeedDocument - operation Status 200 - OK
if (!is_null($createFeedDocumentSpecifications->Response)) {
    // get payload 
    $createFeedDocumentSpecifications = $createFeedDocumentSpecifications->Response;

    // get values
    $url = $createFeedDocumentSpecifications->url;

    // base64 decode
    $initializationVector = base64_decode($createFeedDocumentSpecifications->encryptionDetails->initializationVector, true);
    $key = base64_decode($createFeedDocumentSpecifications->encryptionDetails->key, true);

    // get file to upload 
    $file = file_get_contents('test/dummy.pdf');
    $fileResourceType = gettype($file);

    // resource or string ? make it to a string
    if ($fileResourceType == 'resource') {
        $file = stream_get_contents($file);
    } elseif ($fileResourceType == 'string') {
        $file = $file;
    }   

    // utf8 !
    $UTF8Handler = new UTF8($file,true);
    $file = $UTF8Handler->str;

    // encrypt string and get value as base64 encoded string
    $encryptedFile = xxxxxx\AESCryptoStreamFactory::encrypt($file, $key, $initializationVector);
    $encryptedFilename = $tmpFileDirectory.$tmpFileprefix.'_'.$tmpFilename.'_enc_'.time().'.'.$tmpFilesuffix;
    file_put_contents($encryptedFilename, $encryptedFile);
    $resourceFile = fopen($encryptedFilename,'r');

    // my http client
    $client = new GuzzleHttp\Client(['exceptions' => false]);

    $request = new Request(
        // PUT!
        'PUT',
        // predefined url
        $url,
        // content type equal to content type from response createFeedDocument-operation 
        array('Content-Type' => $contentType),
        // resource File
        $resourceFile
    );

    $response = $client->send($request);
    $HTTPStatusCode = $response->getStatusCode();

    if($HTTPStatusCode == 200){
        unlink($encryptedFilename);
    }else{
        $error = $response->getBody()->getContents();
    }

Hello to all. I have probably found the solution to upload documents. The following things are important:

The ContentType for the request to the predefined URL must be the same as the one specified for the createFeedDocument method.

The file must be AES 256 encrypted with the padding "PKSC5". BTW for the PHP developers among us: In PHP PKSC5 for AES encryption is not possible using the openssl function. I can provide assistance on request.

The key and the IV from the createFeedDocument method response must be decoded base64 before it can be used for encryption.

After the content of the file has been encrypted I encode it back into base64 and temporarily save the file and retrieve it as a resource.

The request method must be "PUT", not "POST"!

I then add the "resource" as body to the request and execute the request (important with PUT!). I get a response with 0 content length, but with HTTP status 200.

Enclosed my implementation using PHP:

<?php

// get amazon feed object
$AmazonFeeds = new Feeds\AmazonFeeds($AmazonAuth);

// example values 
$tmpFileDirectory = dirname(__FILE__) .'/amazon/temp/request/';
$tmpFileprefix = 'amz_feeds';
$tmpFilename = 'random';
$tmpFilesuffix = 'tmp';

$contentType = 'application/pdf';

// do createFeedDocument - operation
$createFeedDocumentSpecifications = new Feeds\CreateFeedDocumentSpecification();
$createFeedDocumentSpecifications->contentType = $contentType;
$createFeedDocumentSpecifications = $AmazonFeeds->createFeedDocument($createFeedDocumentSpecifications);

// if createFeedDocument - operation Status 200 - OK
if (!is_null($createFeedDocumentSpecifications->Response)) {
    // get payload 
    $createFeedDocumentSpecifications = $createFeedDocumentSpecifications->Response;

    // get values
    $url = $createFeedDocumentSpecifications->url;

    // base64 decode
    $initializationVector = base64_decode($createFeedDocumentSpecifications->encryptionDetails->initializationVector, true);
    $key = base64_decode($createFeedDocumentSpecifications->encryptionDetails->key, true);

    // get file to upload 
    $file = file_get_contents('test/dummy.pdf');
    $fileResourceType = gettype($file);

    // resource or string ? make it to a string
    if ($fileResourceType == 'resource') {
        $file = stream_get_contents($file);
    } elseif ($fileResourceType == 'string') {
        $file = $file;
    }   

    // utf8 !
    $UTF8Handler = new UTF8($file,true);
    $file = $UTF8Handler->str;

    // encrypt string and get value as base64 encoded string
    $encryptedFile = xxxxxx\AESCryptoStreamFactory::encrypt($file, $key, $initializationVector);
    $encryptedFilename = $tmpFileDirectory.$tmpFileprefix.'_'.$tmpFilename.'_enc_'.time().'.'.$tmpFilesuffix;
    file_put_contents($encryptedFilename, $encryptedFile);
    $resourceFile = fopen($encryptedFilename,'r');

    // my http client
    $client = new GuzzleHttp\Client(['exceptions' => false]);

    $request = new Request(
        // PUT!
        'PUT',
        // predefined url
        $url,
        // content type equal to content type from response createFeedDocument-operation 
        array('Content-Type' => $contentType),
        // resource File
        $resourceFile
    );

    $response = $client->send($request);
    $HTTPStatusCode = $response->getStatusCode();

    if($HTTPStatusCode == 200){
        unlink($encryptedFilename);
    }else{
        $error = $response->getBody()->getContents();
    }

Hi, @JacksonJeans !

Could you help me with AES 256 encryption with "PKSC5" padding in PHP?

How could I do it?

I would greatly appreciate your help.

JacksonJeans commented 3 years ago

Hello @roger-sanchez117 ,

Please click on my profile and send me an email.

roger-sanchez117 commented 3 years ago

Hello @JacksonJeans

I've already sent you an email. Thanks a lot.

akashkaushik33 commented 3 years ago

I am trying to do the same thing in NodeJS but getting the same error. Is someone able to do that successfully in Node?

The main issue is encryption. NodeJs has a createCipheriv method from the inbuilt crypto class. It accepts IV with a length of 16 and key with a length of 32. Anything larger is returning an invalid length error. But the IV that I am getting from the SP-API call has a length of 24 and after converting it to base64 the buffer length becomes 32. The same issue is with key, the original buffer length is 44 and after converting it to base64 the buffer length becomes 60.

I guess this is the reason why I am getting an invalid signature.

Any help is much appreciated.

JacksonJeans commented 3 years ago

@akashkaushik33 ,

If your IV is still 24 bytes long then it will still be base64 encoded. Please decode the key and the IV before you use it to encrypt your data.

The key and the IV from the createFeedDocument method response must be decoded base64 before it can be used for encryption.

After the content of the file has been encrypted I encode it back into base64 and temporarily save the file and retrieve it as a resource.

akashkaushik33 commented 3 years ago

@JacksonJeans Thanks for the heads-up. Once I decoded them they are usable for encrypting the file.

But I am still getting the signature match error. I am trying to create a new product listing and here is the file that I am using:

Screenshot 2020-11-23 at 7 51 28 PM

// nodejs uses PKCS autopadding

Screenshot 2020-11-23 at 8 08 29 PM

Here is my code for ref

// function to encrypt file
const _encryptFile = (content, IV, key, algorithm = 'aes-256-cbc') => {
    try {
        const cipher = crypto.createCipheriv(algorithm, Buffer.from(key), Buffer.from(IV));
        const crypted = Buffer.concat([cipher.update(Buffer.from(content)), cipher.final()]);
        return crypted.toString('base64');
    } catch (e) {
        console.log('Error in encrypt file', e);
        return e
    }
};

   // reading file in utf-8 format
   const productFileInUTFFormat = fs.readFileSync('../sample-from-amazon.xlsx', 'utf-8');

 // decoding base64 encoded key and IV to ASCII
    const decodedKey = Buffer.from(encryptionDetails.key, 'base64').toString('ascii');
    const decodedIV = Buffer.from(encryptionDetails.initializationVector, 'base64').toString('ascii');

   // encrypting file in base64
    const encryptedFileContent = _encryptFile(productFileInUTFFormat, decodedIV, decodedKey);

   // saving encrypted base 64 content as a file 
    fs.writeFileSync('tempfile', encryptedFileContent);
    console.log("path.join(__dirname, '../tempfile')", path.join(__dirname, '../../tempfile'));

    // using the file as input stream for upload
    const formData = {
        my_file: fs.createReadStream(path.join(__dirname, '../../tempfile'))
    };

   // uploading file
    request.put(url, {
        formData,
        headers: {
            'Content-Type': 'text/tab-separated-values; charset=UTF-8'
        }
    }).then(resp => {
        console.log('>>>>>>>', resp);
    }).catch(e => {
        console.log('Errrrrrr', e);
    });

I am a novice in cryptography and could really use some help with this.

JacksonJeans commented 3 years ago

@akashkaushik33 , Did you specify exactly the same content-type in the createFeedDocument-Operation as in the header of your PUT request and uses as url the prefixed url from the response of the createFeedDocument-Operation??

Note: You transfer your resource as array [attr:val]. But you must transfer the resource as such. Otherwise your function will transfer an object containing a 'resource' although Amazon expects a resource here. I got the information from the Java example from Amazon, look at line 42 to 54:

public void upload(UploadSpecification spec) throws CryptoException, HttpResponseException, IOException {

  var readStream = fs.createReadStream(filename);

   // uploading file
    request.put(url, {
        readStream,
        headers: {
            'Content-Type': 'text/tab-separated-values; charset=UTF-8'
        }
    }).then(resp => {
        console.log('>>>>>>>', resp);
    }).catch(e => {
        console.log('Errrrrrr', e);
    });

Since you use Java anyway, you can also use the DocumentHelper directly from Amazon, which I can only recommend. I had to write a PHP implementation for it, see above, and it works as described there.

I got the same error as you describe only when I tried to transfer the data via POST or specified an incorrect content-type which was not the same as in the createFeedDocument transfer operation. I guess in the end it doesn't matter if the resource is valid or not, until you upload your document it will have nothing to do with the document itself, but with the method you transfer it.

Your encryption will no longer be a problem. It looks completely correct.

akashkaushik33 commented 3 years ago

@JacksonJeans Thanks a ton. I think it just worked. Instead of sending the file as a stream I converted it all to a string and uploaded it. Got no errors, but the response was empty. I guess which is correct from the upper comments.

Thanks a lot, @JacksonJeans . Really appreciate your help.

JacksonJeans commented 3 years ago

@akashkaushik33 , Check the HTTP response status after uploading. If it is 200, then everything is okay. You do not get a response in the HTTPBody.

I am glad that I could help you.

JacksonJeans commented 3 years ago

@akashkaushik33 , @girija45701 ,

Correction of myself: Please do not encode the encrypted content in base64. This makes absolutely no sense at this point. We use base64to make strings URLsafe. Since we send the file as a resource in the HTTPBodyvia PUT, the resource does not need to be urlsafe. I forgot to mention that this leads to FATAL state when calling the getFeed() method after the createFeed() method.

After fixing this by not encoding the encrypted content in base64 my feed documents are successfully processed with DONE.

The example code above still works if you pay attention to the things I describe here.

EDIT

Here is my solution and the example file I uploaded and the response from getFeedDocument: Issue:133

fengmiq commented 3 years ago

@JacksonJeans can you supply the php code about UTF8 AND xxxxxx\AESCryptoStreamFactory

fengmiq commented 3 years ago

@JacksonJeans GuzzleHttp\Exception\ClientException Client error: PUT https://tortuga-prod-na.s3-external-1.amazonaws.com/%2FNinetyDays/amzn1.tortuga.3.a3f5cd28-a862-4fa1-862c-b17696fec904.T1996C2C4DI62X?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20201126T092230Z&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Expires=300&X-Amz-Credential=AKIA5U6MO6RAPIVEYJJ3%2F20201126%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=4a008d9a1962a6d70ede580687707ad5ce91f28a2cdef25a86a5c76637a48571 resulted in a 400 Bad Request response: <?xml version="1.0" encoding="UTF-8"?> InvalidArgumentOnly one auth mechanism allowed; only (truncated...)

fengmiq commented 3 years ago

Client error: PUT https://tortuga-prod-na.s3-external-1.amazonaws.com//NinetyDays/amzn1.tortuga.3.4e1a43c8-8ad3-4e98-bb44-1c0e03ad1cdb.T1U6NC6HHDDUEQ?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20201126T105711Z&X-Amz-SignedHeaders=content-type;host&X-Amz-Expires=300&X-Amz-Credential=AKIA5U6MO6RAPIVEYJJ3/20201126/us-east-1/s3/aws4_request&X-Amz-Signature=c0b47181643096221eda80174d7cd680a1200c68c869f7a40020c5ff1f7d4bec resulted in a 403 Forbidden response: <?xml version="1.0" encoding="UTF-8"?> SignatureDoesNotMatchThe request signature we calcul (truncated...)

JacksonJeans commented 3 years ago

@shaoruisky , I cannot publish the UTF8 class.

How to encode strings in UTF8 with PHP you will find very fast.

Please do not just copy code and hope it works, but understand it. I have a lot of answers here in several issues that have been duplicated. A simple search is enough to understand how to encrypt and decrypt.

As there are some defs in PHP who have problems to encrypt and decrypt documents. Here is my static class.

The following code works for decrypting and encrypting:

Usage:

    /**
     * Receive encrypted string
     * @param string $plainText [required]
     * - unencrypted text
     * @param string $key [required]
     * - Key to encrypt
     * @param string $iv [required]
     * - The salt value for the block to encrypt.
     * @return string 
     * - encrypted string
     */
$enrypt = AESCryptoStreamFactory::encrypt($plainText , $key , $iv );

    /**
     * Receive decrypted string
     * @param string $encryptedText
     * - encoded text
     * @param string $key 
     * - Key to decrypt
     * @param string $iv 
     * - The Salt value for the block to decrypt.
     * @return string 
     * - decrypted string
     */
$decrypt = AESCryptoStreamFactory::decrypt($encryptedString, $key , $iv );

Class::

class AESCryptoStreamFactory
{
    /**
     * default: 8 fpr PKSC5! 
     * - The block size is a property of the used cipher algorithm. 
     * For AES it is always 16 bytes. So strictly speaking, 
     * PKCS5Padding cannot be used with AES since it is defined only for a block size of 8 bytes.
     * 
     * The only difference between these padding schemes is that PKCS7Padding has the block size as a parameter,
     * while for PKCS5Padding it is fixed at 8 bytes. When the Block size is 8 bytes they do exactly the same.
     * 
     * - Eigene Implementierung durch getPaddedText();.
     * 
     * - https://crypto.stackexchange.com/questions/43489/how-does-aes-ctr-pkcs5padding-works-when-the-bits-to-encrypt-is-more-than-8-bits
     * - https://stackoverflow.com/questions/20770072/aes-cbc-pkcs5padding-vs-aes-cbc-pkcs7padding-with-256-key-size-performance-java/20770158
     */
    public const BLOCK_SIZE = 8;

    /**
     * default: 16
     * @var int IV_LENGTH
     * - Vektor mit Zufallsdaten
     */
    public const IV_LENGTH = 16;

    /**
     * default: AES256
     * @var string CIPHER
     * - Verschlüsselung
     */
    public const CIPHER = 'AES256';

    /**
     * Erhalte aufegfüllten Text
     * @param string $plainText
     *  - Plain Text
     * @return string $plainText 
     *  - aufgefüllter Plain Text
     */
    protected static function getPaddedText(string $plainText): string
    {
        $stringLength = strlen($plainText);
        if ($stringLength % static::BLOCK_SIZE) {
            $plainText = str_pad($plainText, $stringLength + static::BLOCK_SIZE - $stringLength % static::BLOCK_SIZE, "\0");
        }
        return $plainText;
    }

    /**
     * Erhalte verschlüsselten String
     * @param string $plainText [required]
     * - unverschlüsselter Text
     * @param string $key [required]
     * - Schlüssel zum verschlüsseln
     * @param string $iv [required]
     * - Der Salt Wert für den Block zum verschlüsseln.
     * @return string 
     * - verschlüsselten String
     */
    public static function encrypt(string $plainText, string $key, string $iv): string
    {
        $plainText = static::getPaddedText($plainText);
        return openssl_encrypt($plainText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }

    /**
     * Erhalte entschlüsselten String
     * @param string $encryptedText
     * - verschlüsselter Text
     * @param string $key 
     * - Schlüssel zum entschlüsseln
     * @param string $iv 
     * - Der Salt Wert für den Block zum entschlüsseln.
     * @return string 
     * - entschlüsselten String
     */
    public static function decrypt(string $encryptedText, string $key, string $iv): string
    {
        return openssl_decrypt($encryptedText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }
}
DivingUp commented 3 years ago

@JacksonJeans HI, in your code you use a "pdf" file for the upload as an example. I am looking for a way to transmit the invoice files with this api. Sending FEEDs is no problem ... only I have not found the right FeedType: "UPLOAD_VAT_INVOICE", nor how I should make a link between the PDF file and the order. You can only upload one file (PDF) -> I did not find the link in another XML file ... even the XSD schemas do not have a "DATA" tag in which the PDF can be encoded as a string. Maybe you can give me a tip ... Thank you

JacksonJeans commented 3 years ago

@JacksonJeans HI, in your code you use a "pdf" file for the upload as an example. I am looking for a way to transmit the invoice files with this api. Sending FEEDs is no problem ... only I have not found the right FeedType: "UPLOAD_VAT_INVOICE", nor how I should make a link between the PDF file and the order. You can only upload one file (PDF) -> I did not find the link in another XML file ... even the XSD schemas do not have a "DATA" tag in which the PDF can be encoded as a string. Maybe you can give me a tip ... Thank you

Hello @DivingUp , You are good. That's exactly what I had tried first - just like that :-D

Currently there is no way to upload invoices via the Selling Partner API. I took my implementation of MWS and wrote myself a SignatureV2 to quickly (if that ever comes) adapt to SP-API.

I have left "pdf" as an example in this case. In the end it does not matter what you upload. Amazon accepts the most common files. Whether or not it makes sense for the feed processing is not clear.

DivingUp commented 3 years ago

@JacksonJeans Hi, then I'm glad that I didn't look through all the API's and found nothing ... It is stupid that I was only activated for the SP-API (not for the old MWS service) at Amazon (and that is also the case with all other new USERS) and I have no chance to automatically upload invoices .... that's not really great ...

JacksonJeans commented 3 years ago

@DivingUp , I understand your problem very well. I think Amazon wants to urge you to use the paid billing service.

Anyway, I would still try to upload the document via MWS FeedAPI. Unfortunately I don't have a new account that is not activated for MWS, otherwise I would try it with my InvoiceUploader.

For the authentication you need SignatureV2, your MerchantId as well as AWSAccessKeyId and AWSSecretAccessKey. Enclosed, I have linked the documentation for the InvoiceUploader.

Documentation

DivingUp commented 3 years ago

@JacksonJeans Thanks for the hint. The old MWS service is not a problem if it were activated for my shop ... But since it is not, I cannot use the SignitureV2 service (I don't get any ... you currently only switch new shops for the SP- API free ... so you are virtually "locked out" to upload invoices ... I have now made a request to Amazon that you should activate the MWS service for me ... let's see if they do it and how long it takes. Thanks anyway...

JacksonJeans commented 3 years ago

@DivingUp , I hope for you and wish you good luck! Gladly tell us here about what Amazon answers.

bastien-effetb commented 3 years ago

Hi @DivingUp, did you manage to upload your invoices ? I'am facing the same problem. Only activated for SP API and not MWS...

DivingUp commented 3 years ago

@bastien-effetb & @JacksonJeans Hi, Unfortunately, I have not found a solution because the service for uploading invoices via SP-Api is currently not provided by amazon. I'm also waiting for a solution ... currently there doesn't seem to be any without MWS .... really sad ..

AmandeepSingh179 commented 3 years ago

@shaoruisky , I cannot publish the UTF8 class.

How to encode strings in UTF8 with PHP you will find very fast.

Please do not just copy code and hope it works, but understand it. I have a lot of answers here in several issues that have been duplicated. A simple search is enough to understand how to encrypt and decrypt.

As there are some defs in PHP who have problems to encrypt and decrypt documents. Here is my static class.

The following code works for decrypting and encrypting:

Usage:

    /**
     * Receive encrypted string
     * @param string $plainText [required]
     * - unencrypted text
     * @param string $key [required]
     * - Key to encrypt
     * @param string $iv [required]
     * - The salt value for the block to encrypt.
     * @return string 
     * - encrypted string
     */
$enrypt = AESCryptoStreamFactory::encrypt($plainText , $key , $iv );

    /**
     * Receive decrypted string
     * @param string $encryptedText
     * - encoded text
     * @param string $key 
     * - Key to decrypt
     * @param string $iv 
     * - The Salt value for the block to decrypt.
     * @return string 
     * - decrypted string
     */
$decrypt = AESCryptoStreamFactory::decrypt($encryptedString, $key , $iv );

Class::

class AESCryptoStreamFactory
{
    /**
     * default: 8 fpr PKSC5! 
     * - The block size is a property of the used cipher algorithm. 
     * For AES it is always 16 bytes. So strictly speaking, 
     * PKCS5Padding cannot be used with AES since it is defined only for a block size of 8 bytes.
     * 
     * The only difference between these padding schemes is that PKCS7Padding has the block size as a parameter,
     * while for PKCS5Padding it is fixed at 8 bytes. When the Block size is 8 bytes they do exactly the same.
     * 
     * - Eigene Implementierung durch getPaddedText();.
     * 
     * - https://crypto.stackexchange.com/questions/43489/how-does-aes-ctr-pkcs5padding-works-when-the-bits-to-encrypt-is-more-than-8-bits
     * - https://stackoverflow.com/questions/20770072/aes-cbc-pkcs5padding-vs-aes-cbc-pkcs7padding-with-256-key-size-performance-java/20770158
     */
    public const BLOCK_SIZE = 8;

    /**
     * default: 16
     * @var int IV_LENGTH
     * - Vektor mit Zufallsdaten
     */
    public const IV_LENGTH = 16;

    /**
     * default: AES256
     * @var string CIPHER
     * - Verschlüsselung
     */
    public const CIPHER = 'AES256';

    /**
     * Erhalte aufegfüllten Text
     * @param string $plainText
     *  - Plain Text
     * @return string $plainText 
     *  - aufgefüllter Plain Text
     */
    protected static function getPaddedText(string $plainText): string
    {
        $stringLength = strlen($plainText);
        if ($stringLength % static::BLOCK_SIZE) {
            $plainText = str_pad($plainText, $stringLength + static::BLOCK_SIZE - $stringLength % static::BLOCK_SIZE, "\0");
        }
        return $plainText;
    }

    /**
     * Erhalte verschlüsselten String
     * @param string $plainText [required]
     * - unverschlüsselter Text
     * @param string $key [required]
     * - Schlüssel zum verschlüsseln
     * @param string $iv [required]
     * - Der Salt Wert für den Block zum verschlüsseln.
     * @return string 
     * - verschlüsselten String
     */
    public static function encrypt(string $plainText, string $key, string $iv): string
    {
        $plainText = static::getPaddedText($plainText);
        return openssl_encrypt($plainText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }

    /**
     * Erhalte entschlüsselten String
     * @param string $encryptedText
     * - verschlüsselter Text
     * @param string $key 
     * - Schlüssel zum entschlüsseln
     * @param string $iv 
     * - Der Salt Wert für den Block zum entschlüsseln.
     * @return string 
     * - entschlüsselten String
     */
    public static function decrypt(string $encryptedText, string $key, string $iv): string
    {
        return openssl_decrypt($encryptedText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }
}

@JacksonJeans Thanks for the code.

I tried your code to submit OrderFulfillment Feed. But Amazon reject the feed with error "The XML you submitted is ill-formed at the Amazon Envelope XML level at (or near) line 18, column 18". For testing, I encrypted xml contents using encrypt($contents, $key, $initializationVector) method and then decrypted the encrypted contents using decrypt($encryptedContent, $key, $initializationVector) I found that getPaddedText method is adding some special characters at the end of file i.e. at line 18, column 18 as reported by Amazon. I opened the file in VS Code and screenshot is attached.

file

Can anyone please help with the issue?

JacksonJeans commented 3 years ago

@AmandeepSingh179 Please comment out the getPaddedText() line in the encrypt method. Then it should work.

class AESCryptoStreamFactory
{
    /**
     * default: 8 fpr PKSC5! 
     * - The block size is a property of the used cipher algorithm. 
     * For AES it is always 16 bytes. So strictly speaking, 
     * PKCS5Padding cannot be used with AES since it is defined only for a block size of 8 bytes.
     * 
     * The only difference between these padding schemes is that PKCS7Padding has the block size as a parameter,
     * while for PKCS5Padding it is fixed at 8 bytes. When the Block size is 8 bytes they do exactly the same.
     * 
     * 
     * - https://crypto.stackexchange.com/questions/43489/how-does-aes-ctr-pkcs5padding-works-when-the-bits-to-encrypt-is-more-than-8-bits
     * - https://stackoverflow.com/questions/20770072/aes-cbc-pkcs5padding-vs-aes-cbc-pkcs7padding-with-256-key-size-performance-java/20770158
     */
    public const BLOCK_SIZE = 8;

    /**
     * default: 16
     * @var int IV_LENGTH
     * - vector 
     */
    public const IV_LENGTH = 16;

    /**
     * default: AES256
     * @var string CIPHER
     * - encryption
     */
    public const CIPHER = 'AES256';

    /**
     * Get encrypted string
     * @param string $plainText [required]
     * - unencrypted text
     * @param string $key [required]
     * - key to encrypt
     * @param string $iv [required]
     * - The salt value for the block to encrypt.
     * @return string 
     * - encrypted string
     */
    public static function encrypt(string $plainText, string $key, string $iv)
    {
        return openssl_encrypt($plainText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }

    /**
     * Get decrypted string
     * @param string $encryptedText
     * - encrypted text
     * @param string $key 
     * - decrypt key
     * @param string $iv 
     * - The salt value for the block to decrypt.
     * @return string 
     * - decrypted string
     */
    public static function decrypt(string $encryptedText, string $key, string $iv)
    {
        return openssl_decrypt($encryptedText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }
}
AmandeepSingh179 commented 3 years ago

@AmandeepSingh179 Please comment out the getPaddedText() line in the encrypt method. Then it should work.

class AESCryptoStreamFactory
{
    /**
     * default: 8 fpr PKSC5! 
     * - The block size is a property of the used cipher algorithm. 
     * For AES it is always 16 bytes. So strictly speaking, 
     * PKCS5Padding cannot be used with AES since it is defined only for a block size of 8 bytes.
     * 
     * The only difference between these padding schemes is that PKCS7Padding has the block size as a parameter,
     * while for PKCS5Padding it is fixed at 8 bytes. When the Block size is 8 bytes they do exactly the same.
     * 
     * 
     * - https://crypto.stackexchange.com/questions/43489/how-does-aes-ctr-pkcs5padding-works-when-the-bits-to-encrypt-is-more-than-8-bits
     * - https://stackoverflow.com/questions/20770072/aes-cbc-pkcs5padding-vs-aes-cbc-pkcs7padding-with-256-key-size-performance-java/20770158
     */
    public const BLOCK_SIZE = 8;

    /**
     * default: 16
     * @var int IV_LENGTH
     * - vector 
     */
    public const IV_LENGTH = 16;

    /**
     * default: AES256
     * @var string CIPHER
     * - encryption
     */
    public const CIPHER = 'AES256';

    /**
     * Get encrypted string
     * @param string $plainText [required]
     * - unencrypted text
     * @param string $key [required]
     * - key to encrypt
     * @param string $iv [required]
     * - The salt value for the block to encrypt.
     * @return string 
     * - encrypted string
     */
    public static function encrypt(string $plainText, string $key, string $iv)
    {
        return openssl_encrypt($plainText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }

    /**
     * Get decrypted string
     * @param string $encryptedText
     * - encrypted text
     * @param string $key 
     * - decrypt key
     * @param string $iv 
     * - The salt value for the block to decrypt.
     * @return string 
     * - decrypted string
     */
    public static function decrypt(string $encryptedText, string $key, string $iv)
    {
        return openssl_decrypt($encryptedText, static::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
    }
}

@JacksonJeans Thank you very much. It worked.

alpapazzo commented 3 years ago

// utf8 ! $UTF8Handler = new UTF8($file,true); $file = $UTF8Handler->str;

Hello, thank you for your code, I m using it do encrypt the file. I m using uft8_encode ( php ) to convert the file, but I got a arror. So can you tell me where I can find the UTF8 class that you use ? Bus isn't correct using the php function uft8_encode ? I m uploading a Flat file, not xml.

Thanks

AceGray commented 3 years ago

$createFeedDocumentSpecifications->encryptionDetails->initializationVector, true)

The createfeeddocument interface I requested did not return the encryptiondetails parameter, only the feeddocumentid and URL parameters