Wie wir bei ZeroTech uns mit Apple Safari und Client-Zertifikaten mit Websocket angefreundet haben

Der Artikel ist nützlich für diejenigen, die:



  • weiß, was Client Cert ist, und versteht, warum er Websockets für mobile Safari benötigt;
  • möchte Webdienste für einen begrenzten Personenkreis oder nur für mich selbst veröffentlichen;
  • denkt, dass alles bereits von jemandem gemacht wurde und möchte die Welt ein wenig bequemer und sicherer machen.


Die Geschichte der Web-Sockets begann vor etwa 8 Jahren. Bisher wurden Methoden in Form von langen http-Anfragen (tatsächlich Antworten) verwendet: Der Browser des Benutzers schickte eine Anfrage an den Server und wartete darauf, dass er etwas beantwortete, nachdem die Antwort erneut verbunden und gewartet wurde. Aber dann kamen Websockets.







Vor einigen Jahren haben wir unsere eigene reine PHP-Implementierung entwickelt, die nicht weiß, wie man https-Anfragen verwendet, da dies die Datenverbindungsschicht ist. Vor nicht allzu langer Zeit haben fast alle Webserver gelernt, Anfragen über https und Support Connection zu aktualisieren: Upgrade.



In diesem Fall wurden Web-Sockets praktisch zum Standarddienst für SPA-Anwendungen, da es so bequem ist, dem Benutzer vom Server initiierte Inhalte bereitzustellen (eine Nachricht von einem anderen Benutzer senden oder eine neue Version eines Bilds, Dokuments oder einer Präsentation hochladen, die gerade von einer anderen Person bearbeitet wird). ...



Obwohl es Client ert schon lange gibt, wird es immer noch schlecht unterstützt, da es bei Versuchen, es zu umgehen, viele Probleme verursacht. Und (möglicherweise: leicht_smiling_face :), daher möchten IOS-Browser (alle außer Safari) es nicht verwenden und vom lokalen Zertifikatspeicher anfordern. Zertifikate haben viele Vorteile gegenüber Login / Pass- oder SSH-Schlüsseln oder dem Schließen der erforderlichen Ports durch die Firewall. Aber darum geht es nicht.



Unter IOS ist das Verfahren zum Installieren eines Zertifikats recht einfach (nicht ohne Einzelheiten), aber im Allgemeinen erfolgt es gemäß den Anweisungen, die im Netzwerk sehr zahlreich sind und nur für den Safari-Browser verfügbar sind. Leider weiß Safari nicht, wie Client ert für Web-Sockets verwendet wird, aber es gibt viele Anweisungen im Internet, wie ein solches Zertifikat erstellt wird. In der Praxis ist dies jedoch nicht möglich.







Um Web-Sockets zu verstehen, haben wir den folgenden Plan verwendet: Problem / Hypothese / Lösung.



Problem: Web-Sockets werden nicht unterstützt, wenn Anforderungen an Ressourcen weitergeleitet werden, die durch ein Client-Zertifikat im mobilen Safari-Browser für IOS und andere Anwendungen mit Zertifikatunterstützung geschützt sind.



Hypothesen:



  1. Es ist möglich, eine solche Ausnahme so zu konfigurieren, dass Zertifikate (in dem Wissen, dass sie nicht verfügbar sind) für Web-Sockets interner / externer Proxy-Ressourcen verwendet werden.
  2. Bei Web-Sockets können Sie mithilfe temporärer Sitzungen, die während einer regulären Browseranforderung (ohne Web-Socket) generiert werden, eine eindeutige sichere Verbindung herstellen.
  3. Temporäre Sitzungen können über einen Proxy-Webserver implementiert werden (nur integrierte Module und Funktionen).
  4. Temporäre Sitzungstoken wurden bereits als vorgefertigte Apache-Module implementiert.
  5. Temporäre Sitzungstoken können durch logisches Entwerfen der Interaktionsstruktur implementiert werden.


Sichtbarer Zustand nach der Implementierung.



Arbeitszweck: Die Verwaltung von Diensten und Infrastrukturen sollte über ein Mobiltelefon unter IOS ohne zusätzliche Programme (z. B. VPN) einheitlich und sicher erfolgen.



