API TransfertPro

Signature des requêtes

Introduction

Pour utiliser l'API TransfertPro, chaque requête doit être authentifiée. Les requêtes sont authentifiées en calculant une signature numérique à l'aide de la méthode HMAC-SHA512. Un client doit signer chaque requête à l'API à l'aide d'une clé privée connue uniquement du client et des serveurs TransfertPro. L'API TransfertPro utilise également des informations d'horodatage pour s'assurer que personne d'autre n'utilise l'API au nom du client.

Authentifier les requêtes aux API TransfertPro

Chaque requête aux API TransfertPro doit inclure les paramètres suivants :
apiKeyName La clé publique fournie au client qui permet aux API TransfertPro de déterminer quel client réalise la requête. Il s'agit du nom de la clé d'API.
hashkey Une signature HMAC-SHA512 de la requête, générée par le client à l'aide de sa clé privée. Le nom du paramètre s'écrit en minuscules (« hashkey ») : le serveur est sensible à la casse.
nonce Un id unique généré aléatoirement par le client pour identifier la requête.

Nom de la clé d'API

Quand une société souscrit à TransfertPro, elle a la possibilité de faire une demande d'accès aux API TransfertPro. Pour cela elle doit créer une clé d'API. TransfertPro fournit alors au client une clé publique (le nom de la clé d'API) et une clé privée (la valeur de la clé d'API). La clé privée est utilisée pour signer les requêtes et ne doit jamais être incluse dans la requête, ni être stockée dans un dépôt de code. La clé publique doit, au contraire, être incluse dans chaque requête afin que le système puisse déterminer la clé privée du client pour générer la même signature de requête afin d'autoriser ou de rejeter la requête.

hashkey

Il s'agit de la signature de la requête. La manière de signer une requête est décrite en détail dans la section suivante.

nonce

Nonce signifie "un nombre utilisé une seule fois", ce qui pour l'API signifie une chaîne de caractères aléatoire unique utilisée pour identifier une requête. Le nonce doit être une chaîne de caractères aléatoire unique d'au moins 8 caractères. Le nonce est utilisé pour prévenir des attaques de type "man in the middle", où quelqu'un essaye de répéter une requête qu'un client a déjà faite. Le serveur refuse un nonce déjà vu : chaque requête doit en porter un nouveau. Générez-le avec une source aléatoire cryptographique — par exemple un GUID (Guid.NewGuid()) ou RandomNumberGenerator. N'utilisez pas d'horloge (DateTime.Now.Ticks) : la valeur est prévisible et deux requêtes parallèles peuvent produire le même nonce.

Signature d'une requête

