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ć.

15 myśli na temat “XAdES-BES – algorytm zorientowany na PHP”

  1. Świetna robota!!! Artykuł bardzo pomocny!

    Mały błąd w przykładowym kodzie, zamiast “$node->” powinno być “$dom->”:

    $node = $node->getElementsByTagName(‘SignedProperties’)->item(0)->C14N();

    Pozdrawiam 🙂

  2. Próbuję powtórzyć digestvalue, używając C#, pierwsze odwołanie w signedInfo, w końcu się udało. C# XmlDocument.looad nie przenosi spacji;
    trzeba użyć PreserveWhitespace = true; Wartość wyliczonego w ten sposób skrótu zgadza się z wyliczoną przez oprogramowaniem Pemi.

    Teraz ładuję wygenrowany przez Pemi węzeł “xades:SignedProperties”, przepuszczam przez XmlDsigC14NTransform, wyliczam hash SHA1, konwwertuję do base64 i niestety mam inny wynik.
    Zastanawiałem się jak powina wyglądać forma kanoniczna węzła signedProperties; piszesz, że to “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.”
    W twoim przykładowym kodzie nie widzę przekształcenia węzła na formę kanoniczną przed czytanie przez metodę C14N().
    Nie wiem niestety jak powinna wyglądać ta forma węzła ???
    Mój kod w C# – podobny do twojego a jednak wynik daje inny:

    XmlDocument myDoc = new XmlDocument();
    myDoc.PreserveWhitespace = true;
    myDoc.Load(“C:\\PIT_11\\2011\\101352154.xml.xades”);
    XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = true;
    doc.LoadXml(myDoc.OuterXml);

    XmlNodeList list = doc.GetElementsByTagName(“xades:SignedProperties”);
    XmlElement node = (XmlElement)list[0];
    string s = node.OuterXml;
    using (MemoryStream msIn = new MemoryStream(Encoding.UTF8.GetBytes(s)))
    {
    XmlDsigC14NTransform t = new XmlDsigC14NTransform(true);

    t.LoadInput(msIn);
    Stream st = (Stream) t.GetOutput(typeof(Stream));
    using (var cryptoService = new SHA1CryptoServiceProvider())
    {
    var hash = cryptoService.ComputeHash(st);
    var hashString = Convert.ToBase64String(hash);
    MessageBox.Show(hashString);

    }
    }

    Czy jestm na dobrym tropie?

    Niżej SignedProperties z wygenerowanego pliku xades;
    2017-03-10T10:33:45ZM1roh5UkuBE+LsBK7yUG5Cb2m3Q=17397885708855712043403751717868631362

    1. W twoim przykładowym kodzie nie widzę przekształcenia węzła na formę kanoniczną przed czytanie przez metodę C14N().

      Źle się wyraziłem – przez przekształcenie węzła na formę kanoniczną miałem na myśli właśnie przekazanie go do C14N() i jeżeli w wyniku masz rozjazd, to prawdopodobnie przyczyna leży w sprowadzaniu do formy kanonicznej (o czym zresztą pisałeś na początku komentarza).

      Najłatwiej będzie Ci porównać treści zwracane po C14N z używanego przez Ciebie kodu z treścią zwracaną przez PHP – postaw sobie na boku jakiś serwer z interpretatorem tego języka i porównaj.

    2. Ja własnie skończyłem pisać klasę do podpisywania xml-a w xades-bes i jako że jest to dla mnie temat z cyklu fire & forget, to po zabawie z klasą XmlDsigC14NTransform i węzłem SignedProperties, podejrzałem jak to wygląda, to ni jak się to miało do wyniku funkcji C14n z php.
      Główny problem polega na tym że namespace ds jest dodawane jako atrybut do każdego węzła z przestrzeni nazw ds.

      Jako że leniwy jestem zrezygnowałem w ogóle z używania klas xml i stworzyłem dokument ze stringów i odpowiedniki (dokładnie 3) do liczenia digestów z fragmentami xml w posatci kanonicznej. Nie jest to szczyt inżnieri programistycznej, a raczej brutalna metoda, ale generuje poprawnie podpisany dokument.

  3. Niżej SignedProperties z wygenerowanego pliku xades;
    2017-03-10T10:33:45ZM1roh5UkuBE+LsBK7yUG5Cb2m3Q=17397885708855712043403751717868631362

  4. Bardzo fajny artykuł, fajnie wszystko wyjaśnione wyjaśnia. 🙂
    Mam jednak problemy z weryfikacją wartości skrótów jakie pojawiają się na stronie 🙁 i nie wiem czy dobrze zrozumiałem jak one powstają.

    Chodzi na razie o pierwsze DigestValue ?
    MLaZQl/toVO57dJsz1JfLIyrrd4=

    Trzeba ją wygenerować takim kodem ?
    $dom = new DOMDocument();
    $dom->loadXML($content);
    $canonicalizedXml = $dom->C14N();
    $hash = sha1($canonicalizedXml, true);
    $digestValue = base64_encode($hash);

    Tylko jaką wartość mam wstawić w zmienną $content ?
    To :
    PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiID8+Cjx0ZXN0PgogIDx0ZXN0PnRlc3Rv
    d3kgd8SZemXFgjwvdGVzdD4KICA8dGVzdD5kcnVnaSB0ZXN0b3d5IHfEmXplxYI8L3Rlc3Q+CjwvdGVz
    dD4=

    Bo gdy wstawiam to nie dostaje tego skrótu: MLaZQl/toVO57dJsz1JfLIyrrd4=
    ? 🙁

    Poproszę o pomoc 😉

    1. W $content powinna być po prostu pierwotna treść dokumentu, który podpisujesz – w moim przykładzie to ten testowy XML.

  5. Podstawiłem do zmiennej $content wartość $xml.
    Mój testowy kod wygląda teraz tak:

    $xml = ‘

    testowy węzeł
    drugi testowy węzeł

    ‘;

    echo ‘XML:’;
    echo “

    \n".htmlentities($xml)."\n

    \n”;
    echo “Base64 z XML:”;
    echo base64_encode($xml);

    echo ‘Węzeł do skrótu:’;
    echo “

    \n".htmlentities($xml)."\n

    \n”;

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

    echo ‘Węzeł do skrótu, postać kanoniczna:’;
    echo “

    \n".htmlentities($canonicalizedXml)."\n

    \n”;

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

    echo ‘Skrót (digestValue):’;
    echo $digestValue.”;

    Wartość skrót dostaje:
    4jYUfs46wp95RKzQE4WP0pvAHQQ=

  6. Pisze, żeby podziękować za wpis bo bardzo dużo ułatwił mi w napisaniu własnej implementacji do generowania podpisu. Także dzięki za podzielenie się wiedzą.

  7. Hey thank you very much for this article, I had some issues with the english translation.
    is there any link to download your final code?

    Thanks in advance

    1. Unfortunately not. This article is to show you steps of XAdES-BES document signing with some code snippets, but it doesn’t provide complete solution.

  8. Świetne omówienie 🙂

    Ale po wnikliwej lekturze nasuwają się dwa pytania:

    1. Do funkcji openssl_sign() podajesz jako pierwszy parametr wartość $signedInfo, która jest skanonizowaną postacią tagu “SignedInfo” (string), czy sha1 na tym stringu?
    2. Funkcja c14N() ma parametr exclusive i zauważyłem, że większość gotowych rozwiązań ustawia go na true. Czy w Twoim przypadku przyjęcie wartości domyślnej false jest zamierzone?

    1. Cześć,
      poniżej odpowiedzi:

      1. Do openssl_sign() podaję w $signedInfo po prostu kanoniczną postać SignedInfo.

      2. Wszędzie wywołuję C14N() bez żadnych parametrów. Parametr, o którym piszesz, ma sens tylko wtedy, gdy podajesz też parametr trzeci lub czwarty (zgodnie z http://php.net/manual/en/domnode.c14n.php). Tutaj nie ma potrzeby wybiórczego tworzenia formy kanonicznej, a więc i przekazywania do C14N() żadnych parametrów.

  9. Witam,
    weryfikując własny podpis programem szafir otrzymuje następujący komunikat o błędzie:
    “Podpis został negatywnie zweryfikowany – Nieprawidłowy skrót w obiekcie Reference w podpisie XML.-#ID-object1”.
    Może ktoś z tu z obecnych miał taki problem i potrafi pomóc?????

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Time limit is exhausted. Please reload CAPTCHA.