Podpisywanie plików XML w Javie. Część druga - podpis pliku XML

Przez Rafał Pydyniak | 2018-04-17

Poniższy wpis jest drugą częścią artykułu dotyczącego podpisywania plików XML w Javie. W pierwszej części dotyczącej obsługi podpisów elektronicznych w Javie przedstawiłem, czym są podpisy elektroniczne oraz cyfrowe, a także przedstawiłem sposób, w jaki można odczytać dane certyfikatu z podpisu elektronicznego.

W drugiej części najpierw przybliżę podstawy teorii standardów podpisywania plików XML, a następnie przedstawię proces podpisywania takiego pliku w języku Java. Wpis zawiera najważniejsze części kodu - całość aplikacji demo można także znaleźć pod adresem https://github.com/RafalPydyniak/XmlDigitalSignatureDemo.

Standardy podpisów XML

Podpisywanie pliku XML polega na wygenerowaniu struktury XML, w której zawarte są informacje zarówno o podpisywanym pliku (np. hash danych, które podpisujemy), jak i informacje o samym podpisie (np. wartość podpisu). Co istotne, istnieją standardy definiujące, jak taka struktura powinna wyglądać. Takimi standardami są:

  • XML Signature (XMLDSig)
  • XAdES

XML Signature

XML Signature jest rekomendacją W3C co do tego, jak powinny wyglądać podpisy cyfrowe plików XML. Co ciekawe, mimo nazwy, standard ten pozwala podpisywać nie tylko pliki XML, ale również inne dane, które posiadają adres URL.

Warto zaznaczyć, że XML Signature określa więcej niż jeden sposób wyglądu podpisu. Możemy wyróżnić:

  • Enveloped Signature
  • Enveloping Signature
  • Detached Signature

Powyższe sposoby różnią się miedzy sobą sposobem reprezentowania podpisu: w Enveloped oraz Enveloping Signature podpisywane dane znajdują się bezpośrednio w pliku z podpisem, natomiast w Detached Signature wskazujemy jedynie URL, pod którym znajdują się  podpisywane dane. Wszelkie szczegóły można oczywiście znaleźć w dokumencie opisującym ten standard.

XAdES

XAdES (XML Advanced Electronic Signatures) jest z kolei rozszerzeniem XML Signature definiującym format podpisu elektronicznego. Jest to standard rekomendowany przez Unię Europejską. Jego zastosowanie w Polsce to m.in. wysyłka PIT'ów drogą elektroniczną. XAdES dodaje całą gamę dodatkowych pól do podpisywanego pliku, które różnią się w zależności od wersji samego XAdES'a. Dokładny format XAdES zostanie przedstawiony w późniejszej części wpisu.

Podpisywanie pliku XML w Javie

Skoro mamy już podstawową wiedzę na temat standardów podpisów cyfrowych i elektronicznych, a także wiemy jak wyciągnąć dane o certyfikacie z podpisu elektronicznego, to możemy przystąpić do podpisania samego pliku XML.

Podpisywać będziemy XML'a o następującej zawartości:

<?xml version="1.0" encoding="UTF-8"?>
<example>
  <message>Hello World!</message>
</example> 

Na początku możemy stworzyć interfejs XmlSigner:

 public interface XmlSigner {
    File sign(Document xmlToSign,File destinationFile, PrivateKey privateKey, X509Certificate certificate) throws XmlSigningException;
    File sign(File xmlToSign,File destinationFile, PrivateKey privateKey, X509Certificate certificate) throws XmlSigningException;
}

Mamy w nim dwie wersje metody sign, różniące się jedynie pierwszym parametrem - wynika to z tego, iż chcemy móc dać możliwość wskazania pliku XML do podpisu bądź też samych danych (obiektu klasy org.w3c.Document). Poza danymi w parametrach przekazujemy także certyfikat X509 oraz klucz prywatny - informacje jak wyciągnąć te dane z podpisu elektronicznego (np. Certum, KIR) znajdują się w pierwszej części artykułu.

W obu wersjach metody sign będziemy rzucać wyjątek XmlSigningException - w związku z tym, iż w demo obsługa wyjątków została pominięta to Exception ten będzie leciał zawsze gdy poleci jakikolwiek wyjątek w kodzie - w prawdziwej aplikacji chcemy oczywiście poprawnie obsługiwać wyjątki.

