PSR-1 i PSR-2

Tym razem krótko, bo chciałem tylko zaznaczyć istnienie PSR-1 i PSR-2. Cieszę się, że te dwa zalecenia powstały i że modne jest stosowanie się do nich, przez co istnieje duże prawdopodobieństwo, że przeglądając źródła zarówno swojego projektu, jak i innych, dostępnych publicznie, a często używanych, trafimy na czytelne i zbieżne formatowanie. Sam musiałem wyplenić kilka złych nawyków, ale dzięki kod jest dużo czytelniejszy.

Wojny edycyjne uznajemy więc za zakończone. 😉

Ratchet – WebSockety w PHP w praktyce

WebSockety to jeden z najciekawszych od kilku lat tematów, z którymi przyszło mi zmierzyć się w pracy. Pierwszy raz słyszałem o nich gdzieś w 2013 roku i od tego czasu zdążyły zdobyć ogromną popularność w internecie. YouTube, Stack Overflow, Facebook – tam wszędzie WebSockety wykorzystywane są już od dawna do różnych rzeczy (przesyłanie i odświeżanie komentarzy oraz głosów, komunikacja w czasie rzeczywistym, itp.). To tyle słowem wstępu – jeżeli tu trafiłeś, prawdopodobnie wszystko to wiesz i szukasz informacji, jak wykorzystać WebSockety w praktyce.

Do zeszłego roku „WebSockety” i „node.js” stanowiły w zasadzie synonimy, jeżeli chodzi o ich implementację. O uruchomieniu ich w PHP czytałem same złe rzeczy i nawet najbardziej zagorzali programiści używający tego języka odradzali. Tematu jednak nie zgłębiałem i przyglądałem się nieco z boku. Jednak gdy w pracy pojawił się temat obsługi zdarzeń z centrali telefonicznej w naszym systemie, nie było wyjścia – trzeba zacząć się nimi szybko interesować.

Kolega z sąsiedniego zespołu już jakiś czas wcześniej podejmował ten temat w oparciu właśnie od node.js. Jego opinie nie były życzliwe, zwłaszcza, że nie miał wiele czasu na stworzenie tego rozwiązania. Podobnie z czasem było u mnie. Dodatkowo zniechęcał fakt, że w zasadzie nie mamy żadnej infrastruktury pod node.js, a ostatnią rzeczą, której bym chciał, to zarządzanie tym wszystkim od strony administracyjnej. Zacząłem więc szukać czegoś o WebSocketach w PHP.

Natychmiast trafiłem na Ratchet – bibliotekę do prostej implementacji WebSocketów w aplikacjach PHP. Brzmiało świetnie zwłaszcza, że mogłem korzystać z „dobrodziejstw” całego naszego systemu. W praktyce okazało się, że jest jeszcze lepiej i łatwiej, niż mi się wydawało.

Stwórzmy więc wspólnie skręcaną z drutów aplikację z pomocą Ratchet, która będzie niczym innym, jak prostym echotestem. Dodatkowo, oprócz odpowiadania na otrzymane wiadomości, serwer będzie sam wysyłał do wszystkich wiadomość, np. co 10 sekund. Dzięki temu otrzymasz bazę do większości zastosowań – nasza aplikacja będzie zarówno odpowiadała na komunikaty z inicjatywy klientów, jak i sama w pętli wysyłała coś od siebie.

Najpierw potrzebujemy samego Ratchet. Najłatwiej zlecić pobranie go composerowi. Jeżeli nie korzystałeś jeszcze z composera, spróbuj po prostu odwiedzić stronę pobierania i przejdź do sekcji „Manual download”, skąd możesz ściągnąć najnowszą wersję composer.phar. Umieść ten plik w katalogu głównym projektu, po czym możesz przejść do pobierania Ratcheta:

php composer.phar require cboden/ratchet

Kolejnym krokiem będzie stworzenie klasy obsługującej wszystkie potrzebne zdarzenia (połączenie, rozłączenie, wystąpienie błędu) oraz zawierającej metodę, którą serwer będzie cyklicznie wywoływać. Oto jej treść:

