Guide d’intégration du proxy TSP
  • 21 Apr 2025
  • 7 Minutes à lire
  • Sombre
    Lumière
  • PDF

Guide d’intégration du proxy TSP

  • Sombre
    Lumière
  • PDF

Résumé de l’article

Un fournisseur de services de confiance (TSP) est un composant ou un service qui fournit des fonctionnalités liées à la confiance pour une utilisation dans la signature électronique. Ces fonctionnalités incluent généralement la gestion et la validation des certificats numériques, la gestion des communications sécurisées et la garantie de l’intégrité et de l’authenticité des données.

Un proxy d’API TSP est un composant côté serveur qui agit comme un intermédiaire entre une application cliente et un serveur distant. Il est souvent utilisé pour traiter les demandes et les réponses, en particulier lorsqu’il s’agit de problèmes interdomaines ou de problèmes de sécurité.

Pour créer une connexion solide entre votre application et le proxy TSP, et pour connecter votre application cliente à la plateforme OneSpan Sign, vous devez vous concentrer sur deux domaines principaux. La première consiste à déchiffrer le MiniToken et la seconde à obtenir le jeton d’accès à partir du service TSP OAuth2 .

Dans le cadre de cet article, l’application cliente fait référence à un programme ou un outil qui interagit avec une API pour envoyer des requêtes et recevoir des réponses.

De plus, pendant le processus de signature de document, vous devez effectuer deux opérations dans le flux de travail : obtenir le hachage du document et ajouter la signature.

Flux de travail du proxy TSP

Flowchart illustrating document hash verification and signature injection process with access tokens.

Décryptage et vérification MiniToken

MiniToken est envoyé à l’aide d’un chiffrement Web JSON (JWE) standard chiffré avec AES256 et signé avec RSA. L’application cliente reçoit un code secret de 256 bits pour déchiffrer le JWE et doit vérifier le jeton Web JSON (JWT) à l’aide d’une clé publique en ligne. Cette vérification est très importante.

Voici un exemple courant de décryptage et de validation d’un MiniToken en Java ; Vous pouvez utiliser ce code comme référence pour d’autres langages de programmation :

Pour vous assurer que votre code fonctionne correctement, assurez-vous d’avoir ajouté les dépendances suivantes à votre projet :

<dependencies>
    <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>8.3</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

Par exemple:

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.nimbusds.jose.*;

import com.nimbusds.jose.crypto.AESDecrypter;

import com.nimbusds.jose.crypto.RSASSAVerifier;

import com.nimbusds.jose.jwk.JWK;

import com.nimbusds.jose.jwk.JWKSet;

import com.nimbusds.jose.jwk.RSAKey;

import com.nimbusds.jwt.SignedJWT;

import com.onespan.external_connector.ec_mockca.model.MiniToken;

import net.minidev.json.JSONObject;

import org.springframework.web.reactive.function.client.WebClient;

import javax.crypto.SecretKey;

import javax.crypto.spec.SecretKeySpec;

import java.security.interfaces.RSAPublicKey;

import java.text.ParseException;

import java.util.Date;

import java.util.List;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

