SecureFile2 na MacOS

SecureFile2 na MacOS

SecureFile2 można jeszcze (choć z trudem) uruchomić na MacOS.

Java

Jeśli masz już działające logowanie do strony GIIF z przeglądarki, to pomiń ten krok.

Należy pobrać Javę w wersji 8

Tak pobraną Javę należy zainstalować. Celowo pobrana została wersja x86 64-bit. Używana przez SecureFile2 biblioteka do obsługi podpisu elektronicznego autorstwa KIR nie ma wersji obsługującej procesory Apple Silicon. MacOS użyje Rossetta do uruchomienia tej Javy, to podobno działa (ale jedyne jabłuszko, jakie udostępnono mi do testów ma procesor Intela, więc powtarzam jedynie opinie zasłyszane telefonicznie).

Pobranie SecureFile2

Pobieram ze strony systemu testowego aktualną wersję.

NIE ROZPAKOWUJĘ używając narzędzi graficznych (klikając na archiwum). Mac na którym testowałem, uniemożliwił mi uruchomienie plików jar, które wypakowałem graficznie.

Rozpakowanie

Rozpakowuję z terminala. Terminal otwieram z Launchpada w Docku wpisując term. W terminalu wydaję komendę:

1
cd ~/Downloads/

i jestem witany poniższym zapytaniem, które zatwierdzam OK.

następnie rozpakowuję SecureFile2 i uruchamiam:

1
2
3
tar xzf SecureFile2_macosx.tar.gz
cd SecureFile2_macosx
java -jar SecureFile2-macosx.jar

Jeśli nie będzie problemów, to powinno pojawić się okno programu:

Jeśli będą problemy to do maila ze zgłoszeniem błędu należy skopiować zawartość terminala, od polecenia java -jar ... do końca.

Ponowne uruchomienie

Jeśli udało się w poprzednim kroku zainstalować i uruchomić SecureFile2. To kolejne uruchomienie można wykonać w terminalu komendą:

1
cd ~/Downloads/SecureFile2_macosx && java -jar SecureFile2-macosx.jar

Dodatkowy hint

Walidator transakcji wygląda jak program Windowsowy, ale to tylko złudzenie. W środku jest to taki sam plik jar jak SecureFile2. Po pobraniu można go uruchomić w terminalu:

1
java -jar ~/Downloads/giif-xml2018-cli.exe --help

Pieczęć elektroniczna w pliku P12, graficznie

Pieczęć elektroniczna w pliku P12

Mniej bezpieczna, ale prostsza metoda na dodanie pieczęci elektronicznej z pliku P12 do systemu.

Podstrona do konfiguracji Szafira.

Moduł do podpisywania na stronie można konfigurować. W tym celu należy wejść na stronę: https://test.giif.mofnet.gov.pl/#/glowna/settings w systemie testowym lub na stronę: https://www.giif.mofnet.gov.pl/#/glowna/settings w systemie produkcyjnym.

** Konfiguracja jest ściśle powiązana ze środowiskiem.** Konfiguracja ze środowiska testowego dotyczy włącznie środowiska testowego i nie ma wpływu na środowisko produkcyjne. Podobnie konfiguracja środowiska produkcyjnego dotyczy wyłącznie środowiska produkcyjnego i nie ma wpływu na środowisko testowe. Konfiguracja jest przechowywana wewnątrz przeglądarki, konfiguracja dla Firefoxa nie wpływa na Chrome, podobnie konfiguracja przeprowadzona na koncie Administrator nie wpływa na konta użytkowników.

Konfiguracja Szafir SDK

Szafir SDK korzysta z pliku konfiguracjynego settings.xml, w którym wskazane są certyfikaty kwalifikowanych urzędów certyfikacji oraz biblioteki PKCS#11 do komunikacji z kartami z podpisami elektronicznymi.

Domyślnie konfiguracja pobierana jest z serwera, ale można ją nadpisać. W tym celu w sekcji Szafir SDK zmieniamy rodzaj ustawień z ustawienia domyślne na ustawienia lokalne, to powinno włączyć edycję w textarea podpisanym Ustawienia kontrolki. W tymże textarea znajduje się treść pliku settings.xml.

Domyślnie pod koniec tego pliku, są taki podobne do poniższych:

1
2
3
4
5
    <HardwareProvider>
<Name>PKCS#11 Type A</Name>
<URI>SimplySignPKCS64.dll</URI>
</HardwareProvider>
</CryptoProviders>

Modyfikujemy ten tekst tak, aby przypominał poniższy:

1
2
3
4
5
6
7
8
9
    <HardwareProvider>
<Name>PKCS#11 Type A</Name>
<URI>SimplySignPKCS_64-MS-1.0.20.so</URI>
</HardwareProvider>
<SoftwareProvider>
<Name>PKCS#12</Name>
<URI>file:///e:/katalog/pieczec.p12</URI>
</SoftwareProvider>
</CryptoProviders>

Oczywiście w miejsce e: wstawiamy literę dysku, na którym znajduje się plik pieczęci, katalog zastępujemy ścieżką, a pieczec.p12 zastępujemy nazwą pliku z pieczęcią. Wewnątrz tagu URI nie stosujemy Windowsowego backslasha \\, ale slash / taki jak w adresach stron www.

Po modyfikacji pliku klikamy przycisk Zapisz ustawienia Szafir SDK.

Strona GIIF nie widzi kart Certum.

Przed kilkoma dniami musiałem skorzystać z obywatel.gov.pl i zainstalowałem aplikację Podpis Kwalifikowany. Po jej zainstalowaniu przestało działać podpisywanie dokumentów na stronie GIIFa z użyciem karty Certum. Podobny problem może dotknąć użytkowników nowo wystawionych przez Certum kart, w tym wypadku Szafir SDK widzi podpis na karcie, ale próba podpisania dokumentu kończy się błędem.

Geneza problemu

Wraz z instalacją aplikacji Podpis Kwalifikowany w moim katalogu tymczasowym: c:\Users\ksm\AppData\Local\Temp pojawiły się pliki cryptoCertum3PKCS.dll oraz cryptoCertum3PKCS-64.dll.

Przy próbie podpisania czegokolwiek na stronie GIIF, w konsoli Szafir SDK pojawia się poniższy błąd (około linii 175):

1
2
Wed Dec 09 11:45:04 CET 2020 C:\Users\ksm\AppData\Local\Temp\cryptoCertum3PKCS.dll- PKCS11Exception: %1 nie jest prawidlowa aplikacja systemu Win32.
C:\Users\ksm\AppData\Local\Temp\cryptoCertum3PKCS.dll

Powyższy błąd oznacza, że plik cryptoCertum3PKCS.dll ma inną architekturę niż domyślna Java w komputerze. W moim przypadku jest zainstalowana Java dla architektury x64, a biblioteka cryptoCertum3PKCS.dll wrzucona przez Podpis Kwalifikowany jest dla architektury x86, razem tworzą mieszankę wybuchową.

Dla nowych kart wystawionych przez Certum, problemem jest zbyt stara wersja biblioteki cryptoCertum3PKCS.dll w katalogu tymczasowym.

Obejście problemu.

Użytkownicy zgłosili, że dla nowych kart Certum wymagany jest restart komputera, co wydaje mi się logiczne, Szafir SDK ma załadowaną starą bibliotekę i dopiero przy kolejnym uruchomieniu SzafiirHosta wczyta biblotekę Certum ze wskazanej lokalizacji.

Program do walidacji plików

Udostępniliśmy do pobrania off-line’owy walidator plików.

Jest to aplikacja uruchamiana z linii poleceń, do jej uruchomienia wymagana jest Java w wersji 1.8 lub wyższej.

Pod Windows można ją uruchomić jako:

1
giif-xml2018-cli.exe --help

Pod Linuksem i OS-X jako:

1
java -jar giif-xml2018-cli.exe --help

W rezultacie otrzymujemy wynik podobny do tego:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
maj 20, 2020 1:46:30 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 2.4.8 (Final)
maj 20, 2020 1:46:30 PM org.jboss.weld.environment.deployment.discovery.ReflectionDiscoveryStrategy processAnnotatedDiscovery
INFO: WELD-ENV-000014: Falling back to Java Reflection for bean-discovery-mode="annotated" discovery. Add org.jboss:jandex to the classpath to speed-up startup.
maj 20, 2020 1:46:30 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
maj 20, 2020 1:46:31 PM org.jboss.weld.environment.se.WeldContainer fireContainerInitializedEvent
INFO: WELD-ENV-002003: Weld SE container STATIC_INSTANCE initialized
Usage: <main class> [-hV] [COMMAND]
Prosta apka do walidacji plików.
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Commands:
validate Walidacja zgodno?ci z regu?ami i schem?.
rule-validate Walidacja zgodno?ci z regu?ami.
xsd-validate Waliduj zgodno?? z XSD.
Weld SE container STATIC_INSTANCE shut down by shutdown hook

Linie od 1 do 8 to log uruchamiania programu, jest on wypisywany na STDERR więc można go przekierować do /dev/null. Logi są wypisywane przez java.utils.logging, więc można je skonfigurować jak w każdym innym programie javowym przez, przez dodanie -Djava.util.logging.config.file=logging.properties przed -jar

Linie 9 do 15 to pomoc do programu.

Sprawdzenie zgodności ze schemą i regułąmi