<?php
namespace WebSocket;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Handler implements MessageComponentInterface
{
    protected $clients;

    public function __construct()
    {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn);

        echo 'Nowe połączenie: ' . $conn->resourceId . PHP_EOL;
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
        // iteracja po dostępnych połączeniach
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // bieżące połączenie to nadawca wiadomości
                
                // odsyłamy wiadomość
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);

        echo 'Zamknięto połączenie: ' . $conn->resourceId . PHP_EOL;
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo 'Wystąpił błąd: ' . $e->getMessage() . PHP_EOL;

        $conn->close();
    }
    
    public function onLoop($options = array())
    {
        foreach($this->clients as $client) {
            $client->send('Cykliczna wiadomość');
    
            echo 'Wysłano komunikat cykliczny' . PHP_EOL;
        }
    }
}

Klasę umieściłem w pliku handler.php.

Metody onOpen, onClose i onError są raczej oczywiste. W onMessage występuje obsługa zdarzenia pojawiającego się w momencie otrzymania wiadomości od klienta. Iterujemy tam po prostu po tablicy dostępnych połączeń i sprawdzamy, czy trafiliśmy na połączenie zgodne z identyfikatorem zasobu nadawcy. Jeżeli tak, to korzystamy z tego zasobu do odesłania odpowiedzi tej samej treści, którą otrzymaliśmy.

Metodę onLoop wykorzystamy natomiast do wywoływania cyklicznego. Wysyła ona do wszystkich połączeń komunikat o tej samej treści.

Mając taką klasę, możemy stworzyć plik, w którym uruchomimy sam serwer. W moim wypadku jest to server.php o następującej treści:

<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use WebSocket\Handler;

require 'vendor/autoload.php';
require 'handler.php';

// utworzenie obiektu klasy obsługującej
$handler = new Handler();

// stworzenie instancji serwera websocketów
$server = IoServer::factory(
    new HttpServer(
        new WsServer($handler)
    ),
    8080    // port
);

// ustawienie pętli
$server->loop->addPeriodicTimer(10, function() use ($server, $handler) {
    // uruchomienie metody obrotu pętli
    $handler->onLoop(array(
        'server' => $server,
    ));
});

echo 'Serwer uruchomiony' . PHP_EOL;

// uruchomienie serwera
$server->run();

Jak widać, tworzymy najpierw instancję serwera nasłuchującego na porcie 8080, podając mu klasę z obsłużonymi zdarzeniami, a następnie dodajemy pętlę, która będzie wywoływana co 10 sekund.

Teraz wystarczy uruchomić serwer poprzez wywołanie:

php server.php

W konsoli powinniśmy dostawać informacje o przebiegu działania serwera.

Jak się połączyć z naszym serwerem? Najprościej uruchamiając konsolę w przeglądarce (w Firefoksie możemy wywołać ją kombinacją CTRL-SHIFT-K) i wklejając:

var conn = new WebSocket('ws://localhost:8080');
conn.onopen = function(e) {
    console.log("Połączono pomyślnie!");
};

conn.onmessage = function(e) {
    console.log(e.data);
};

Po chwili powinniśmy otrzymywać z serwera co 10 sekund wiadomość.

Aby wysłać wiadomość do serwera, wystarczy, że w tej samej konsoli wkleimy:

conn.send('Test');

To wszystko! Mamy już działający serwer.

Oczywiście, docelowa aplikacja musi zawierać obsługę uwierzytelniania, jednak celem tego wpisu było pokazanie prostoty stworzenia serwera WebSocketów.

Wpis był mocno inspirowany oficjalnym tutorialem Ratcheta, który rozbudowałem o cykliczne wywoływanie zdarzeń przez serwer.

Eclipse – utrata możliwości debugowania w trakcie używania Xdebug

Po poznaniu rozwiązania problem wydaje się być głupi, ale powstrzymał mnie skutecznie przez bardzo długi czas przed debugowaniem aplikacji w PHP z użyciem Xdebug.