public class XmlSigningException extends Exception{
}

Implementacja interfejsu XmlSigner

Najważniejszym etapem tej części artykułu jest implementacja wcześniej zdefiniowanego interfejsu XmlSigner. Poniżej przedstawiam najważniejsze etapy tej implementacji.

Zacznijmy od samej metody sign:

@Override
    public File sign(Document xmlToSign,File destinationFile, PrivateKey privateKey, X509Certificate certificate) throws XmlSigningException {
        try {
            return tryToSignOrThrow(xmlToSign, destinationFile, privateKey, certificate);
        } catch (Exception e) {
            //TODO this is just a demo, in real life we want to do something better with exceptions
            e.printStackTrace();
            throw new XmlSigningException();
        }
    }

Jak widzimy zawiera ona tak naprawdę jedynie obsługę wyjątków (a przynajmniej powinna ją zawierać ;)). Dalej idąc mamy metodę tryToSignOrThrow:

private File tryToSignOrThrow(Document xmlToSign,  File resultFile, PrivateKey privateKey, X509Certificate certificate) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyException, ParserConfigurationException, MarshalException, XMLSignatureException, TransformerException, FileNotFoundException {
        XMLSignature xmlSignature = prepareXmlSignature(certificate.getPublicKey(), xmlToSign, certificate);
        Document doc = getEmptyDocument();
        DOMSignContext domSignContext = new DOMSignContext(privateKey, doc);
        domSignContext.setDefaultNamespacePrefix("ds");
        xmlSignature.sign(domSignContext);
 
        saveDocumentToFile(doc, resultFile);
        return resultFile;
    }

W kolejnych liniach tej metody wykonujemy kilka czynności:

  • Przygotowujemy obiekt XMLSignature (linijka 2)
  • Tworzymy nowy dokument, do którego zostanie zapisany wyjściowy XML (linia 3)
  • Przygotowujemy obiekt klasy DOMSignContext, przy użyciu, którego dokonujemy następnie podpisu (linie 4-6)
  • Zapisujemy końcowy XML do pliku i go zwracamy (linie 8-9)

Kroki 2-4 powyższej listy są dość proste i raczej nie wymagają dodatkowego wyjaśnienia. Kluczowy jest natomiast krok pierwszy, w którym przygotowujemy całą strukturę zgodną z formatem XAdES, którą możemy podpisać.

private XMLSignature prepareXmlSignature(PublicKey publicKey, Document xmlToSign, X509Certificate certificate) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyException, ParserConfigurationException {
        XMLSignatureFactory xmlSignFactory = XMLSignatureFactory.getInstance("DOM");
        List<XMLObject> objects = getObjectsFromData(xmlToSign, xmlSignFactory, certificate);
        SignedInfo signedInfo = getSignedInfo(xmlSignFactory);
        KeyInfo keyInfo = getKeyInfo(publicKey, xmlSignFactory, certificate);
        return xmlSignFactory.newXMLSignature(signedInfo, keyInfo, objects, "Signature", "SignatureValue");
    }
 
private List<XMLObject> getObjectsFromData(Document xmlToSign, XMLSignatureFactory xmlSignFactory, X509Certificate certificate) throws ParserConfigurationException {
        List<XMLObject> objects = new ArrayList<>();
        XMLObject dataXmlObject = prepareDataXmlObject(xmlToSign, xmlSignFactory);
        XMLObject xadesXmlObject = prepareXadesXmlObject(xmlSignFactory, certificate);
        objects.add(dataXmlObject);
        objects.add(xadesXmlObject);
 
        return objects;
    }
 
private XMLObject prepareDataXmlObject(Document xmlToSign, XMLSignatureFactory xmlSignFactory) {
        XMLStructure content = new DOMStructure(xmlToSign.getFirstChild());
        return xmlSignFactory.newXMLObject(Collections.singletonList(content), "Data", "text/xml", null);
    }
 
private XMLObject prepareXadesXmlObject(XMLSignatureFactory xmlSignFactory, X509Certificate certificate) throws ParserConfigurationException {
        Element qualifyingPropertiesElement = createXadesObject(certificate);
        return xmlSignFactory.newXMLObject(Collections.singletonList(new DOMStructure(qualifyingPropertiesElement)), null, null, null);
    }