public class MiniTokenDecryptionExample {

public static void main(String[] args) throws ParseException, JOSEException, JsonProcessingException {

String minitokenStr = "a JWE carried the MiniToken";

String sharedSecret = "a shared secret";

String targetEnv = "https://sandbox.e-signlive.com";

String jwksUrl = targetEnv + "/rs/oauth2/jwks";

String jtspUrl = targetEnv + "/rs";

String yourInitUrl = "https://your-domain.com/ec/init";

// 1. generate a symmetric key from the shared secret

SecretKey key = new SecretKeySpec(sharedSecret.getBytes(), "AES");

// 2. decrypt the JWE into JWT

JWEObject jweObject = JWEObject.parse(minitokenStr);

// 2.1 verify the encryption algorithm

EncryptionMethod enc = jweObject.getHeader().getEncryptionMethod();

assertEquals(EncryptionMethod.A256GCM, enc);

jweObject.decrypt(new AESDecrypter(key));

SignedJWT jwt = jweObject.getPayload().toSignedJWT();

// 2.2 verify the signing algorithm

JWSAlgorithm algo = jwt.getHeader().getAlgorithm();

assertEquals(JWSAlgorithm.RS256, algo);

// 3. verify if the JWT is expired

Date exp = jwt.getJWTClaimsSet().getExpirationTime();

assertTrue(exp.after(new Date()));

// 4.1. obtain the public key

WebClient httpClient = WebClient.builder().build();

String response = httpClient.get()

.uri(jwksUrl)

.retrieve()

.bodyToMono(String.class)

.block();

JWKSet jwks = JWKSet.parse(response);

JWK jwk = jwks.getKeyByKeyId(jwt.getHeader().getKeyID());

RSAPublicKey publicKey = ((RSAKey) jwk).toRSAPublicKey();

// 4.2. verify the signature with the public key

JWSVerifier verifier = new RSASSAVerifier(publicKey);

assertTrue(jwt.verify(verifier));

// 4.3. verify the issuer and audience

String iss = jwt.getJWTClaimsSet().getIssuer();

assertEquals(jtspUrl, iss);

List<String> auds = jwt.getJWTClaimsSet().getAudience();

assertTrue(auds.contains(yourInitUrl));

// 5. decode the claim set to get the MiniToken

JSONObject mt = (JSONObject) jwt.getJWTClaimsSet().getClaim("MiniToken");

ObjectMapper mapper = new ObjectMapper();

MiniToken minitoken = mapper.readValue(mt.toJSONString(), MiniToken.class);

Map<String, String> params = minitoken.getContents();

assertFalse(params.isEmpty());

}

}

La classe MiniToken

@Data

public class MiniToken {

private String sid;

private String lang;

private String callbackUrl;

private String otp;

private Map<String, String> contents;

}

Vérification

La vérification par rapport au MiniToken JWE est obligatoire. Les éléments suivants doivent être vérifiés :

  • Méthode de cryptage JWE : Prévu pour être A256GCM.

  • Méthode de signature JWT : Prévu pour RS256.

  • Expiration du jeton : Vérifiez si le jeton a expiré.

  • Vérification du jeton : Vérifiez si le jeton est valide et fiable.

  • Émetteur du jeton : Il doit s’agir de l’URL de base de l’environnement cible, suivie de « /rs ».

  • Audience du jeton : Le point d’entrée que vous avez fourni à OneSpan devrait figurer sur la liste.

Tout échec de vérification indique qu’il s’agit d’un JWE illégal qui doit être refusé, et l’application doit être résiliée immédiatement.

Jeton d’accès

Le service TSP OAuth2 prend uniquement en charge le type d’octroi d’informations d’identification du client . Une fois que vous vous êtes authentifié avec succès, vous recevrez un jeton d’accès.

Authentification

Les applications clientes ont besoin d’informations d’identification pour obtenir un jeton d’accès afin d’utiliser l’API TSP Proxy. Un justificatif d’identité, composé d’un code d’identification et d’une clé secrète client, vous sera fourni une fois que vous aurez terminé votre inscription à OneSpan.

Autorisation

L’application cliente recevra une étendue spécifique pour utiliser l’API TSP Proxy attribuée.

Pour obtenir le jeton d’accès, effectuez une requête HTTP similaire à celle ci-dessous :

curl --location --request POST 'https://{hostname}/oauth2/token' \

--header 'Authorization: Basic ${base64(clientId:clientSecret)}' \

--header 'Content-Type: application/x-www-form-urlencoded' \

--data-urlencode 'grant_type=client_credentials' \

--data-urlencode 'otp=${OTP from Mini-Token}' \

--data-urlencode 'scope=${assigned scope}'

Cet appel ne peut être effectué qu’une seule fois au cours de la procédure de signature, car l’OTP (One Time Passcode) est créé juste avant que l’application cliente n’obtienne le MiniToken et est supprimé une fois que le jeton d’accès est accordé. Par conséquent, il est crucial pour l’application cliente de stocker le jeton d’accès pour toutes les demandes futures.

Vérification

