URL: https://linuxfr.org/forums/programmationweb/posts/signature-s3-et-comportement-du-navigateur-web
Title: Signature S3 et comportement du navigateur Web
Authors: JulienG
Date: 2023年06月04日T13:56:57+02:00
License: CC By-SA
Tags: aws, s3, bash et javascript
Score: 2
Bonjour,
Je suis bloqué depuis quelques hier sur quelque chose qui ne me semblait pas compliqué au démarrage : faire du S3 depuis le navigateur (sans se payer une bibliothèque obscure et/ou lourde). Le contexte : avoir la possibilité d'envoyer et récupérer du contenu S3 depuis une WebExtension (je devrais en parler bientôt sur LinuxFr).
En soi le plus compliqué est [de générer l'entête `Authorization`](https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html#RESTAuthenticationExamples). Je reste sur la v2 de l'authentification AWS, à destination d'un serveur local MinIO.
Voici l'équivalent Bash et Javascript que j'utilise :
```bash
# bash
signature=`echo -en "PUT\n\n${type}\n${date}\n${chemin}" | openssl sha1 -hmac ${s3Secret} -binary | base64`
```
```javascript
// javascript
function signer(message, secretKey) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const key = encoder.encode(secretKey);
return crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
.then(
(key) => crypto.subtle.sign('HMAC', key, data)
)
.then(
(signature) => {
return btoa(
String.fromCharCode.apply(
null,
new Uint8Array(signature)
)
);
}
);
}
```
Les deux me fournissent une signature valide. Par acquis de conscience, __j'ai aussi testé en remettant la clé générée par Bash directement dans la requête de Firefox (avec la bonne date évidemment)__.
Rien à faire : le serveur n'en veut pas.
Firefox envoie la requête suivante :
```
PUT /tout/test-text.json HTTP/1.1
Host: localhost:9000
User-Agent: curl/7.81.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
authorization: AWS F1vfymAX4V4yp25DbX33:GbddtmO6/A2PGse8ySAwEPNn3vk=
content-type: text/plain
x-amz-date: 2023年6月04日 13:13:28 +0200
Content-Length: 11
Origin: moz-extension://183b3036-20b3-4bc3-82e2-46f80d95c2ca
Connection: keep-alive
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Cache-Control: max-age=0
```
Le message de retour est sans appel : la requête par le navigateur échoue sur une 403...
```xml
SignatureDoesNotMatchThe request signature we calculated does not match the signature you provided. Check your key and signing method.test-text.jsontout/tout/test-text.json17657120F9D3A9D3dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8
```
Chose étrange, le navigateur me permet l'ajout de `x-amz-date` mais refuse semble-t-il (silencieusement) l'envoi de l'entête `Date` :
```javascript
var headers = new Headers();
headers.set('User-Agent', 'curl/7.81.0');
headers.set('Host', 'localhost:9000');
headers.set('Date', date); // ça ne passe pas ?
headers.set('X-Amz-Date', date); // ça passe
headers.set( "Content-Type", contentType );
headers.set( "Expect", "100-continue" );
headers.set('Authorization', `AWS ${s3Key}:${signature}`);
var contexteRequete = {
method: 'PUT',
headers: headers,
mode: 'cors', // j'ai essayé aussi plusieurs valeurs suivant la doc du MDN, sans succès
cache: 'no-cache',
body: "Hello world"
};
var requete = new Request(
`http://${domain}/${bucket}/${filename}`,
contexteRequete,
);
fetch(requete,contexteRequete)
.then(function(response) {
console.log("réponse s3 ok", response.text().then( (c)=>console.log(c) ))
})
.catch(function(erreur) {
console.log("réponse s3 ko", erreur)
});
```
Quant aux journaux de CURL, je ne vois pas d'écart notable :
```
* Trying 127.0.0.1:9000...
* Connected to localhost (127.0.0.1) port 9000 (#0)> PUT /tout/test-text.json HTTP/1.1> Host: localhost:9000> User-Agent: curl/7.81.0> Date: 2023年6月04日 13:13:28 +0200> Content-Type: text/plain> Authorization: AWS F1vfymAX4V4yp25DbX33:GbddtmO6/A2PGse8ySAwEPNn3vk=> Accept: */*> Accept-Language: en-US,en;q=0.5> Accept-Encoding: gzip, deflate, br> Origin: moz-extension://183b3036-20b3-4bc3-82e2-46f80d95c2ca> Connection: keep-alive> Sec-Fetch-Dest: empty> Sec-Fetch-Mode: cors> Sec-Fetch-Site: same-origin> Cache-Control: max-age=0> Content-Length: 9547> Expect: 100-continue>
* Mark bundle as not supporting multiuse
< HTTP/1.1 100 Continue HTTP/1.1 100 Continue * We are completely uploaded and fine * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK HTTP/1.1 200 OK (...) ``` J'ai pensé que l'entête `Date` serait la source du problème (au détriment de `x-amz-date`), mais si je retire celui-ci dans Bash, l'erreur est bien différente d'une 403 (il lui faut une date) : ```xml AccessDeniedAWS authentication requires a valid Date or x-amz-date header/tout/test-text.json
```
Point intéressant, la requête n'est pas valide par défaut de signature (403, `SignatureDoesNotMatch`) si je rajoute `x-amz-date` dans les entêtes CURL (avec ou sans `Date`) - ici le bout de script pour tester avec les entêtes de Firefox que je teste :
```bash
curl -v -I -X PUT -T "${fileorigin}" \
-H "Host: ${domain}" \
-H "User-Agent: curl/7.81.0" \
-H "Date: ${dateValue}" \
-H "x-amz-date: ${dateValue}" \
-H "Content-Type: ${contentType}" \
-H "Authorization: AWS ${signature}" \
-H "Accept: */*" \
-H "Accept-Language: en-US,en;q=0.5" \
-H "Accept-Encoding: gzip, deflate, br" \
-H "Origin: moz-extension://183b3036-20b3-4bc3-82e2-46f80d95c2ca" \
-H "Connection: keep-alive" \
-H "Sec-Fetch-Dest: empty" \
-H "Sec-Fetch-Mode: cors" \
-H "Sec-Fetch-Site: same-origin" \
-H "Cache-Control: max-age=0" \
"http://${domain}/${bucket}/${filename}"
```
Notez bien que le contenu textuel qui sert à la signature (en plus du secret), est correct (sinon ça ne fonctionnerait jamais en Bash).
Selon vous, quelle serait ma méprise ? Est-ce seulement cette question de l'entête `Date`, mais si oui, comment "forcer" son envoi par le navigateur ?
_PS : ce sont des clés bidon évidemment._