Aller au contenu

Authentification

L’API ExaTrust applique deux couches indépendantes :

  1. Signature applicative — obligatoire, sur chaque requête. Format RFC 9421 + Ed25519.
  2. mTLS de transport — optionnel, activé pour les partenaires telecom et infrastructure interne (hôte partner.exatrust.cg). Géré par notre Nginx.

Les deux mécanismes sont indépendants : un appel mTLS sans signature applicative est refusé en 401, tout comme un appel signé mais sans cert client sur l’hôte mTLS.

flowchart LR
A[Backend partenaire] -->|TLS + cert client| B[Nginx ExaTrust]
B -->|HTTP + headers| C[Publication API]
C -->|Vérif Ed25519| C
C -->|Anti-replay Redis| C
C -->|Rate-limit + crédits| C
C -->|200 JSON| A

Signature Ed25519 (RFC 9421)

Ce qui doit être signé

ComposantValeurNotes
@methodGETMéthode HTTP.
@path/v1/meChemin sans query string.
@query? ou ?foo=barQuery string complète, avec le ?. Si pas de query, c’est ? (vide).

Métadonnées obligatoires

ParamètreValeurNotes
created1715680570 (Unix seconds)Doit être au plus ±5 min de l’heure serveur.
keyid"pk_orange_8a3b"Votre key_id exact, entre guillemets.
alg"ed25519"Littéral. Seul algorithme accepté.
nonce"MmJ6sjJQRWQntzqjjLPpM0l9z8Onu2sDGNQpQO5woOM="32 octets aléatoires en base64. Obligatoire en pratique : sans, deux requêtes dans la même seconde produisent la même signature et la seconde est rejetée en REPLAY_DETECTED.

Le label de la signature doit être sig1 (sinon 400).

En-têtes envoyés

Vous produisez deux en-têtes HTTP :

Signature-Input: sig1=("@method" "@path" "@query");
created=1715680570;
keyid="pk_orange_8a3b";
alg="ed25519";
nonce="MmJ6sjJQRWQntzqjjLPpM0l9z8Onu2sDGNQpQO5woOM="
Signature: sig1=:WsV/LpsuuS5RVFA0u+xIG47oOHXjC6pyLuSSWVJspt4s2A8Djvt1GuoHxh8KItt2...:

Le Signature-Input et le Signature-Params à l’intérieur de la base de signature doivent être strictement identiques.

Base de signature

Pour GET /v1/me, la base que vous signez est littéralement (chaque ligne séparée par \n) :

"@method": GET
"@path": /v1/me
"@query": ?
"@signature-params": ("@method" "@path" "@query");created=1715680570;keyid="pk_orange_8a3b";alg="ed25519";nonce="MmJ6sjJQRWQntzqjjLPpM0l9z8Onu2sDGNQpQO5woOM="

Vous signez cette chaîne en UTF-8 avec votre clé privée Ed25519, encodez en base64, et la mettez dans Signature: sig1=:<base64>: (les deux-points entourants font partie de la syntaxe).

Snippets

signer.go
import (
"crypto/ed25519"
"net/http"
"github.com/remitly-oss/httpsig-go"
"github.com/remitly-oss/httpsig-go/keyutil"
"github.com/remitly-oss/httpsig-go/sigtypes"
)
func signRequest(req *http.Request, privPath, keyID string) error {
priv, err := keyutil.ReadPrivateKeyFile(privPath)
if err != nil {
return err
}
return httpsig.Sign(req,
httpsig.SigningProfile{
Algorithm: sigtypes.Algo_ED25519,
Fields: httpsig.Fields("@method", "@path", "@query"),
Metadata: []httpsig.Metadata{
httpsig.MetaCreated,
httpsig.MetaKeyID,
httpsig.MetaAlgorithm,
httpsig.MetaNonce,
},
Nonce: httpsig.NonceRandom32,
Label: "sig1",
},
httpsig.SigningKey{
Key: priv.(ed25519.PrivateKey),
MetaKeyID: keyID,
},
)
}

Librairie : github.com/remitly-oss/httpsig-go (≥ v1.2, support RFC 9421).

Modes d’échec courants

ErreurCauseCorrection
400 The signature for label '' not foundLabel ≠ sig1Forcer label: "sig1" dans la config de signature.
401 INVALID_SIGNATURE — signature expiredcreated > 5 min écartSynchroniser NTP. Signer juste avant l’envoi.
401 REPLAY_DETECTEDSignature identique rejouéeNouveau nonce aléatoire par requête.
401 INVALID_KEYkeyid inconnu ou révoquéVérifier le key_id exact. Si révoqué, demander un nouveau.
401 INVALID_SIGNATURE — could not verifyClé publique côté serveur ≠ votre privéeRégénérer le keypair et retransmettre la publique.

mTLS (optionnel)

Pour les partenaires recevant un certificat client mTLS, l’URL de base est https://partner.exatrust.cg/v1. Notre Nginx valide votre certificat contre notre CA avant même d’évaluer la signature applicative.

Ce que vous avez reçu

FichierFormatDétention
ca.crtPEM, publicPour vérifier notre serveur.
<slug>.crtPEM, signé par notre CA, valide 12 moisVotre certificat client.
<slug>.keyPEM, secretVotre clé privée mTLS (différente de la clé Ed25519 de signature).

Stockez <slug>.key à chmod 600, dans /etc/publication-api/certs/ ou équivalent. Sur Kubernetes, montez les trois fichiers via un Secret en lecture seule.

Utilisation par langage

import "crypto/tls"
cert, _ := tls.LoadX509KeyPair("partner.crt", "partner.key")
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
},
},
}

Erreurs mTLS courantes

Erreur OpenSSLCause
tlsv13 alert certificate requiredAucun certificat client envoyé.
tlsv13 alert unknown caCertificat non signé par notre CA.
certificate has expiredRenouvellement nécessaire (12 mois).

L’erreur applicative correspondante côté Nginx :

{
"error": {
"code": "NO_CLIENT_CERT",
"message": "Certificat client mTLS requis ou invalide"
}
}

ExaTrust vous contacte 30 jours avant l’expiration de votre certificat. La procédure de rollover consiste à déployer les nouveaux fichiers en parallèle, basculer la configuration, valider avec un GET /health, puis retirer l’ancien.