1
java -jar giif-xml2018-cli.exe validate --help 2> /dev/null
1
2
3
4
5
6
7
8
9
10
11
12
Usage: <main class> validate [-hqV] [-f=<outputFormat>] [-o=<outputFile>]
plik...
Walidacja zgodno?ci z regu?ami i schem?.
plik... Pliki do walidacji.
-f, --format=<outputFormat>
Format wyniku walidacji: TEXT, JSON, CSV, domy?lnie TEXT.
-h, --help Show this help message and exit.
-o, --output=<outputFile>
Plik wynikowy, domy?lnie STDOUT.
-q, --quiet Nie wypisuj komunikatów na STDOUT.
-V, --version Print version information and exit.
Weld SE container STATIC_INSTANCE shut down by shutdown hook

Przykład użycia

1
2
3
4
5
6
7
8
9
10
11
12
13
$ java -jar giif-xml2018-cli.exe validate /tmp/test.xml 
test.xml: {
położenieKarty: Dokument/TrescDokumentu/Karta[2]/DanePodmiotu[2]/ZleceniodawcaOsobaFizyczna/OsobaFizyczna/AdresZamieszkania/KodPocztowy
aktualnaKarta: Rekord 1 » 1
wyjątek: cvc-complex-type.2.4.b: The content of element 't:KodPocztowy' is not complete. One of '{"http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/":KodPL, "http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/":Kod}' is expected.
}
{
położenieKarty: po tagu zamykającym Dokument/TrescDokumentu/Karta[2]/DanePodmiotu[2]/ZleceniodawcaOsobaFizyczna/OsobaFizyczna/AdresZamieszkania
aktualnaKarta: Rekord 1 » 1
wyjątek: cvc-complex-type.2.4.a: Invalid content was found starting with element 'OsFizycznaOsobaUpowazniona'. One of '{"http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/":DzialalnoscGosp, "http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/":KrajUrodzeniaOsoby}' is expected.
}
Plik niepoprawny: /tmp/test.xml
Weld SE container STATIC_INSTANCE shut down by shutdown hook

Sprawdzenie zgodności ze schemą

1
java -jar giif-xml2018-cli.exe xsd-validate --help 2> /dev/null
1
2
3
4
5
6
7
8
9
10
11
12
Usage: <main class> xsd-validate [-hqV] [-f=<outputFormat>] [-o=<outputFile>]
plik...
Waliduj zgodność z XSD.
plik... Pliki do walidacji.
-f, --format=<outputFormat>
Format wyniku walidacji: TEXT, JSON, CSV, domyślnie TEXT.
-h, --help Show this help message and exit.
-o, --output=<outputFile>
Plik wynikowy, domyślnie STDOUT.
-q, --quiet Nie wypisuj komunikatu błędu na STDOUT.
-V, --version Print version information and exit.
Weld SE container STATIC_INSTANCE shut down by shutdown hook

Jeżeli żaden plik nie zawierał błędu aplikacja zwróci status 0.

Jeżeli co najmniej jeden z przetwarzanych plików zawierał błędy aplikacja zwróci 1.

Przykład

1
2
3
$ java -jar xml2018-cli-1.0.1.jar xsd-validate ~/Downloads/wymiana_export_0000000000_Transakcja_testowa\(1\).xml 2> /dev/null
Plik poprawny: /home/ksm/Downloads/wymiana_export_0000000000_Transakcja_testowa(1).xml
Weld SE container STATIC_INSTANCE shut down by shutdown hook

Przykład z błędem

1
2
3
$ java -jar giif-xml2018-cli.exe xsd-validate -f CSV -o wymiana.csv ~/Downloads/wymiana_export_0000000000_Transakcja_testowa\(1\).xml 2> /dev/null
Plik niepoprawny: /home/ksm/Downloads/wymiana_export_0000000000_Transakcja_testowa(1).xml
Weld SE container STATIC_INSTANCE shut down by shutdown hook

Powyższe polecenie sprawdza plik i zapisuje napotkane błędy do pliku wymiana.csv w formacie CSV. Utworzony plik wymiana CSV

1
2
Plik,Typ,Położenie karty,Karta po tagu,Identyfikator karty,Poprzedni identyfikator,Położenie błędu,Błąd po tagu,Komunikat,Dotyczy tagów,Wartość,Wyjątek
wymiana_export_0000000000_Transakcja_testowa(1).xml,BŁĄD,,/Dokument,Rekord Transakcja testowa,Przed pierwszym rekordem.,,,,,,"cvc-complex-type.2.4.a: Invalid content was found starting with element 't:DokumentTozsamosci'. One of '{""http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/"":PESEL, ""http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/"":DataUrodzenia}' is expected."

Sprawdzenie zgodności z regułami

1
java -jar giif-xml2018-cli.exe rule-validate --help 2> /dev/null
1
2
3
4
5
6
7
8
9
10
11
12
Usage: <main class> rule-validate [-hqV] [-f=<outputFormat>] [-o=<outputFile>]
plik...
Walidacja zgodności z regułami.
plik... Pliki do walidacji.
-f, --format=<outputFormat>
Format wyniku walidacji: TEXT, JSON, CSV, domyślnie TEXT.
-h, --help Show this help message and exit.
-o, --output=<outputFile>
Plik wynikowy, domyślnie STDOUT.
-q, --quiet Nie wypisuj komunikatu błędu na STDOUT.
-V, --version Print version information and exit.
Weld SE container STATIC_INSTANCE shut down by shutdown hook

Jeżeli żaden plik nie zawierał błędu aplikacja zwróci status 0.

Jeżeli co najmniej jeden z przetwarzanych plików zawierał błędy aplikacja zwróci 1.

Przykład

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ java -jar giif-xml2018-cli.exe validate ~/Downloads/wymiana_export_0000000000_Transakcja_testowa.xml 2>/dev/null
wymiana_export_0000000000_Transakcja_testowa.xml: {
położenieKarty: Dokument/TrescDokumentu/Karta
aktualnaKarta: Rekord Transakcja testowa
położenieBłędu: TrZlecanaPrzezKlienta/ZleceniodawcaOsobaFizyczna/OsFizycznaKlientIO/PESEL
komunikat: Niepoprawna suma kontrolna numeru PESEL.
wartość: 00000000001
}
{
położenieKarty: Dokument/TrescDokumentu/Karta
aktualnaKarta: Rekord Transakcja testowa
położenieBłędu: TrZlecanaPrzezKlienta/ZleceniodawcaOsobaFizyczna/OsFizycznaKlientIO/DokumentTozsamosci/SeriaINumerDokumentu
komunikat: Niepoprawna suma kontrolna numeru dowodu osobistego.
wartość: AAA000001
}
Plik niepoprawny: /home/ksm/Downloads/wymiana_export_0000000000_Transakcja_testowa.xml
Weld SE container STATIC_INSTANCE shut down by shutdown hook

Kolejne zmiany w walidatorze

Zmiany w walidatorze off-line

Zmianie uległ walidator udostępniany off-line.

Zmiana znaczenia komendy validate

Komenda validate wykonuje teraz sprawdzenie zgodności ze schemą, a następnie sprawdzenie zgodności z regułami.

Zmiana nazwy komendy validate na rule-validate.

Stara komenda validate wykonująca wyłącznie sprawdzenie zgodności z regułami występuje pod nazwą rule-validate

Poprawka do lokalizacji błędu schemy

Walidator poda element nadrzędny do miejsca wystąpienia błędu, oraz poprzednio przetwarzany (właśnie zamknięty) element.

Walidacja formatu numeru IBANu.

Dotychczas walidator, sprawdzał poprawnośc numeru IBAN wyłącznie na podstawie weryfikacji sumy kontrolnej. Umożliwiało to przesyłanie jako numerów IBAN numerów rachunków, które na na trzeciej lub czwartej pozycji zawierały litery. Aktualnie sprawdzena będzie poprawnośc numeru (wg. wzorca [A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}) i suma kontrolna.

Poprawka dla walidacji numeru PESEL.

Element PESEL, jeżeli jest użyty nie może być pusty.

Poprawki w walidacji kodów pocztowych

W adresach, wewnątrz elementu KodPocztowy wymagany jest niepusty element Kod lub KodPL.

Najczęściej występujące błędy w walidacji XSD

Włączenie walidacji XSD, może spowodować pojawienie się nowych błędów, najczęstsze z nich to:

cvc-pattern-valid

Znak końca linii w polu tekstowym

Objawia się parą błędów:

1
2
cvc-pattern-valid: Value 'KILKA LINIJEK TEKSTU' is not facet-valid with respect to pattern '.*' for type 'TUwagi'.
cvc-type.3.1.3: The value 'Te same KILKA LINIEJEK TEKSTU' of element 'Uwagi' is not valid.

Może dotyczyć pola TytulTr i Uwagi na kartach transakcji, jak również pola Nazwa dla osoby prawnej i danych uproszczonych beneficjenta, DaneAdresowe w danych uproszczonych beneficjenta.

W polach tekstowych, dla których schema narzuca wzorzec wyrażenia regularnego .*, nie mogą występować znaki nowej linii. Polecam wykonanie normalizacji białych znaków, tj. zastąpienie tabulatorów i znaków końca linii spacjami, a następnie zastąpienie powtórzonych spacji, pojedynczą spacją.

