Authentifizierung

Jede Anfrage an unser REST-API muss authentifiziert werden, um unserem System Ihre Identität zu bestätigen. Der Ablauf der Authentifizierung lehnt sich eng an die Authentifizierung von REST-Anfragen in Amazon S3 an.

Der Authentication-Header

Anfragen an unser REST-API verwenden den standard HTTP-Header Authorization um Authentifizierungsinformationen zu transportieren. Der Authentifizierungsheader hat die folgenden Form:

Authorization: COB CobaiWebServicesAccessKeyID:Signatur

Die Access-Key-Id und Ihren geheimen Schlüssel können Sie im jeweiligen Zielsystem selbst erzeugen. Über die Access-Key-Id kann das System erkennen, wer die Anfrage signiert hat und welcher geheime Schlüssel verwendet wurde um die Anfrage zu signieren.

Die Signatur ist eine Auswahl von Elementen aus der REST-Anfrage, die mit RFC 2104 HMAC-SHA1 verschlüsselt werden, die Signatur ändert sich daher mit jeder Anfrage. Bei einer eingehenden REST-Anfrage erzeugt das System die selbe Auswahl der Anfrageelemente und verschlüsselt sie mit dem geheimen Schlüssel der zur übermittelten Access-Key-Id gehört. Stimmen die übermittelte Signatur und die vom System erzeugte Signatur überein gilt die Anfrage als authentifiziert und wird mit der Authorisierung des Schlüsselinhabers ausgeführt.

Der Authorisation-Header wird wie folgt erzeugt:

Authorization = "COB" + " " + CobaiWebServiceAccessKeyId + ":" + Signatur;

Signatur = Base64( HMAC-SHA1 ( UTF8-Encoding-von ( IhrGeheimerSchüssel, ZuSignierenderString ) ) );

ZuSignierenderString = HTTPVerb + "\n" +
                       Content-MD5 + "\n" +
                       Content-Type + "\n" +
                       Date + "\n" +
                       Header-Normalform = <siehe weiter unten> +
                       URL-Normalform = <siehe weiter unten>

In Java würden Sie dazu etwas in dieser Art machen, wobei RequestDescription das Erzeugen der Normalform kapselt:

package net.lacho.remoting.api;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;


public class RequestSignature {

    private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
    private String signature;

    public RequestSignature(final String privateKey, final RequestDescription requestDescription)  {
        try {
            SecretKeySpec signingKey = new SecretKeySpec(privateKey.getBytes(), HMAC_SHA1_ALGORITHM);
            Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
            mac.init(signingKey);
            byte[]rawHmac = mac.doFinal(requestDescription.getBytes());
            signature = new Base64(1000, new byte[] {}).encodeToString(rawHmac);   
        }
        catch (Exception e) {
            signature = "";
        }

    }

    @Override
    public String toString() {
        return signature;
    }
}

Erzeugung der Anfrage-Normalform

Wie weiter oben ausgeführt erzeugt das System bei einer Anfrage eine Signatur für die Anfrage und vergleicht sie mit der übermittelten Signatur. Sie müssen daher die Signatur mit der selben Methode erzeugen wie das System. Der zu signierenden String heißt auch Anfrage-Normalform. Um die Anfrage-Normalform erzeugen zu können müssen sie die URL-Normalform und die Header-Normalform erzeugen.

Header-Normalform

Um die Header-Normalform zu erzeugen verwenden Sie alle HTTP-Header aus Ihrer Anfrage die mit 'x-cob-' beginnen und wenden den folgenden Prozess darauf an:

  1. Wandeln Sie alle Headernamen in Kleinbuchstaben um, aus 'X-Cob-Date' wird also 'x-cob-date'.
  2. Sortieren Sie die Headerfelder lexikografisch nach den Headernamen.
  3. Kombinieren Sie mehrere Header mit identischem Namen in einen neuen Header, wie in RFC 2616, Abschnitt 4.2 beschrieben. Zwischen den einzelnen kommagetrennten Werten ist kein Whitespace erlaubt. Zum Bespiel wird aus den beiden Headern x-cob-username:user1 und x-cob-username:user2 der Header x-cob-username:user1,user2.
  4. Wandeln Sie Header deren Werte über mehrere Zeilen gehen in eine einzeilige Form um (siehe RFC 2616, Abschnitt 4.2).
  5. Entfernen Sie eventuell vorhanden Leerraum um den Doppelpunkt in den Headern.
  6. Fügen Sie an jeden Header ein Newline an (U+000A). Verbinden Sie anschließend alle Header zu einem einzelnen String. Dieser String ist die Header-Normalform.

URL-Normalform

Erzeugung der URL-Normalform:

  1. Entfernen Sie den Domänenteil (FQDN) aus der Anfrage-URL.
  2. Entfernen Sie den Query-String, sofern vorhanden, inklusive des "?" aus der Anfrage URL.
  3. URL-encoden Sie alle Meta-Characters im verbliebenen String. Verwenden Sie dabei UTF-8.

Damit entsteht zum Beispiel aus

api.cobai.com/v2/orders/pending?sort=desc

die Normalform

/v2/orders/pending

Benannte Headerelemente und Headerelemente an festen Positionen

Die ersten vier Headerelemente unterscheiden sich von den anderen dadurch das sie an einer festen Position stehen. Von ihnen wird daher in den zu signierenden String nur der Wert, nicht aber der Headername aufgenommen.

Wenn einer dieser Header in Ihrer Anfrage nicht vorkommt (manche sind optional, z.B. Content-Type bei einer PUT-Anfrage) verwenden sie an der entsprechenden Stelle den leeren String ("").

Wenn Ihre Anfrage einen x-cob-date Header enthält müssen Sie bei der Erzeugung der Header-Normalform den Wert des Date Headers weglassen, also den leeren String verwenden.

Anforderungen an Zeitstempel

Für jede Anfrage ist ein gültiger Zeitstempel, entweder als HTTP Date Header oder alternativ als x-cob-date Header notwendig. Der Zeitstempel darf maximal 15 Minuten (vor oder zurück) von unserer aktuellen Systemzeit abweichen wenn die Anfrage abgesendet wird um Replay-Angriffe zu erschweren. Anfragen die ausserhalb der erlaubten Zeitfensters eingehen werden mit einem RequestTimeTooSkewed-Fehler abgelehnt.

Wenn Ihre HTTP-Clientlibrary das Setzen des Date Headers nicht erlaubt können Sie alternativ den x-cob-date Header verwenden. Das Datum muss in einem der von RFC 2616, Abschnitt 3.3 vorgegebenen Formate übergeben werden. Wenn Sie einen x-cob-date Header übergeben ignoriert das System bei der erzeugung des zu signierenden Strings den Date Header. Denken Sie also in diesem Fall daran bei der Erzeugung Ihres zu signierenden Strings für den Date Header den leeren String ("") zu verwenden.

Sie sollten Ihre lokale Uhrzeit mit einer Normalzeitquelle abgleichen, Quellen finden Sie hier. Alternativ können Sie auch die API-Methode 'ping' verwenden, die Ihnen die Systemzeit des API-Servers bereitstellt.

Signaturprobleme

Wenn die Authentifizierung fehlschlägt antwortet das System mit einem SignatureDoesNotMatch-Fehler (ein XML-Dokument). Dieses Dokument soll Ihnen helfen mögliche Fehlergründe zu erkennen und enthält dazu unter anderem im Element requestDescription den kompletten zu signierenden String basierend auf der von Ihnen übermittelten Anfrage.