La vérification par rapport au jeton d’accès est facultative. Les éléments suivants peuvent être vérifiés.

  • Expiration du jeton : vérifiez si le jeton a expiré.

  • Vérification du jeton : assurez-vous que le jeton est valide et digne de confiance. Cela peut être fait de la même manière que JWT est validé ci-dessus.

  • Fournisseur de jetons : il doit s’agir de l’URL de base de l’environnement cible suivie de « /rs ».

OAuth2 API

  • GET/rs/oauth2/token pour obtenir un jeton d’accès.

  • POST/rs/oauth2/jwks pour obtenir un JWKS (JSON Web Key Set) à partir duquel la clé publique peut être obtenue.

Proxy API

  • GET/rs/proxy/api/hash pour obtenir un hachage de document

  • POST/rs/proxy/api/inject pour injecter le hachage du document signé (signature) dans le document (pdf)

Pour plus d’informations sur l’API Proxy, reportez-vous à la https://sandbox.esignlive.com/rs/proxy.

Processus de signature

Le processus de signature proprement dit se produit au cours des étapes 4 à 6 du diagramme ci-dessus.

Flowchart illustrating document hash verification and signature injection process with access tokens.

Pour terminer un processus de signature, vous devez procéder comme suit :

  1. Obtenir un hachage de document

  2. Signer le hachage du document

  3. Injecter le hachage du document signé dans le document

Obtention d’un hachage de document

Pour obtenir un hachage de document, les données suivantes doivent être fournies dans le corps de la demande :

  • SID : ID de session fourni par le MiniToken ;

  • Type de signature : PKCS1, PKCS7 ou CMS ;

  • Certificat du signataire : Doit être au format PEM. Le certificat du signataire peut avoir un en-tête et un pied de page ou aucun en-tête ni pied de page, mais il ne peut pas avoir simplement un en-tête ou un simple pied de page.

Par exemple:

{
  "sid": "string",
  "lang": "string",
  "content": {
    "signatureType": "PKCS1",
    "signerCert": "string"
  }
}

Ensuite, effectuez un appel POST avec le corps de la requête ci-dessus à /proxy/api/hash. La réponse suivante vous sera retournée :

{
  "sid": "string",
  "lang": "string",
  "result": {
    "hash": "string"
  }
}

Le hachage du document se trouve dans le champ de hachage . Le hachage utilise RSA SHA256.

Signature d’un hachage de document

Cette étape doit être effectuée par une autorité de certification, et chaque autorité de certification dispose de son propre flux de travail. Reportez-vous au processus de l’autorité de certification de votre organisation pour signer le hachage du document.

Le proxy TSP prend uniquement en charge les types de signature PKCS1, PKCS7 et CMS.

Injection d’une signature

Dès que le hachage du document est signé, il peut être envoyé au proxy TSP pour injection. Pour ce faire, les données suivantes doivent être préparées :

  • Type de signature : Doit être PKCS1 ou PKCS7 et doit avoir la même valeur que l’appel précédent.

  • Hachage signé : hachage du document signé.

Par exemple:

{
  "sid": "string",
  "lang": "string",
  "content": {
    "signatureType": "PKCS1",
    "signedHash": "string"
  }
}

Ensuite, effectuez un appel POST avec le corps de la requête ci-dessus à /proxy/api/inject. La réponse suivante vous sera retournée :

{
  "sid": "string",
  "lang": "string",
  "result": {
    "status": "string",
    "returnUrl": "string"
  }
}

Si le statuschamp " a une valeur autre que success, alors cet appel doit être réessayé.

Le returnUrl champ fournit une URL de redirection pour ramener le signataire au début du processus de signature.

Appendice

Un JWE typique transportait un MiniToken

Ce qui suit montre un JWE standard qui contient un MiniToken.