Niezgodność treści ze wzorcem.

Objawia się parą błędów:

1
2
cvc-pattern-valid: Value '' is not facet-valid with respect to pattern '\d{11}' for type 'TPESEL'.
cvc-type.3.1.3: The value '' of element 't:PESEL' is not valid.

Należy dostosować pisownię treści do wzorca. Warto zwrócić uwagę, że podanie pustego elementu, nie jest równoważne pominięciu elementu. Element, który ma w schemie atrybut minOccurs="0" ale ma ustawiony wzorzec dla typu, musi być pominięty lub mieć wartość zgodną ze wzorcem.

cvc-complex-type.2.4.a

Niewłaściwa kolejność elementów

Objawia się parą błędów, na przykład:

1
2
cvc-complex-type.2.4.d: Invalid content was found starting with element 't:KodPocztowy'. No child element is expected at this point.
cvc-complex-type.2.4.a: Invalid content was found starting with element 't:Ulica'. One of '{""http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/"":KodPocztowy}' is expected.

W schemie używane jest grupowanie elementów przy użyciu typu xsd:sequence, nakłada to na implementację obowiązek zachowania tej samej kolejności elementów co w schemie.

Należy sprawdzić w schemie w jakim typie elementu występuje element, który spowodował błąd, i dostosować kolejność elementów do schemy.

Niewłaściwy namespace elementu

Objawia się poniższym błędem, proszę zwrócić uwagę na brak prefixu w elemencie AdresZamieszkania:

1
cvc-complex-type.2.4.a: Invalid content was found starting with element 'AdresZamieszkania'. One of '{""http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/"":AdresZamieszkania, ""http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/"":DzialalnoscGosp, ""http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/"":KrajUrodzeniaOsoby}' is expected.

Należy uzupełnić element o prefix lub deklaracje namespace.

Niewłaściwy element

Objawia się takim samym komunikatem jak błąd powyżej.

Może oznaczać:

  • literówkę w nazwie elementu, warto zwrócić uwagę na OsFizycznaOsobaUpowaniona w karcie transferu i OsFizycznaOsobaUpowa**z**niona na innych kartach,
  • umieszczenie elementu na niewłaściwym poziomie struktury, np. osoby upoważnionej wewnątrz danych osoby fizycznej, a nie obok.

Należy poprawić pisownię lub przenieść elementy na właściwy poziom struktury.

cvc-complex-type.2.4.b

Objawia się błędem podobnym do:

1
cvc-complex-type.2.4.b: The content of element 't:KodPocztowy' is not complete. One of '{""http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/"":KodPL, ""http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2018/10/10/giif/typy/"":Kod}' is expected.

Brakuje zawartości w miejscu gdzie jest ona wymagana, należy uzupełnić zawartość, lub jeśli element jest opcjonalny (ma minOccur="0" w schemie) zupełnie go pominąć.

cvc-complex-type.3.2.2

Objawia się błędem podobnym do:

1
cvc-complex-type.3.2.2: Attribute 'Korekta' is not allowed to appear in element 'Karta'.

Może być spowodowany błędną pisownią nazwy atrybutu, nazwy atrybutów są wrażliwe na wielkość znaków!!!

cvc-datatype-valid.1.2.1

Objawia się błędem:

1
cvc-datatype-valid.1.2.1: '1970-01-01T00:00:00' is not a valid value for 'date'.

W polach typu xsd:date, można umieszczać wyłącznie datę, nie może być ona uzupełniona o czas, czyli nie wolno dodawać tam T00:00:00.

Zmiany w systemie testowym

W systemie testowym została uruchomiona taka sama walidacja plików jak w walidatorze off-line.

Pusta nazwa przesłanego pliku z transakcjami

Problem

Jeden z integratorów zgłosił następujący problem:

Pliki przesłane przez API mają pustą nazwę (a na stronie www nazwa prezentowana jest jako null).

Przyczyna problemu

Ani aktualna Ustawa, ani rozporządzenia wykonawcze nie narzucają nazewnictwa plików przesyłanych do GIIF. Dlatego API nie wymusza podawania nazwy pliku. Plik przesłany przez przeglądarkę ma domyślnie przekazaną nazwę pliku, ale przy wywołaniach API, oprogramowanie musi wprost wskazać nazwę pliku.

Rozwiązanie

W dokumentacji API znajdziemy wzmiankę wskzaującą na wykorzystanie nagłówka Content-Disposition: „Nazwę pliku można przekazać ustawiając nagłówek Content-Disposition na attachment z nazwą pliku w polu filename lub filename*”.

Przykład

Bez polskich znaków

W tym miejscu odwołam się do kwietniowego wpisu Flow-część-3 W tamtym wpisie wysyłałem plik o nazwie gotowka.xml.enc do instytucji o nipie 0123456789, jednakże oryginalne wywołanie:

1
2
curl --data-binary @gotowka.xml.enc \
https://test.giif.mofnet.gov.pl/api/rest2018/instytucje/0123456789/pliki/

nie przesyłało nazwy pliku. Dodaję więc nagłówek:

1
2
3
curl --data-binary @gotowka.xml.enc \
--header 'Content-Disposition: attachment; filename="gotowka.xml.enc"' \
https://test.giif.mofnet.gov.pl/api/rest2018/instytucje/0123456789/pliki/

W rezultacie otrzymuję następującego XMLa:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<plk:plik xmlns:plk="http://www.giif.mofnet.gov.pl/xsd/rest/pliki20171017"
id="7659"
nazwa="gotowka.xml.enc"
hashZaszyfrowany="47F0D3338A4DDE8657B49F5B9CFAC8C088C27BDB4855C7BC1E99EBC94C125C06"
status="Z"
dataOtrzymania="2019-12-13T07:44:31"/>

Jak widać pojawiła się nazwa pliku.

Z polskimi znakami

Powyższy przykład działa wyłącznie wtedy, gdy nazwa pliku ogranicza się do znaków ASCII. Zastosowanie polskich znaków w nazwach plików jest możliwe, ale wymaga zastosowania atrybutu filename* w nagłówku Content-Disposition.

Atrybut filename* zawiera nazwę kodowania, a następnie nazwę pliku w tym kodowaniu, z bajtami spoza zakresu drukowalnych znaków ASCII zapisanymi jako hex encoded binary.

Na przykład nazwa pliku: test-aącćeęlłnńoósśzźzż-AĄCĆEĘLŁNŃOÓSŚZŹZŻ.xml zostanie zamieniona na: UTF-8''test-a%c4%85c%c4%87e%c4%99l%c5%82n%c5%84o%c3%b3s%c5%9bz%c5%baz%c5%bc-A%c4%84C%c4%86E%c4%98L%c5%81N%c5%83O%c3%93S%c5%9aZ%c5%b9Z%c5%bb.xml

Przykładowe wywołanie curl.

1
2
3
curl --data-binary @gotowka.xml.enc \
--header "Content-Disposition: attachment; filename=\"test-aacceel_nnoosszzzz-AACCEEL_NNOOSSZZZZ.xml\"; filename*=UTF-8''test-a%c4%85c%c4%87e%c4%99l%c5%82n%c5%84o%c3%b3s%c5%9bz%c5%baz%c5%bc-A%c4%84C%c4%86E%c4%98L%c5%81N%c5%83O%c3%93S%c5%9aZ%c5%b9Z%c5%bb.xml" \
https://test.giif.mofnet.gov.pl/api/rest2018/instytucje/0123456789/pliki/

Warto zwrócić uwagę na użycie cudzysłowów zamiast apostrofów do otoczenia nagłówka Content-Disposition. Atrybut filename* używa apostrofów, a w bashu cytowanie apostrofami wyłącza escape-sequences. Zamiast tego użyłem cydzysłowów i zacytowałem cudzysłowy w atrybucie filename. Po przetworzeniu przez basha, faktyczna wartość przekazana po parametrze --header będzie miała postać: Content-Disposition: attachment; filename="test-aacceel_nnoosszzzz-AACCEEL_NNOOSSZZZZ.xml"; filename*=UTF-8''test-a%c4%85c%c4%87e%c4%99l%c5%82n%c5%84o%c3%b3s%c5%9bz%c5%baz%c5%bc-A%c4%84C%c4%86E%c4%98L%c5%81N%c5%83O%c3%93S%c5%9aZ%c5%b9Z%c5%bb.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<plk:plik xmlns:plk="http://www.giif.mofnet.gov.pl/xsd/rest/pliki20171017"
id="7660"
nazwa="test-aącćeęlłnńoósśzźzż-AĄCĆEĘLŁNŃOÓSŚZŹZŻ.xml"
hashZaszyfrowany="47F0D3338A4DDE8657B49F5B9CFAC8C088C27BDB4855C7BC1E99EBC94C125C06"
status="Z"
dataOtrzymania="2019-12-13T08:18:31"/>

Dla piszących w Javie dorzucam klasę narzędziową, której używam do generownia nazw plików:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package pl.gov.mofnet.giif.rest.api;

import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
import java.util.Arrays;