Do sedna: jeżeli w trakcie debugowania nagle tracisz możliwość sterowania debuggerem (dezaktywują się przyciski od nawigowania po kodzie, itp.), ale sama aplikacja i sesja debuggera nadal działa (jesteś w stanie ją pomyślnie zakończyć), to prawdopodobnie Eclipse stracił focus na debugowanym przez Ciebie wątku. Stan taki pokazuje poniższy zrzut ekranu:

eclipse-xdebug-lost-focus

Co w takiej sytuacji? Wystarczy kliknąć na „Remote launch” w karcie „Debug” (u mnie jest to lewa górna część ekranu):

eclipse-xdebug-recovered-focus

Jak widać, przyciski do sterowania debuggerem są znów aktywne i można kontynuować tę sesję Xdebug.

 

XAdES-BES – algorytm zorientowany na PHP

Niedawno w pracy zetknąłem się z koniecznością podpisywania cyfrowego komunikatów przesyłanych przez jedną z naszych aplikacji. Akceptowanym przez odbiorcę formatem podpisu jest XAdES-BES. Po dłuższym przeszukiwaniu Google’a sprawa nie wyglądała dobrze – nie ma żadnej gotowego rozwiązania dla PHP umożliwiającego złożenie takiego podpisu. Co najgorsze, nie mieliśmy pojęcia o sposobie „ręcznego” tworzenia takiego podpisu, a kilkudziesięciostronicowa specyfikacja bez choćby jednego przykładu nie zachęcała do lektury. W sieci nie było też ani jednego snippeta, na którym można by się oprzeć.

tl;dr
Co znajdziesz w tym wpisie?

  • opis sposobu wygenerowania podpisu w formacie XAdES-BES enveloping (w którym do podpisu dołączana jest podpisywana treść) na konkretnym przykładzie, krok po kroku,
  • przykłady na realizację poszczególnych kroków w języku PHP,
  • wybór przydatnych linków.

Czego nie znajdziesz w tym wpisie?

  • gotowej implementacji generowania podpisu w PHP,
  • opisu generowania podpisu enveloped (takiego, który dołączany jest do dokumentu).

Temat nie rokował zbyt dobrze, a czas gonił. Po głębszym zbadaniu temat wydawał się ciekawy, więc postanowiłem przebić się przez oficjalną specyfikację. Dokumentu jednak nie polecam – jak się później okazało, była to najmniej przydatna dla mnie lektura.

Co ciekawe, dużo poukładał mi w głowie lakoniczny artykuł na Wikipedii, który w zasadzie w jednym zdaniu opisywał XAdES:

XAdES jest rozwinięciem XML-DSig, wypełnia pole ds:Object, powiązany jest z ds:SignedInfo za pomocą elementu ds:Reference wskazującego na SignedProperties.

Skierowałem się więc w stronę XML-DSig. Tutaj już z materiałami było znacznie lepiej, a wiedząc o tym, że XAdES to w zasadzie XML-DSig z dodatkowymi informacjami, mogłem przystąpić do własnych prób podpisania czegokolwiek, na próbę.

Nieocenioną pomocą był ten opis, który zawierał strukturę przykładowego podpisu wraz ze sposobem wyliczania wartości digest. Dokładnie przeanalizowałem ten dokument i udało mi się (po dłuższych walkach) odtworzyć identyczne wartości. Potem sam podpisałem dokument za pomocą aplikacji Szafir Krajowej Izby Rozliczeniowej (głównie przez to, że jest używana przez odbiorcę do weryfikacji oraz ze względu na istnienie wersji pod Linuksa).

Podpisywana treść

Będziemy podpisywać następującego XML-a:

<?xml version="1.0" encoding="utf-8" ?>
<test>
  <test>testowy węzeł</test>
  <test>drugi testowy węzeł</test>
</test>

Sama treść nie jest jednak ważna, ponieważ całość zakodujemy i tak w BASE64.

Struktura podpisu

Aby nie przedłużać, tak wygląda podpisana wyżej treść po sformatowaniu:

