236 lines
7.0 KiB
Plaintext
236 lines
7.0 KiB
Plaintext
<page xmlns="http://projectmallard.org/1.0/"
|
||
type="topic" id="api-auth" xml:lang="fr">
|
||
|
||
<info>
|
||
<link type="guide" xref="index#api" />
|
||
<revision docversion="0.1" date="2013-01-04" status="draft"/>
|
||
<revision docversion="0.2" date="2015-12-18" status="draft"/>
|
||
<credit type="author">
|
||
<name>Frédéric Péters</name>
|
||
<email>fpeters@entrouvert.com</email>
|
||
</credit>
|
||
<desc>Clé d’utilisation, utilisateurs, sessions, signatures, etc.</desc>
|
||
|
||
</info>
|
||
|
||
<title>Authentification</title>
|
||
|
||
<section>
|
||
<title>Usager concerné</title>
|
||
|
||
<p>
|
||
Pour les appels concernant un usager particulier (tel que la récupération de la
|
||
liste de ses formulaires en cours), l’usager est précisé en ajoutant une query
|
||
string avec un paramètre <code>email</code> (pour trouver l’usager selon son
|
||
adresse électronique) ou un paramètre <code>NameID</code> (pour trouver
|
||
l’usager selon son NameID SAML).
|
||
</p>
|
||
|
||
</section>
|
||
|
||
<section id="req-security-shared-secret">
|
||
<title>Signature des requêtes</title>
|
||
|
||
<p>
|
||
Les appels aux API doivent être signés, cela passe par une clé partagée à
|
||
configurer des deux cotés de la liaison, la signature est du type HMAC;
|
||
l’algorithme de hash à employer est passé en paramètre.
|
||
</p>
|
||
|
||
<note><p>En ce qui concerne l’algorithme de hash, il est préconisé d’utiliser
|
||
SHA-256 par respect du <link
|
||
href="http://references.modernisation.gouv.fr/securite">Référentiel Général
|
||
de Sécurité (RGS)</link>.</p></note>
|
||
|
||
<p>
|
||
La signature est à calculer sur la query string encodée complète, en
|
||
enlevant les paramètres terminaux <code>algo</code>, <code>timestamp</code>,
|
||
<code>nonce</code>, <code>orig</code> et <code>signature</code> s’ils sont
|
||
présents.</p>
|
||
|
||
<p>La formule de calcul de la signature est la suivante :</p>
|
||
|
||
<code>
|
||
BASE64(HMAC-HASH(query_string+'algo=HASH&timestamp=' + timestamp + '&nonce=' + nonce '&orig=" + orig, clé))
|
||
</code>
|
||
|
||
<list>
|
||
|
||
<item><p><code>timestamp</code> est la date dans la zone GMT au format ISO8601
|
||
en se limitant à la précision des secondes (ex : 2012-04-04T12:34:00Z),
|
||
</p></item>
|
||
|
||
<item><p><code>nonce</code> est un aléa, typiquement la réprésentation hexa
|
||
d’un nombre pseudo-aléatoire de 128 bits,</p></item>
|
||
|
||
<item><p><code>orig</code> est une chaîne précisant l’émetteur de la
|
||
requête,</p></item>
|
||
|
||
<item><p>algo est une chaîne représentant l’algorithme de hachage utilisé, sont
|
||
définis : sha1, sha256, sha512 pour les trois algorithmes correspondant.
|
||
L’utilisation d’une valeur différente n’est pas définie. L’algorithme sha256
|
||
est préconisé.</p></item>
|
||
|
||
</list>
|
||
|
||
<p>
|
||
La query string définitive est ainsi :
|
||
</p>
|
||
|
||
<code>
|
||
<var>qs_initial</var>&algo=<var>algo</var>&timestamp=<var>timestamp</var>&nonce=<var>nonce</var>&orig=<var>orig</var>&signature=<var>signature</var>
|
||
</code>
|
||
|
||
</section>
|
||
|
||
<section>
|
||
<title>Configuration des clés partagées</title>
|
||
|
||
<p>
|
||
Les clés partagées doivent être définies dans le fichier
|
||
<code>site-options.cfg</code>, dans une section <code>[api-secrets]</code>, par
|
||
exemple :
|
||
</p>
|
||
|
||
<code>
|
||
[api-secrets]
|
||
intranet = 12345
|
||
</code>
|
||
|
||
</section>
|
||
|
||
<section>
|
||
<title>Exemples de code de signature</title>
|
||
|
||
<p>
|
||
Voici des exemples de code pour créer des URLs signées selon l’algorithme
|
||
expliqué ci-dessus.
|
||
</p>
|
||
|
||
<listing>
|
||
<title>Python</title>
|
||
<code mime="text/x-python">
|
||
#! /usr/bin/env python3
|
||
|
||
import base64
|
||
import datetime
|
||
import hashlib
|
||
import hmac
|
||
import random
|
||
import urllib.parse
|
||
|
||
|
||
def sign_url(url, key, algo='sha256', orig=None, timestamp=None, nonce=None):
|
||
parsed = urllib.parse.urlparse(url)
|
||
new_query = sign_query(parsed.query, key, algo, orig, timestamp, nonce)
|
||
return urllib.parse.urlunparse(parsed[:4] + (new_query,) + parsed[5:])
|
||
|
||
|
||
def sign_query(query, key, algo='sha256', orig=None, timestamp=None, nonce=None):
|
||
if timestamp is None:
|
||
timestamp = datetime.datetime.utcnow()
|
||
timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||
if nonce is None:
|
||
nonce = hex(random.getrandbits(128))[2:].rstrip('L')
|
||
new_query = query
|
||
if new_query:
|
||
new_query += '&'
|
||
new_query += urllib.parse.urlencode((('algo', algo), ('timestamp', timestamp), ('nonce', nonce)))
|
||
if orig is not None:
|
||
new_query += '&' + urllib.parse.urlencode({'orig': orig})
|
||
signature = base64.b64encode(sign_string(new_query, key, algo=algo))
|
||
new_query += '&' + urllib.parse.urlencode({'signature': signature})
|
||
return new_query
|
||
|
||
|
||
def sign_string(s, key, algo='sha256', timedelta=30):
|
||
if not isinstance(key, bytes):
|
||
key = key.encode('utf-8')
|
||
if not isinstance(s, bytes):
|
||
s = s.encode('utf-8')
|
||
digestmod = getattr(hashlib, algo)
|
||
hash = hmac.HMAC(key, digestmod=digestmod, msg=s)
|
||
return hash.digest()
|
||
|
||
|
||
# usage:
|
||
url = sign_url('https://www.example.net/uri/?arg=val&arg2=val2', 'user-key', orig='user')
|
||
</code>
|
||
</listing>
|
||
|
||
<listing>
|
||
<title>PHP</title>
|
||
<code mime="application/x-php">
|
||
<?php
|
||
|
||
function sign_url(string $url, string $orig, string $key) {
|
||
$parsed_url = parse_url($url);
|
||
$timestamp = gmstrftime("%Y-%m-%dT%H:%M:%SZ");
|
||
$nonce = bin2hex(random_bytes(16));
|
||
$new_query = '';
|
||
if (isset($parsed_url['query'])) {
|
||
$new_query .= $parsed_url['query'] . '&';
|
||
}
|
||
$new_query .= http_build_query(array(
|
||
'algo' => 'sha256',
|
||
'timestamp' => $timestamp,
|
||
'nonce' => $nonce,
|
||
'orig' => $orig));
|
||
$signature = base64_encode(hash_hmac('sha256', $new_query, $key, $raw_output = true));
|
||
$new_query .= '&' . http_build_query(array('signature' => $signature));
|
||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
|
||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
|
||
$pass = ($user || $pass) ? "$pass@" : '';
|
||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||
return "$scheme$user$pass$host$port$path?$new_query$fragment";
|
||
}
|
||
|
||
# usage:
|
||
url = sign_url("http://www.example.net/uri/?arg=val&arg2=val2", "user", "user-key");
|
||
|
||
?>
|
||
</code>
|
||
</listing>
|
||
|
||
<listing>
|
||
<title>Shell (bash)</title>
|
||
<code mime="application/x-shellscript">
|
||
#!/bin/bash
|
||
|
||
url="http://www.example.net/uri/?arg=val&arg2=val2"
|
||
orig="user"
|
||
key="user-key"
|
||
|
||
function rawurlencode() {
|
||
local string="${1}"
|
||
local strlen=${#string}
|
||
local encoded=""
|
||
local pos c o
|
||
for ((pos=0; pos<strlen; pos++)); do
|
||
c=${string:$pos:1}
|
||
case "$c" in
|
||
[-_.~a-zA-Z0-9] ) o="${c}" ;;
|
||
* ) printf -v o '%%%02x' "'$c"
|
||
esac
|
||
encoded+="${o}"
|
||
done
|
||
echo "${encoded}"
|
||
}
|
||
|
||
now=$(date -u +%FT%TZ);
|
||
nonce=$(od -N16 -txL /dev/urandom | cut -c8- | tr -d " ")
|
||
qs="algo=sha256&timestamp=$now&nonce=$nonce&orig=$orig"
|
||
sig=$(rawurlencode $(echo -n "$qs" | openssl dgst -binary -sha256 -hmac "$key" | base64))
|
||
signed="${url}?$qs&signature=$sig"
|
||
echo "$signed"
|
||
</code>
|
||
</listing>
|
||
|
||
</section>
|
||
|
||
</page>
|