public class ApiUtils {

/**
* Tworzy zawartość nagłówka Content-Disposition dla załącznika o wskazanej nazwie.
*
* @param fileName nazwa pliku, którego nazwa ma być przekazana
* @return treść nagłówka Content-Disposition dla wskazanej nazwy pliku
*/
public static String buildContentDispositionFromFileName(String fileName) {
return "attachment; filename=\"" + ApiUtils.toAscii(fileName) + "\";"
+ " filename*=" + ApiUtils.encodeRFC5987(fileName);
}

/**
* Koduje nazwę pliku w RFC5987 dla pola filename*
*
* @param s Nazwa pliku do zakodowania.
* @return Zakodowana nazwa pliku.
*/
private static String encodeRFC5987(final String s) {
final byte[] rawBytes = s.getBytes(StandardCharsets.UTF_8);
final int len = rawBytes.length;
final StringBuilder sb = new StringBuilder(len << 1);
sb.append("UTF-8''");
final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
final byte[] attributeChars = {
'!', '#', '$', '&', '+', '-', '.',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'^', '_', '`',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'|', '~'
};
for (final byte b : rawBytes) {
if (Arrays.binarySearch(attributeChars, b) >= 0) {
sb.append((char) b);
} else {
sb.append('%');
sb.append(digits[15 & (b >>> 4)]);
sb.append(digits[b & 15]);
}
}
return sb.toString();
}

/**
* Koduje nazwę pliku dla pola filename.
*
* Usuwa znaki diakrytyczne, zamienia znaki spoza ASCII na podkreślenia.
*
* @param s Nazwa pliku do zakodowania.
* @return Zakodowana nazwa pliku.
*/
private static String toAscii(final String s) {
return Normalizer.normalize(s, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "")
.replaceAll("[^a-zA-Z0-9._\\-+,@$!~'=()\\[\\]{}]", "_");
}
}

Użycie podpisu elektronicznego w Javie

Kontytnuję opis korzystania z podpisu elektronicznego w Linuksie.

Poniższy wpis odwołuje się do przykładowej implementacji, którą umieściłem na githubie.

PKCS#11 w Java

Implementacja SunPKCS11

Java od wersji 1.5 ma wbudowaną implementację interfejsów kryptograficznych (JCE) korzystającą z PKCS#11. Jest to klasa sun.security.pkcs11.SunPKCS11 w implementacji Sun/Oracle oraz OpenJDK.

Implementacja ta jest nieco ułomna.

  • brakuje w niej obsługi szyfrowania RSAES-OAEP. Ten problem wymusił stosowanie Szafir SDK do szyfrowania kart transakcji na stronie GIIF, nawet dla transakcji bez podpisu elektronicznego).
  • nawet dostęp do publicznych obiektów na tokenie PKCS#11, wymaga zalogowania się do tokena (podania PINu).
  • KeyStore odnajduje wyłącznie te wpisy, dla których certyfikat jest przechowywany na karcie (tak konfigurowane są wszystkie obecnie wystawiane podpisy na kartach kryptograficznych, ale w HSMach nCipher i Gemalto na których pracowałem konieczne było dogranie certyfikatów do HSMa.)

Pomimo powyższych problemów wbudowana implementacja jest wystarczająca do składania podpisów.

Implementacja IAIK

Istnieje również komercyjna implementacja JCE korzystająca z PKCS#11, stworzona w Austrii.

Ta impementacja jest droga, ale jej twórcy udostępniają jako freeware wrapper dla bibliotek PKCS#11.

Wrapper jest niskopoziomy, pozwala na wywoływanie poszczególnych funkcji z PKCS#11 API. Użycie wrappera nie jest jednak przedmiotem poniższego wpisu.

BouncyCastle PKIX

Interfejsy kryptograficzne w Java, udostępniają podstawowe operacje kryptograficzne, ale nie umożliwiają tworzenia plików CAdES, czy plików zaszyfrowanych CMS Enveloped.

BouncyCastle PKIX jest biblioteką, służącą (między innymi) do tworzenia plików CMS (Crypto Message Syntax, specyfikacja budowy plików podpisanych i/lub zaszyfrowanych, na tejże specyfikacji oparto format pliku CAdES).

W przykładzie będę wykorzystywał bibliotekę w wersji org.bouncycastle:bcpkix-jdk15on:1.64.

Biblioteka udostępnia dwie wersje narzędzi do tworzenia plików CMS: zwykłą i strumieniową.

Wersja zwykła wymaga aby zarówno cały źródłowy dokument do podpisania, jak i cały wynikowy dokument podpisany znalazły się się na stosie (heap) JVM.

Wersja strumieniowa jest nieco mniej intuicyjna w konfiguracji, ale nie nakłada takiego ograniczenia.

Przegląd kodu

Zaszyta konfiguracja

Starałem się jak najbardziej uprościć kod, dlatego pliku Main.java w kodzie zaszyłem:

  • lokalizacja bibloteki PKCS#11
  • numer slotu na tokenie PKCS#11
  • PIN do tokena PKCS#11
  • numer seryjny certyfikatu do podpisywania
  • certyfikat do szyfrowania
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main {
// Wstaw lokalizację bibloteki implementującej PKCS#11
private static final String PKCS11_LIBRARY = "/home/ksm/src/securefile/szafir-plugin/src/main/binaries/linux-x64/pkcs11/cryptoCertum3PKCS-2.0.0.43.r2-MS.so";
// Wstaw numer slotu
private static final long TOKEN_SLOT = 1L;

// Tutaj możesz wstawić swój numer PIN, jeżeli pozostawisz tekst "USTAW_PIN",
// to użyty zostanie ConsoleCallbackHandler, który zapyta o PIN w terminalu.
private static final String TOKEN_PIN_STRING = "USTAW_PIN";
private static final char[] TOKEN_PIN = TOKEN_PIN_STRING.toCharArray();

// Tutaj wstaw numer Twojego certyfikatu kwalifikowanego.
private static final BigInteger CERT_SERIAL = new BigInteger("0123456789abcdef", 16);

// Certyfikat do szyfrowania
private static final String ENCRYPTION_CERTIFICATE_PEM = "-----BEGIN CERTIFICATE-----\n" +
"MIIG0DCCBLigAwIBAgIQSVemUhcVjMhEjlQA5XClTDANBgkqhkiG9w0BAQsFADCB\n" +
// [...]
"clQR0Mu3U9wOmKBhg7czoTwck1TDnTU9u+06cRebQe0rO0hKzbzQlDwc3Sn5bzFd\n" +
"xr/BJV7K4nvRkEuNoWEDiMgT0EY=\n" +
"-----END CERTIFICATE-----\n";
}

Dostęp do materiału kryptograficznego

Aby uzyskać dostęp do klucza prywatnego na karcie należy:

  • załadować provider, loadProvider
  • wczytać KeyStore, getKeyStore
  • odnaleźć alias dla certyfikatu i klucza, getAliasByCertificateSerialNumber
  • pobrać uchwyt klucza.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException,
KeyStoreException, IOException,
UnrecoverableKeyException, CMSException,
OperatorCreationException {
Provider provider = loadProvider(PKCS11_LIBRARY, TOKEN_SLOT);
KeyStore keyStore = getKeyStore(provider);
String alias = getAliasByCertificateSerialNumber(keyStore, CERT_SERIAL);
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
PrivateKey privateKey = null;
if ("USTAW_PIN".equals(TOKEN_PIN_STRING)) {
// używamy CallbackHandler
privateKey = (PrivateKey) keyStore.getKey(alias, null);
} else {
privateKey = (PrivateKey) keyStore.getKey(alias, TOKEN_PIN);
}

// [...]
}
}

Załadowanie proviedera PKCS#11

Konstruktor providera wymaga przekazania mu strumienia z plikiem konfiguracyjnym.

Plik konfiguracyjny ma następującą strukturę.

1
2
3
name = NazwaProvidera
library = /lokalizacja/biblioteki/implementującej/pkcs11.dll
slot = 0

NazwaProvidera jest wymagana przez Javę, ale do niczego jej potem nie używamy, najlepiej ograniczyć się do liter łacińskich, cyfr i podkreślnika.

slot jest zapisywany dziesiętnie, jest to ten sam numer, który pkcs11-tool pokazywało w nawiasie w zapisie szestnastkowym.

1
2
3
4
5
6
7
8
9
public class Main {
private static Provider loadProvider(String libraryFile, long slot) {
String config = String.format("name = %s%nlibrary = %s%nslot = %d%n",
"PodpisElektroniczny", libraryFile, slot);
InputStream providerParameter = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8));
Provider loadedProvider = new sun.security.pkcs11.SunPKCS11(providerParameter);
return loadedProvider;
}
}

Wczytanie KeyStore

KeyStore to obiekt pozwalający zajrzeć jakie certyfikaty i klucze znajdują się na karcie kryptograficznej.

Można go zainicjować na dwa sposoby.

  • przekazując wprost PIN (gałąź else)
  • przekazując obiekt typu CallbackHandler, który zapyta użytkownika o PIN (gałąź if)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
private static KeyStore getKeyStore(Provider provider) throws KeyStoreException, CertificateException,
NoSuchAlgorithmException, IOException {
KeyStore keystore = null;
if ("USTAW_PIN".equals(TOKEN_PIN_STRING)) {
KeyStore.CallbackHandlerProtection chp =
new KeyStore.CallbackHandlerProtection(new ConsoleCallbackHandler());
KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS11", provider, chp);
keystore = builder.getKeyStore();
} else {
keystore = KeyStore.getInstance("PKCS11", provider);
keystore.load(null, TOKEN_PIN);
}
return keystore;
}
}

