Bu örnekte X.509 Public Key Infrastructure (PKI) standardıyla istemci isteğini doğrulayacağız. Bu kimlik doğrulama standardının Basic, Digest, JWT, OAuth vb. gibi diğer türlere göre kullanılmasının temel avantajı, istemcinin sunucuya bir kullanıcı adı veya şifre göndermemesidir. Bu, man-in-the-middle attack (MITM) saldırısını (saldırgan gizlice iletiyi gizler ve muhtemelen iletişimi değiştirir) zorlaştırır. İstemci, kişiselleştirilmiş bir sertifika ve sunucuya bir anahtar gönderir. Genel olarak giriş kimlik bilgilerini çalmak bir sertifika çalmaktan daha kolaydır.


Sistem


Sistem Debian 8.10'a (Jessie) dayanıyor ve PHP 7.2.6 ile birlikte Nginx Server 1.6.2 kullanıyor.


Adımlar


  1. PHP API oluşturma ve Nginx sunucusunu yapılandırma: İstemci, herhangi bir kimlik doğrulaması yapmadan tüketebileceği şekilde sahte bir PHP API'sı kurun.

  2. Sunucu ve istemci sertifikaları oluşturma: Hem sunucu hem de istemci için gerekli sertifika ve anahtar dosyaları oluşturun.

  3. Nginx sunucusu yeniden yapılandırma: Nginx yapılandırmasını güncelleyin, böylece API'ye girmeden önce X.509 kimlik doğrulaması sunucu düzeyinde yapılır.

  4. İstemci sertifikası ve anahtar transferi: İstemciye kendi sertifika ve anahtarını vererek, API'yi kullanmasını sağlayın.

  5. Test: Kimlik doğrulama işlemini sınayın.

1. PHP API oluşturma ve Nginx sunucusunu yapılandırma


İstemci, herhangi bir kimlik doğrulaması yapmadan tüketebileceği şekilde sahte bir PHP API'sı kurun.


$ mkdir /srv/www/football

$ nano /srv/www/football/index.php

# This is the file content
echo PHP_EOL;
print_r($_SERVER);
echo PHP_EOL;

$ sudo nano /etc/nginx/sites-available/football

# This is the file content
server {
listen 83;

root /srv/www/football;

server_name football.dev;

location / {
index index.php index.html index.htm;
try_files $uri $uri/ =404;
}

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

error_log /var/log/nginx/football_error.log;
access_log /var/log/nginx/football_access.log;
}

$ sudo ln -s /etc/nginx/sites-available/football /etc/nginx/sites-enabled/football

$ sudo service nginx restart

$ curl http://192.168.99.20:83

