kliens tanúsítványok készítése php-vel website autentikációhoz

Fórumok

sziasztok,

a cél, hogy egy webszerveren olyan kliens-tanúsitványokat tudjak létrehozni, melyek a kliensoldalon böngészőbe telepítve alkalmasak a kliens autentikálására szerveroldalon az adott website-hoz. sajnos kezdő vagyok a témában, nem kizárt, hogy akár alapvető dolgokat rosszul gondolok.

a szerveren (Ubuntu 20.04) certbot hozza létre a Let's Encrypt tanúsítványt és kulcsokat az adott vhost számára. ezzel a cert-tel és privát kulccsal próbálom létrehozni a client cert-et, de jelenleg az így generált cert-et nem fogadja el a szerver érvényes tanúsítványnak. aktuális kód-állapottól függően kétféle hibaüzenetet kapok:

Certificate Verification: Error (19): self signed certificate in certificate chain
Certificate Verification: Error (20): unable to get local issuer certificate

sajnos nem tiszta, mitől függ, mikor melyiket kapom. próbálkoztam self signed cert-tel is LE előtt, de a 19-es hiba is LE cert által generált client cert-re jött. a cert telepítve van MacOS Keychain Access-be, fel is ugrik a browser ablak, ki is tudom választani, akkor jön az ERR_BAD_SSL_CLIENT_AUTH_CERT.

a releváns kódrészletek:

$this->cert = openssl_x509_read("file://$cert_path");
$this->privkey = openssl_pkey_get_private("file://$privkey_path");

openssl_pkcs12_export($this->cert, $client_cert, $this->privkey, $pass, ["friendly_name" => $name]);

az így létrejött .p12 file-t telepítem a böngészőbe. kérdéseim:

- érdemes-e php openssl függvényekkel kísérletezni, vagy jobban járok curl-lel, esetleg exec(openssl)-lel. nem generálnék szerver oldalon cert file-t, ha nem muszáj, ezért tetszene a tisztán php megoldás.
- elképzelhető, hogy bármilyen openssl-lel generált cert self signed-nak minősül, és ez a gond?
- mit gondolok rosszul, mit csinálok rosszul, hogyan csináljam jól?
- hol találok érthető, jól használható leírást és példát erre?

köszönöm

Hozzászólások

Amit a Let's Encrypt-től kapsz tanúsítványt a weboldalad számára, az egy kiszolgálói tanúsítvány, nem CA, így azzal további tanúsítványokat nem tudsz aláírni. Ez a vonal teljesen felejtős.

Amit tehetsz, hogy csinálsz egy saját, self singned CA-t, és azzal írsz alá egy tanúsítványt a webszerver részére és a klienseknek is egyet-egyet, szintén a saját CA-val aláírva. Ekkor a Te általad készített CA-nak be kell kerülnie minden kliens CA tárolójába, hogy megbízzanak benne (az ilyen CA-val aláírt webszerverben), és a webszerverre is fel kell kerülnie a CA-nak és az általa aláírt webszerver cert-nek, hogy megbízzon a kliensek által bemutatott tanúsítványban.

Az, hogy meg tudod-e, meg lehet-e oldani, hogy a webszerver a nagyvilág felé a Let's Encrypt tanúsítványt mutassa (amit minden böngésző elfogad a Te saját CA-d nélkül is), de megbízzon a kliensek által bemutatott, saját CA által aláírt tanúsítványban, azt fejből nem tudom megmondani, utána kell olvasnod.

Mindettől függetlenül az nem jó gyakorlat, hogy a webszerver állítja ki a tanúsítványt a kliens közreműködése nélkül, mert ez nem biztosnágos. A normál eljárás, hogy a kliens generál magának privát kulcsot, azzal aláír egy CSR-t (certificate singing request), ezt a CSR-t megkapja a saját CA-d, és ha megfelel a tartalma a kritériumaidnak, akkor aláírja, és visszaküldi a certet a kliens részére valami módon. Így az igazi védelmet adó privát kulcs csak a kliensen létezik, nem utazik védtelenül a neten. 
Alternatívaként megcsinálhatod, hogy mégis Te állítod ki a kliensek cert-jét privát kulcsostól, de akkor ki kell alakítanod egy biztoságos csatornát, ahol a privát kulcs eljut a kliensig... Ha nem így teszel, akkor kb. mit sem ér az egész.