UWAGA, po trzeciem niepoprawnym podaniu PINu karta zostanie zablokowana!!!

Odnalezienie aliasu dla certyfikatu

KeyStore przypomina trochę słownik (Map), w którym aliasom typu String przypisane są obiekty typu Certificate oraz Key.

W implementacji SunPKCS11, alias jest pobierany z atrybutu CKA_LABEL. Jeżeli ten atrybut zawiera polskie znaki diakrytyczne, to alias jest nieprawidłowo dekodowany, co utrudnia pobranie aliasu wprost.

Zamiast tego przekazuję do metody numer seryjny certyfikatu, a w metodzie iteruję po wszystkich aliasach, aż znajdę certyfikat, o takim numerze seryjnym jak przekazałem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
private static String getAliasByCertificateSerialNumber(KeyStore keyStore, BigInteger certSerial)
throws KeyStoreException {
Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
logger.log(Level.INFO, "Alias: {0}", alias);
X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
logger.log(Level.INFO, " Certificate Serial: {0}", cert.getSerialNumber().toString(16));
if (CERT_SERIAL.equals(cert.getSerialNumber())) {
return alias;
}
}
throw new IllegalStateException("Brak certyfikatu o numerze " + certSerial.toString(16) + " w KeyStore.");
}
}

Podpisywanie

Podpisywanie wykonywane jest w trybie strumieniowym. Jest to implementacja wzorca projektowego dekorator.

Biblioteka BouncyCastle tworzy strumień (OutputStream), który konfiguruję, wskazując strumień, do którego zapisany zostanie podpisany plik, certyfikat i klucz prywatny używany do podpisu, algorytm podpisu i kilka innych szczegółów. Do tak utworzonego strumienia zapisuję dane, które chę podpisać.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Main {
public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, CMSException, OperatorCreationException {
// [...]
try (OutputStream result = new FileOutputStream("/home/ksm/test.signed");
OutputStream signed =
new SigningStreamBuilder()
.setSigningCertificate(certificate)
.setSigningProvider(provider)
.setSigningKey(privateKey)
.setSignatureAlgorithm("SHA256withRSA")
.setDestination(result)
.build();
InputStream source = new FileInputStream("/home/ksm/test.plain")) {
IOUtils.copy(source, signed);
}
}
}

Konfigurowanie strumienia

Magia kryje się w metodzie SigningStreamBuilder::build. Poniższy kod opiera się głównie na przykładzie z metody testSHA1WithRSAEncapsulated w klasie NewSignedDataStreamTest.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private static class SigningStreamBuilder {

public OutputStream build() throws IOException, CertificateEncodingException,
CMSException, OperatorCreationException,
NoSuchAlgorithmException {

final ContentSigner contentSigner =
new JcaContentSignerBuilder(signatureAlgorithm)
.setProvider(signingProvider)
.build(signingKey);

final DigestCalculatorProvider digestCalculatorProvider =
new JcaDigestCalculatorProviderBuilder()
.setProvider(signingProvider)
.build();

final SignerInfoGenerator signerInfoGenerator =
new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider)
.setSignedAttributeGenerator(
buildSignedAttributeTableGenerator(
signingCertificate
)
)
.build(contentSigner, signingCertificate);

final CMSSignedDataStreamGenerator sgen =
new CMSSignedDataStreamGenerator();
sgen.addSignerInfoGenerator(signerInfoGenerator);
sgen.addCertificates(
new JcaCertStore(Collections.singleton(signingCertificate)));
return sgen.open(destination, true);
}
}

Jak widać strumień potrzebuje:

  • podpisywacza contentSigner, który złoży podpis RSA z użyciem klucza prywatnego z karty.
  • kalkulatora sumy kontrolnej digestCalculatorProvider, który wyliczy sumę kontrolną z wiadomości i dodatkowych podpisanych atrybutów.
  • generatora podpisywanej zawartości signerInfoGenerator, który połączy sumę kontrolną wiadomości, dodatkowych podpisywanych atrybutów i wywoła na niej podpisywanie.
  • listy certyfikatów do załączenia w podpisanym dokumencie.

SigningCertificateV2

Dodatkowo przy tworzeniu signerInfoGenerator pojawia się metoda buildSignedAttributeTableGenerator. Jej kod opiera się tej odpowiedzi ze StackOverflow.

Europejski podpis elektroniczny wymaga aby jednym z podpisanych atrybutów podpisu był identyfikator certyfikatu kwalifikowanego użytego do podpisu. Wymaga to zbudowania elementu ESSCertIDv2 i umieszczenia go wewnątrz SigningCertificateV2, co robi metoda constructSigningCertificateV2.

Tak przygotowany element, w metodzie buildSignedAttributeTableGenerator, opakowuje jako atrybut o OID wskazanym przez PKCSObjectIdentifiers.id_aa_signingCertificateV2 czyli 1.2.840.113549.1.9.16.2.47. Ten atrybut umieszczam w obiekcie klasy DefaultSignedAttributeTableGenerator. DefaultSignedAttributeTableGenerator łączy atrybuty przekazane przez użytkownika z domyślnymi wymaganymi atrybutami (np. signingTime).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private static class SigningStreamBuilder {
private CMSAttributeTableGenerator buildSignedAttributeTableGenerator(X509Certificate signingCert)
throws CertificateEncodingException, NoSuchAlgorithmException, IOException {
ASN1EncodableVector signedAttributes = new ASN1EncodableVector();
signedAttributes.add(
new Attribute(
PKCSObjectIdentifiers.id_aa_signingCertificateV2,
new DERSet(constructSigningCertificateV2(signingCert))
)
);
AttributeTable signedAttributesTable = new AttributeTable(signedAttributes);
return new DefaultSignedAttributeTableGenerator(signedAttributesTable);
}

private SigningCertificateV2 constructSigningCertificateV2(X509Certificate cert)
throws CertificateEncodingException, IOException, NoSuchAlgorithmException {
byte[] certEncoded = cert.getEncoded();
final X500Name issuerX500Name = new X509CertificateHolder(certEncoded).getIssuer();
final GeneralName generalName = new GeneralName(issuerX500Name);
final GeneralNames generalNames = new GeneralNames(generalName);
final BigInteger serialNumber = cert.getSerialNumber();
final IssuerSerial issuerSerial = new IssuerSerial(generalNames, serialNumber);
final ESSCertIDv2 essCertIDv2 =
new ESSCertIDv2(
new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256),
this.digest(certEncoded),
issuerSerial
);
return new SigningCertificateV2(essCertIDv2);
}

private byte[] digest(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
return sha256.digest(data);
}
}

Szyfrowanie

Szyfrowanie również wykonywane jest metodą strumieniową, i również wykorzystuje wzorzec dekoratora. 1. Otwieram strumień wyjściowy result. 2. Konfiguruję i otwieram strumień szyfrujący encrypted, zapisuje on wynik do strumienia result. 3. Konfiguruję i otwieram strumień kompresujący compressed, zapisuje on wynik do strumienia encrypted. 4. Konfiguruję i otwieram strumień podpisujący signed, zapisuje on wynik do strumienia compressed. 5. Otwieram strumień z danymi wejściowymi source. 6. Kopiuję dane ze strumienia source do signed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Main {
public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, UnrecoverableKeyException, CMSException, OperatorCreationException {
// [...]
try (OutputStream result = new FileOutputStream("/home/ksm/test.encrypted");
OutputStream encrypted =
new EncrypingStreamBuilder()
.setRecipientCertificate(toX509Certificate(ENCRYPTION_CERTIFICATE_PEM))
.setContentOID(CMSObjectIdentifiers.compressedData)
.setDestination(result)
.build();
OutputStream compressed =
new CompressingStreamBuilder()
.setContentOID(CMSObjectIdentifiers.signedData)
.setDestination(encrypted)
.build();
OutputStream signed =
new SigningStreamBuilder()
.setSigningCertificate(certificate)
.setSigningProvider(provider)
.setSigningKey(privateKey)
.setSignatureAlgorithm("SHA256withRSA")
.setDestination(compressed)
.build();
InputStream source = new FileInputStream("/home/ksm/test.plain")) {
IOUtils.copy(source, signed);
}
}
}

Konfiguracja strumienia kompresującego

Konfigurując strumień przekazuję:

  • contentOID czyli informację jakiego typu dane zostały skompresowane, domyślnie jest to CMSObjectIdentifiers.data czyli typ nieokreślony, wskazanie wprost CMSObjectIdentifiers.signedData pozwala oprogramowaniu rozszyfrowującemu na użycie parsera dla danych podpisanych CAdES bez konieczności rozpoznawania typu danych na podstawie zawartości.
  • outputCompressor jedynym dostępnym kompresorem jest ZLibCompressor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static class CompressingStreamBuilder {


private ASN1ObjectIdentifier contentOID = CMSObjectIdentifiers.data;
private OutputStream destination;
private OutputCompressor outputCompressor = new ZlibCompressor();

// [...]

public OutputStream build() throws IOException {
CMSCompressedDataStreamGenerator cgen =
new CMSCompressedDataStreamGenerator();
return cgen.open(contentOID, destination, outputCompressor);
}
}

Konfiguracja strumienia szyfrującego