<?xml version="1.0" encoding="UTF-8"?>
<Signatures Id="ID-36e3801c-b360-4958-86e5-0b82cbd2d366">
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="ID-88c32693-8f48-4cc8-9764-d10f0fea51b5">
    <ds:SignedInfo Id="ID-4c61b0d9-e5da-4eea-97a1-623b9e4fa2b2">
      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <ds:Reference Id="ID-888d7eba-e17b-4f0c-8d5b-efa29359ebd7" URI="#ID-78a661eb-d6b2-4776-9b06-3d9e0afadab6">
        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <ds:DigestValue>MLaZQl/toVO57dJsz1JfLIyrrd4=</ds:DigestValue>
      </ds:Reference>
      <ds:Reference Id="ID-ad71d14c-447c-44a3-a00c-2e902bb73a03" URI="#ID-9542235a-b703-477a-8702-8999387d943e" Type="http://uri.etsi.org/01903#SignedProperties">
        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <ds:DigestValue>R2SnnM9tlyt4HZtC2qGWKB2XIUY=</ds:DigestValue>
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue Id="ID-c83aa77f-c39b-45ba-9368-25ba362bc57a">Td43...</ds:SignatureValue>
    <ds:KeyInfo>
      <ds:KeyValue>
        <ds:RSAKeyValue>
          <ds:Modulus>...</ds:Modulus>
          <ds:Exponent>...</ds:Exponent>
        </ds:RSAKeyValue>
      </ds:KeyValue>
      <ds:X509Data>
        <ds:X509Certificate>...</ds:X509Certificate>
      </ds:X509Data>
    </ds:KeyInfo>
    <ds:Object>
      <xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="ID-e42ab543-3f7f-4473-9dba-6cb105b5ce27" Target="#ID-88c32693-8f48-4cc8-9764-d10f0fea51b5">
        <xades:SignedProperties Id="ID-9542235a-b703-477a-8702-8999387d943e">
          <xades:SignedSignatureProperties>
            <xades:SigningTime>2016-07-29T19:03:50Z</xades:SigningTime>
            <xades:SigningCertificate>
              <xades:Cert>
                <xades:CertDigest>
                  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                  <ds:DigestValue>...</ds:DigestValue>
                </xades:CertDigest>
                <xades:IssuerSerial>
                  <ds:X509IssuerName>...</ds:X509IssuerName>
                  <ds:X509SerialNumber>...</ds:X509SerialNumber>
                </xades:IssuerSerial>
              </xades:Cert>
            </xades:SigningCertificate>
          </xades:SignedSignatureProperties>
          <xades:SignedDataObjectProperties>
            <xades:DataObjectFormat ObjectReference="#ID-888d7eba-e17b-4f0c-8d5b-efa29359ebd7">
              <xades:Description>Dokument w formacie xml [XML]</xades:Description>
              <xades:MimeType>text/plain</xades:MimeType>
              <xades:Encoding>http://www.w3.org/2000/09/xmldsig#base64</xades:Encoding>
            </xades:DataObjectFormat>
          </xades:SignedDataObjectProperties>
        </xades:SignedProperties>
      </xades:QualifyingProperties>
    </ds:Object>
    <ds:Object Encoding="http://www.w3.org/2000/09/xmldsig#base64" Id="ID-78a661eb-d6b2-4776-9b06-3d9e0afadab6" MimeType="text/plain">PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiID8+Cjx0ZXN0PgogIDx0ZXN0PnRlc3Rv
d3kgd8SZemXFgjwvdGVzdD4KICA8dGVzdD5kcnVnaSB0ZXN0b3d5IHfEmXplxYI8L3Rlc3Q+CjwvdGVz
dD4=</ds:Object>
  </ds:Signature>
</Signatures>

Pozwoliłem sobie wyciąć dane o certyfikacie i wartości innych węzłów, których nie chcę upubliczniać.

Treść dokumentu w podpisie

Treść zawiera się w węźle:

<ds:Object Encoding="http://www.w3.org/2000/09/xmldsig#base64" Id="ID-78a661eb-d6b2-4776-9b06-3d9e0afadab6" MimeType="text/plain">PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiID8+Cjx0ZXN0PgogIDx0ZXN0PnRlc3Rv
d3kgd8SZemXFgjwvdGVzdD4KICA8dGVzdD5kcnVnaSB0ZXN0b3d5IHfEmXplxYI8L3Rlc3Q+CjwvdGVz
dD4=</ds:Object>