Egyébként a kérdéseid alapján keress rá a PKI kulcsszóra, és olvass el minél töb leírást, hogyan is működik egy saját PKI rendszer.

Ezen felül szerintem több hasznos választ fogsz kapni, ha leírod, mi a konkrét elérendő cél azzal, hogy a kliens is hitelesíti magát (pontosabban a futtató gép jelen leírásod alapján) a webszerver felé?

köszönöm, sokat segítettél az elmélet megértésében.

amit a kiállítás módjának biztonságosságáról írsz, azt értem, de nem szeretném nagyon megbonyolítani a folyamatot a kliens userek számára, mert jellemzően laikusok, nem tudnak privát kulcsot generálni, csr-t aláírni stb. a cél nem elsősorban az extrém biztonság megteremtése, inkább kényelmi funkciónak szánom, hogy konfigurálni lehessen a belépési módot: user - pass / cert / választható / mindkettő. azt a kockázatot bevállalom, hogy harmadik fél elcsípi a szerverről letöltött kliens cert-et, és kinyeri belőle a privát kulcsot.

azt jól értem, hogy elképzelhetőnek tartod, hogy két különböző cert-et tud használni a webszerver (apache) a https kommunikációhoz és a kliens cert autentikációhoz? mert enélkül nem megoldható, amit szeretnék, csak ha a self signed-dal futtatom a vhost-ot, azt meg nem szeretném.

https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcacertificatefile

SSLCACertificateFile

This directive sets the all-in-one file where you can assemble the Certificates of Certification Authorities (CA) whose clients you deal with. These are used for Client Authentication. Such a file is simply the concatenation of the various PEM-encoded Certificate files, in order of preference. This can be used alternatively and/or additionally to SSLCACertificatePath.

https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslcertificatechainf…

SSLCertificateChainFile

This directive sets the optional all-in-one file where you can assemble the certificates of Certification Authorities (CA) which form the certificate chain of the server certificate. This starts with the issuing CA certificate of the server certificate and can range up to the root CA certificate. Such a file is simply the concatenation of the various PEM-encoded CA Certificate files, usually in certificate chain order.

Két külön helyen adod meg a saját certed és a CA-ig vezető certeket és azt, hogy kitől fogadsz el kliens tanúsítványt.

BlackY

"Gyakran hasznos ugyanis, ha számlálni tudjuk, hányszor futott le már egy végtelenciklus." (haroldking)

Tehát ha jól értem, szeretnél client cert-eket kreálni a usereknek úgy, hogy közben az oldal zöld marad és nincs hiszti a kliens böngészőkből, igaz?

 

Let's encript-el nem próbáltam még ilyet, de sima digicert-es cert-el simán. Sőt volt olyan is, hogy applikációknak generáltunk így real time cert-eket php api-n keresztül.

 

Nincs most előttem a kód, de ha a fenti módi kell neked, akkor holnap szívesen előkeresem.

összefoglalom, hol tartok most:

- elméletben és gyakorlatban is szétválasztottam a server auth-t és a client auth-t, LE kikerült a képből (köszi ggallo)
- bekonfiguráltam külön a self signed cert-et client auth-hoz, server auth-hoz maradt a LE (köszi SzBlackY)
- továbbra sem sikerül php-ból használható root/client certet generálni, de XCA-val sikerült (köszi kila)

van tehát most egy működő megoldásom. maradék kérdések, problémák:

