PHP krypto 6 - TLS/SSL

Tags:    php sikkerhed kryptering ssl tls
Skrevet af Bruger #2695 @ 10.05.2012

Introduktion


I sidste artikel så vi på digitale certifikater, som gør udvekslingen af offentlige nøgler lidt sikrere.
Nu kan vi udveksle nøgler, generere og udveksle hemmelige sessions nøgler, kryptere, padde, signere og en hel masse andet, men det tager jo en evighed, hvis vi skal implementere alt det bare for at lave en simpel klient og server.

Krypteret internet kommunikation er så brugbart og så almindeligt, at nogen har tænkt over det hele for os og lavet Transport Layer Security (TLS), som er en efterfølger til Secure Sockets Layer (SSL).

TLS er en "wrapper" rundt om vores almindelige sockets, som er filedescriptors, som bruges til netværks kommunikation. Man kan forbinde en socket til en server og derefter læse fra den eller skrive til den. Med TLS bliver der bare udvekslet digitale certifikater som det første i forbindelsen, og så vil al data, som sendes frem og tilbage blive krypteret og signeret af afsenderen og dekrypteret og valideret af modtageren. Har man implementeret en client/server software pakke, er det altså ret nemt at lægge et TLS lag ovenpå uden at skulle ændre en hel masse.

Igen er der mange detaljer, som måske er relevante for nogen, men jeg holder mig til det vigtigste, så må I selv "lege" videre med de mere avancerede ting...eller efterspørge noget konkret til en senere artikel :-)

Echo server


Vores TLS projekt bliver at lave en echo server og klient. En echo server er ultra simpel, du forbinder til den og så skriver du til den. Alt, hvad den modtager, sender den tilbage til dig.

Men vores echo server kræver, at alle klienter har et gyldigt certifikat, som er udstedt af vores egen CA, og så identificerer den sig selv med et certifikat, som også er udstedt af vores CA. Vi starter ud med nye filer, så lad os lave en CA:
Fold kodeboks ind/udKode 


Jeg slettede den offentlige nøgle, for den ligger i certifikatet og kan også udledes af den private nøgle. Nu vi er i gang laver vi lige et certifikat til vores server:
Fold kodeboks ind/udKode 


Det næstsidste jeg gjorde var at konkatenere to filer, certifikatet og den private nøgle, til én fil. Vi kan i PHP ikke specificere filerne separat overfor TLS, så det her er altså tricket. Fordi denne fil nu indeholder både serverns certifikat og private nøgle, så behøver jeg faktisk ikke alle de andre filer, så dem sletter jeg til sidst.

Jeg laver også lige certifikater til et par klienter:
Fold kodeboks ind/udKode 


Godt, nu har jeg certifikater til to klienter og igen har jeg slettet alle unødvendige filer. Når vi har fået vores server til at køre, så vil vi prøve at forbinde ved hjælp af 'openssl' kommandoen fra Linux kommandolinjen.

Koden til serveren er følgende:
Fold kodeboks ind/udPHP kode 


Når nogen forbinder, så tjekkes hans certifikats gyldighed, hvis han har sendt et med. Hvis han ikke har, så springer vi direkte ned og lukker forbindelsen.

Vi kan tjekke, at serveren virker, ved at bruge 'openssl' programmet:


Først forsøgte jeg at forbinde til min server uden at autentificere mig med et certifikat. Det lykkedes ikke.
Derefter forsøgte jeg mig med henholdsvis 'robert.pem' og derefter med 'poul.pem', og det lykkedes. Serveren kunne udtrække brugerens navn fra certifikatet, så vi behøver ikke at sende brugernavn og password til serveren, som heller ikke behøver, at slå brugeren op i en database. Vores CA har sagt god for dem, og det stoler serveren på.

Echo klient


Serveren virker, men vi vil ikke bruge 'openssl' kommandoen, vi vil kode vores egen klient.

Den skal selvfølgelig forbinde til serveren, og vi ved, at serveren skal hedde 'echo.the-playground.dk', så det lader vi TLS tjekke for os. Når vi har en forbindelse, vil vi læse fra tastaturet og sende til serveren. Derefter forventer vi én linje fra serveren, som vi læser og skriver ud til standard out. Simpelt:
Fold kodeboks ind/udPHP kode 


Vi prøver det af:
Fold kodeboks ind/udKode 


Det virker! Prøv at sniffe forbindelsen med Wireshark. Den er som forventet ret svær at læse.

