J'essaie d'écrire un code C# qui effectue une requête web contre un point de terminaison d'un service REST utilisé pour calculer la taxe sur les ventes dans une application web. Il s'agit d'un service tiers, sécurisé par SSL. Il y a deux environnements, UAT et production. Le code qui exécute la requête web ressemble à ceci :
...
var req = WebRequest.Create(url) as HttpWebRequest;
req.Method = "POST";
req.ContentType = "application/json";
...
using (var webresponse = req.GetResponse())
{
using (var responseStream = new StreamReader(webresponse.GetResponseStream()))
{
var respJson = responseStream.ReadToEnd();
calcResult = BuildResponse(calcRequest, respJson, consoleWriteRawReqResponse);
}
}
return calcResult;
Cela fonctionne parfaitement dans l'environnement UAT. Mais lorsque j'exécute le même code dans l'environnement de production, j'obtiens l'erreur suivante :
"Impossible de créer un canal sécurisé SSL/TLS"
I am capable d'exécuter les deux requêtes à partir de Postman sans problème, sans aucune modification particulière.
Cela m'a conduit à enquêter sur cette erreur, et j'ai trouvé de nombreux messages utiles de l'OS sur le sujet, y compris :
La demande a été interrompue : Impossible de créer un canal sécurisé SSL/TLS
Ils m'ont aidé en m'orientant dans la bonne direction, c'est-à-dire en cherchant à définir le paramètre ServicePointManager.SecurityProtocol sur une valeur différente et en utilisant ServicePointManager.ServerCertificateValidationCallback pour enquêter sur les erreurs.
Voici ce que j'ai constaté après avoir joué avec eux :
- L'appel à l'environnement UAT fonctionnera avec le paramètre par défaut de Ssl3 | Tls (par défaut pour .NET 4.5.2), alors que l'environnement de production ne le sera pas.
- L'appel de production fonctionnera UNIQUEMENT si je règle ce paramètre sur explicitement à Ssl3 .
Ce code se présente comme suit :
...
var req = WebRequest.Create(url) as HttpWebRequest;
req.Method = "POST";
req.ContentType = "application/json";
...
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CertValidationCallback);
using (var webresponse = req.GetResponse())
{
using (var responseStream = new StreamReader(webresponse.GetResponseStream()))
{
var respJson = responseStream.ReadToEnd();
calcResult = BuildResponse(calcRequest, respJson, consoleWriteRawReqResponse);
}
}
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
return calcResult;
C'est d'autant plus déroutant qu'en regardant les points d'extrémité dans un navigateur web, je peux voir qu'ils sont tous les deux sécurisés par le même certificat wildcard et qu'ils utilisent tous les deux TLS 1.0.
Je m'attendais donc à ce que le réglage de ServicePointManager.SecurityProtocol sur TLS fonctionne, mais ce n'est pas le cas.
Je veux vraiment éviter de définir ServicePointManager.SecurityProtocol explicitement à SSL3 parce que notre application est une application web et a plusieurs autres points d'intégration qui communiquent par SSL. Ils fonctionnent tous très bien et je ne veux pas affecter négativement leur fonctionnalité. Même si je définis ce paramètre juste avant l'appel, et que je le modifie juste après, je risque de rencontrer des problèmes de concurrence puisque ServicePointManager.SecurityProtocol est statique.
J'ai également enquêté sur ce sujet et je n'ai pas aimé ce que j'ai lu. Il est fait mention de l'utilisation de différents domaines d'application :
Requêtes https .NET avec différents protocoles de sécurité entre les threads
Comment utiliser SSL3 au lieu de TLS dans un HttpWebRequest particulier ?
Mais cela me semble trop complexe ou trop compliqué. Est-ce que la création d'un domaine d'application est vraiment la seule solution ? Ou est-ce que c'est quelque chose que je ne devrais tout simplement pas essayer de résoudre et plutôt en parler avec le propriétaire du service en question ? Il est très curieux pour moi que cela fonctionne avec TLS sur un environnement / serveur, mais pas sur l'autre.
EDIT J'ai fait quelques essais supplémentaires. J'ai modifié mon client pour utiliser l'approche décrite dans ce billet de blog (en utilisant un domaine d'application différent pour isoler le code qui modifie le ServicePointManager.SecurityProtocol) :
https://bitlush.com/blog/executing-code-in-a-separate-application-domain-using-c-sharp
Cela a en fait très bien fonctionné et pourrait être une solution de repli. Mais j'ai également appris que le fournisseur du service en question dispose d'un point de terminaison différent (même URL, port différent) qui est sécurisé à l'aide de TLS 1.2. Heureusement, en élargissant mon paramètre SecurityProtocol comme suit dans l'événement de démarrage de l'application global.asax.cs :
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
Je suis capable de communiquer avec le service dans tous les environnements. Cela n'affecte pas non plus mes intégrations existantes avec d'autres services (CyberSource, par exemple).
Cependant, une nouvelle question se pose à présent, mais elle est liée à la précédente. Pourquoi Est-ce que cet appel ne fonctionne que si je développe SecurityProtocolType comme indiqué ci-dessus ? Mes autres intégrations, comme CyberSource, n'exigeaient pas cela. Pourtant, celle-ci l'exige. Et elles semblent toutes être sécurisées en utilisant TLS 1.2 d'après ce que j'ai vu dans le navigateur.