- hogyan tudok php-ből erre alkalmas self signed root certet és client certeket generálni?
- Chrome-ban, Brave-ben tökéletesen működik, Safariban nem, dobja az SSL kapcsolatot, talán a self signed cert miatt, nem túl bőbeszédű.

ha még ezekben tudtok segíteni, azt köszi.

1) openssl for dummies - csinalsz egy CA-t, megcsinalod a kliens certeket, alairod, odaadod a kliensnek. importalja, boldog.

2) elvileg ennek nem lenne hozza koze, mert TE a szerver oldalon csak annyit kersz, hogy "hoci, mutass egy cert- et." - erre a kliens megmondja a fingerprintjet meg h ki irta neki ala - ezt TE ellenorzod, hogy aki alairta neki abban megbizol -e (es milyen melysegben)

köszi a segítséget.

1. pontosan így járok el, a végeredmény mégsem jó. létrejön a CA cert és a privkey, de a kliens cert-ek létrehozásakor vagy a cert nem jó, vagy a privkey, vagy a kettő nem korrelál egymással. elég sokat olvastam tegnap a témában, elég különböző leírásokkal találkoztam, pl. https://www.php.net/manual/en/function.openssl-pkcs12-export.php

in order to export a private key to pkcs12 format, the input certificate must contain both private and associated public key in PEM format ,  

-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----

-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----

else this function might return the following error "openssl_pkcs12_export(): cannot get cert from parameter 1"

de ez sem segített. úgy tűnik, ennyire azért nem triviális a folyamat.

2. számomra nem ez derült ki, hanem azt kérem szerveroldalon, hogy "hoci, mutass egy cert-et, amit azzal a cert-tel írtak alá, ami nekem itt meg van adva a konfigurációmban.". amíg ilyet nem tud felmutatni, nincs további kommunikáció a klienssel, csak BAD_SSL_AUTH_CERT_ERROR, és a backend (php jelen esetben) meg sem kapja a client cert paramétereit. a Safari issue-val amúgy tele a net, pl.:

https://support.secureauth.com/hc/en-us/articles/360035348611-Client-Ce…
https://www.jeffgeerling.com/blog/2018/fixing-safaris-cant-establish-se…

a client cert érvényessége nem hosszabb 13 hónapnál, nem ez a probléma.

Vagy nem jól írod, vagy nem jól csinálod. CA certet (és hozzá tartozó privát kulcsot) csak egyetlen egyszer kell (jellemzően kézzel) csinálni, és utána azt használod az összes "normál" cert aláírásához. Nem kell CA certet generálni újra és újra. Szerintem azt kellene csinálnod, hogy CSR-t generálsz a kliens részére, és aláíratod a már meglévő self-signed CA-ddal.

openssl_pkey_new() - új privát kulcs generálása a kliensnek
openssl_csr_new() - új CSR generálása, ehhez az előbbi pkey kell
openssl_csr_sign() - új cert előállítása a CSR-ből, ehhez az előző CSR kell, meg a CA cert-je és a CA privát kulcsa

Ha ez megvan, akkor az első lépésben készült pkey-t és az utolsó lépésben előállt certet kell a kliensnek átadnod (esetleg openssl_pkcs12_export()-tal előállítasz a kettőből egy állományt).

nem jól írtam akkor, mert pontosan így csinálom. CA cert-et csak egyszer, ezekkel a fv-ekkel, ebben a sorrendben. létre is jön a cert és a pkey. aztán amikor openssl_pkcs12_export()-tal létre akarok hozni egy kliens .p12-t, akkor az export hibával megáll, hogy a cert nem cert, vagy a pkey nem pkey, vagy a kettő nem passzol egymáshoz, aszerint hogy épp mit variálok a kódban kínomban. mutatom is:

$privkey = openssl_pkey_new($this->config);
$csr = openssl_csr_new($dn, $this->privkey, $this->config);
$cert = openssl_csr_sign($csr, null, $this->privkey, $days, $this->config);

openssl_x509_export($cert, $this->cert);
openssl_pkey_export($privkey, $this->privkey);
openssl_csr_export($csr, $this->csr);