Zusätzliches Ziel: Zeit- und Ressourceneinsparung / Telefonverkehr (einige Dienste ohne Web-Sockets erzeugen unnötige Anforderungen) bei gleichzeitiger Beschleunigung der Bereitstellung von Inhalten im mobilen Internet.



Wie zu überprüfen?



1. Seiten öffnen:



— , 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. Oder in der Entwicklerkonsole:







Hypothesentest:



1. Es ist möglich, eine solche Ausnahme für die Verwendung von Zertifikaten (in dem Wissen, dass sie nicht vorhanden sind) für Web-Sockets interner / externer Proxy-Ressourcen zu konfigurieren.



Hier wurden 2 Lösungen gefunden:



a) Auf der Ebene



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


Zugriffsebene ändern.



Diese Methode hat die folgenden Nuancen:



  • Die Zertifikatsüberprüfung erfolgt nach einer Anforderung an die Proxy-Ressource, d. H. Nach dem Handshake nach der Anforderung. Dies bedeutet, dass der Proxy zuerst die Anforderung an den geschützten Dienst lädt und dann abschneidet. Das ist schlecht, aber nicht kritisch;
  • In der http2. Es ist noch im Entwurf, und Browser - Hersteller wissen nicht , wie es zu implementieren #info über tls1.3 http2 Post - Handshake (jetzt nicht mehr in Betrieb) Implementieren RFC 8740 „Verwenden von TLS 1.3 mit HTTP / 2“ ;
  • Es ist nicht klar, wie diese Verarbeitung vereinheitlicht werden soll.


b) Erlauben Sie ssl grundsätzlich ohne Zertifikat.



SSLVerifyClient erfordert => SSLVerifyClient optional, dies verringert jedoch die Schutzstufe des Proxyservers, da eine solche Verbindung ohne Zertifikat verarbeitet wird. Sie können den Zugriff auf Proxy-Dienste jedoch mit der folgenden Anweisung weiter verweigern:



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"


Weitere Informationen finden Sie im Artikel über ssl: Apache Server Client-Zertifikatauthentifizierung.



Beide Optionen wurden getestet. Die Option "b" wurde aus Gründen der Universalität und Kompatibilität mit dem http2-Protokoll ausgewählt.



Um die Überprüfung dieser Hypothese abzuschließen, wurden viele Experimente mit der Konfiguration durchgeführt. Die Konstruktionen wurden getestet:



if = require = rewrite



Die folgende Grundkonstruktion hat sich herausgestellt:
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>




Unter Berücksichtigung der vorhandenen Berechtigung des Inhabers des Zertifikats, jedoch mit dem fehlenden Zertifikat, mussten wir den nicht vorhandenen Eigentümer des Zertifikats in Form einer der verfügbaren SSl_PROTOCOL-Variablen (anstelle von SSL_CLIENT_S_DN_CN) hinzufügen. Weitere Informationen finden Sie in der Dokumentation:



Apache Module mod_ssl







2. Für Web-Sockets können Sie eine eindeutige sichere und geschützte Verbindung herstellen Verwenden temporärer Sitzungen, die während einer regulären Browseranforderung (kein Web-Socket) generiert werden.



Basierend auf früheren Erfahrungen müssen Sie der Konfiguration einen zusätzlichen Abschnitt hinzufügen, um temporäre Token für eine Web-Socket-Verbindung mit einer normalen Anforderung (keine Web-Socket-Anforderung) vorzubereiten.



#   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>


Tests haben gezeigt, dass es funktioniert. Es ist möglich, Cookies über einen Benutzerbrowser zu übertragen.



3. Transiente Sitzungen können mit einem Proxy-Webserver implementiert werden (nur integrierte Module und Funktionen).



Wie wir bereits herausgefunden haben, verfügt Apache über viele Kernfunktionen, mit denen Sie bedingte Konstrukte erstellen können. Wir benötigen jedoch Mittel zum Schutz unserer Informationen, während sie sich im Browser des Benutzers befinden. Daher legen wir fest, was und warum gespeichert werden soll und welche integrierten Funktionen wir verwenden werden:



  • Wir brauchen ein Token, das sich einer einfachen Dekodierung entzieht.
  • Wir brauchen ein Token, in dem Veralterung und die Möglichkeit, Veralterung auf dem Server zu überprüfen, verkabelt sind.
  • Sie benötigen ein Token, das dem Eigentümer des Zertifikats zugeordnet wird.