eyJlbmMiOiJBMjU2R0NNIiwidGFnIjoiTHFRVHUwN2xocVpISHl6TF9qT0pBZyIsImFsZyI6IkEyNTZHQ01LVyIsIml2IjoiVHFHYjFvRFFtbkhOZXVBVyJ9.5POunw5dPvtem2MHxJsNodj_iHtqkFoL4c6cBIpy4iA.Q1d3XKUMB3WsAghA.rOE6xDKdMjYzEyQgQvcdqszGVeC-OFh74ue-MpfTlxYyZVsmUGjOo62eWOSJ_lfBDngaWycyziPeDTzUtMV63uVM-m5TanIcQC5Za3uYHGqDkgFHMyCRlZiVWDtgKCL9SDGpAkVzq6jtAKu6xeN5F-0DGZVKLMUHtKBpoJ8DaTXos275OpsD9fu_WykTJqoMAoTQy6KwjE2BurjW1eikOtoIS0CxobrpzsP_FkEeR7W0OQKC6U_VVk-5SO4Az5HWH96YtZIolYmw5TBhN0E45mkllMrgQD9ZHIWoMNOAjfS-G6SP2tmxT4Clpu0OQ3ycMtJHXqAGVIwJxJ-i_8L79H-u7j4YsVoH4_QuOK_trU7SI0M_HtrZB86qdseQUmehgrLjfa0kFGlQnIMuaRc-WLBJXT8VBdUaSJSS8xJSiesVBK6XjGR8bGNwfaMsUn86kACrQd26ftbH6d7SnYE2GLYQvSxIyUaPK5DczWTXztN1gVAuWZvDlEHZlkhbNXTrENRunnQn_Ekk4-bDC44bYerhIWk3BRNKNK7BMZL-SqppJu9pTL3ZaDMgDn4gRF6Xele1oMAPEw85XAQJIQzZwhJkpx-t6_fZiZcW1NAmcdN0jMl90FYsA9eVlJfmxIiGtYBLYxF67xUddwExUyawHYI-wDs7wfmZO2k8cVFycZFf-x68u-jIG-a_BYWBP5uGO0h8iwn8J-UUwDmTSUEE2n_2ZHF9mtakuhZV_-G9ld1ZMRPi-hVdcQUp_1lvsyVO1Efizy4mRuuoMvXsWDc-W7tM5IdPxLCT7GtgJXTeZOHdrF4zuZ4zZAOT4-zMfxDhlkXLpTCZQtI-FOFCaeGgxc4oxVrVwkt75rTY3WhRQiohyNP8LLBTcKpoiWamNo3Y6vaIFSIW9JWtUKRbU6fpWcDi-0TFB7x7x9DqXe9vHE1r73haa9Dwye_YQy_dmIfOQIK04GCbE96D8kH_LPHrNhiVlBBEkWJhe6TJ0H9K5En5oIYfhGs37uolPaW7cVxD32ZgvxEM6XVZNYs-qJSYRg3-_n5ImqYdCfzZDF7TETAfQJfE_jrTRmuNi22Fis9jPPxp9BAWUk2Hk0i5A_K4rZJEK-3N_is42dYtXc3l6A_o1lmKiX7sQ9eYB9G5VAPdD5nbo0FjHCMflWT_ymp98BPl044jiIeNKtbePQis_4vD5T6v1gbyIpURJ9YmqCm29k0G4S7HkpS9xeePsdahuv8cGRo4i9esFjr5CxXREB_nS-vyFgWAj1fD7aHbQ2ZbtpU9MAw0O0yCqUHQ8rcdUXmwktNmxa1aP5nExSTtRk6hNQP3JkgYpA5QGjGtzbqrEcJouw-K97oxz4ETOBENk25ZvI5Rv5Lqx5q5RC91xZEUnqhk1u4osXwDAYrex_YTz-o5ghtp4zBgT08iZftZ6ZEvvYPOmlHq8GNAxKcPZkGcJceIj-WExE8ugOTF9R-M3gVPmC2WmBBK5r-omlN7mCebl5CQnrvWsnOYT5VjIzh0wyhciP3zo5TUyyEKv3wL6r48NFWN3Czj0U7WZPtYJvaouci-gfnGoNhlRMxXUZvIWYqbawbsJ--FiyBDVb82xjWWqYeyfuUJ_c4yK1soY6PuJxuFagvBCdDnibvfZePcJVMWaWv8pLKna9uUNQ6hWI36-BreTQ-0t6sb0pa5TQkmbhP8ieVOciQ8e4tPV72OjU9ElTBclCY80JXaLuNr50dDIyw-7eI8l_jS.PHmauhRpPtD4-BLTmafCSw

En-tête après décodage