if ($path) {
    openssl_x509_export_to_file($cert, $path . "/$organization.crt");
    openssl_pkey_export_to_file($privkey, $path . "/$organization.key", $pass, $this->config);
    openssl_csr_export_to_file($csr, $path . "/$organization.csr");
}

...

openssl_pkcs12_export($this->cert, $client_cert, $this->privkey, $pass, ["friendly_name" => $name]);

egy kivétellel sikerült mindent megoldanom, a tanulságokat itthagyom, hátha hasznos másnak is:

- a kliens tanúsítványok nem úgy jönnek létre, ahogy először próbáltam, hogy a CA cert-et exportálom ki PKCS12-be, hanem csinálok egy új tanúsítványt, amit a CA-val írok alá, és azt exportálom. (szóltam, hogy kezdő vagyok!)

- php-ban egy openssl cert-nek 4 féle formátuma lehet:
1. PEM enkódolt szöveg változóban
2. PEM enkódolt szöveg file-ban
3. php resource
4. php object instance (>php 8.0)
ezeket nem szerencsés keverni, és értelemszerűen mindet másképp kell beolvasni.

- SHA1 digest algoritmusra több böngésző hibaüzenettel eldobja a kapcsolatot, de csak a szerver logból derült ki, hogy túl gyenge így a titkosítás. SHA256 már elég.

- MacOS-en a telepített kliens tanúsítványt mindenképpen trusted-ra kell állítani, addig nem ajánlja fel a böngészőnek, hogy választhassa.

és itt el is érkeztünk a megoldatlan problémához: Safari nem hajlandó tanúsítványt választani, fel se ugrik az ablak. gyanús, hogy a self signed CA cert az oka. chromium alapú böngészőkben (Chrome, Brave) hibátlanul megy, mással még nem próbáltam. tudtok erre bármilyen megoldást? illetve ha az ügyfélnek van vásárolt tanúsítványa (pl. RapidSSL), akkor kapott hozzá CA cert-et is, ami megoldhatná ezt a problémát? vagy csak EV-hez és OV-hez, az alaphoz nem kap?

Safariban biztos, hogy mukodik a vasarolt cert maganak a webservernek es a self signed ca-val alairt kliens cert. Mindenfele trusted meg egyeb allitgatasa nelkul. Openssl command line generaltam a ca-t, a kliens keyt, a csr-t es szinten openssl segitsegevel irtam ala a csr-t a ca-val. Majd a vegeredmenyt p12-re konvertalva az egeszet behuztam a keychainbe. A safari felajanlja a certvalasztast es siman mukodik. 

ez érdekes, mert nálam trusted-ra állítás nélkül egyik böngészőnek sem ajánlja fel ("meg egyéb állítgatása" nincs). meg a leírások is mind ezt állítják, hogy ez szükséges, pl.:

https://support.securly.com/hc/en-us/articles/206058318-How-to-install-…-
https://support.apple.com/hu-hu/guide/server/apd2474fbab/mac
https://docs.vmware.com/en/Horizon-FLEX/1.12/com.vmware.horizon.flex.ad…

úgyanúgy jártam el én is, ahogyan te (bár nem sok részletet írtál), az egyetlen különbség látszólag annyi, hogy én command line helyett php-ből generálok, de lévén ugyanazt az openssl lib-et használja mindkettő, nem kéne eltérő kimenetet produkálniuk.

a server auth cert-nek (SSLCertificateFile) onnantól, hogy SSLCACertificateFile-lal adom meg a client auth-hoz használandó CA cert-et, szerintem nincs köze, de nálam ez amúgy Let's Encrypt.

van tipped, mit kéne tennem, hogy működjön mindenféle trusted meg egyéb állítgatása nélkül, Safariban is? elég részletesen leírtam minden körülményt, de ha mégsem, pótolom.