Zgodnie z atrybutami, jest zakodowana przy użyciu base64 – możesz sprawdzić, czy powyższa wartość jest zgodna z wynikiem funkcji base64_encode()  z PHP.

Węzeł posiada unikalne ID – nie musi być akurat w takim formacie, jak powyżej, jednak zapisz sobie gdzieś to ID – będzie potrzebne do odwołań podpisu.

Pierwsze odwołanie w SignedInfo

Pierwsze odwołanie w SignedInfo dotyczy właśnie opisanego wyżej węzła z treścią i wygląda następująco:

<ds:Reference Id="ID-888d7eba-e17b-4f0c-8d5b-efa29359ebd7" URI="#ID-78a661eb-d6b2-4776-9b06-3d9e0afadab6">
  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
  <ds:DigestValue>MLaZQl/toVO57dJsz1JfLIyrrd4=</ds:DigestValue>
</ds:Reference>

Atrybut „URI” wskazuje właśnie na ID węzła z treścią, który kazałem Ci zapisać. Odwołanie zawiera dwa węzły: DigestMethod oraz DigestValue. Wartość DigestValue jest obliczana przy pomocy funkcji skrótu określonej w DigestMethod, czyli w tym przypadku SHA1.

Zanim jednak zaczniesz liczyć skrót z treści, musisz zwrócić na węzeł znajdujący się trochę wyżej:

<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />

Mówi on o tym, że musisz doprowadzić węzeł do formy kanonicznej, w tym przypadku za pomocą C14N.

Jak to zrobić? W PHP DOMDocument posiada do tego gotową metodę. Wystarczy, że wczytasz treść gałęzi i użyjesz metody C14N() :

$dom = new DOMDocument();
$dom->loadXML($content);
$canonicalizedXml = $dom->C14N();

Taką treść możemy już poddać funkcji skrótu – w PHP sha1(), po czym należy zakodować wynik w BASE64. Jeżeli jednak zrobisz to w ten sposób, nie otrzymasz prawidłowego wyniku:

$hash = sha1($canonicalizedXml);
$digestValue = base64_encode($hash);

W BASE64 musimy zakodować binarną wartość skrótu, więc musisz podać true jako drugi argument funkcji sha1() :

$hash = sha1($canonicalizedXml, true);
$digestValue = base64_encode($hash);

Wartość w $digestValue w moim przypadku powinna być zgodna z tą w treści XML:

MLaZQl/toVO57dJsz1JfLIyrrd4=

DigestValue pierwszego odwołania za nami!

Drugie odwołanie w SignedInfo

Drugie odwołanie w SignedInfo wskazuje na węzeł SignedProperties.

Jako, iż mamy wyliczyć skrót, zakładamy, że na tym etapie mamy już wypełnioną całą treść tego węzła. Jak ją wypełnić pokażę później.

I tutaj ważna uwaga – ze względu na to, że w treści SignedProperties występują elementy zarówno z węzła z namespace xades, jak i ds, to nie możemy po prostu wyciąć go tak, jak przy poprzednim odwołaniu. Musimy wybrać przy pomocy DOMDocument tę konkretną wartość i przekształcić na formę kanoniczną tak, aby metoda C14N()  miała dostęp do kontekstu XML-a i mogła na tej podstawie wstawić w SignedProperties dwa atrybuty xmlns. Powinno wyglądać to mniej więcej tak:

$dom = new DOMDocument();
$dom->loadXML($fullContent);
$node = $dom->getElementsByTagName('SignedProperties')->item(0)->C14N();

Z takiej formy węzła SignedProperties znów liczymy skrót przy pomocy SHA1 (bo DigestMethod ma taką samą wartość, jak w poprzednim odwołaniu):

$hash = sha1($node, true);
$digestValue = base64_encode($hash);

W $digestValue powinienem otrzymać (dla danych mojego certyfikatu, itp.):

R2SnnM9tlyt4HZtC2qGWKB2XIUY=

Kolejne DigestValue za nami.

Właściwy podpis

