Comment, chez ZeroTech, nous avons fait des amis Apple Safari et des certificats clients avec des Websockets

L'article sera utile Ă  ceux qui:



  • sait ce qu'est Client Cert et comprend pourquoi les websockets sur mobile Safari sont pour lui;
  • Je souhaite publier des services Web Ă  un cercle restreint de personnes ou uniquement Ă  moi-mĂȘme;
  • pense que tout a dĂ©jĂ  Ă©tĂ© fait par quelqu'un et aimerait rendre le monde un peu plus pratique et plus sĂ»r.


L'histoire des sockets Web a commencĂ© il y a environ 8 ans. Auparavant, les mĂ©thodes Ă©taient utilisĂ©es sous la forme de longues requĂȘtes http (en fait, des rĂ©ponses): le navigateur de l'utilisateur envoyait une requĂȘte au serveur et attendait qu'il y rĂ©ponde, aprĂšs la rĂ©ponse, il se reconnectait et attendait. Mais ensuite vinrent les sockets Web.







Il y a plusieurs annĂ©es, nous avons dĂ©veloppĂ© notre propre implĂ©mentation pure php, qui ne sait pas comment utiliser les requĂȘtes https, puisqu'il s'agit de la couche liaison de donnĂ©es. Il n'y a pas si longtemps, presque tous les serveurs Web ont appris Ă  faire des requĂȘtes proxy via https et Ă  prendre en charge la connexion: mise Ă  niveau.



Lorsque cela s'est produit, les sockets Web sont devenues presque le service par défaut pour les applications SPA, car il est pratique de fournir à l'utilisateur du contenu à l'initiative du serveur (envoyer un message d'un autre utilisateur ou télécharger une nouvelle version d'une image, d'un document, d'une présentation que quelqu'un d'autre est en train de modifier maintenant) ...



Bien que Client ert existe depuis longtemps, il reste mal pris en charge, car il crĂ©e de nombreux problĂšmes avec les tentatives de contournement. Et (peut-ĂȘtre: lĂ©gĂšrement_smiling_face :) donc les navigateurs IOS (tous sauf Safari) ne veulent pas l'utiliser et demandent au magasin de certificats local. Les certificats prĂ©sentent de nombreux avantages par rapport aux clĂ©s de connexion / passe ou ssh ou au pare-feu des ports appropriĂ©s. Mais ce n'est pas le but.



Sur IOS, la procédure d'installation d'un certificat est assez simple (non sans spécificités), mais en général elle se fait selon les instructions, trÚs nombreuses sur le réseau et disponibles uniquement pour le navigateur Safari. Malheureusement, Safari ne sait pas comment utiliser Client Cert pour les sockets Web, mais il existe de nombreuses instructions sur Internet pour créer un tel certificat, mais en pratique, cela est impossible.







Pour comprendre les websockets, nous avons utilisé le schéma suivant: problÚme / hypothÚse / solution.



ProblÚme: il n'y a pas de prise en charge des sockets Web lors de la transmission par proxy de demandes à des ressources protégées par un certificat client sur le navigateur Safari mobile pour IOS et d'autres applications qui ont inclus la prise en charge des certificats.