W linijce 3 pobieramy (tworzymy) obiekty. Pierwszym z obiektów jest po prostu obiekt z samymi podpisywanymi danymi (choć potencjalnie mogłoby go nie być, a do danych moglibyśmy się odnosić poprzez adres URL - wtedy byłby to "Detached Signature", o którym pisałem wcześniej). Drugim obiektem jest natomiast struktura zdefiniowana w formacie XAdES, zawierająca dodatkowe informacje o podpisie. Tworzenie tych obiektów widać dodatkowo w linijkach 11 i 12. Samo przygotowanie pliku z danymi (w metodzie prepareDataXmlObject) jest bardzo proste i polega jedynie na stworzeniu osobnego obiektu z całością podpisywanych danych. Przygotowanie obiektu XAdES (metoda prepareXadesXmlObject) jest z kolei bardziej skomplikowane i opisane zostanie poniżej.

Przygotowanie obiektu XAdES

Pełna struktura obiektu XAdES jest następująca:

<ds:Object>
   <QualifyingProperties>
     <SignedProperties>
       <SignedSignatureProperties>
         (SigningTime)
         (SigningCertificate)
         (SignaturePolicyIdentifier)
         (SignatureProductionPlace)?
         (SignerRole)?
       </SignedSignatureProperties>
       <SignedDataObjectProperties>
         (DataObjectFormat)*
         (CommitmentTypeIndication)*
         (AllDataObjectsTimeStamp)*
         (IndividualDataObjectsTimeStamp)*
       </SignedDataObjectPropertiesSigned>
     </SignedProperties>
     <UnsignedProperties>
       </UnsignedSignatureProperties>
         (CounterSignature)*
         (SignatureTimeStamp)+
         (CompleteCertificateRefs)
         (CompleteRevocationRefs)
         ((SigAndRefsTimeStamp)*
         (RefsOnlyTimeStamp)*
         (CertificatesValues)
         (RevocationValues)
         (ArchiveTimeStamp)
       </UnsignedSignatureProperties>
     </UnsignedProperties>
   </QualifyingProperties>
 </ds:Object>   

W powyższym kodzie nie wszystkie elementy są wymagane - znaki *, + oraz ? mają takie samo znaczenie jak w wyrażeniach regularnych, a co za tym idzie, wszystkie elementy oznaczone gwiazdką lub znakiem zapytania są opcjonalne. Inna sprawa - schemat, choć pobrany z oficjalnej dokumentacji, nie oddaje wszystkiego. Przykładowo, UnsignedProperties nie są wymagane, mimo że na schemacie tego nie widać. Dodatkowo niektóre z elementów istnieją tylko w wyższych wersjach XAdES'a.

Do podstawowych elementów tego obiektu należą:

  • QualifyingProperties - jest kontenerem dla obiektów SignedProperties i UnsignedProperties
  • SignedProperties - zawiera właściwości, które zostaną podpisane
  • UnsignedProperties - zawiera właściwości, które nie będą podpisywane

Oczywiście dokładny opis każdego z elementów (wraz z definicją XSD) można znaleźć w oficjalnym dokumencie opisującym XAdES'a.

Gdy już wiemy, co powinien zawierać podpis, możemy przejść do jego implementacji. Co istotne, w naszym podpisie zupełnie pominiemy UnsignedProperties, które zazwyczaj nie są wymagane i nie wnoszą wiele do podpisu.

Poniżej najważniejsze części kodu (reszta dostępna na Githubie):

private Element createXadesObject(X509Certificate certificate) throws ParserConfigurationException {
        Document doc = getEmptyDocument();
        Element qualifyingProperties = createQualifyingPropertiesObject(doc);
        Element signedProperties = prepareSignedPropertiesElement(doc, certificate);
        qualifyingProperties.appendChild(signedProperties);
        return qualifyingProperties;
    }
 
private Element createQualifyingPropertiesObject(Document doc) {
        Element qualifyingProperties = createElementInDocument(doc, "xades:QualifyingProperties", "QualifyingProperties");
        qualifyingProperties.setAttribute("Target", "#Signature");
        qualifyingProperties.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xades", "http://uri.etsi.org/01903/v1.3.2#");
        return qualifyingProperties;
    }
 
private Element prepareSignedPropertiesElement(Document doc, X509Certificate certificate) {
        Element signedProperties = createElementInDocument(doc, "xades:SignedProperties","SignedProperties" );
 
        Element signedSignatureProperties = prepareSignedSignatureProperties(doc, certificate);
        Element signedDataObjectProperties = prepareSignedDataObjectProperties(doc);
 
        signedProperties.appendChild(signedSignatureProperties);
        signedProperties.appendChild(signedDataObjectProperties);
        return signedProperties;
    }
 
    private Element prepareSignedSignatureProperties(Document doc, X509Certificate certificate) {
        Element signedSignatureProperties = createElementInDocument(doc, "xades:SignedSignatureProperties", "SignedSignatureProperties");
        Element signingTime = prepareSigningTimeElement(doc);
        Element signingCertificate = prepareSigningCertificateElement(doc, certificate);
        signedSignatureProperties.appendChild(signingTime);
        signedSignatureProperties.appendChild(signingCertificate);
        return signedSignatureProperties;
    }
 
    private Element prepareSigningCertificateElement(Document doc, X509Certificate certificate) {
        Element signingCertificate = doc.createElement("xades:SigningCertificate");
        Element certificateElement = doc.createElement("xades:Cert");
        Element certDigest = doc.createElement("xades:CertDigest");
        certificateElement.appendChild(certDigest);
 
        Element issuerSerial = doc.createElement("xades:IssuerSerial");
        certificateElement.appendChild(issuerSerial);
        Element x509IssuerName = doc.createElement("X509IssuerName");
        issuerSerial.appendChild(x509IssuerName);
        x509IssuerName.appendChild(doc.createTextNode(certificate.getIssuerDN().getName()));
        Element x509SerialNumber = doc.createElement("X509SerialNumber");
        issuerSerial.appendChild(x509SerialNumber);
        x509SerialNumber.appendChild(doc.createTextNode(certificate.getSerialNumber().toString()));
 
        signingCertificate.appendChild(certificateElement);
        return signingCertificate;
    }
 
    private Element prepareSignedDataObjectProperties(Document doc) {
        Element signedDataObjectProperties = doc.createElement("xades:SignedDataObjectProperties");
        signedDataObjectProperties.setAttribute("Id", "SignedDataObjectProperties");
        Element dataObjectFormat = doc.createElement("xades:DataObjectFormat");
        dataObjectFormat.setAttribute("ObjectReference", "#Data-Reference");
        Element mimeType = doc.createElement("xades:MimeType");
        mimeType.appendChild(doc.createTextNode("text/xml"));
        dataObjectFormat.appendChild(mimeType);
        signedDataObjectProperties.appendChild(dataObjectFormat);
 
        Element commitmentTypeIndicationElement = doc.createElement("xades:CommitmentTypeIndication");
        signedDataObjectProperties.appendChild(commitmentTypeIndicationElement);
        Element commitmentTypeId = doc.createElement("xades:CommitmentTypeId");
        commitmentTypeIndicationElement.appendChild(commitmentTypeId);
        Element identifier = doc.createElement("xades:Identifier");
        identifier.appendChild(doc.createTextNode("http://uri.etsi.org/01903/v1.2.2#ProofOfApproval"));
        commitmentTypeId.appendChild(identifier);
        Element allSignedDataObjects = doc.createElement("xades:AllSignedDataObjects");
        commitmentTypeIndicationElement.appendChild(allSignedDataObjects);
        return signedDataObjectProperties;
    }

I kolejno:

  • W linijce 3 tworzymy element QualifyingProperties
  • W linijce 4 tworzymy element SignedProperties, który następnie dodajemy jako "dziecko" elementu QualifyingProperties w linijce 5
  • Samo tworzenie SignedProperties odbywa się w metodzie prepareSignedPropertiesElement. Tworzone są dwa elementy: SignedSignatureProperties oraz SignedDataObjectProperties, które następnie dodawane są pod elementem "signedProperties"

W powyższym zrzucie kodu można ponadto znaleźć informacje o tworzeniu różnych elementów, takich jak np. IssuerSerial czy SigningTime, ale są to już szczegóły implementacyjne - dokładny opis, co oznacza każdy z tworzonych elementów, można znaleźć w dokumentacji, natomiast sam proces ich tworzenia jest na tyle prosty, że raczej nie trzeba go szczegółowo opisywać. Generalna idea jest taka, że tworzymy elementy z odpowiednimi danymi i dopinamy je do innych, bardziej ogólnych elementów.

Wygenerowanie danych z standardu XML Signature

Gdy już mamy nasze obiekty, jeden z danymi, a drugi z informacjami XAdES, to następnym krokiem jest wygenerowanie elementów określonych w XML Signature. Uproszczony schemat XML Signature znajduje się poniżej:

<Signature>
  <SignedInfo>
    <CanonicalizationMethod />
    <SignatureMethod />
    <Reference>
       <Transforms />
       <DigestMethod />
       <DigestValue />
    </Reference>
    <Reference /> etc.
  </SignedInfo>
  <SignatureValue />
  <KeyInfo />
  <Object />
</Signature>

Elementy, które musimy sami wygenerować to SignedInfo oraz KeyInfo. Element SignatureValue zostanie za nas wygenerowany podczas samego podpisu, który jest realizowany przy pomocy klasy XMLSignatureFactory.

W wcześniej przytoczonym fragmencie z metodą prepareXmlSignature:

private XMLSignature prepareXmlSignature(PublicKey publicKey, Document xmlToSign, X509Certificate certificate) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyException, ParserConfigurationException {
        XMLSignatureFactory xmlSignFactory = XMLSignatureFactory.getInstance("DOM");
        List<XMLObject> objects = getObjectsFromData(xmlToSign, xmlSignFactory, certificate);
        SignedInfo signedInfo = getSignedInfo(xmlSignFactory);
        KeyInfo keyInfo = getKeyInfo(publicKey, xmlSignFactory, certificate);
        return xmlSignFactory.newXMLSignature(signedInfo, keyInfo, objects, "Signature", "SignatureValue");
    }

Po wygenerowaniu obiektów, w liniach 4-5, generowane są właśnie elementy signedInfo oraz keyInfo metodami getSignedInfo oraz getKeyInfo. Poniżej przedstawiony jest ich kod:

private SignedInfo getSignedInfo(XMLSignatureFactory xmlSignFactory) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        Reference dataReference = createDataReference(xmlSignFactory, "#Data", null, "Data-Reference");
        SignatureMethod signatureMethod = xmlSignFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
        CanonicalizationMethod canonicalizationMethod = xmlSignFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null);
        return xmlSignFactory.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(dataReference));
    }
 
    private Reference createDataReference(XMLSignatureFactory xmlSignFactory, String s, String o, String s2) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        DigestMethod digestMethod = xmlSignFactory.newDigestMethod(DigestMethod.SHA1, null);
        Transform transform = xmlSignFactory.newTransform(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS, (TransformParameterSpec) null);
        return xmlSignFactory.newReference(s, digestMethod, Collections.singletonList(transform), o, s2);
    }
 