# This is the result (Access to http://192.168.99.20:83/ via browser produces the similar output)
Array
(
[USER] => www-data
[HOME] => /var/www
[HTTP_ACCEPT] => */*
[HTTP_HOST] => 192.168.99.20:83
[HTTP_USER_AGENT] => curl/7.38.0
[REDIRECT_STATUS] => 200
[SERVER_NAME] => football.dev
[SERVER_PORT] => 83
[SERVER_ADDR] => 192.168.99.20
[REMOTE_PORT] => 40541
[REMOTE_ADDR] => 192.168.99.20
[SERVER_SOFTWARE] => nginx/1.6.2
[GATEWAY_INTERFACE] => CGI/1.1
[SERVER_PROTOCOL] => HTTP/1.1
[DOCUMENT_ROOT] => /srv/www/football
[DOCUMENT_URI] => /index.php
[REQUEST_URI] => /
[SCRIPT_NAME] => /index.php
[CONTENT_LENGTH] =>
[CONTENT_TYPE] =>
[REQUEST_METHOD] => GET
[QUERY_STRING] =>
[SCRIPT_FILENAME] => /srv/www/football/index.php
[PATH_INFO] =>
[FCGI_ROLE] => RESPONDER
[PHP_SELF] => /index.php
[REQUEST_TIME_FLOAT] => 1539377667.6224
[REQUEST_TIME] => 1539377667
)

2. Sunucu ve istemci sertifikaları oluşturma


Aşağıdaki tüm komutları sunucuda çalıştırın. İstemci anahtarını ve sertifikayı, API'yi kullanması için sahibine güvenilir bir şekilde verin. Not: İsteğe bağlı olarak, bir pem dosyası oluşturmak için istemci anahtarını ve sertifikayı birleştirebilir ve istemcilerin iki farklı dosya kullanmasını istemek yerine kullanmasını sağlayabilirsiniz. Eğer Certificate revocation list (CRL) kullanmak isterseniz pem kullanmanız gerekecektir ama biz şu anda kullanmayacağız. Bazen herhangi bir sebepten dolayı son kullanma tarihinden önce sertifikaları iptal etmek isteriz, bu yüzden bu önemli bir özelliktir.


Sertifika Yetkilisi (CA) özel anahtarı ve sertifikası oluşturma


Bunlar kendi kendimize istemci sertifikalarını imzalamak için kullanılacaktır. CA, gerçek hayat örneklerinde, VeriSign, Thawte, Symantec, Comodo vb. gibi şirketlerdir ama biz bu seferlik kendi kendimize CA olacağız. Bu tarz şirketler X.509 sertifikalarını verirler.


$ sudo mkdir -p /etc/nginx/ssl/key
$ sudo mkdir -p /etc/nginx/ssl/certificate

$ sudo openssl genrsa -out /etc/nginx/ssl/key/football_ca.key 4096

Generating RSA private key, 4096 bit long modulus
............++
.....++
65537 (0x10001)

$ sudo openssl req -new -x509 -days 365 -key /etc/nginx/ssl/key/football_ca.key -out /etc/nginx/ssl/certificate/football_ca.crt

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:UK
State or Province Name (full name) [Some-State]:Greater London
Locality Name (eg, city) []:City of London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Football Stats Ltd.
Organizational Unit Name (eg, section) []:Tech
Common Name (e.g. server FQDN or YOUR name) []:football.dev
Email Address []:info@football.dev

$ ls -l /etc/nginx/ssl/key
-rw-r--r-- 1 root root 3247 Oct 16 22:13 football_ca.key

$ ls -l /etc/nginx/ssl/certificate
-rw-r--r-- 1 root root 2183 Oct 16 22:18 football_ca.crt

Sunucu özel anahtarı ve sertifika imzalama talebi (CSR) oluşturma


Bir sertifika imzalama talebi (CSR), bir dijital kimlik sertifikası başvurusunda bulunmak için başvuru sahibinden bir sertifika yetkilisine (CA) gönderilen bir mesajdır.


$ sudo openssl genrsa -out /etc/nginx/ssl/key/football.key 1024

Generating RSA private key, 1024 bit long modulus
...++++++
.........................++++++
65537 (0x10001)

$ sudo openssl req -new -key /etc/nginx/ssl/key/football.key -out /etc/nginx/ssl/certificate/football.csr

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:UK
State or Province Name (full name) [Some-State]:Greater London
Locality Name (eg, city) []:City of London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Football Stats Ltd.
Organizational Unit Name (eg, section) []:Tech
Common Name (e.g. server FQDN or YOUR name) []:football.dev
Email Address []:info@football.dev

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:123123
An optional company name []:Footy

$ ls -l /etc/nginx/ssl/key
-rw-r--r-- 1 root root 3247 Oct 16 22:13 football_ca.key
-rw-r--r-- 1 root root 887 Oct 16 22:24 football.key

$ ls -l /etc/nginx/ssl/certificate
-rw-r--r-- 1 root root 2183 Oct 16 22:18 football_ca.crt
-rw-r--r-- 1 root root 798 Oct 16 22:27 football.csr

Kendi kendimize sunucu sertifikalarını imzalama


Bu işlemi production ortamında yapmayın. Bu işlem "riskli" kabul edildiği için, onun yerine sertifika yetkilisinden (VeriSign, Thawte, Symantec, Comodo vs) bir tane alın. Her ne kadar kendinden imzalı SSL sertifikalar istemcilerin giriş kimlik bilgilerini ve diğer hassas verileri şifrelese bile, web sunucularında bir güvenlik uyarısı göstermesine sebep olurlar. Aynı uyarı cURL ve Guzzle kullanırkende oluşur. Bunun nedeni, sertifikanın güvenilir bir sertifika yetkilisi (CA) tarafından doğrulanmadığıdır. Sonuç olarak bu tür bir uyarı, kullanıcılarınızın sitenizi terk etmesine neden olur.


$ sudo openssl x509 -req -days 365 -in /etc/nginx/ssl/certificate/football.csr -CA /etc/nginx/ssl/certificate/football_ca.crt -CAkey /etc/nginx/ssl/key/football_ca.key -set_serial 01 -out /etc/nginx/ssl/certificate/football.crt

Signature ok
subject=/C=UK/ST=Greater London/L=City of London/O=Football Stats Ltd./OU=Tech/CN=football.dev/emailAddress=info@football.dev
Getting CA Private Key

$ ls -l /etc/nginx/ssl/certificate
-rw-r--r-- 1 root root 2183 Oct 16 22:18 football_ca.crt
-rw-r--r-- 1 root root 1529 Oct 16 22:33 football.crt
-rw-r--r-- 1 root root 798 Oct 16 22:27 football.csr

İstemci anahtarı ve sertifika imzalama isteği (CSR) oluşturma


İstemci anahtarı, gerçek istemciye güvenilir bir şekilde gönderilir, böylece API'mızı tüketirken kullanabilirler.


$ sudo openssl genrsa -out /etc/nginx/ssl/key/client_a.key 1024

Generating RSA private key, 1024 bit long modulus
.......++++++
.++++++
65537 (0x10001)

$ sudo openssl req -new -key /etc/nginx/ssl/key/client_a.key -out /etc/nginx/ssl/certificate/client_a.csr

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:UK
State or Province Name (full name) [Some-State]:Greater Manchester
Locality Name (eg, city) []:City of Manchester
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Client A Ltd.
Organizational Unit Name (eg, section) []:Sales
Common Name (e.g. server FQDN or YOUR name) []:client_a.com
Email Address []:info@client_a.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:aaaaaa
An optional company name []:CA Ltd.

$ ls -l /etc/nginx/ssl/key
-rw-r--r-- 1 root root 887 Oct 16 22:36 client_a.key
-rw-r--r-- 1 root root 3247 Oct 16 22:13 football_ca.key
-rw-r--r-- 1 root root 887 Oct 16 22:24 football.key

$ ls -l /etc/nginx/ssl/certificate
-rw-r--r-- 1 root root 802 Oct 16 22:40 client_a.csr
-rw-r--r-- 1 root root 2183 Oct 16 22:18 football_ca.crt
-rw-r--r-- 1 root root 1529 Oct 16 22:33 football.crt
-rw-r--r-- 1 root root 798 Oct 16 22:27 football.csr

İstemci sertifikasını imzalama


İstemci sertifikası, gerçek istemciye güvenilir bir şekilde gönderilir, böylece API'mızı tüketirken kullanabilirler.


$ sudo openssl x509 -req -days 365 -in /etc/nginx/ssl/certificate/client_a.csr -CA /etc/nginx/ssl/certificate/football_ca.crt -CAkey /etc/nginx/ssl/key/football_ca.key -set_serial 01 -out /etc/nginx/ssl/certificate/client_a.crt

Signature ok
subject=/C=UK/ST=Greater Manchester/L=City of Manchester/O=Client A Ltd./OU=Sales/CN=client_a.com/emailAddress=info@client_a.com
Getting CA Private Key

$ ls -l /etc/nginx/ssl/certificate

-rw-r--r-- 1 root root 1533 Oct 16 22:45 client_a.crt
-rw-r--r-- 1 root root 802 Oct 16 22:40 client_a.csr
-rw-r--r-- 1 root root 2183 Oct 16 22:18 football_ca.crt
-rw-r--r-- 1 root root 1529 Oct 16 22:33 football.crt
-rw-r--r-- 1 root root 798 Oct 16 22:27 football.csr

Pem dosyası oluşturma (isteğe bağlı)


İstemci anahtarını ve sertifikayı pem dosyası oluşturmak için birleştirebilir ve istemcilerin iki farklı dosya kullanmasını istemek yerine bir dosya kullanmasını sağlayabilirsiniz. Bu sadece yukarıda belirttiğimiz gibi Certificate revocation list (CRL) kullanacaksanız önemlidir. Gizlilik gelişmiş posta (PEM) bir Base64 kodlanmış sertifikadır. Web sunucuları için PEM sertifikaları sıklıkla kullanılır. --cert olarak kullanılır, --key olarak değil.


$ sudo bash -c 'cat /etc/nginx/ssl/certificate/client_a.crt /etc/nginx/ssl/key/client_a.key > /etc/nginx/ssl/certificate/client_a.pem'

3. Nginx sunucusu yeniden yapılandırma


Nginx yapılandırmasını güncelleyin, böylece API'ye girmeden önce X.509 kimlik doğrulaması sunucu düzeyinde yapılır.


server {
listen 83;

root /srv/www/football;

server_name football.dev;

listen 443;
ssl on;
ssl_certificate /etc/nginx/ssl/certificate/football.crt;
ssl_certificate_key /etc/nginx/ssl/key/football.key;
ssl_client_certificate /etc/nginx/ssl/certificate/football_ca.crt;
ssl_verify_client optional; # Set to "on" if you only allow authenticated requests

location / {
index index.php index.html index.htm;
try_files $uri $uri/ =404;
}

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SSL_CLIENT_VERIFY $ssl_client_verify;
fastcgi_param SSL_CLIENT_S_DN $ssl_client_s_dn;
include fastcgi_params;
}

error_log /var/log/nginx/football_error.log;
access_log /var/log/nginx/football_access.log;
}

Sertifika geçersiz olduğunda API'ye ulaşılmasını önlemek istiyorsanız, isteğe bağlı olarak yukarıdaki yapılandırmanın location ~ \.php$ bloğuna aşağıdaki kodu ekleyebilirsiniz.


if ($ssl_client_verify != SUCCESS) {
return 403;
}

$ sudo nginx -c /etc/nginx/nginx.conf -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

$ sudo service nginx restart

4. İstemci sertifikası ve anahtar transferi


İstemciye kendi sertifika client_a.crt ve anahtarını client_a.key vererek, API'yi kullanmasını sağlayın.


5. Test


Şu anda istemcinin bilgisayarındayız ve sunucu bilgisayarındaki API'yi kullanıyoruz. Aşağıdaki SSL_CLIENT_VERIFY ve SSL_CLIENT_S_DN anahtarlarına dikkat edin. Kendinden imzalı SSL sertifikaları kullanıldığımız için --insecure ekini kullanmalıyız. Aksi takdirde curl: (60) SSL certificate problem: self signed certificate hatası alırız.


# VALID
$ curl --insecure --key /srv/www/client_a.key --cert /srv/www/client_a.crt https://192.168.99.20:83

# OR pem version
# $ curl --insecure --cert /srv/www/client_a.pem https://192.168.99.20:83

Array
(
[USER] => www-data
[HOME] => /var/www
[HTTP_ACCEPT] => */*
[HTTP_HOST] => 192.168.99.20:83
[HTTP_USER_AGENT] => curl/7.38.0
[SSL_CLIENT_VERIFY] => SUCCESS
[SSL_CLIENT_S_DN] => /C=UK/ST=Greater Manchester/L=City of Manchester/O=Client A Ltd./OU=Sales/CN=client_a.com/emailAddress=info@client_a.com
[REDIRECT_STATUS] => 200
[SERVER_NAME] => football.dev
[SERVER_PORT] => 83
[SERVER_ADDR] => 192.168.99.20
[REMOTE_PORT] => 42553
[REMOTE_ADDR] => 192.168.99.30
[SERVER_SOFTWARE] => nginx/1.6.2
[GATEWAY_INTERFACE] => CGI/1.1
[HTTPS] => on
[SERVER_PROTOCOL] => HTTP/1.1
[DOCUMENT_ROOT] => /srv/www/football
[DOCUMENT_URI] => /index.php
[REQUEST_URI] => /
[SCRIPT_NAME] => /index.php
[CONTENT_LENGTH] =>
[CONTENT_TYPE] =>
[REQUEST_METHOD] => GET
[QUERY_STRING] =>
[SCRIPT_FILENAME] => /srv/www/football/index.php
[PATH_INFO] =>
[FCGI_ROLE] => RESPONDER
[PHP_SELF] => /index.php
[REQUEST_TIME_FLOAT] => 1539727722.2362
[REQUEST_TIME] => 1539727722
)

# MALFORMED certificate
$ curl --insecure --key /srv/www/client_a.key --cert /srv/www/client_a.crt https://192.168.99.20:83
curl: (58) unable to use client certificate (no key found or wrong pass phrase?)

# WRONG certificate
$ curl --insecure --key /srv/www/client_a.key --cert /srv/www/client_x.crt https://192.168.99.20:83
curl: (58) unable to set private key file: '/srv/www/client_a.key' type PEM

# WRONG key
$ curl --insecure --key /srv/www/client_x.key --cert /srv/www/client_a.crt https://192.168.99.20:83
curl: (58) unable to set private key file: '/srv/www/client_x.key' type PEM

Referanslar