{
  "enc": "A256GCM",
  "tag": "LqQTu07lhqZHHyzL_jOJAg",
  "alg": "A256GCMKW",
  "iv": "TqGb1oDQmnHNeuAW"
}

Le JWT du décryptage du JWE

eyJraWQiOiJjNjQzNjk5MC05YjMzLTRiZjEtYTI0Ny1kMTNlYjMwNDMyZjMiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL29zc3EzLnJuZC5lc2lnbmxpdmUuY29tL3JzIiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6OTg5OS9lYy9pbml0IiwiTWluaVRva2VuIjp7InNpZCI6ImE0Y2E0Zjc3MGU4ZTZkMDg5ZjhmZjFiMmUxMmZlYTNiMTg3N2NjNzE0YzcyZGYzMmIyYWE0NmRjZTc0YmZkMzkiLCJsYW5nIjoiZW4iLCJjYWxsYmFja1VybCI6Imh0dHBzOi8vb3NzcTMucm5kLmVzaWdubGl2ZS5jb20vdHJhbnNhY3Rpb24vWDdhT1BQTldSQkQzb1l4ZGFyMjlaT1ZvRUtVPS9zaWduP2luaXRpYWxEb2N1bWVudElkPTI5Mzc0MWRiMzg4ZjQ1MGE1N2U4YTRjMWZmMzViZTg3ZjI3M2I1NDBhNDBmMGM5MCZzaWduZXJJZD1kOTNhdU1WUlNyOFImZG9jdW1lbnROYXZPcGVyYXRpb249bmV4dCIsIm90cCI6IjZiZWEzYmQzLWRmNTQtNDVlYy04ODZiLTYyMTM5ZTE2ZWZkOSIsImNvbnRlbnRzIjp7ImRvY05hbWUiOiJUZXN0X0RvY3VtZW50X0VtcHR5Iiwicm9sZUlkIjoibkt1dXgxNjNpQnNNIiwiZG9jSWQiOiIyOTM3NDFkYjM4OGY0NTBhNTdlOGE0YzFmZjM1YmU4N2YyNzNiNTQwYTQwZjBjOTAiLCJ0cmFuc0lkIjoiWDdhT1BQTldSQkQzb1l4ZGFyMjlaT1ZvRUtVPSIsInNpZ25hdHVyZVR5cGUiOiJQS0NTMSJ9fSwiZXhwIjoxNzQyMjQzODE3LCJpYXQiOjE3NDIyNDM1MTd9.aM54pCQ_RyEKIFxqzeY-KGvML7s-Z1Q7NXE-VqV1O1HuJU__KU_77bOEJuBw7bQbQPk_6sTSWrwPZ71V9QBxMT6KFu0zUGfTxMcN90x9gyYN1QMshQDhv8wUjvCk9KmIjLtMxaqGAPS0BD5xxpHt9a9B2D9aGhqAhqYMZx20GMlTjVLkyEi40IG5kU9s5UYSxbg9RgFGSV_55kK3B3vLQRiGV2qqAVSzmVCyB4lPKDQNViiOZyyweaaLx0Ri7d1AmYU5NTRV8DrtEcFbF4Uq-VxdWf6MZrFnlI9UFLdGcr89V4Q3zG2QPWtSftp5hyW0ldZvBD1Pt-JN_s-AItig6w

En-tête après décodage

{

"kid": "c6436990-9b33-4bf1-a247-d13eb30432f3",

"alg": "RS256"

}

Charge utile après décodage :

{
  "iss": "https://ossq3.rnd.esignlive.com/rs",
  "aud": "https://localhost:9899/ec/init",
  "MiniToken": {
    "sid": "a4ca4f770e8e6d089f8ff1b2e12fea3b1877cc714c72df32b2aa46dce74bfd39",
    "lang": "en",
    "callbackUrl": "https://ossq3.rnd.esignlive.com/transaction/X7aOPPNWRBD3oYxdar29ZOVoEKU=/sign?initialDocumentId=293741db388f450a57e8a4c1ff35be87f273b540a40f0c90&signerId=d93auMVRSr8R&documentNavOperation=next",
    "otp": "6bea3bd3-df54-45ec-886b-62139e16efd9",
    "contents": {
      "docName": "Test_Document_Empty",
      "roleId": "nKuux163iBsM",
      "docId": "293741db388f450a57e8a4c1ff35be87f273b540a40f0c90",
      "transId": "X7aOPPNWRBD3oYxdar29ZOVoEKU=","signatureType": "PKCS1"
    }
  },
  "exp": 1742243817,
  "iat": 1742243517
}

