HSTS sur IIS : oublions le http non sécurisé

Maintenant que Let's Encrypt est disponible, et fonctionne bien, nous n'avons plus d'excuse pour ne pas chiffrer nos sites webs. Pour ceux qui l'auraient loupé, la procédure pour activer https sur IIS est dispo dans un précédent article.

Aujourd'hui il est temps d'oublier http pour de bon. Nous allons voir comment configurer IIS pour rediriger correctement nos visiteurs.

Redirection HTTP vers HTTPS

Normalement, une fois configuré correctement, votre site IIS a 2 bindings : un en http, et un en https, pour le même nom d'hôte.

La première chose à faire, la plus évidente, est de faire une redirection de chaque requête http vers la même requête https. Pour faire ça, on va utiliser le module URL Rewrite.

Une fois le module installé, ajoutez un fichier de configuration web.config -ou modifiez l'existant- et ajoutez dans la section configuration/system.webServer :

<rewrite>
    <rules>
        <rule name="http to https" stopProcessing="true">
            <match url="(.*)" />
            <conditions>
                <add input="{HTTPS}" pattern="off" />
            </conditions>
            <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" />
        </rule>
    </rules>
</rewrite>

La règle est en 3 parties :

  • match : on utilise (.*) pour intercepter toutes les urls
  • conditions : on ajoute une condition, la requête ne doit pas être https (on ne veut rediriger que les requêtes http)
  • action : on définit l'url de redirection

Et ça suffit, à partir de maintenant toutes les requêtes http seront redirigées (code http 301 - redirection permanente) vers la page https correspondante. Attention, à vous maintenant de faire attention à ne plus avoir de liens internes http dans votre site, sous peine de doubler toutes les requêtes.

HSTS : pour informer le navigateur de n'utiliser que https

Cette redirection que nous avons mis en place nous permets de nous assurer que le site web ne sera accessible que en https. Mais on a un petit soucis : on a une première requête de redirection qui est faite en http. On aura donc cette requête inutile à chaque fois, si votre visiteur a mis votre site en favoris avec l'adresse http.

Pour éviter ça, il existe un moyen de dire au navigateur de se connecter systématiquement en https pour le domaine. Il suffit d'ajouter un en-tête HTTP Strict Transport Security (HSTS) à la requête.

Transport-Security: "max-age=31536000"

Ce simple en-tête permet de dire au navigateur de pas faire de requête http sur le domaine pendant x secondes (ici 31536000, soit un an), et d'appeller la requête en https à la place. Vous pouvez aussi activer HSTS pour le domaine en cours et pour les sous-domaine, en ajoutant includeSubDomains

Transport-Security: "max-age=31536000; includeSubDomains"

Pour activer cet en-tête dans IIS, il suffit de modifier le fichier web.config: pour ajouter un customHeader, toujours dans dans la section configuration/system.webServer :

<httpProtocol>
    <customHeaders>
        <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains"/>
    </customHeaders>
</httpProtocol>

Attention, même si cet en-tête est défini ici pour être envoyé systématiquement, il ne sera pris en compte par le navigateur que s'il est défini dans une requête https. C'est pour ça que la redirection qu'on a définie reste indispensable.

Maintenant tout est en place, IIS est configuré pour rediriger correctement les requêtes http vers https, et demander au navigateur de ne plus faire de requête http. Voici donc toute la configuration à ajouter à votre web.config :

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <httpProtocol>
            <customHeaders>
                <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains"/>
            </customHeaders>
        </httpProtocol>
        <rewrite>
            <rules>
                <rule name="http to https" stopProcessing="true">
                    <match url="(.*)" />
                    <conditions>
                        <add input="{HTTPS}" pattern="off" />
                    </conditions>
                    <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" />
                </rule>
            </rules>
        </rewrite>
    </system.webServer>
</configuration>

Précharger son site dans le navigateur

Ce dernier point est vraiment facultatif, mais si vous voulez vraiment ne plus recevoir la moindre requête http sur votre serveur, il faudra passer par là. Il est possible de s'enregistrer directement dans les navigateurs pour que hsts soit activé, avant même que l'utilisateur n'ait visité votre site. Pour s'ajouter à la liste des sites webs https only, directement dans le code source des navigateurs, il faudra vous enregistrer sur cette page.

Attention, pour pouvoir vous enregistrer, il y a plusieurs contraintes :

  • Vous devez avoir activé https, et avoir un certificat valide
  • Toutes les requêtes http doivent être redirigées en https
  • https doit être activé pour le domaine ET tous ses sous-domaines

Il faut aussi que l'en-tête HSTS contienne includeSubDomains et preload, pour certifier que la demande d'intégration à la liste preload vienne bien du propriétaire du site.

Transport-Security: "max-age=31536000; includeSubDomains, preload"