HypothĂšses:



  1. Il est possible de configurer une telle exception pour utiliser des certificats (sachant qu'ils ne seront pas disponibles) sur les sockets Web des ressources proxy internes / externes.
  2. Pour les sockets Web, vous pouvez établir une connexion sûre et sécurisée unique à l'aide de sessions temporaires générées par une demande de navigateur normale (sans socket Web).
  3. Les sessions transitoires peuvent ĂȘtre implĂ©mentĂ©es Ă  l'aide d'un serveur Web proxy unique (modules et fonctions intĂ©grĂ©s uniquement).
  4. Des sessions de jetons temporaires ont dĂ©jĂ  Ă©tĂ© implĂ©mentĂ©es en tant que modules Apache prĂȘts Ă  l'emploi.
  5. Les jetons de session temporaires peuvent ĂȘtre implĂ©mentĂ©s en concevant logiquement la structure d'interaction.


État visible aprùs la mise en Ɠuvre.



Objectif du travail: la gestion des services et des infrastructures doit ĂȘtre accessible depuis un tĂ©lĂ©phone mobile vers IOS sans programmes supplĂ©mentaires (tels que VPN), unifiĂ©e et sĂ©curisĂ©e.



Un objectif supplĂ©mentaire: gagner du temps et des ressources / trafic tĂ©lĂ©phonique (certains services sans web sockets gĂ©nĂšrent des requĂȘtes inutiles) avec une diffusion plus rapide du contenu sur l'Internet mobile.



Comment vérifier?



1. Pages d'ouverture:



— , https://teamcity.yourdomain.com    Safari (    ) —     -.
— , https://teamcity.yourdomain.com/admin/admin.html?item=diagnostics&tab=webS
—  ping/pong.
— , https://rancher.yourdomain.com/p/c-84bnv:p-vkszd/workload/deployment:danidb:ph
-> viewlogs —   .


2. Ou dans la console du développeur:







Test d'hypothĂšses:



1. Il est possible de configurer une telle exception pour utiliser des certificats (sachant qu'ils ne seront pas disponibles) sur des sockets web de ressources proxy internes / externes.



2 solutions ont été trouvées ici:



a) Au niveau



<Location sock*> SSLVerifyClient optional </Location>
<Location /> SSLVerifyClient require </Location>


changer le niveau d'accĂšs.



Cette méthode présente les nuances suivantes:



  • Le certificat est vĂ©rifiĂ© aprĂšs une demande Ă  la ressource mandatĂ©e, c'est-Ă -dire aprĂšs la prise de contact de la demande. Cela signifie que le proxy chargera d'abord, puis coupera la demande au service protĂ©gĂ©. C'est mauvais, mais pas critique;
  • Dans le http2. Il est encore Ă  l'Ă©tat de brouillon et les fournisseurs de navigateurs ne savent pas comment l'implĂ©menter #info sur tls1.3 http2 post handshake (ne fonctionne pas maintenant) ImplĂ©mentez RFC 8740 "Utilisation de TLS 1.3 avec HTTP / 2" ;
  • Il n'est pas clair comment unifier ce traitement.


b) Au niveau de base, activez ssl sans certificat.



SSLVerifyClient requiert => SSLVerifyClient en option, mais cela réduit le niveau de protection du serveur proxy, car une telle connexion sera traitée sans certificat. Cependant, vous pouvez en outre refuser l'accÚs aux services proxy avec la directive suivante:



RewriteEngine        on
RewriteCond     %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteRule     .? - [F]
ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"


Pour plus d'informations, consultez l'article sur ssl: authentification du certificat client du serveur Apache Les



deux options ont été testées, l'option "b" a été choisie pour l'universalité et la compatibilité avec le protocole http2.



Pour terminer la vérification de cette hypothÚse, il a fallu beaucoup d'expériences avec la configuration, les constructions ont été testées:



if = require = rewrite



Nous avons la construction de base suivante:
SSLVerifyClient optional
RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule     .? - [F]
#ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"

#websocket for safari without cert auth
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
...
    #         
    SSLUserName SSl_PROTOCOL
</If>
</If>




En tenant compte de l'autorisation existante du propriétaire du certificat, mais avec le certificat manquant, nous avons dû ajouter le propriétaire inexistant du certificat sous la forme de l'une des variables SSl_PROTOCOL disponibles (au lieu de SSL_CLIENT_S_DN_CN), pour plus de détails voir la documentation:



Module Apache mod_ssl







2. Pour les sockets web, vous pouvez faire une connexion unique sĂ©curisĂ©e et protĂ©gĂ©e avec en utilisant des sessions temporaires qui sont gĂ©nĂ©rĂ©es lors d'une requĂȘte de navigateur normale (pas de socket Web).



Sur la base de l'expérience précédente, vous devez ajouter une section supplémentaire à la configuration, de sorte que lors d'une demande réguliÚre (pas de socket Web), des jetons temporaires pour les connexions de socket Web soient préparés.



#   ookie   
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
Header set Set-Cookie "websocket-allowed=true; path=/; Max-Age=100"
</If>
</If>

# Cookie   - 
<source lang="javascript">
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
#check for exists cookie

#get and check
SetEnvIf Cookie "websocket-allowed=(.*)" env-var-name=$1

#or rewrite rule
RewriteCond %{HTTP_COOKIE} !^.*mycookie.*$

#or if
<If "%{HTTP_COOKIE} =~ /(^|; )cookie-name\s*=\s*some-val(;|$)/ >
</If

</If>
</If>


La vérification a montré que cela fonctionne. Il est possible de transmettre un cookie au navigateur de l'utilisateur.



3. Les sessions transitoires peuvent ĂȘtre implĂ©mentĂ©es Ă  l'aide d'un serveur Web proxy (uniquement les modules et fonctions intĂ©grĂ©s).



