Bu örnekte, istemci verileri sunucuya göndermeden önce imzalamak için OpenSSL'yi kullanacak. Sunucu verileri aldığında, verilen imzayla imzalanıp imzalanmadığını kontrol etmek için OpenSSL kullanır. Doğrulama başarılı olduysa, istemci güvenilir olarak kabul edilir. Bu süreçte istemci "private key", sunucu doğrulama işlemi için istemcinin "public key" bilgisini kullanır. Daha fazla bilgi için Signing HTTP Messages, openssl_sign ve openssl_verify sayfalarını okuyun.


Akış mantığı



Classlar


SignatureController


Burada biraz tembellik yapıp controller içinde çok fazla iş yapıyorum ama bunun nedeni yazıyı mümkün olduğunca kısa tutmak istememdir. Siz mantığı bir servis sınıfına taşıyabilirsiniz - sadece bir örnek.


namespace App\Controller;

use App\Util\SignatureUtil;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

/**
* @Route("/signatures")
*/
class SignatureController
{
private $signatureUtil;
private $publicKey;
private $privateKey;

public function __construct(
SignatureUtil $signatureUtil,
string $publicKey,
string $privateKey
) {
$this->signatureUtil = $signatureUtil;
$this->publicKey = $publicKey;
$this->privateKey = $privateKey;
}

/**
* @Route("/client/create-keys", methods={"GET"})
*/
public function createKeys(): Response
{
$keys = $this->signatureUtil->createKeys();

file_put_contents($this->publicKey, $keys['public_key']);
file_put_contents($this->privateKey, $keys['private_key']);

return new JsonResponse('Done!');
}

/**
* @Route("/client/create-signature", methods={"POST"})
*/
public function createSignature(Request $request): Response
{
$privateKey = file_get_contents($this->privateKey);
$data = json_decode($request->getContent(), true)['data'];
$signature = $this->signatureUtil->createSignature($data, $privateKey);

return new JsonResponse($signature);
}

/**
* @Route("/server/verify-signature", methods={"POST"})
*/
public function verifySignature(Request $request): Response
{
$publicKey = file_get_contents($this->publicKey);
$data = json_decode($request->getContent(), true)['data'];
$signature = $request->headers->get('X-Signature');

$valid = $this->signatureUtil->verifySignature($data, $signature, $publicKey);

return new JsonResponse(['valid' => $valid]);
}
}

services:
...

App\Controller\SignatureController:
arguments:
$publicKey: '%kernel.project_dir%/config/ssl/public_key.pem'
$privateKey: '%kernel.project_dir%/config/ssl/private_key.pem'


SignatureUtil


Lütfen koddaki yorumları okuyun.


namespace App\Util;

use RuntimeException;

class SignatureUtil
{
/**
* This is only relevant to server API.
*
* The "public_key" is shared with server.
* The "private_key" is kept securely by the client only.
*/
public function createKeys(): array
{
$keys = openssl_pkey_new(['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);

openssl_pkey_export($keys, $privateKey);

$keyDetails = openssl_pkey_get_details($keys);
$publicKey = $keyDetails['key'];

return [
'public_key' => $publicKey,
'private_key' => $privateKey,
];
}

/**
* This is only relevant to client API.
*
* The client calls this method to create a "signature" based on the "data".
* The client then sends the "signature" and the "data" to the server.
*/
public function createSignature(string $data, string $privateKey): string
{
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);

return base64_encode($signature);
}

/**
* This is only relevant to server API.
*
* The server checks "data" against the "signature" to see if the client is genuine or not.
*/
public function verifySignature(string $data, string $signature, string $publicKey): bool
{
$verify = openssl_verify($data, base64_decode($signature), $publicKey, 'sha256WithRSAEncryption');

if (1 == $verify) {
return true;
}

if (0 === $verify) {
return false;
}

throw new RuntimeException('An error occurred while verifying the signature.');
}
}

Test


İstemcinin anahtarları oluşturun


$ ls -l config/ssl/
total 0

$ curl -X GET \
> http://localhost/signatures/client/create-keys
"Done!"

$ ls -l config/ssl/
-rw-rw-rw- 1 501 dialout 1704 Jan 13 2019 private_key.pem
-rw-rw-rw- 1 501 dialout 451 Jan 13 2019 public_key.pem

İmzayı oluşturun


$ curl -X POST \
http://localhost/signatures/client/create-signature \
-H 'Content-Type: application/json' \
-d '{
"data": "Hello World!"
}'

"S3PRm0yePp3fIgEhtIYXq+oQrFkbkxSHiJHbyESu0pLRILg7N2AWrdsvkK6LzK9F2WB1DKXrAAXD3L+R0au2penzBCnX9ERtJXZq71h2qVZYbkrxbY4jQsYU/aUg3KxakVQxTi4gehj0zeGvTV6OxkjFbKq0/KLgWD9AjYZxeWnmFhh8wbbGtmFMZzBkGU7iXSC+/5kKguI28WWt1ALYHJJxSsJWmZnccnAELo6tVhqBKxavuB/E6TZTEiWGeTC0wAWcjuqfEIFoA5b1fx73L6vcCGqf17Ov42NIJL50BRlmRcHkmnNJXVUUbSLmI+Cu+uzwE3KN9z/pVv8dfLIFAA=="

API kullanımı


İmzayı X-Signature kafasında kullanın.


$ curl -X POST \
> http://localhost/signatures/server/verify-signature \
> -H 'Content-Type: application/json' \
> -H 'X-Signature: S3PRm0yePp3fIgEhtIYXq+oQrFkbkxSHiJHbyESu0pLRILg7N2AWrdsvkK6LzK9F2WB1DKXrAAXD3L+R0au2penzBCnX9ERtJXZq71h2qVZYbkrxbY4jQsYU/aUg3KxakVQxTi4gehj0zeGvTV6OxkjFbKq0/KLgWD9AjYZxeWnmFhh8wbbGtmFMZzBkGU7iXSC+/5kKguI28WWt1ALYHJJxSsJWmZnccnAELo6tVhqBKxavuB/E6TZTEiWGeTC0wAWcjuqfEIFoA5b1fx73L6vcCGqf17Ov42NIJL50BRlmRcHkmnNJXVUUbSLmI+Cu+uzwE3KN9z/pVv8dfLIFAA==' \
> -d '{
> "data": "Hello World!"
> }'

{"valid":true}