Un JWK typique

{
    "keys": [
        {
            "kty": "RSA",
            "e": "AQAB",
            "kid": "c6436990-9b33-4bf1-a247-d13eb30432f3",
            "n": "1WzJWdMmvdkah4szAo6t0VA5jEGNG6KFSMwB7ZLNAyQR2zfZ-HTYCfZGbC-BOnyodzsVoBA6MCWHak1nN098ZTrp0rWFz3WeeYRPYaJGLzmoeKzhoFyEg2WVYXzHBJfn95vYfgT1POhuCDofZnuZl5CpdOUxN6E-MOU6asZ7JtPwluqlMxeJH1CXr5hKdvy3GC3aOXwWdQw5edloGEglPmHpMGuXH667-mDPFSfGISaBn5qIqsDLXPLujim_Cgfuvm7kxAqjmxOjQPInsw6lpdOHc-EZpHLHoIzxtafPzX7TYCUmjv3Bkpe3teBJMHGp0Y_YCn97DZdLbOeyskpWdQ"
        }
    ]
}

Un jeton d’accès typique

eyJraWQiOiJjNjQzNjk5MC05YjMzLTRiZjEtYTI0Ny1kMTNlYjMwNDMyZjMiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJwcm94eS1vbmVzcGFuIiwiYXVkIjoiaHR0cHM6Ly9vc3NxMy5ybmQuZXNpZ25saXZlLmNvbS9ycy9wcm94eSIsIm5iZiI6MTc0MjI0MzUyNiwic2NvcGUiOlsicHJveHkuYXBpIl0sImlzcyI6Imh0dHBzOi8vb3NzcTMucm5kLmVzaWdubGl2ZS5jb20vcnMiLCJleHAiOjE3NDIyNDUzMjYsImlhdCI6MTc0MjI0MzUyNiwianRpIjoiMDk2YTIwMTUtYTY1ZC00YTc3LWI5ZjEtOWNkYzg2NWU2MDhlIn0.0NidkTxNEdidO4jx50sD5tnwMSX6Jhy9ggIGG34fBhr33w6OHsqKFtRAfo7xAUOysCyk3Flw8ZgsUKFDmich1rYznu5Zwm6xA-3O4d_LlDm7-dDRzR_sECrBagyllwZQVXHmvuSOha63W-PBkjixxn3UwpK93pjSyXKCBSrhFCwzx63gKsd0jPaN_DP7mDJ8jBSGbfEfCCbEkCduU1-zNCSfAFWrEuKvKzdUgV8hoOn9Dzs48vTuTih2PCPQRIEyNnpn3A2mH10D3-TIHcBsAFFNg5QK7A8LTcWu5C9aCgCaF9zKjEYspUArzzxWSfMHAJJ5E5slSnmwwQIKSdFmbQ

En-tête après décodage

{
  "kid": "cb6ffa24-bcaf-4aae-904b-a0ce979b846d",
  "alg": "RS256"
}

Charge utile après décodage

{
  "sub": "proxy-onespan",
  "aud": "https://domain.com/rs/proxy",
  "nbf": 1742243526,
  "scope": [
    "proxy.api"
  ],
  "iss": "https://domain.com/rs",
  "exp": 1742245326,
  "iat": 1742243526,
  "jti": "096a2015-a65d-4a77-b9f1-9cdc865e608e"
}



Cet article vous a-t-il été utile ?

Changing your password will log you out immediately. Use the new password to log back in.
First name must have atleast 2 characters. Numbers and special characters are not allowed.
Last name must have atleast 1 characters. Numbers and special characters are not allowed.
Enter a valid email
Enter a valid password
Your profile has been successfully updated.
ESC

Ozzy, facilitant la découverte de connaissances grâce à l’intelligence conversationnelle