Comme nous l'avons découvert plus tÎt, Apache possÚde de nombreuses fonctionnalités de base qui vous permettent de créer des conditions. Cependant, nous avons besoin d'un moyen de protéger nos informations lorsqu'elles se trouvent dans le navigateur de l'utilisateur, nous établissons donc quoi et pourquoi stocker, et quelles fonctions intégrées nous utiliserons:



  • Nous avons besoin d'un jeton qui dĂ©fie le simple dĂ©codage.
  • Nous avons besoin d'un jeton dans lequel l'obsolescence et la possibilitĂ© de vĂ©rifier l'obsolescence sur le serveur sont cĂąblĂ©es.
  • Vous avez besoin d'un jeton qui sera associĂ© au propriĂ©taire du certificat.


Pour ce faire, vous avez besoin d'une fonction de hachage, d'un sel et d'une date pour que le jeton expire. Sur la base des expressions dans la documentation Apache HTTP Server , nous avons tout sorti de la boĂźte sha1 et% {TIME}.



Le résultat est la construction suivante:
# ,    websocket
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
    SetEnvIf Cookie "zt-cert-sha1=([^;]+)" zt-cert-sha1=$1
    SetEnvIf Cookie "zt-cert-uid=([^;]+)" zt-cert-uid=$1
    SetEnvIf Cookie "zt-cert-date=([^;]+)" zt-cert-date=$1

#     ,   env-    ,         (  ,   ,     )
    <RequireAll>
        Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1}
        Require expr %{env:zt-cert-sha1} =~ /^.{40}$/
    </RequireAll>
</If>
</If>

# ,   websocket
<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
    SetEnvIf Cookie "zt-cert-sha1=([^;]+)" HAVE_zt-cert-sha1=$1

    SetEnv zt_cert "path=/; HttpOnly;Secure;SameSite=Strict"
#  ,   
    Header add Set-Cookie "expr=zt-cert-sha1=%{sha1:salt1%{TIME}salt3%{SSL_CLIENT_S_DN_CN}salt2};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
    Header add Set-Cookie "expr=zt-cert-uid=%{SSL_CLIENT_S_DN_CN};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
    Header add Set-Cookie "expr=zt-cert-date=%{TIME};%{env:zt_cert}" env=!HAVE_zt-cert-sha1
</If>
</If>




L'objectif a été atteint, mais il y a des problÚmes d'obsolescence du serveur (vous pouvez utiliser un cookie il y a un an), ce qui signifie des jetons, bien que sûrs pour un usage interne, mais dangereux pour les industriels (de masse).







4. Les jetons de session temporaires ont dĂ©jĂ  Ă©tĂ© implĂ©mentĂ©s en tant que modules Apache prĂȘts Ă  l'emploi.



De l'itération précédente, un problÚme important restait - l'incapacité de contrÎler l'expiration du jeton.



Nous recherchons un module prĂȘt Ă  l'emploi qui le fait, selon: apache token json two factor auth





Oui, il existe des modules prĂȘts Ă  l'emploi, mais tous sont liĂ©s Ă  des actions spĂ©cifiques et ont des artefacts sous la forme d'un dĂ©marrage de session et de cookies supplĂ©mentaires. Autrement dit, pas pendant un moment.

Il nous a fallu cinq heures pour fouiller, ce qui n'a donné aucun résultat concret.



5. Des sessions de jetons temporaires peuvent ĂȘtre implĂ©mentĂ©es en concevant logiquement la structure d'interaction.



Les modules prĂȘts Ă  l'emploi sont trop compliquĂ©s car nous n'avons besoin que de quelques fonctions.



Cela dit, le problÚme avec la date est que les fonctions intégrées d'Apache ne permettent pas de générer une date à partir du futur, et il n'y a pas d'addition / soustraction mathématique lors de la vérification de l'obsolescence dans les fonctions intégrées.



Autrement dit, vous ne pouvez pas Ă©crire:



(%{env:zt-cert-date} + 30) > %{DATE}


Seuls deux nombres peuvent ĂȘtre comparĂ©s.



Lors de la recherche d'une solution de contournement pour le problÚme de Safari, un article intéressant a été trouvé: Sécurisation de HomeAssistant avec des certificats clients (fonctionne avec Safari / iOS)

Il décrit un exemple de code Lua pour Nginx, et qui, il s'est avéré, répÚte trÚs bien la logique de cette partie de la configuration que nous avons déjà implémentée précédemment. à l'exception de l'utilisation de la méthode hmac pour organiser le sel pour le hachage (cela n'a pas été trouvé dans Apache).