private KeyInfo getKeyInfo(PublicKey publicKey, XMLSignatureFactory xmlSignFactory, X509Certificate certificate) throws KeyException {
        KeyInfoFactory keyInfoFactory = xmlSignFactory.getKeyInfoFactory();
        X509Data x509Data = keyInfoFactory.newX509Data(Collections.singletonList(certificate));
        KeyValue keyValue = keyInfoFactory.newKeyValue(publicKey);
        return keyInfoFactory.newKeyInfo(Arrays.asList(keyValue, x509Data));
    }

SignedInfo zgodnie z specyfikacją zawiera referencje do podpisywanych danych oraz bardziej szczegółowe informacje o tym jak podpisywane były dane (np. algorytm generowania hasha). Najważniejsze miejsca kodu:

  • W linii 2 tworzona jest referencja do podpisywanych danych (którym wcześniej nadaliśmy odpowiednie ID - Data)
  • W linii 3 określamy algorytm hashowania danych - w naszym przypadku użyjemy RSA_SHA1
  • W linii 4 określamy metodę kanonizacji (Canonicalization Method), czyli algorytm transformacji, który zostanie użyty na danych przed ich podpisem
  • W linii 5 tworzymy ostatecznie obiekt klasy SignedInfo przy użyciu XmlSignFactory, któremu w parametrach podajemy obiekty stworzone w wcześniej opisanych liniach 2-4

