<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&amp;timestamp=' + timestamp + '&amp;nonce=' + nonce '&amp;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>&amp;algo=<var>algo</var>&amp;timestamp=<var>timestamp</var>&amp;nonce=<var>nonce</var>&amp;orig=<var>orig</var>&amp;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 += '&amp;'
    new_query += urllib.parse.urlencode((('algo', algo), ('timestamp', timestamp), ('nonce', nonce)))
    if orig is not None:
        new_query += '&amp;' + urllib.parse.urlencode({'orig': orig})
    signature = base64.b64encode(sign_string(new_query, key, algo=algo))
    new_query += '&amp;' + 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&amp;arg2=val2', 'user-key', orig='user')
</code>
</listing>

<listing>
<title>PHP</title>
<code mime="application/x-php">
&lt;?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'] . '&amp;';
	}
	$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 .= '&amp;' . 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&amp;arg2=val2", "user", "user-key");

?&gt;
</code>
</listing>

<listing>
<title>Shell (bash)</title>
<code mime="application/x-shellscript">
#!/bin/bash

url="http://www.example.net/uri/?arg=val&amp;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&lt;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&amp;timestamp=$now&amp;nonce=$nonce&amp;orig=$orig"
sig=$(rawurlencode $(echo -n "$qs" | openssl dgst -binary -sha256 -hmac "$key" | base64))
signed="${url}?$qs&amp;signature=$sig"
echo "$signed"
</code>
</listing>

</section>

</page>
