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. Les responsabilités d’un TSP incluent généralement la gestion et la validation des certificats numériques, l’authentification et l’autorisation des utilisateurs finaux (signataires) pour l’utilisation de leurs clés privées, ainsi que la production de signatures basées sur ces ressources. Ensemble, ces capacités contribuent à garantir l’intégrité et l’authenticité des données.
TSP Proxy est un composant côté serveur de la plateforme OneSpan Sign (OSS) qui envoie les requêtes de signature vers des services externes (EC) distants. Chaque EC agit comme intermédiaire entre OSS et un TSP arbitraire. Les intégrateurs sont responsables de la mise en œuvre et de l’hébergement de leur propre EC, qui doit se conformer à une API décrite par ce document et encapsuler toutes les préoccupations associées au TSP concerné. TSP Proxy fournit également une API qui peut être utilisée par le CE invoqué pour récupérer un hachage de document et finaliser la signature produite par le TSP. Une fois qu’un service de production robuste répondant à ces exigences a été produit, il passe par un processus de test et d’approbation, après quoi sa configuration est déployée sur OneSpan Sign pour être utilisée par TSP Proxy. De cette manière, OneSpan Sign peut être intégré à un TSP de conception arbitraire.
Pour intégrer avec succès votre connecteur externe à la plateforme OneSpan Sign, vous devrez vous concentrer sur deux axes principaux. Le premier est le déchiffrement, la vérification et le traitement du jeton de requête, et le second est la récupération d’un jeton d’accès depuis le service Proxy TSP OAuth2 . Le premier représente la requête de signature transmise à la CE par le Proxy TSP, et le second est nécessaire pour interagir avec l’API Proxy TSP.
Flux de travail proxy TSP