Oprócz SignedInfo tworzymy także KeyInfo, czyli strukturę zawierającą informacje o użytym kluczu. Samym stworzeniem KeyInfo zajmuje się KeyInfoFactory, któremu należy jedynie dostarczyć klucz publiczny i dane certyfikatu X509.

Użycie aplikacji

Aby skorzystać z aplikacji, użyjemy poniższej klasy Main:

public class Main {
    /**
     * Just a demo - exceptions are NOT handled and they SHOULD be.
     * Exception can occur at many places like giving wrong PIN or picking not existing slot
     * @param args
     * @throws Exception
     */
    public static void main(String... args) throws Exception {
        SmartCardCertificate certificate = getCertificate();
        XmlSigner xmlSigner = new XadesXmlSigner();
        File signedFile = new File("out/signed.sig");
        xmlSigner.sign(new File("test_xml_to_sign.xml"), signedFile, certificate.getPrivateKey(), certificate.getX509Certificate());
        System.out.println("Signed file saved in: " + signedFile.getAbsolutePath());
    }
 
    private static SmartCardCertificate getCertificate() throws TokenException, IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
 
 
        SmartCardReader smartCardReader = new GeneralSmartCardReader("CCPkiP11.dll");
        List<Token> avaliableTokens = smartCardReader.getAvaliableTokens();
        avaliableTokens.forEach(System.out::println);
        System.out.println("Pick which token to use: ");
        String slotId = br.readLine();
        System.out.println("Enter pin:");
        String pin = br.readLine();
        List<SmartCardCertificate> certificates = smartCardReader.getCertificates(Integer.parseInt(slotId), pin.toCharArray());
        for (int i=0; i<certificates.size(); i++) {
            SmartCardCertificate certificate = certificates.get(i);
            System.out.println(i+": " + certificate.getAlias());
        }
 