Mając SignedInfo z wyliczonymi wartościami digest, możemy przejść do liczenia właściwego podpisu.

Metoda jego liczenia jest określona wewnątrz SignedInfo poprzez:

<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />

W tym wypadku nie użyjemy już zwykłego SHA1, jednak jest to najprostsza część wyliczeń – w PHP możemy skorzystać z funkcji openssl_sign() :

$privateKey = openssl_pkey_get_private('file://path');
        
$actualDigest = null;
openssl_sign(
    $signedInfo, 
    $actualDigest, 
    $privateKey, 
    'sha1WithRSAEncryption'
);

$actualDigestEncoded = base64_encode($actualDigest);

Kilka uwag:

  • otwieramy plik w formacie PEM zawierający klucz prywatny, którym podpiszemy zawartość SignedInfo (pamiętaj o „file://” na początku, zaś przy podawaniu ścieżki na uniksach o tym, że powinny tam być trzy slashe, np.: „file:///var/cert.pem”),
  • do $actualDigest zostanie zwrócona binarny wynik szyfrowanej funkcji skrótu,
  • w $signedInfo podajemy formę kanoniczną węzła SignedInfo.

Na tym kończy się generowanie podpisu cyfrowego XAdES-BES. Z racji tego, że nie przedstawiłem wcześniej szczegółów wypełniania wartości dot. certyfikatu, informacje na ten temat zostawiam na koniec.

Retrospekcja – dane certyfikatu

We wpisie chciałem skoncentrować się na sposobie wyliczania wartości digest dla XAdES-BES, dlatego przedstawiłem te informacje w pierwszej kolejności. Jeżeli jednak masz problem z uzyskaniem danych certyfikatu występujących w pozostałych węzłach, to ta część może być dla Ciebie przydatna.

  • DigestValue w CertDigest

Wartość liczymy z treści certyfikatu w PEM po wycięciu markerów:

$certResource = openssl_x509_read('file://path');
$certPem = null;
openssl_x509_export($certResource, $certPem);
$certContent = str_replace('-----BEGIN CERTIFICATE-----', '', $certPem);
$certContent = trim(str_replace('-----END CERTIFICATE-----', '', $certContent));

$certFingerprint = base64_encode(sha1(base64_decode($certContent), true));
  • X509Certificate

Wartość mamy w zmiennej $certContent z poprzedniego punktu.

  • Modulus i Exponent z RSAKeyValue
$publicKey = openssl_pkey_get_public('file://path');
                
$data = openssl_pkey_get_details($publicKey);
        
$modulusHex = '00' . bin2hex($data['rsa']['n']);
$modulusEncoded = base64_encode(hex2bin($modulusHex));
        
$exponentEncoded = base64_encode($data['rsa']['e']);

Zauważyłem, że zwrócone $data[‚rsa’][‚n’] nie zawiera bajtu zerowego na początku, dlatego dopisałem go ręcznie. Nie udało mi się jednak ustalić, z czego to wynika – czy z używanej przez nas wersji PHP, z jakiegoś błędu, czy po prostu tak ma być, dlatego porównaj wyliczone wartości z wyjściem openssl:

openssl rsa -noout -text -in [public_key_filename]
  • X509SerialNumber
$certData = openssl_x509_parse($certResource);
$serialNumber = $certData['serialNumber'];
  • IssuerName
$issuerArray = array();
foreach($certData['issuer'] as $issueKey => $issue) {
    $issuerArray[] = $issueKey . '=' . $issue;
}
$issuer = implode(',', $issuerArray);

Podsumowanie

Mam nadzieję, że powyższe informacje przydadzą się chociaż częściowo osobom, które zupełnie nie znają formatu XAdES-BES, a muszą wprowadzić go w swoich aplikacjach.

Pamiętaj, że podane w treści wpisu fragmenty kodu mają na celu jedynie zobrazowanie sposobu uzyskiwania celu, zaś produkcyjny kod powinien brać pod uwagę wszelkie możliwe błędy, które mogą wystąpić podczas całego procesu.

Jeżeli coś jest niejasne lub o czymś zapomniałem – daj znać w komentarzu, postaram się to poprawić.