Demande de déchiffrement et de vérification des jetons
Le jeton de requête possède une structure imbriquée. La couche externe est un jeton JSON Web Encryption (JWE) standard chiffré avec AES256. L’intégrateur reçoit un secret de 256 bits utilisé pour déchiffrer le JWE. Une fois que le CE a déchiffré la requête, il doit évaluer soigneusement l’exactitude de plusieurs revendications contenues dans le jeton JSON Web Signature (JWS) inclus et vérifier sa signature avec une clé publique RSA en ligne. Ces étapes de vérification sont essentielles et sont abordées plus en détail ci-dessous.
Voici un exemple simplifié de la façon dont un token de requête peut être déchiffré et validé en Java. Vous pouvez utiliser ce code comme référence pour implémenter des solutions dans d’autres langages de programmation.
Pour garantir que votre code fonctionne sans problème, 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>Le code 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 RequestTokenProcessingExample {
public static void main(String[] args) throws ParseException, JOSEException, JsonProcessingException {
String requestTokenStr = "the request token";
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. parse and decrypt the outer JWE layer of the request
JWEObject jweObject = JWEObject.parse(requestTokenStr);
EncryptionMethod enc = jweObject.getHeader().getEncryptionMethod();
assertEquals(EncryptionMethod.A256GCM, enc);
jweObject.decrypt(new AESDecrypter(key));
// 3. extract and process inner JWS layer of the request
SignedJWT jwt = jweObject.getPayload().toSignedJWT();
// 3.1. verify the signing algorithm
JWSAlgorithm algo = jwt.getHeader().getAlgorithm();
assertEquals(JWSAlgorithm.RS256, algo);
// 3.2. verify that the JWS has not expired
Date exp = jwt.getJWTClaimsSet().getExpirationTime();
assertTrue(exp.after(new Date()));
// 3.3. obtain the public key from the OneSpan-hosted JWKS
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();
// 3.4. verify the signature with the public key
JWSVerifier verifier = new RSASSAVerifier(publicKey);
assertTrue(jwt.verify(verifier));
// 3.5. verify the issuer and audience
String iss = jwt.getJWTClaimsSet().getIssuer();
assertEquals(jtspUrl, iss);
List<String> auds = jwt.getJWTClaimsSet().getAudience();
assertTrue(auds.contains(yourInitUrl));
// 4. extract the MiniToken claim from the request
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 du jeton de demande est obligatoire. Les éléments suivants doivent être vérifiés :
Méthode de chiffrement JWE : Prévue pour A256GCM.
Méthode de signature JWS : Prévu pour RS256.
Expiration du jeton : Vérifiez que le jeton de demande n’a pas expiré.
Signature du jeton : Vérifiez que la signature du jeton de requête est valide.
Émetteur de jeton : L’URL de base de l’environnement OneSpan Sign cible suivie de « /rs ».
Audience de jetons : L’URL du point d’entrée de l’EC telle que fournie à OneSpan doit apparaître dans cette liste.
Si l’un de ces contrôles échoue, la demande doit être rejetée.
Jeton d’accès
Le service TSP OAuth2 ne prend en charge que le type d’attribution de la Clientèle Crédential . Après que le CE a été authentifié avec succès, il recevra un jeton d’accès.
Authentification
Un EC a besoin de références pour obtenir un jeton d’accès afin d’utiliser l’API Proxy du TSP. Ces identifiants consistent en un identifiant client et un secret client, et seront fournis après que vous ayez complété votre enregistrement avec OneSpan avec succès.
Autorisation
Le CE recevra un Scope spécifique pour utiliser l’API Proxy TSP assigné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 MiniToken}' \
--data-urlencode 'scope=${assigned scope}'Cet appel ne peut être effectué avec succès qu’une seule fois par jeton de requête, car l’OTP (code d’accès à usage unique) fourni par le token de requête sera invalidé une fois le token d’accès accordé. Il est donc crucial que l’EC stocke le token d’accès jusqu’à la fin du flux de travail de signature.
Vérification
La vérification contre le jeton d’accès est optionnelle. 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 fiable. Cela peut se faire de manière similaire à la validation de JWT ci-dessus.
Fournisseur de jetons : doit être 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 des informations plus détaillées sur l’API proxy, voir https://sandbox.esignlive.com/rs/proxy.
Processus de signature
Le processus de signature réel se produit lors des étapes 4 à 6 du diagramme ci-dessus.

Pour mener à bien un processus de signature, vous devez procéder à ce qui suit, dans l’ordre suivant :
Obtenir un hachage de document
Hachage de signe du document
Injectez le hachage du document signé dans le document
Obtenir 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 de signataire : doit être au format PEM. Le certificat de signataire peut avoir un en-tête et un pied de page ou pas d’en-tête et de pied de page, mais il ne peut pas avoir un seul 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 demande ci-dessus à /proxy/api/hash. La réponse suivante sera renvoyée :
{
"sid": "string",
"lang": "string",
"result": {
"hash": "string"
}
}Le hachage du document se trouve dans le champ de hachage . Le hachage utilise RSA SHA256.
Signer un hachage de document
Cette étape doit être réalisée par une autorité de certification, et chaque autorité de certification dispose de son propre flux de travail. Référez-vous au processus d’autorité de certification de votre organisation pour signer le hachage du document.
Le proxy TSP ne prend en charge que les types de signature PKCS1, PKCS7 et CMS.
Injection d’une signature
Dès que le hachage du document est signé, celui-ci peut être envoyé au proxy TSP pour injection. Pour cela, 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é : Le 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 demande ci-dessus à /proxy/api/inject. La réponse suivante sera renvoyée :
{
"sid": "string",
"lang": "string",
"result": {
"status": "string",
"returnUrl": "string"
}
}Si le statuschamp " a une valeur autre que le succès, 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 signation.
Annexe
En-tête après décodage
{
"enc": "A256GCM",
"tag": "LqQTu07lhqZHHyzL_jOJAg",
"alg": "A256GCMKW",
"iv": "TqGb1oDQmnHNeuAW"
}JWS extrait du JWE déchiffré
<token key>En-tête après décodage
{
"kid": {"kid": "00000000-0000-0000-0000-000000000000", "alg": "RS256"}
"alg": "RS256"
}Charge utile après décodage :
{
"iss": "https://domain.com/rs",
"aud": "https://localhost:9899/ec/init",
"MiniToken": {
"sid": "EXAMPLE-SESSION-ID",
"lang": "en",
"callbackUrl": "https://domain.com/transaction/EXAMPLE-TRANSACTION-ID/sign?initialDocumentId=EXAMPLE-DOCUMENT-ID&signerId=EXAMPLE-SIGNER-ID&documentNavOperation=next",
"otp": "00000000-0000-0000-0000-000000000000",
"contents": {
"docName": "Example_Document",
"roleId": "EXAMPLE-ROLE-ID",
"docId": "EXAMPLE-DOCUMENT-ID",
"transId": "EXAMPLE-TRANSACTION-ID",
"signatureType": "PKCS1"
}
},
"exp": 1700000300,
"iat": 1700000000
}Un JWKS typique
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "00000000-0000-0000-0000-000000000000",
"n": "EXAMPLE-RSA-MODULUS-BASE64URL-NOT-A-REAL-KEY"
}
]
}En-tête après décodage
{
"kid": "00000000-0000-0000-0000-000000000000",
"alg": "RS256"
}Charge utile après décodage
{
"sub": "proxy-onespan",
"aud": "https://domain.com/rs/proxy",
"nbf": 1700000000,
"scope": [
"proxy.api"
],
"iss": "https://domain.com/rs",
"exp": 1742245326,
"iat": 1700001800,
"jti": "00000000-0000-0000-0000-000000000000"
}