Quand le client crée une requête aux API TransfertPro, il doit d'abord générer une signature numérique de la requête en utilisant sa clé privée. Cette signature est incluse dans la requête ainsi que la clé publique du client. Lorsque l'API TransfertPro reçoit la requête, le système détermine la clé privée du client à partir de la clé publique fournie. Le système essaye alors de générer la même signature en utilisant la clé privée du client. Si la signature générée correspond à la signature dans la requête la requête est acceptée et l'action est effectuée. Autrement, le serveur retournera un message d'erreur.
Puisque seuls le client et le serveur ont accès à la clé privée du client, personne d'autre ne peut générer une signature de requête valide au nom du client. Pour s'assurer que la signature du client et la signature du serveur correspondent, les étapes suivantes doivent être suivies par les 2 parties.
Ce que la signature couvre. Le calcul porte uniquement sur les paramètres apiKeyName et nonce, jamais sur les paramètres fonctionnels de la requête (identifiants de fichier, de dossier ou de partage, index de chunk, etc.). La signature atteste donc de l'identité du client et, grâce au nonce à usage unique, empêche le rejeu d'une requête déjà émise — mais elle ne garantit pas l'intégrité du reste de la requête. Utilisez systématiquement HTTPS : c'est le transport, et non la signature, qui protège les paramètres contre une altération en chemin.
Concaténez les paramètres "apiKeyName" et "nonce" et leurs valeurs avec comme séparateur le caractère '|' (aussi bien entre chaque couple clé/valeur qu'entre la clé et la valeur - ne pas utiliser '&' et '='). Les couples sont classés par ordre alphabétique du nom du paramètre — ce qui donne bien "apiKeyName" puis "nonce" ; c'est la règle à appliquer si un paramètre signé s'ajoute à l'avenir. Au résultat obtenu, concaténez la valeur de la clé d'API (la clé privée) avec comme séparateur le caractère '|'. Calculez le HMAC (Hash-based Message Authentication Code) de la chaîne de caractères obtenue en utilisant la fonction de hachage SHA512. Exemple de signature d'une requête de récupération de dossier de premier niveau :' Les valeurs ci-dessous sont fictives et ne servent qu'à illustrer le calcul : elles ne correspondent à aucune clé active. Utilisez les valeurs qui vous ont été fournies. Le nonce de cet exemple historique est un nombre de tics d'horloge : conservez-le pour vérifier votre implémentation, mais générez les vôtres aléatoirement comme indiqué plus haut.
API key : 1854-SalesforceKey Secret key (exemple fictif) : 68f4bf5c-58a0-4b88-9fbc-1c4540e0e5dc nonce : 636021993082569669 Url sans la signature : https://ext.transfertpro.com/api/v5/Directory/Root?apiKeyName=1854-SalesforceKey&nonce=636021993082569669 Chaîne concaténée : apiKeyName|1854-SalesforceKey|nonce|636021993082569669|68f4bf5c-58a0-4b88-9fbc-1c4540e0e5dc Signature : 19c8497e1189ba6feb0802c337f243db5b5be9d1b7cee86267c8e32e936c4a01173f0667098316b3f77376807024e7320889d0ad146072f58c84b94745b676f5 Url avec la signature : https://ext.transfertpro.com/api/v5/Directory/Root?apiKeyName=1854-SalesforceKey&nonce=636021993082569669&hashkey=19c8497e1189ba6feb0802c337f243db5b5be9d1b7cee86267c8e32e936c4a01173f0667098316b3f77376807024e7320889d0ad146072f58c84b94745b676f5
Utilisation de la clé d'API dans les requêtes :

        protected string privateApiKeyName; // Nom de la clé d'API utilisé pour réaliser vos requêtes.
        private const char hashSeparator = '|';

        protected HttpRequestMessage createRequest(string url, string mediaType, HttpMethod method, Dictionary<string, string> requestParameters)
        {
        Dictionary<string, string> globalParameters = new Dictionary<string, string>();
        globalParameters["apiKeyName"] = privateApiKeyName.ToString();
        globalParameters["nonce"] = Guid.NewGuid().ToString(); // Source aléatoire, jamais une horloge.
        string hashKey = GetHashKey(globalParameters, privateApiKeyValue);
        globalParameters.Add("hashkey", hashKey); // Nom en minuscules : le serveur est sensible à la casse.

        var parametersToSend = globalParameters.Concat(requestParameters).ToDictionary(x => x.Key, x => x.Value);

        StringBuilder urlParameters = new StringBuilder();

        foreach (string parameterName in parametersToSend.Keys)
        {
        if (urlParameters.Length != 0)
        {
        urlParameters.Append("&");
        }

        urlParameters.Append(parameterName);
        urlParameters.Append("=");
        urlParameters.Append(parametersToSend[parameterName]);
        }

        var request = new HttpRequestMessage();
        request.RequestUri = new Uri(string.Format("{0}?{1}",url, urlParameters.ToString()));
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
        request.Method = method;
        return request;
        }

        private string GetHashKey(Dictionary<string, string> urlParameters, string keyPassValue)
        {
        StringBuilder sb = new StringBuilder();
        SortedSet<string> urlParameterNames = new SortedSet<string>(urlParameters.Keys);

        foreach (string urlParameterName in urlParameterNames)
        {
        if (sb.Length != 0)
        {
        sb.Append(hashSeparator);
        }

        sb.Append(urlParameterName);
        sb.Append(hashSeparator);
        sb.Append(urlParameters[urlParameterName]);
        }

        sb.Append(hashSeparator);
        sb.Append(keyPassValue);
        return ConvertSHA512(keyPassValue, sb.ToString());
        }

        private string ConvertSHA512(string keyPassValue, string value)
        {
        byte[] key = Encoding.UTF8.GetBytes(keyPassValue);
        byte[] valueBytes = Encoding.UTF8.GetBytes(value);
        HMACSHA512 myhmacsha512 = new HMACSHA512(key);
        byte[] hashValue = myhmacsha512.ComputeHash(valueBytes);
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < hashValue.Length; i++)
        {
        sb.Append(hashValue[i].ToString("x2"));
        }

        return sb.ToString();
        }