- 21 Apr 2025
- 7 Minutes à lire
- Impression
- SombreLumière
- PDF
Guide d’intégration du proxy TSP
- Mis à jour le 21 Apr 2025
- 7 Minutes à lire
- Impression
- SombreLumière
- PDF
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
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.
Pour terminer un processus de signature, vous devez procéder comme suit :
Obtenir un hachage de document
Signer le hachage du document
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 status
champ " 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"
}