Szyfrowanie składa się z następujących faz:

  • Generowany jest losowy klucz symetryczny do zaszyfrowania danych.
  • Klucz symetryczny jest szyfrowany kluczem publicznym certyfikatu odbiorcy, informacja o certyfikacie odbiorcy (wystawca i numer seryjny) i zaszyfrowany klucz symetryczny są zapisywane na początku pliku zaszyfrowanego.
  • Dane zapisywane, które trafiają do strumienia szyfrującego są szyfrowane z użyciem klucza symetrycznego.

Konfigurując strumień przekazuję:

  • symmetricEncryptionAlgorithm, algorytm dla szyfrowania symetrycznego, domyślnie AES o długości klucza 256 pracujący w trybie CBC.
  • recipientCertificate, certyfikat odbiorcy, jeśli zmodyfikujemy kod, tak aby dodać kolekcję certyfikatów, to możemy stworzyć jeden plik zaszyfrowany do dwóch lub więcej odbiorców, na przykład do siebie, w ten sposób będziemy w stanie sami rozpakować dokładnie taki plik jaki został przesłany do GIIF.
  • contentOID czyli informację jakiego typu dane zostały skompresowane, domyślnie jest to CMSObjectIdentifiers.data czyli typ nieokreślony, wskazanie wprost CMSObjectIdentifiers.signedData lub CMSObjectIdentifiers.compressedData pozwala oprogramowaniu rozszyfrowującemu na użycie odpowiedniego parsera bez konieczności rozpoznawania typu danych na podstawie zawartości.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static class EncrypingStreamBuilder {

private final ASN1ObjectIdentifier symmetricEncryptionAlgorithm = CMSAlgorithm.AES256_CBC;
private X509Certificate recipientCertificate;
private ASN1ObjectIdentifier contentOID = CMSObjectIdentifiers.data;
private OutputStream destination;

// [...]

public OutputStream build() throws CertificateEncodingException, CMSException, IOException {

final JceKeyTransRecipientInfoGenerator recipientInfoGenerator =
new JceKeyTransRecipientInfoGenerator(recipientCertificate);

final OutputEncryptor outputEncryptor =
new JceCMSContentEncryptorBuilder(symmetricEncryptionAlgorithm).build();

CMSEnvelopedDataStreamGenerator egen = new CMSEnvelopedDataStreamGenerator();
egen.addRecipientInfoGenerator(recipientInfoGenerator);
return egen.open(contentOID, destination, outputEncryptor);
}
}

Użycie podpisu elektronicznego pod Linuksem

W podobnym tonie jak w poprzednim wpisie umieszczę tutaj kilka spostrzeżeń na temat użycia podpisu elektronicznego w systemie Linux.

Podpis elektroniczny a karta kryptograficzna

Z przepisów prawa wynika, że materiał kryptograficzny konieczny do złożenia podpisu elektronicznego musi znajdować się na komponencie technicznym. W praktyce oznacza to, że klucz prywatny do podpisu elektronicznego musi znajdować się na karcie kryptograficznej (lub HSM), z którego nie może on zostać wyciągnięty (w rozwiązaniu SimplySign taka karta/HSM jest przechowywana w serwerowni u operatora usługi).

PKCS#11

PKCS#11 to API udostępniające funkcje potrzebne do implementacji kryptografii z wykorzystaniem kluczy przechowowanych w sprzęcie oraz akceleratorów kryptografii. Nie jest to jedyne API do realizacji tej funkcji w Java bywa używane JCE (providery JCE są dostępne dla HSMów nCipher oraz Gemalto), Microsoft również ma własne API dostępu do kart kryptograficznych wbudowane w Windows (sterowniki dla tego API są instalowane przy instalacji oprogramowania do podpisu).

PKCS#11 zostało opracowane w latach 90tych, dla języka C. Jest to API niezależne od platformy, obecnie istnieją wrappery pozwalające na wywoływanie tego API w różnych językach programowania.

Producenci kart kryptograficznych dostarczają bibliotekę, która implementuje API PKCS#11 i komunikuje się z kartą kryptograficzną aby użyć materiału kryptograficznego przechowywanego na karcie.

Dostępne biblioteki

Biblioteki PKCS#11 są specyficzne dla modelu karty kryptograficznej.

Na dzień 3 grudnia 2019 dostępność bibliotek przedstawia się następująco:

Wystawca Karta Biblioteka
Certum crypotCertum 3.x cryptoCertum3PKCS-2.0.0.43.r2-MS.so
Certum SimplySign SimplySignPKCS_64-MS-1.0.20.so
KIR/Sigillum CC Carbon libccpkip11-2.01.00161.so
CenCert IAS-ECC libencardp11-4.1.1.9.so
  1. Certum udostępnia bibloteki na swojej stronie.
  2. Biblioteki dla kart Carbon, można pobrać ze strony ich producenta firmy CryptoTech.
  3. CenCert udostępnia program PEM-HEART na swojej stronie w nim jest biblioteka PKCS#11.

Ze swojej strony mogę napisać, że testowałem podpisywanie kartami Certum, KIR Carbon i CenCert w systemie Linux i nie miałem problemów. Osobiście nie testowałem podpisu SimplySign (z kartą „w chmurze”).

Niedostępne biblioteki

NIE znalazłem bibliotek PKCS#11 dla systemu Linux do następujących kart:

  • KIR Graphite
  • Sigillum Dark, jest to dziwne, bo pod Windows karta korzysta ze sterowników Athena NXP IDProtect Client, a Athena-SCS chwali się w zakładce „Products”, że IDProtect Client ma Windows, Mac OSX and LINUX support.
  • EuroCert, to też jest dziwne, bo pod Windows i MacOS karty EuroCert korzystają ze sterowników CSSI firmy Charismathics, która chwali się, że ich middleware jest Fully Windows Compliant plus support for Mac, Linux, Embedded and Mobile platforms.

Sprawdzenie czy karta działa

Do sprawdzenia wykorzystuję polecenie pkcs11-tool dla ClearLinux wymaga ono skompilowanie ze źródeł programu OpenSC, a w Ubuntu i RHEL jest dostępne w pakiecie opensc.

Po skonfigurowaniu podpisywania na stronie https://test.giif.mofnet.gov.pl biblioteki zostały pobrane do katalogu /tmp.

CenCert

Sprawdzam czy pkcs11-tool rozpoznaje bibliotekę.

1
2
3
4
5
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libencardp11-4.1.1.9.so -I
Cryptoki version 2.11
Manufacturer ENIGMA SOI Sp. z o.o.
Library ENCARD PKCS#11 (ver 4.1)
Using slot 0 with a present token (0x0)

Jak widać rozpoznał i wygrył obsługiwaną kartę.

Dla porównania bez karty.

1
2
3
4
5
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libencardp11-4.1.1.9.so -I
Cryptoki version 2.11
Manufacturer ENIGMA SOI Sp. z o.o.
Library ENCARD PKCS#11 (ver 4.1)
No slot with a token was found.

Teraz wyświetlam listę slotów.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libencardp11-4.1.1.9.so -L
Available slots:
Slot 0 (0x0): Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00
token label : ENCARD
token manufacturer : Enigma SOI Sp. z o.o.
token model : IAS-ECC
token flags : login required, rng, token initialized, PIN initialized
hardware version : 5.14
firmware version : 7.0
serial num : 63XXXXXXXXXXXX04
pin min/max : 4/127
Slot 1 (0x1): Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00
token label : ENCARD 2
token manufacturer : Enigma SOI Sp. z o.o.
token model : IAS-ECC
token flags : login required, rng, token initialized
hardware version : 5.14
firmware version : 7.0
serial num : 63XXXXXXXXXXXX04
pin min/max : 4/127
Slot 2 (0x2): Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00
token label : ENCARD 3
token manufacturer : Enigma SOI Sp. z o.o.
token model : IAS-ECC
token flags : login required, rng, token initialized
hardware version : 5.14
firmware version : 7.0
serial num : 63XXXXXXXXXXXX04
pin min/max : 4/127

Ta karta ma 3 sloty, każdy slot może mieć przypisany inny PIN.

Wyświetlam listę obiektów publicznych w slocie (0x0), numer slotu podawany jest w notacji szestnastkowej, a w linni poleceń należy podać numer dziesiętnie!!!

1
2
3
4
5
6
7
8
9
10
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libencardp11-4.1.1.9.so --slot 0 -O
Public Key Object; RSA 2048 bits
label:
ID: 518XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX32bca
Usage: none
Access: local
Certificate Object; type = X.509 cert
label: Imię Nazwisko
subject: DN: C=PL, GN=Imi\xC4\x99, SN=Nazwisko, CN=Imi\xC4\x99 Nazwisko/serialNumber=PESEL: 00010100009, O=Ministerstwo Finans\xC3\xB3w, ST=mazowieckie, L=Warszawa/postalAddress=0*\x0C\x16ul. \xC5\x9Awi\xC4\x99tokrzyska 12\x0C\x0600-916\x0C\x08Warszawa
ID: 518XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX32bca