HTTPS


Da mange af os har med web udvikling at gøre vil jeg lige tage et hurtigt kig på, hvordan HTTPS (HTTP med SSL) virker. For at kunne det, skal vi først lige dække lidt af HTTP protokollen.

En web server leverer data på baggrund af en HTTP forespørgsel. Hvis HTTP klienten vil have 'index.html' filen fra 'www.udvikleren.dk', så forbinder den til 'www.udvikleren.dk' port 80 og sender følgende forespørgsel:
Fold kodeboks ind/udKode 

...plus det løse.

Men nu er det sådan, at samme server godt kan hoste flere domæners hjemmesider, så hvad nu hvis mit domæne (the-playground.dk) blev hostet på samme maskine ? Var det så 'index.html' fra udvikleren.dk eller fra the-playground.dk vi ønskede ?

Her kommer 'Host' headeren ind i billedet. Sammen med forespørgslen sender klienten en række headere, som uddyber forespørgslen:
Fold kodeboks ind/udKode 


Sådan. Nu ved serveren, at den skal sende indholdet af 'index.html' fra 'www.udvikleren.dk', og det har man kunnet siden HTTP version 1.1, hvor Host headeren blev tilføjet. Men hvordan hænger det så sammen med TLS ?

Hvis begge domæner bliver hostet på samme server, så skal vi jo også kunne få to forskellige certifikater, afhængig af, hvilket domæne, vi vil tilgå. Vil vi have 'index.html' fra 'www.udvikleren.dk' så skal vi også have 'udvikleren.dk's certifikat.

Men certifikatet sendes, inden web klienten kan indikere, hvilken host den vil have data fra. Det giver et problem. Vi skal sende certifikatet, inden vi ved, hvilket certifikat vi skal sende.

I lang tid har der ikke været anden løsning end at bruge enten forskellige servere eller forskellige porte til hvert domæne, så 'udvikleren.dk' og 'the-playground.dk' enten lå på to forskellige IP adresser eller to forskellige porte, og det er også den løsning, som virker i flest browsere i dag. Men der er forskellige andre løsninger til problemet.

Ligesom HTTP protokollen fik Host headeren har TLS fået noget tilsvarende, nemlig 'Server Name Indication' (SNI). Her fortæller klienten hvilket certifikat, den vil have. Det løser jo alle problemer, men ikke alle browsere understøtter SNI, så man risikerer, at skære nogen af sine brugere fra.

En anden løsning er wildcards. Når browseren skal afgøre, om et certifikat tilhører det domæne, som den forbandt til, så kigger den på 'Common Name' (CN) delen af distinguished name. Hvis CN er det samme som host navnet, så er det det rigtige certifikat. Men man kan bruge et wildcard, så hvis CN er '*.udvikleren.dk' så dækker det både 'www.udvikleren.dk' og 'php.udvikleren.dk'. Men man kan ikke bruge et wildcard til at dække to forskellige domæner.

En tredje løsning er "Subject Alternative Names" feltet, som kan tilføjes et certifikat. Så kan et certifikat dække flere domæner, men nu har jeg ikke noget med udvikleren.dk at gøre, og Kasper har intet med the-playground.dk at gøre, så vi vil nok ikke dele certifikat. Men det kan bruges af firmaer, som har flere domæner. F.eks. 'dell.dk', 'dell.com', 'dell.se', osv. Dette burde virke i alle browsere.

Vores echo server autentificerede brugere ved at tjekke deres certifikat. Det gør HTTPS servere typisk ikke, de bruger SSL/TLS til at identificere sig selv overfor brugerne, men de kan godt bede om et certifikat fra brugeren. Så skal certifikatet installeres i browseren (det er så forskelligt fra browser til browser hvordan man gør dette), og sådan kunne man nok have lavet NemID, men hvis man f.eks. bruger sin web server til Subversion server, så kan det give god mening at autentificere adgangen med klient certifikater.

Afslutning


Det var alt for denne gang. Som vi kan se, er det ikke så svært at sikre sine servere med TLS/SSL. Der er igen meget, vi ikke har gået i dybden med, men PHPs API er heller ikke så komplet, som man kunne have ønsket, men man kan da det mest nødvendige.

Hvad mangler vi at afdække ?
Kom med ønsker (evt. på udviklerens ønskeliste), og læg meget gerne en kommentar.


Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (0)

Du skal være logget ind for at skrive en kommentar.
t