Dazu benötigen Sie eine Hash-Funktion, Salt und ein Datum, damit das Token abläuft. Basierend auf der Dokumentation zu Ausdrücken in Apache HTTP Server haben wir alles sofort, sha1 und% {TIME}.



Das Ergebnis ist ein solches Design:
# ,    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>




Das Ziel wurde erreicht, aber es gibt Probleme mit der Veralterung des Servers (Sie können ein Cookie von vor einem Jahr verwenden), was bedeutet, dass Token, obwohl sie für den internen Gebrauch sicher sind, für den industriellen (Massen-) Gebrauch unsicher sind.







4. Temporäre Sitzungstoken wurden bereits als vorgefertigte Apache-Module implementiert.



Von der vorherigen Iteration blieb ein wesentliches Problem bestehen - die Unfähigkeit, den Ablauf des Tokens zu kontrollieren.



Wir suchen nach einem vorgefertigten Modul, das dies tut, gemäß: apache token json two factor auth





Ja, es gibt vorgefertigte Module, aber alle sind an bestimmte Aktionen gebunden und weisen Artefakte in Form eines Sitzungsstarts und zusätzlicher Cookies auf. Das heißt, nicht für eine Weile.

Die Suche dauerte fünf Stunden, was zu keinem konkreten Ergebnis führte.



5. Temporäre Sitzungstoken können implementiert werden, indem die Struktur von Interaktionen logisch entworfen wird.



Fertige Module sind zu komplex, da wir nur einige Funktionen benötigen.



Das Problem mit dem Datum ist jedoch, dass die integrierten Funktionen von Apache kein Generieren eines Datums aus der Zukunft ermöglichen und es keine mathematische Addition / Subtraktion in den integrierten Funktionen gibt, wenn auf Veralterung geprüft wird.



Das heißt, Sie können nicht schreiben:



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


Es können nur zwei Zahlen verglichen werden.



Bei der Suche nach einer Problemumgehung für Safari wurde ein interessanter Artikel gefunden: Sichern von HomeAssistant mit Client-Zertifikaten (funktioniert mit Safari / iOS).

Er beschreibt ein Beispiel für Lua-Code für Nginx und wiederholt, wie sich herausstellte, die Logik des Teils der Konfiguration, den wir bereits implementiert haben. mit Ausnahme der Verwendung der hmac-Methode zum Anordnen von Salz zum Hashing (dies wurde in Apache nicht gefunden).



Es wurde klar, dass Lua eine Sprache mit klarer Logik ist, es ist möglich, etwas Einfaches für Apache zu tun:





Nachdem ich den Unterschied zu Nginx und Apache untersucht habe:





Und die verfügbaren Funktionen des Herstellers der Lua-Sprache:

22.1 - Datum und Uhrzeit Es wurde



eine Möglichkeit gefunden, env-Variablen in einer kleinen Lua-Datei festzulegen, um ein Datum aus der Zukunft festzulegen und mit dem aktuellen zu überprüfen.



So sieht ein einfaches Lua-Skript aus:
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




Und so funktioniert alles in Summe, mit der Optimierung der Cookie-Nummer und dem Ersetzen des Tokens, wenn die Halbzeit vor dem Ablauf des alten Cookies (Tokens) liegt:
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 




Weil LuaHookAccessChecker erst nach Zugriffsprüfungen basierend auf diesen Informationen von Nginx aktiviert wird.







Link zur Bildquelle .



Noch ein Punkt.



Im Allgemeinen spielt es keine Rolle, in welcher Reihenfolge die Anweisungen in der Apache-Konfiguration geschrieben werden (wahrscheinlich auch in Nginx), da am Ende alles nach der Reihenfolge sortiert wird, in der die Anforderung vom Benutzer übergeben wird, was dem Schema für die Verarbeitung von Lua-Skripten entspricht.



Abschluss:



Sichtbarer Status nach der Implementierung (Ziel): Das

Service- und Infrastrukturmanagement ist von einem Mobiltelefon unter IOS ohne zusätzliche Programme (VPN) verfügbar, einheitlich und sicher.



Das Ziel ist erreicht, Websockets funktionieren und haben nicht weniger Sicherheit als ein Zertifikat.






All Articles