Dodanie opcji logowania -l pozwala na wyświetlenie także obiektów prywatnych.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libencardp11-4.1.1.9.so --slot 0 -O -l
Logging in to "ENCARD".
Please enter User PIN:
Private Key Object; RSA
label:
ID: 518XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX32bca
Usage: sign
Access: sensitive, always sensitive, never extractable, local
Public Key Object; RSA 2048 bits
label:
ID: 518XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX32bca
Usage: none
Access: local
Certificate Object; type = X.509 cert
label: Imię Nazwisko
subject: DN: C=PL, GN=Imi\xC4\x99, SN=Nazwisko, CN=Imi\xC4\x99 Nazwisko/serialNumber=PESEL: 00010100009, O=Ministerstwo Finans\xC3\xB3w, ST=mazowieckie, L=Warszawa/postalAddress=0*\x0C\x16ul. \xC5\x9Awi\xC4\x99tokrzyska 12\x0C\x0600-916\x0C\x08Warszawa
ID: 518XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX32bca

Proszę zauważyć, że ID dla obiektów klucza publicznego, klucza prywatnego i certyfikatu jest takie samo. Jest to bardzo ważne, wrapper dla PKCS#11 używany w Javie łączy te trzy obiekty, w jeden wpis w KeyStore i robi to na podstawie ID (a nie label).

KIR Carbon

Sprawdzam czy pkcs11-tool rozpoznaje bibliotekę.

1
2
3
4
5
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libccpkip11-2.01.00161.so -I
Cryptoki version 2.1
Manufacturer CryptoTech
Library Cryptoki DLL (ver 2.1)
Using slot 0 with a present token (0x0)

Teraz wyświetlam listę slotów.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libccpkip11-2.01.00161.so -L
Available slots:
Slot 0 (0x0): Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00
token label : PKI Token 1
token manufacturer : CryptoTech Ltd.
token model : CC Carbon
token flags : login required, rng, token initialized, PIN initialized
hardware version : 3.2
firmware version : 6.0
serial num : 10XXXXXXXXXXXX33
pin min/max : 4/8
Slot 1 (0x1): Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00
token label : PKI Token 2
token manufacturer : CryptoTech Ltd.
token model : CC Carbon
token flags : login required, rng, token initialized
hardware version : 3.2
firmware version : 6.0
serial num : 10XXXXXXXXXXXX33
pin min/max : 4/8
Slot 2 (0x2): Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00
token label : PKI Token 3
token manufacturer : CryptoTech Ltd.
token model : CC Carbon
token flags : login required, rng, token initialized
hardware version : 3.2
firmware version : 6.0
serial num : 10XXXXXXXXXXXX33
pin min/max : 4/8
Slot 3 (0x3): Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00
token label : QESv2
token manufacturer : CryptoTech Ltd.
token model : CC Carbon
token flags : login required, token initialized, PIN initialized, readonly
hardware version : 3.20
firmware version : 6.0
serial num : 10XXXXXXXXXXXX33
pin min/max : 6/8

Ta karta ma cztery sloty, dla tutaj dla odmiany podpis elektroniczny został umieszczony w slocie (0x3).

Obiekty w slocie 0x3

1
2
3
4
5
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libccpkip11-2.01.00161.so -O --slot 3
Certificate Object; type = X.509 cert
label: Imię Nazwisko
subject: DN: C=PL, O=Ministerstwo Finans\xC3\xB3w/serialNumber=PESEL: 00010100009, CN=Imi\xC4\x99 Nazwisko/postalAddress=0:\x0C\x12\xC5\x9Awi\xC4\x99tokrzyska 12\x0C\x0F00-916 Warszawa\x0C\x06Polska\x0C\x0Bmazowieckie, GN=Imi\xC4\x99, SN=Nazwisko
ID: edXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0b

Obiekty w slocie 0x3 widoczne po zalogowaniu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/libccpkip11-2.01.00161.so -O --slot 3 -l
Logging in to "QESv2".
Please enter User PIN:
Certificate Object; type = X.509 cert
label: Imię Nazwisko
subject: DN: C=PL, O=Ministerstwo Finans\xC3\xB3w/serialNumber=PESEL: 00010100009, CN=Imi\xC4\x99 Nazwisko/postalAddress=0:\x0C\x12\xC5\x9Awi\xC4\x99tokrzyska 12\x0C\x0F00-916 Warszawa\x0C\x06Polska\x0C\x0Bmazowieckie, GN=Imi\xC4\x99, SN=Nazwisko
ID: edXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0b
Private Key Object; RSA
label: No Friendly Name Available
ID: edXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0b
Usage: sign
warning: PKCS11 function C_GetAttributeValue(ALWAYS_AUTHENTICATE) failed: rv = CKR_ATTRIBUTE_TYPE_INVALID (0x12)

Access: none

W konsoli pojawia się błąd, ten błąd nie przeszkadza w użyciu karty.

Certum

Sprawdzam czy pkcs11-tool rozpoznaje bibliotekę.

1
2
3
4
5
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/cryptoCertum3PKCS-2.0.0.43.r2-MS.so -I
Cryptoki version 2.11
Manufacturer Unizeto Technologies SA
Library x64 PKCS #11 Cryptoki Library (ver 2.0)
Using slot 0 with a present token (0x1)

Wyświetlam dostępne sloty

1
2
3
4
5
6
7
8
9
10
11
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/cryptoCertum3PKCS-2.0.0.43.r2-MS.so -L
Available slots:
Slot 0 (0x1): Broadcom Corp 5880 [Contacted SmartCard] (0123456789ABCD) 00 00
token label : profil bezpieczny
token manufacturer : Unizeto Technologies SA
token model : cryptoCertum 3.2
token flags : login required, rng, token initialized, PIN initialized
hardware version : 5.0
firmware version : 1.0
serial num : 29XXXXXXXXXX08
pin min/max : 4/8

Obiekty w slocie 0x1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/cryptoCertum3PKCS-2.0.0.43.r2-MS.so --slot 1 -O
Public Key Object; RSA 2048 bits
label: Imię Nazwisko
ID: 1bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX6e
Usage: encrypt, verify, wrap
Access: local
Public Key Object; RSA 2048 bits
label:
ID: 94XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX97
Usage: encrypt, verify, wrap
Access: local
Public Key Object; RSA 2048 bits
label:
ID: 28XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX4e
Usage: encrypt, verify, wrap
Access: local
Certificate Object; type = X.509 cert
label: Imię Nazwisko
subject: DN: CN=Imi\xC4\x99 Nazwisko, GN=Imi\xC4\x99, SN=Nazwisko/serialNumber=PNOPL-00010100009, C=PL
ID: 1bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX6e

Tutaj widać, że Certum generuje 3 klucze na karcie, a po wystawieniu certyfikatu kwalifikowanego, dogrywa certyfikat do karty.

Obiekty w slocie 0x1 widoczne po zalogowaniu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
ksm@ksm-7530/tmp $ pkcs11-tool --module /tmp/cryptoCertum3PKCS-2.0.0.43.r2-MS.so --slot 1 -O -l
Logging in to "profil bezpieczny".
Please enter User PIN:
Private Key Object; RSA
label: Imię Nazwisko
ID: 1bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX6e
Usage: decrypt, sign, unwrap
warning: PKCS11 function C_GetAttributeValue(ALWAYS_AUTHENTICATE) failed: rv = CKR_ATTRIBUTE_TYPE_INVALID (0x12)

Access: sensitive, always sensitive, never extractable, local
Public Key Object; RSA 2048 bits
label: Imię Nazwisko
ID: 1bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX6e
Usage: encrypt, verify, wrap
Access: local
Private Key Object; RSA
label:
ID: 94XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX97
Usage: decrypt, sign, unwrap
warning: PKCS11 function C_GetAttributeValue(ALWAYS_AUTHENTICATE) failed: rv = CKR_ATTRIBUTE_TYPE_INVALID (0x12)

Access: sensitive, always sensitive, never extractable, local
Public Key Object; RSA 2048 bits
label:
ID: 94XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX97
Usage: encrypt, verify, wrap
Access: local
Private Key Object; RSA
label:
ID: 28XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX4e
Usage: decrypt, sign, unwrap
warning: PKCS11 function C_GetAttributeValue(ALWAYS_AUTHENTICATE) failed: rv = CKR_ATTRIBUTE_TYPE_INVALID (0x12)

Access: sensitive, always sensitive, never extractable, local
Public Key Object; RSA 2048 bits
label:
ID: 28XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX4e
Usage: encrypt, verify, wrap
Access: local
Certificate Object; type = X.509 cert
label: Imię Nazwisko
subject: DN: CN=Imi\xC4\x99 Nazwisko, GN=Imi\xC4\x99, SN=Nazwisko/serialNumber=PNOPL-00010100009, C=PL
ID: 1bXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX6e

Podsumowanie

pkcs11-tool można użyć aby sprawdzić, czy biblioteka pkcs11, którą posiadamy współpracuje z naszą kartą. Należy sprawdzić pod jakim numerem slotu jest zapisany nasz certyfikat kwalifikowany lub pieczęć elektroniczna. Należy też sprawdzić czy klucz prywatny i certyfikat mają takie same ID (na kartach podpisu elektronicznego, które widziałem tak było, ale klucze generowane na HSM, mogą nie mieć tej informacji).

Szafir SDK i SzafirHost

Zanotuję tutaj kilka spostrzeżeń na temat Szafir SDK abym za parę miesięcy nie musiał odkrywać tego na nowo.

Rozszerzenie przeglądarki

Pobieranie