MacOS Big Sur Version 11.4 (20F71), Safari Version 14.1.1 (16611.2.7.1.4), PHP Version 7.4.3, OpenSSL 1.1.1f 31 Mar 2020

Nalam a kliens tanusitvanyhoz tartozo root CA nincs benne a keychainben, csak a kliens tanusitvany es a hozza kapcsolodo kulcs. A kulcs is megvan nalad? Tehat a keychainben siman ki tudod "nyitni" es latud a certificate alatt a private keyt is? Nalam, mivel a CA nincs benne, igy az nem is trusted (emiatt irja is pirossal, hogy az XYZROOT nem trusted). Maganak a kliens certnek a "Trust" szekcion belul pedig a "When using this certificate" beallitasnal az "Use System Defaults" opcio van kivalasztva, az osszes tobbi alopcional pedg a "no value specified". A webserverem nginx, ahol sima server oldali SSL-re vonatkozo opciokon kivul, csak ezek vannak benn:

Nalam a kliens tanusitvanyhoz tartozo root CA nincs benne a keychainben, csak a kliens tanusitvany es a hozza kapcsolodo kulcs. A kulcs is megvan nalad? Tehat a keychainben siman ki tudod "nyitni" es latud a certificate alatt a private keyt is? Nalam, mivel a CA nincs benne, igy az nem is trusted (emiatt irja is pirossal, hogy az XYZROOT nem trusted). Maganak a kliens certnek a "Trust" szekcion belul pedig a "When using this certificate" beallitasnal az "Use System Defaults" opcio van kivalasztva, az osszes tobbi alopcional pedg a "no value specified". A webserverem nginx, ahol sima server oldali SSL-re vonatkozo opciokon kivul, csak ezek vannak benn:

    ssl_client_certificate /etc/ssl/client/sslauthtest-ca.crt;
    ssl_verify_client on;
    ssl_verify_depth 1;

a ca igy:

openssl genrsa -out root.key 4096
openssl req -new -subj "/C=HU/ST=Budapest/L=Budapest/O=XYZ/OU=ABCD/CN=XYZROOT" -key root.key -out root.csr
openssl x509 -in root.csr -out root.crt -req -signkey root.key -days 7300

a kliens cert pedig igy:

openssl genrsa -out client.key 4096
openssl req -new -subj "/C=HU/ST=Budapest/L=Budapest/O=XYZ/OU=ABCD/CN=client" -key client.key -out client.csr
openssl x509 -req -in client.csr -CA root.crt -CAkey root.key -CAcreateserial -out client.crt -days 365

Big Sur itt is (jelenleg mar 11.5/20G71, de pont igy megy 10.5 ota), a Safari pedig jelenleg 14.1.2 (de pont igy ment az 5.0-val is), az OpenSSL pedig macportsbol jott, jelenleg eppen 1.1.1k verzio. Most ezeket a dolgokat ujra kiprobaltam neked es minden ugyonigy mukodik.

köszi. nálam sincs a root CA a keychain-ben, csak a kliens tanúsítvány és a hozzá kapcsolódó kulcs. tehát ki tudom nyitni és látom, hozzáférést is tudok adni itt a Safari-nak, de az sem oldja meg a problémát (a többi böngésző megtalálja a kulcsot a keychain-ben, és kér hozzáférést, azokat sem kell itt külön hozzáadni).

nálam apache van:

SSLCertificateFile /etc/letsencrypt/live/domain.tld/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/domain.tld/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/domain.tld/chain.pem
SSLProtocol all -SSLv2 -SSLv3
SSLVerifyClient optional
SSLVerifyDepth 1
SSLOptions +StdEnvVars -ExportCertData
SSLCACertificateFile "/path/to/selfsignedca.crt"

arra gyanakszom, hogy valamelyik paraméter nem elég szigorú a Safarinak, csak erről semmilyen részletet nem árul el. elsőre most az tűnt fel, hogy a te kulcsod 4096 bites, az enyém 2048 bites. kipróbálom, hátha ez a gond.