Il est devenu clair que Lua est un langage avec une logique claire, il est possible de faire quelque chose de simple pour Apache:





AprÚs avoir examiné la différence avec Nginx et Apache:





Et les fonctions disponibles chez le fabricant du langage Lua:

22.1 - Date et heure



Trouver un moyen de définir des variables d'environnement dans un petit fichier Lua afin de fixer une date du futur pour vérifier avec l'actuel.



Voici Ă  quoi ressemble un simple script Lua:
require 'apache2'

function handler(r)
    local fmt = '%Y%m%d%H%M%S'
    local timeout = 3600 -- 1 hour

    r.notes['zt-cert-timeout'] = timeout
    r.notes['zt-cert-date-next'] = os.date(fmt,os.time()+timeout)
    r.notes['zt-cert-date-halfnext'] = os.date(fmt,os.time()+ (timeout/2))
    r.notes['zt-cert-date-now'] = os.date(fmt,os.time())

    return apache2.OK
end




Et c'est ainsi que tout fonctionne en somme, avec l'optimisation du numéro de Cookie et le remplacement du token lorsque la mi-temps vient avant l'expiration de l'ancien Cookie (token):
SSLVerifyClient optional

#LuaScope thread
#generate event variables zt-cert-date-next
LuaHookAccessChecker /usr/local/etc/apache24/sslincludes/websocket_token.lua handler early

#   - ,  webscoket
RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !=SUCCESS
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule     .? - [F]
#ErrorDocument 403 "You need a client side certificate issued by CAcert to access this site"

#websocket for safari without certauth
<If "%{SSL:SSL_CLIENT_VERIFY} != 'SUCCESS'">
<If "%{HTTP:Upgrade} = 'websocket'">
    SetEnvIf Cookie "zt-cert=([^,;]+),([^,;]+),[^,;]+,([^,;]+)" zt-cert-sha1=$1 zt-cert-date=$2 zt-cert-uid=$3

    <RequireAll>
        Require expr %{sha1:salt1%{env:zt-cert-date}salt3%{env:zt-cert-uid}salt2} == %{env:zt-cert-sha1}
        Require expr %{env:zt-cert-sha1} =~ /^.{40}$/
        Require expr %{env:zt-cert-date} -ge %{env:zt-cert-date-now}
    </RequireAll>
   
    #         
    SSLUserName SSl_PROTOCOL
    SSLOptions -FakeBasicAuth
</If>
</If>

<If "%{SSL:SSL_CLIENT_VERIFY} = 'SUCCESS'">
<If "%{HTTP:Upgrade} != 'websocket'">
    SetEnvIf Cookie "zt-cert=([^,;]+),[^,;]+,([^,;]+)" HAVE_zt-cert-sha1=$1 HAVE_zt-cert-date-halfnow=$2
    SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1

    Define zt-cert "path=/;Max-Age=%{env:zt-cert-timeout};HttpOnly;Secure;SameSite=Strict"
    Define dates_user "%{env:zt-cert-date-next},%{env:zt-cert-date-halfnext},%{SSL_CLIENT_S_DN_CN}"
    Header set Set-Cookie "expr=zt-cert=%{sha1:salt1%{env:zt-cert-date-next}sal3%{SSL_CLIENT_S_DN_CN}salt2},${dates_user};${zt-cert}" env=!HAVE_zt-cert-sha1-found
</If>
</If>

SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge %{TIME} && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1
,

    
SetEnvIfExpr "env('HAVE_zt-cert-date-halfnow') -ge  env('zt-cert-date-now') && env('HAVE_zt-cert-sha1')=~/.{40}/" HAVE_zt-cert-sha1-found=1 




Parce que LuaHookAccessChecker ne sera activé qu'aprÚs des contrÎles d'accÚs basés sur ces informations de Nginx.







Lien vers la source de l' image .



Encore un point.



En gĂ©nĂ©ral, peu importe dans quel ordre les directives sont Ă©crites dans la configuration Apache (probablement Nginx Ă©galement), car Ă  la fin, tout sera triĂ© en fonction de l'ordre dans lequel la requĂȘte de l'utilisateur passe, ce qui correspond au schĂ©ma de traitement des scripts Lua.



AchĂšvement:



Ă©tat visible aprĂšs mise en Ɠuvre (objectif): la

gestion des services et de l'infrastructure est disponible depuis un téléphone mobile sur IOS sans programmes supplémentaires (VPN), unifiée et sécurisée.



L'objectif est atteint, les websockets fonctionnent et n'ont pas moins de sécurité qu'un certificat.






All Articles