        System.out.println("Pick certificate:");
        String pickedCertificate = br.readLine();
 
        return certificates.get(Integer.parseInt(pickedCertificate));
    }
}

Większa część powyższej klasy, dotycząca pobierania certyfikatu z podpisu elektronicznego, pojawiła się już wcześniej w pierwszej części artykułu. Jedyne zmiany to tak naprawdę linijki 10-13, w których dokonujemy podpisu pliku "test_xml_to_sign.xml" i zapisujemy rezultat w katalogu out/ jako signed.sig.

Efekt uruchomienia aplikacji jest następujący:

Wybór certyfikatu
Wybór certyfikatu

Początek wygląda dokładnie tak samo jak w pierwszej części artykułu - wybieramy token, wpisujemy pin, a następnie wybieramy jeden z dostępnych certyfikatów.

Następnie w tle podpisywany jest plik i otrzymujemy informacje o tym, gdzie został on zapisany:

Informacja o poprawnym zapisie podpisanego pliku
Informacja o poprawnym zapisie podpisanego pliku

Gdy otworzymy ten plik, zobaczymy mniej więcej taką zawartość:

Zawartość podpisanego XML'a
Zawartość podpisanego XML'a

Jeśli natomiast otworzymy ten plik w aplikacji sprawdzającej podpisy (np. Szafir), to będziemy mogli zobaczyć, że podpis rzeczywiście jest rozpoznawany i informacje o podpisującym wyświetlają się poprawnie:

Podpisany plik - widok w Szafirze
Podpisany plik - widok w Szafirze

Podsumowanie

Podsumowując, w pierwszej części artykułu dowiedzieliśmy się czym właściwie są podpisu elektroniczne i czym różnią się od podpisów cyfrowych, a także udało nam się dostać do danych zapisanych na podpisie elektronicznym. W drugiej części artykułu wykorzystaliśmy natomiast dane zawarte w podpisie elektronicznym do wykonania podpisu pliku XML zgodnego z standardem XAdES. Udało nam się także zweryfikować w zewnętrznej aplikacji, że taki podpis jest poprawnie widziany i odczytywany.

Copyright: Rafał Pydyniak