Voilà, tout est bon ? Si vous êtes sûr de vous, vous pouvez vous lancer. Sachez juste que l'ajout à la liste peut prendre plusieurs semaines - la validation est faite manuellement, et l'acceptation n'est pas garantie. Faites aussi très attention : vous pouvez essayer de sortir de la liste, mais ça peut aussi être très long. Si pour une raison quelconque vous ne pouvez plus faire de https sur ce domaine, votre site sera totalement inaccessible.

Création d'un helper pouvant contenir du HTML

Depuis le tout début, ASP.NET MVC fournit des helpers permettant de créer des liens vers des actions ou des routes particulières. Et depuis le tout début, ces helpers n'autorisent rien d'autre que du texte. Si on veut mettre du html, il ne faut pas utiliser ces helpers (ou faire d'immondes bidouilles)

La solution la plus simple est de créer un simple lien html, en utilisant le Helper Url

<a href="@Url.Action("Index")"><img src="image.png"/></a>

Mais malheureusement, ceci n'est pas possible lorsqu'on veut créer des liens Ajax.

Vu que MVC est open-source, j'ai décidé d'aller voir un peu comment sont fait ces helpers, et d'en créer un nouveau, sur le modèle du BeginForm, permettant d'insérer du HTML dans un lien.
Le package MvcHtmlLink - permettant de créer des liens avec du html - est disponibles sur NuGet ; le code source et une mini doc sont sur GitHub

Le fonctionnement est simple : il faut créer un object IDisposable. Dans son constructeur on génère le tag d'ouverture, et dans le Dispose le tag de fermeture. Lorsqu'on l'utilise dans une vue Razor MVC avec un using, le html écrit entre les accolades sera donc au milieu de notre tag html.

@using (Html.BeginActionLink("Index", new { Id = 1 }, new { @class = "some-css-class" }))
{
    <span>Html link</span>
}

va générer :

<a class="some-css-class" href="/Home/Index/1">Mvc RouteLink with params</a>

Pour faire ça, j'ai créé une classe HtmlLink :

public class HtmlLink : IDisposable
{
    private readonly ViewContext _viewContext;

    public HtmlLink(ViewContext viewContext, string targetUrl)
    {
        _viewContext = viewContext;         
        _viewContext.Writer.Write("<a href=\"" + targetUrl + "\">");
        // Ici le lien doit être créé correctement, les attributs html et les options Ajax
    }

    public void Dispose()
    {
        _viewContext.Writer.Write("</a>");
    }
}

Cette classe doit recevoir le ViewContext d'ASP.NET MVC, c'est ce qui va permettre d'écrire dans la page.
La façon dont est générée le lien est reprise du code d'ASP.NET, et est visible sur GitHub.

Pour pouvoir ensuite utiliser cette classe, on va créer les helpers. Pour qu'ils soient plus facile d'accès, on va les créer sous forme de méthodes d'extension de la classe HtmlHelper, et dans le même namespace, afin qu'elles apparraissent sur l'objet @Html et @Ajax des vues MVC.

namespace System.Web.Mvc.Html
{
    public static class HtmlLinkHelper
    {
        public static HtmlLink BeginActionLink(this HtmlHelper htmlHelper, string actionName, string controllerName)
        {
            string targerUrl = UrlHelper.GenerateUrl(null, actionName, controllerName, null, htmlHelper.RouteCollection, htmlHelper.ViewContext.RequestContext, true);
            return new HtmlLink(htmlHelper, targetUrl);
        }

        public static HtmlLink BeginRouteLink(this HtmlHelper htmlHelper, string routeName)
        {
            string targerUrl = UrlHelper.GenerateUrl(null, routeName, null, null, htmlHelper.RouteCollection, htmlHelper.ViewContext.RequestContext, true);
            return new HtmlLink(htmlHelper, targetUrl);
        }
    }
}

Je n'ai mis que deux exemples pour illustrer le principe, mais il faut créer toutes les différentes surcharges.

Cette fois c'est bon, notre helper peut être utilisé dans nos vues.

Si vous souhaitez créer des helpers de ce type, vous voyez donc que ce n'est pas compliqué, si vous souhaitez juste utiliser ces méthodes BeginActionLink ou BeginRouteLink, vous avez juste à référencer le package NuGet

Activer HTTPS sur IIS avec Let's Encrypt (Mode Facile)

Il y a déjà un an, j'avais fait un article expliquant comment activer HTTPS sur IIS. Il est temps aujourd'hui d'en faire une nouvelle version, car depuis quelques jours est arrivé Let's Encrypt.

Let's Encrypt est une nouvelle autorité de certification qui fournit gratuitement, et de manière totalement automatisée, des certificats pour sécuriser les sites internet. Chaque certificat est valide pour un seul sous-domaine, et pour une durée de 90 jours. Mais la création et le renouvellement étant automatisés, ce n'est pas vraiment un problème. Si par contre vous avez besoin de certificats plus avancés (wildcards, vérification de l'identité), il faudra aller voir une autre autorité de certification.

Fonctionnement : la théorie

Avant de délivrer un certificat, l'autorité de certification doit vérifier que le demandeur est bien propriétaire du nom de domaine. Cela peut se faire par l'envoi d'un e-mail, ou l'ajout d'une entrée dns. Let's Encrypt fait cette vérification automatiquement en proposant un outil à installer sur le serveur, qui va créer un fichier dans le site web, et en voyant s'il est bien accessible : si c'est bon, on sait que le domaine est valide.

Une fois le domaine validé, ce même outil va faire tout le nécessaire : créer une Cert Request, l'envoyer à Let's Encrypt, récupérer le certificat, et configurer le serveur web. Puis s'enregistrer sur le serveur pour refaire tout ça avant que le certificat n'expire 3 mois plus tard.

Cet outil a été créé par Let's Encrypt, mais il n'existe pas de version officielle pour IIS. Heureusement, il en existe des versions non-officielles, qui marchent plutot bien.

Le client Let's Encrypt pour IIS

Il existe plusieurs clients pour Windows, j'en ai choisi un simple à utiliser : letsencrypt-win-simple.

Téléchargez la dernière release, et dézippez la sur votre serveur. Attention à l'endroit où vous le mettez, il faudra laisser l'exécutable en place, car il sera appelé régulièrement pour gérer les renouvellements des certificats. Lancez letsencrypt.exe avec les droits administrateurs ; il va se connecter à votre IIS pour voir les sites actifs.

Scanning IIS 7 Site Bindings for Hosts
 1: IIS blog.adhess.net (C:\inetpub\wwwroot\redirblog)
 2: IIS blog.lacasa.fr (C:\inetpub\wwwroot\blog)
 3: IIS www.lacasa.fr (C:\inetpub\wwwroot\www)

 M: Generate a certificate manually.
 A: Get certificates for all hosts
 Q: Quit
Which host do you want to get a certificate for:

Choisissez le numéro correspondant au domaine pour lequel vous voulez créer le certificat, ou 'A' pour tous les faire. Vous pouvez aussi faire 'M' pour en créer un manuellement, mais IIS ne se mettra pas à jour.

Attention, dans la version actuelle, la validation peut ne pas fonctionner correctement à cause de la gestion des fichiers statiques dans IIS. Le message d'erreur suivant apparait :

This could be caused by IIS not being setup to handle extensionless static
files. Here's how to fix that:
1. In IIS manager goto Site/Server->Handler Mappings->View Ordered List
2. Move the StaticFile mapping above the ExtensionlessUrlHandler mappings.
(like this http://i.stack.imgur.com/nkvrL.png)"

Si vous avez ce message, vous pouvez suivre les indications pour corriger le problème, mais ça risque d'empêcher le fonctionnement des applications ASP.NET MVC. Le bug est connu et sera probablement corrigé dans les prochains jours - en attendant la version corrigée peut être téléchargée ici : [https://github.com/glacasa/letsencrypt-win-simple/releases/tag/v1.7.1]

Une fois que c'est fait, une tâche planifiée s'est enregistrée, qui va vérifier chaque jour si il est temps de renouveler un certificat. Vous n'avez plus rien à faire.

Désactivation de SSL, et activation de TLS

J'en parlais dans mon article de l'an dernier, et c'est toujours d'actualité : si vous ne l'avez pas encore fait sur votre serveur, il vous faut activer TLS et désactiver SSL, pour des raisons de sécurité. Ces protocoles ne sont pas liés à Let's Encrypt, et sont gérés par Windows. Il faut modifier certaines clés de registre, il vous suffit de taper les lignes suivantes dans dans une invite de commande avec les droits administrateurs :

REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\SSL 2.0\Server" /v Enabled /t REG_DWORD /d 0 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\SSL 2.0\Client" /v Enabled /t REG_DWORD /d 0 /f

REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\SSL 3.0\Server" /v Enabled /t REG_DWORD /d 0 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\SSL 3.0\Client" /v Enabled /t REG_DWORD /d 0 /f

REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.0\Server" /v Enabled /t REG_DWORD /d 1 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.0\Server" /v DisabledByDefault /t REG_DWORD /d 0 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.0\Client" /v Enabled /t REG_DWORD /d 1 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.0\Client" /v DisabledByDefault /t REG_DWORD /d 0 /f

REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.1\Server" /v Enabled /t REG_DWORD /d 1 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.1\Server" /v DisabledByDefault /t REG_DWORD /d 0 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.1\Client" /v Enabled /t REG_DWORD /d 1 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.1\Client" /v DisabledByDefault /t REG_DWORD /d 0 /f

REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.2\Server" /v Enabled /t REG_DWORD /d 1 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.2\Server" /v DisabledByDefault /t REG_DWORD /d 0 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.2\Client" /v Enabled /t REG_DWORD /d 1 /f
REG ADD "HKLM\System\CurrentControlSet\Control\SecurityProviders\SChannel\Protocols\TLS 1.2\Client" /v DisabledByDefault /t REG_DWORD /d 0 /f

Ceci n'a besoin d'être fait qu'une seule fois sur votre serveur - jusqu'à la prochaine faille de sécurité d'un de ces protocoles.