Dla Chrome jest ono dostępne w webstore. Dla Opery jest ono dostępne w webstore. Dla Firefoxa NIE jest ono dostępne wśród rozszerzeń i instalowane jest ze strony GIIFa.

Linki do pobrania rozszerzenia są zdefiniowane w pliku szafirsdk-module.js dostarczanym przez KIR wraz z biblioteką.

Zadanie rozszerzenia

Rozszerzenie przeglądarki jest konieczne aby używać mechanizmu native messaging (patrz MDN). Ten mechanizm pozwala przegladarce na uruchomienie zewnętrznej aplikacji, z którą rozszerzenie może komunikować się wysyłając tekstowe komendy i odbierając tekstowe odpowiedzi na te komendy, ta komunikacja jest asynchroniczna!!!

Konsola Szafir SDK

Na stronach, które mają znacznik włączający rozszerzenie, w pobliżu paska adresu pojawia się granatowy kwadrat z ikoną „KIR”, po kliknięciu w niego można otworzyć konsolę Szafir SDK.

SzafirHost

Zewnętrzną aplikacją z którą komunikuje się rozszerzenie jest SzafirHost. Linki do pobrania aplikacji SzafirHost są zaszyte w rozszerzeniu. W chwili pisania tego wpisu były to: dla Windows, dla Linux i MacOS. Instalację dla MacOS opisałem w poprzednim wpisie instalacja w systemie Linux jest przeprowadzana analogicznie do instalacji w MacOS.

Zadania aplikacji

Aplikacja składa się z pliku SzafirHost.jar oraz skryptu uruchamiania SzafirHost.sh lub SzafirHost.bat który służy, do uruchomienia SzafirHost.jar z wykorzystaniem maszyny wirtualnej Java (JVM) zainstalowanej na komputerze użytkownika.

Prawdziwa magia aplikacji ukryta jest jednak w instalatorze, który dogrywa pliki manifestu aplikacji natywnej do konfiguracji przegladarki. Aplikacja natywna nazywa się pl.com.kir.szafirhost. Manifestu należy szukać w poniższych lokalizacjach: Chrome, Firefox patrz na NativeMessagingHosts.

Modyfikacja sposobu uruchamiania

Skrypty wykorzystują JVM dostępne na ścieżce uruchamiania. Można wskazać inną maszyną wirtualną Java poprzez poprzez modyfikację zmiennej systemowej PATH lub bezpośrednie wskazanie programu java lub java.exe.

Uruchomienie SzafirHost

  1. Rozszerzenie przeglądarki po wykryciu znacznika w kodzie strony dodaje zmienną globalą pozwalającą JS na stronie komunikować się z rozszerzeniem.

  2. JS wywołuje inicjalizację wywołuje połączenie z natywną aplikacją, na co przeglądarka reaguje uruchomieniem aplikacji wskazanej w pliku manifestu.

    Może się zdarzyć, że proces SzafirHost pracujący w tle zostanie ubity, co skutkuje błędem braku komunikacji z SzafirHost, Wystąpił błąd: Szafir SDK Chrome Host nie został uruchomiony, po wystąpieniu takiego błędu można jedynie zapisać XML z aktualnie edytowanego formularza, zamknąć wszystkie okna przeglądarki i ubić procesy przeglądarki, które pozostały w tle, o ile takie będą, a następnie ponownie uruchomić przeglądarkę (wylogowanie i ponowne zalogowanie użytkownika lub ponowne uruchomienie komputera również zadziała ale jest bardziej inwazyjne)

  3. JS przekazuje do rozszerzenia konfigurację, konfiguracja zawiera

    • URL z katalogiem, w którym na stronie GIIF znajdują się pliki Szafir SDK
    • nazwę manifestu versions.xml z listą plików do pobrania i ich wielkościami.
    • czy debugowanie jest włączone
    • czy używać proxy systemowych
    • adres proxy dla http
    • adres proxy dla https
    • lista nazw/adresów serwerów dla których nie należy używać proxy

    Pozycje zaznaczone italiką można ostrożnie modyfikować na teście, na produkcji. Ustawienia dla testu i dla produkcji są rozdzielne. Mechanizm do modyfikacji konfiguracji SzafirHost i pliku settings.xml jest zaszyty w kodzie JS stron giif.mofnet.gov.pl.

    Według mojego stanu wiedzy na dzieć 2019-11-27, SzafirHost nie jest wstanie przebić się przez proxy wymagające autoryzacji. Kod aplikacji został zabezpieczony przed dekompilacją, co znacznie utrudnia analizę.

  4. SzafirHost pobiera plik versions.xml, następnie pobiera do katalogu tymczasowego pliki tam wymienione.

  5. JS wysyła do SzafirHost plik settings.xml, zawiera on listę certyfikatów urzędów kwalifikowanych, listę certyfikatów wystawców UPO, wskazuje algorytm użyty do wyliczania sumy kontrolnej przy podpisie elektronicznym oraz listę bibliotek PKCS#11.

    Plik settings.xml można ostrożnie modyfikować na teście, na produkcji.

    Należy zwrócić uwagę, że przekazujemy URL do biblioteki a nie położenie pliku. Czyli zamiast: C:\Program Files (x86)\CryptoTech\CryptoCard\CCPkiP11.dll musimy wpisać: file:///C:/Program%20Files%20(x86)/CryptoTech/CryptoCard/CCPkiP11.dll. W elemencie URL można też wpisać samą nazwę biblioteki, w takim wypadku SzafirHost będzie jej szukał w katalogu tymczasowym do którego pobrał elementy Szafir SDK ze strony, a następnie w ścieżce wyszukiwania bibliotek (patrz na zmienną java.library.path w wywołaniu java -XshowSettings:properties -version).

  6. Do tak zainicjalizowanego SzafirHosta, JS wysyła żądanie podpisania danych.

    • SzafirHost iteruje po liście bibliotek PKCS#11, jeśli biblioteki nie ma we wskazanej lokalizacji lub na ścieżce, to w konsoli wyrzuca błąd otwarcia strumienia.
    • Dla każdej znalezionej biblioteki, wywołuje wyszukiwanie kart, jeżeli nie ma czytnika lub karty w czytniku, to w konsoli wyrzuca błąd TOKEN_NOT_FOUND.
    • Dla każdej znalezionej karty wywołuje wyszukiwanie certyfikatów, domyślnie ukrywa certyfikaty przeterminowane, nie pozwala na podpisywanie przeterminowanym certyfikatem.
  7. SzafirHost wyświetla okno podpisu, ze znalezionym certyfikatem (lub bez niego). A po podpisaniu zwraca wynik lub błąd do rozszerzenia (a ono do JS).

Problemy z SzafirHost

Po instalacji SzafirHost, rozszerzenie nadal domaga się instalacji SzafirHost

Najprawdopodobniej przeglądarka nie może uruchomić SzafirHost. Należy sprawdzić czy użytkownik może uruchomić skrypt startowy rozszerzenia, po starcie powinna wyświetlić się wersja rozszerzenie, np. 1.0.7 i kilka innych znaków.

Jeśli nie, to należy sprawdzić czy zainstalowano Java i czy użytkownik ma uprawnienia do uruchomienia tego programu.

Zatrzymuje się na pobieraniu versions.xml, „Trwa inicjalizowanie komponentów podpisu elektronicznego”.

Ten błąd widać na konsoli Szafir SDK wyłącznie po włączeniu debugowania.

SzafirHost nie może przebić się przez proxy, jeśli proxy wymaga autoryzacji to problem jest nie do przeskoczenia (trzeba dodać wyjątek na proxy dla żądań GET na adresy poniżej https://www.giif.mofnet.gov.pl/szafir/sdk_builds/build/). Jeśli proxy bez autoryzacji to można zmienić ustawienia.

W dialogu podpisu nie ma certyfikatów.

Kliknąć ‘Wybierz Certyfikaty’,

  • jeżeli nie znajduje karty, to należy:

    • Upewnić się czy karta jest włożona do czytnika.
    • Upewnić się czy czytnik jest dobrze podłączony do komputera (ekipa sprzątająca potrafi wysunać każdy kabel).

    jeżeli to nie pomogło, to można wskazać ręcznie bibliotekę PKCS#11 lub przystąpić do debugowania.

    • Sprawdzić w konsoli Szafir SDK czy biblioteki PKCS#11 zostały znalezione,
      • jeżeli nie zostały znalezione, to można zmodyfikować plik settings.xml aby je odnajdywał.
      • jeżeli zostały odnalezione, to trzeba sprawdzić w jakiej lokalizacji, może zdarzyć się, że 32 bitowe JVM odnalazło 64 bitowe biblioteki, lub 64 bitowe JVM odnalazło 32 bitowe biblioteki. Aby to naprawić należy posprzątać biblioteki, zmodyfikować settings.xml (wskazać bibliotekę w prawidłowej architekturze), lub zmodyfikować skrypt startowy (wskazać JVM w prawidłowej architekturze).
  • jeżeli znajduje kartę (nie wyświetla błędu braku karty), to być może na karcie są wyłącznie przeterminowane certyfikaty, należy wtedy zmienić kartę lub przedłużyć certyfikat. (tik w dialogu pozwala wyłączyć odfiltrowywanie przeterminowanych certyfikatów, po jego zaznaczeniu widać certyfikaty ale nadal nie można ich użyć).