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

git-svn – pracuj lokalnie z git, synchronizuj się z SVN

Wszystkie projekty w mojej pracy są wersjonowane mocno na SVN. Ten system kontroli wersji ma już swoje lata i już dawno temu zaczął być wypierany przez gita. Niestety, u nas perspektyw na migrację nie ma w najbliższej przyszłości, więc od kilku lat korzystałem bezpośrednio z SVN.

Na początku niezbyt mi to przeszkadzało – najpierw się wdrażałem, potem zmieniłem zespół, potem zmiany wprowadzałem liniowo i nie było zapotrzebowania na “skakanie” pomiędzy zagadnieniami. Jedynie czasem trzeba było wybrać część zmian z wielu innych w tych samych plikach, ale mniej więcej w tym czasie odkryłem możliwości pary diff + patch, przez co wybrane zmiany można było łatwo scommitować gdzieś na boku bez zbędnego komentowania zmian, które wejść nie powinny.

Sytuacja zmieniła się radykalnie wraz ze zmianami w szeregach naszego biznesu. Coraz częściej wykonywaliśmy po kilka zagadnień “jednocześnie”, przestała mieć też znaczenie kolejność wykonywania zmian – czasem zagadnienia skończone jednego dnia musiały wejść na produkcję jeszcze w tym samym dniu, inne zaś musiały czekać tygodniami, a czasem nawet zostawały zaorane.

Miało to co najmniej trzy poważne konsekwencje:

  • wiecznie zapchane working copy: po roku takiej pracy miałem zawsze kilkadziesiąt zmienionych plików i efektywne przejrzenie diffa przed commitem było praktycznie niemożliwe, przez co często zdarzało się zapomnieć o jakimś pliku; nie muszę też pisać, że codzienny widok niewdrożonych zmian sprzed np. kilku tygodni ma katastrofalny wpływ na motywację,
  • utrudnione testy: zmiany bardzo często dotyczyły tych samych plików, codziennością było kilka zagadnień w tym samym module, co powodowało, że testowanie niektórych zmian było bezcelowe w oderwaniu od innych, zaś czasem sztucznie wstrzymywałem pracę nad dalszymi zmianami w oczekiwaniu na testy ze strony biznesu,
  • wieczny ból dupy przed wdrożeniami: po pewnym czasie nakładających się na siebie zmian było tyle, że diff i patch na boku musiał być używany hurtowo; ręczna edycja diffów bywa czasem utrudniona, co w połączeniu z wybieraniem zmian z kilkudziesięciu plików było szalenie czasochłonne; przygotowania do wdrożenia zaczęły być pasmem kombinowania.

Mniej więcej w tym samym czasie zacząłem podchodzić dużo poważniej do kwestii testowania oprogramowania, co wcale nie ułatwiało sytuacji. SVN był w takich sytuacjach po prostu bezbronny – jego branche w tak szybkim i zmiennym procesie rozwoju oprogramowania nie mają prawa bytu i są strasznie ciężkie. Zacząłem czytać coraz więcej o gicie.

Gita znałem jeszcze z czasów swojej freelancerki, jednak z racji tego, że pracowałem sam, to tak naprawdę go nie znałem. Nie korzystałem nawet aktywnie z branchów – wszystkie moje projekty z tamtego czasu to jedynie master, służący jako kopia zapasowa i archiwum zmian. Wiedziałem jednak, że możliwości są dużo większe, aż w końcu trafiłem na sposób pogodzenia SVN i git – projekt git-svn.

Po przejściu przez kilka tutoriali wiedziałem, że to będzie to. Git-svn pozwala stworzyć gitowe repozytorium na podstawie brancha z SVN, który będzie synchronizowany w dwie strony.

Użyłem więc git-svn do clone’a z SVN. Po kilkudziesięciu minutach mielenia (nasz projekt miał wtedy ponad 2,5k commitów) miałem już gotowe repo, które spatchowałem niescommitowanymi zmianami z kopii roboczej SVN, której dotąd używałem.

Zacząłem ostrożnie – dalej miałem mnóstwo niescommitowanych zmian, dalej nie używałem branchy, jednak git add –patch i selektywne dodawanie zmian do scommitowania w obrębie jednego pliku zrobił furorę – to był ten moment, w którym przestałem zaprzątać sobie głowę myśleniem o wtórnych problemach: czyli “jak tym razem scommitować zmiany wymieszane pomiędzy zagadnieniami?”.

Po miesiącu takiej pracy okazało się, że w zasadzie nie występują większe problemy. Dlaczego więc nie pójść o krok dalej? Tym krokiem było oczywiście przeniesienie zmian do osobnych gałęzi zgodnie z git flow. Dzięki temu rozwiązałem raz na zawsze problem zapchanego working copy (czy – w przypadku gita – indeksu).

Podaję materiały, które mogą się przydać każdemu znajdującemu się w sytuacji podobnej do mojej, a z których sam korzystałem:

8.1 Git and Other Systems – Git and Subversion

Effectively Using Git With Subversion

Na koniec moje protipy:

  • unikaj rebase na synchronizowanym z SVN branchu (przeważnie master), przepisanie historii commitów powoduje czasem zaburzenie liniowości historii, przez co synchronizacja się nie udaje; sam rozwiązuję to w ten sposób, że feature branche merge’uję do mastera z przełącznikiem –no-ff,
  • pamiętaj, że korzystając z git-svn wszelkie zmiany w lokalnych branchach nie są wypychane tak, jak przy “tradycyjnym” repozytorium gita, posiadasz więc prawdopodobnie jedyną kopię tych zmian – pamiętaj o wykonywaniu kopii zapasowych, w przypadku uszkodzenia lokalnego repozytorium możesz stracić wszystkie efekty swojej pracy.

Muszę też ostrzec potencjalnych użytkowników – spotkałem się z wieloma opiniami mówiącymi o tym, że git-svn tak bardzo usprawnia pracę, że jest pierwszym krokiem do całkowitego przejścia na gita w pracy. Sprawdziło się to i u mnie – narzędziem tym zaraziłem kolegę z zespołu. Czas na resztę. 😉

Uruchomienie starego kodu na nowych wersjach jQuery

Jeżeli w swoich projektach masz dużo legacy kodu napisanego w JS z użyciem starych wersji biblioteki jQuery, a nie chcesz/nie możesz w danej chwili przepisać jego części niedziałających w nowszych wersjach – polecam zacząć migrację od pluginu jquery-migrate.

Plugin ten wykrywa i przywraca usunięte od wersji 1.9 funkcje jQuery, dzięki czemu stary kod wykona się poprawnie na nowych, niekompatybilnych wstecz wersjach biblioteki.

Oczywiście podpięcie go powinno być stanem przejściowym do czasu pełnej migracji i przepisania wywołań usuniętych funkcji. Sam plugin doskonale w tym pomaga dzięki możliwości generowania ostrzeżeń przy każdym wywołaniu nieobsługiwanej w nowej wersji funkcji. Można dzięki temu korzystać natychmiast z nowych wersji jQuery, stopniowo przepisując stary kod na podstawie zaraportowanych wywołań.

SQL savepoint – obsługa złożonych transakcji

Jakiś czas temu natknąłem się w pracy na problem obsługi złożonych transakcji.

Jako, iż SQL (a przynajmniej DBMS, z którego korzystamy) nie obsługuje zagnieżdżonych transakcji, do tej pory – dla wygody – wystarczała nam prosta modyfikacja adaptera bazodanowego, która pozwalała na “zagnieżdżanie transakcji”, a polegała jedynie na zliczaniu żądań rozpoczęcia transakcji i pilnowaniu liczby wywołań commitów/rollbacków na adapterze. W przypadku niezgodności rzeczywista transakcja (otwierana przy pierwszym żądaniu) nie była commitowana, podobnie, gdy wystąpiło przynajmniej jedno wywołanie rollback na adapterze – żadne zmiany nie były stosowane.

W rzadkich sytuacjach nietypowych, gdzie część zapytań miała zostać zastosowana, a część – w razie potrzeby – nie, obsługa odbywała się w kilku niezależnych transakcjach.

Jednak w końcu przyszedł ten moment, gdy rozbicie na wiele transakcji było praktycznie niemożliwe ze względu na złożoność procesu, który trzeba było zmodyfikować.

W skrajnym przypadku mogło to wyglądać tak:

  1. Początek procesu – wykonanie podstawowych zapytań INSERT/UPDATE
  2. Wykonanie serii zapytań INSERT/UPDATE z możliwością warunkowego ich cofnięcia.
  3. Zakończenie procesu – kolejne zapytania INSERT UPDATE.

W pewnych warunkach powinny być więc zastosowane zmiany z punku 1., cofnięte wszystkie z punktu drugiego i zastosowane zapytania z punktu trzeciego.

I tutaj przydatna okazała się klauzula SAVEPOINT, o której istnieniu wcześniej nie wiedziałem, natomiast idealnie wpasowywała się w tego typu sytuacje.

Jej zasada działania jest banalnie prosta. W momencie wywołania:

SAVEPOINT nazwa_savepointa

wewnątrz transakcji tworzony jest punkt, po którym następują kolejne zapytania i do którego można – w razie potrzeby – cofnąć się poprzez

ROLLBACK TO nazwa_savepointa

W ten sposób “zapominamy” o wykonanych po utworzeniu savepointa zapytaniach SQL, natomiast wszystkie zapytania sprzed tego czasu zostaną zastosowane, jeżeli na całej transakcji zostanie wykonany COMMIT.

Bitcoin nie daje rady

Bitcoin to niewątpliwie waluta przyszłości. Prostota użycia, implementacji, anonimowość i błyskawiczne płatności, a to wszystko niezależnie od rządów i – przy stosowaniu się do kilku reguł – podatków. Właśnie tak myślałem, dopóki nie przyszło mi skorzystać z tej waluty w praktyce. Całe szczęście, że nie do końca do moich celów.

Bitcoin stał się popularny w polskich mediach w zeszłym roku, pewnie ze względu na gwałtownie rosnący kurs BTC do USD (jednak ta bańka pękła prawdopodobnie na początku grudnia). To spowodowało, że zainteresowałem się tematem i postanowiłem przetestować, jak płatności Bitcoin sprawdzają się w praktyce.

I żeby nie przedłużać – nie sprawdzają się. Spostrzeżeń jest kilka:

  1. Bitcoin nie nadaje się do mikropłatności. Jeżeli masz serwis oferujący relatywnie tanie usługi i chcesz wdrożyć płatności w tej walucie, szybko o tym zapomnij – unikniesz wyrzucenia pieniędzy w błoto. Małe transakcje są znaczącym problemem dla sieci i są przez osoby z nią związane wprost nazywane spamem. W efekcie oficjalny klient, dla transakcji niskopriorytetowych – wymusza minimalną opłatę transakcyjną na poziomie 0,0001 BTC, czyli ok. 0,26 zł w dniu i godzinie redagowania tego wpisu. W praktyce jednak opłata ta jest wyższa. Przykładowo – przy wykonaniu pojedynczej, testowej płatności pomiędzy swoimi portfelami na kwotę 0.00006 BTC (~0,16 zł) zapłaciłem 0.00146 BTC (~0,38 zł) prowizji. Słabym pocieszeniem jest brak faktycznej konieczności płacenia prowizji od transakcji powyżej 0,01 BTC (~26 zł). W pozostałych przypadkach, jeżeli nie zapłacisz prowizji, zapomnij o potwierdzeniach transakcji w rozsądnym czasie. Lepiej skorzystaj z ELIXIR.
  2. Zerowa wiarygodność kursu. O ile punkt pierwszy Cię nie dotyczy, przygotuj się na konieczność szybkiego i częstego “przewalutowania” płatności otrzymanych w Bitcoin na coś stabilniejszego, lub na ryzyko utraty sporego odsetku pieniędzy w ciągu długiego weekendu. Zdaję sobie sprawę, że jest to pewnie kwestia młodego wieku tej waluty, jednak niedogodność tę trzeba mieć “z tyłu głowy” przy podejmowaniu decyzji o wdrożeniu płatności w Bitcoinach.
  3. Anonimowość w płatnościach Bitcoin to fikcja. Wszystkie transakcje są dostępne publicznie. Co z tego, że do informacji należy tylko adres portfela źródłowego, docelowego i kwota i że nie ma tam żadnych danych stron transakcji, tytułów, itp.? W praktyce jednak zawsze dojdzie do powiązania adresu portfela z podmiotem gospodarczym. A do tego wystarczy już tylko Google i np. https://blockchain.info/, i już każdy zna nie tylko saldo Twojego portfela, ale też szczegóły wszystkich jego transakcji. Jeżeli chcesz w miarę szybko zacząć przygodę z Bitcoin, prawdopodobnie kupisz pierwsze Bitcoiny w którymś z dużych kantorów. Przyda Ci się więc informacja, że jeden z największych – https://www.mtgox.com/ – przy rejestracji może poprosić Cię nie tylko o skan dokumentu tożsamości, ale też np. wystawionego na Ciebie rachunku za bieżące opłaty.

Powyższe powody dyskwalifikują dla mnie BTC w obecnym czasie. A szkoda, bo zakochałem się w tym, jak proste jest korzystanie z tej waluty – zarówno w zwykłych transakcjach, jak i samym wdrożeniu płatności w tej walucie w aplikacjach internetowych.

Usługi dodatkowe jabbim.cz – płatne od 15 listopada

Jeśli kogoś ominęła wiadomość – praktycznie wszystkie transporty i usługi dodatkowe serwera jabbim.cz będą od 15 listopada 2010 r. dostępne odpłatne – w tzw. pakiecie VIP. Jest to o tyle ważna informacja, że jabbim.cz jest też właścicielem domen: njs.netlab.cz, jabbim.sk, jabbim.com, jabbim.pl oraz jabster.pl. Dla osób, które nie wykupią subskrypcji (7€ rocznie) po ww. dniu, dostępnych będzie tylko kilka usług oraz – co oczywiste – komunikacja poprzez Jabbera. Piszę o tym fakcie, ponieważ wiem, jak wiele osób korzysta z jabster.pl – z reguły razem z transportem GG, itp.

UWAGA! Ten wpis ma już 7 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Jeśli kogoś ominęła wiadomość – praktycznie wszystkie transporty i usługi dodatkowe serwera jabbim.cz będą od 15 listopada 2010 r. dostępne odpłatne – w tzw. pakiecie VIP. Jest to o tyle ważna informacja, że jabbim.cz jest też właścicielem domen: njs.netlab.cz, jabbim.sk, jabbim.com, jabbim.pl oraz jabster.pl. Dla osób, które nie wykupią subskrypcji (7€ rocznie) po ww. dniu, dostępnych będzie tylko kilka usług oraz – co oczywiste – komunikacja poprzez Jabbera. Piszę o tym fakcie, ponieważ wiem, jak wiele osób korzysta z jabster.pl – z reguły razem z transportem GG, itp.

Przy okazji – zna ktoś w miarę stabilny serwer Jabbera z transportem GG w miarę świeżej wersji? Większość ma jakieś archaiczne, przez co wycięta jest komunikacja z wyższymi numerami GG (od bodajże 15 mln w górę).

Zawiodłem się na home.pl

UWAGA! Ten wpis ma już 8 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Niby tytuł wpisu mówi wszystko, ale po kolei. Byłem klientem home.pl od 3 lat, i – niestety – przez swoje lenistwo będę nim przez rok czwarty.

A chodzi dokładniej o domenę .pl. Gdy ją rejestrowałem, zapłaciłem w promocji 20 zł, przez co nie mogłem uzyskać kodu AUTHINFO, by móc ją przenieść do innego operatora. Nie bolało mnie to specjalnie, bo bardzo sobie ceniłem wygodny panel obsługi domeny i ichniejszego DNS-u. Po roku postanowiłem przedłużyć domenę. Cena była trochę wysoka – 120 zł brutto, ale do przeżycia. Dostałem najpierw przypomnienie na mail o końcu okresu abonamentu, równolegle fakturę proforma pocztą. Po opłaceniu, dostałem już normalną fakturę, oraz – co mnie mile zaskoczyło – dwa kupony rabatowe: na którąś z usług Google oraz na serwery home.pl. Jako, że ani jednego, ani drugiego nie potrzebowałem, sprzedałem je z powodzeniem na Allegro. Ze sprzedaży uzyskałem 40 zł, więc cena domeny w zasadzie spadła dla mnie do 80 zł. I byłem bardzo zadowolony. Gdy podpinałem domenę pod Google Apps, bloga, itp., wystarczyło dosłownie wyklikać i powklejać odpowiednie wpisy w panelu home.pl. Gdy czegoś brakowało, pisałem o dowolnej porze na czat z operatorem i otrzymywałem po kilku minutach rozwiązanie problemu lub informację od konsultantki, że właśnie zrobili to ich technicy. Nic dziwnego więc, że za rok chętnie przedłużyłem domenę w home.pl. Oprócz kolejnych kuponów, otrzymywałem też tzw. Netpunkty – drobna sprawa, ale miło co jakiś czas dostać kubek albo koszulkę za darmo.

Cieszyłem się aż do tego roku. Zaczęło się od zapytania, czy w związku z tym, iż jestem stałym klientem, otrzymam rabat na kolejny rok. Nie otrzymałem, lecz zbytnio się tym nie przejąłem, zrobiłem to raczej z ciekawości. W sieci szumiało o tym, że NASK obniża cenę przedłużenia domen. Czekałem więc na ruch home.pl, tym bardziej, że na Joggerze widziałem kilka wpisów, których autorzy chwalili się szokująco niskimi cenami u swoich operatorów. Postanowiłem więc wystąpić do home.pl o kod AUTHINFO, na wypadek, gdyby mi coś zaskoczyło i chciałbym się przenieść do innego operatora. Kod dostałem (swoją drogą, musiałem przefaksować lub wysłać tradycyjną pocztą specjalny druk), jednak nie znalazłem czasu ani ochoty na przeniesienie się gdzie indziej. Niestety.

Siłą rzeczy, po otrzymaniu przypomnienia o końcu okresu abonamentowego na moją domenę, opłaciłem ją na kolejny rok. Cena nadal nie spadła – zapłaciłem tyle, ile płaciłem wcześniej – 120 zł brutto, mimo obniżek u prawie wszystkich operatorów. Przypomnienie dostałem też tylko mailem, nie otrzymałem już faktury proforma. Ale nic to – myślałem. Sprzedam sobie przecież kupony i nie będzie źle.

Dzisiaj otrzymałem list z home.pl. Fakturę, i tylko ją. Dodatkowo na papierze przypominającym w dotyku ten z Tesco za 9 zł/ryza. Na fakturze parę okienek, kwota zapłacona, itp. Na drugiej stronie FAKTURY – reklama. Żadnych kuponów nie było, domena kosztowała mnie pełne 120 zł, czyli cena jak zawsze. Tyle, że kiedyś nie było takiego dziadostwa, jak teraz. Samym faktem reklamy na odwrocie faktury, brakiem kuponów i tym żałosnym papierem czuję się obrzygany – jako 4 letni klient.

Napisałem kontrolnie do obsługi technicznej. Okazuje się, że kupony są rozdawane losowo (jakież to trzeba mieć szczęście, żeby dostawać rok w rok kupony, i parę miesięcy po otrzymaniu AUTHINFO już ich nie dostać). Dodatkowo okazało się, że od końca 2008 za przedłużenie domeny nie są przydzielane Netpunkty. Bardzo miło.

I to by było chyba tyle wylewania żalów. Domenę na przyszły rok opłaciłem, więc już “po ptokach”. Chciałem tylko zwrócić uwagę na to, że home.pl z jego profesjonalną obsługą klienta, zaczyna się zmieniać w dziada, drukującego mi na odwrocie faktury reklamy i inne rzeczy wymienione w tym wpisie. Jako, iż kod AUTHINFO mam, w przyszłym roku nie będę już taki głupi i leniwy – zmienię operatora.

Mistrzowie developerki i naprawiania błędów

UWAGA! Ten wpis ma już 9 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Jakiś czas temu, przeżywając kryzys dystrybucyjny systemu Linux, zacząłem eksperymentować z cookerem systemu Mandriva 2009.1. Wersja rozwojowa, więc zamiast narzekać, zacząłem karmić ichnią Bugzillę zauważonymi błędami i propozycjami ulepszeń. Gdy na początku zaczęli poprawiać drobne niedopatrzenia, byłem pełen nadziei, że zaczną coś robić z pozostałymi, poważniejszymi. Dopiero teraz zdałem sobie sprawę, jaki byłem głupi.

To, że Mandriva 2009.1 jest totalną porażką i krokiem wstecz, jest oczywiste. W dniu premiery nawet najwierniejsi fani na wszystkim znanych mi forach łapali się za głowy, że wydano taki bubel. I pewnie jest to wynikiem wyścigów z Ubuntu, które również wydaje nową wersję w kwietniu. I tak oto w 2009.1 KDE4.2 jest już systemem wiodącym do tego stopnia, że KDE3 w repozytoriach pozbawiono go połowy aplikacji. Nie ma starego Amaroka, nie ma KPowersave, nie ma nawet prostego KMiksa. Nie muszę chyba dodawać, że aplikacje backportowane mulą niemiłosiernie (a szczytem jest już to, że po zmianie głośności za pomocą klawiszy funkcyjnych laptopa muszę czekać 2-3 sekundy na reakcję systemu). Do tego dochodzi masa innych, poważnych błędów: po rekonfiguracji system dobiera zły moduł do obsługi WiFi, pojawiają się śmieci w polach tekstowych aplikacji KDE3, itp. No cóż – “to tylko cooker” – myślałem. Jednak moje błędy nadal wisiały na Bugzilli.

Ostatnio jednak zacząłem dostawać optymistycznie zatytułowane maile z ww. systemu, zawierające w sobie “RESOLVED”. Ucieszony zacząłem czytać raporty, jednak mocno tego żałowałem. Otóż zgłoszony przeze mnie bug o tym, iż po instalacji KDE3 z repozytorium, KBluetooth nie wykrywa adaptera BT na USB, został rozwiązany. Tyle, że z etykietą “WONTFIX”. Oto słowa genialnego developera:

we will remove it from cooker, it doesn’t work with bluez4

Cóż to oznacza? Ano to, że KDE3 w Mandriva 2009.1 zostanie praktycznie pozbawiony możliwości korzystania z BT – KBluetooth4 nie działa bowiem na starym KDE. Naprawdę, jestem pełen podziwu dla geniuszu pomysłowych developerów. Tylko życzyć takich innym dystrybucjom.

Dzisiaj dostałem jednak kolejny mail, podobnie zatytułowany do poprzedniego. Tym razem chodzi o Amaroka 1.4.10, czyli ostatnią stabilną wersję “starego” Amaroka. Sprawa zupełnie niekontrowersyjna, ponieważ nie ma chyba osób, które używałyby z powodzeniem Amaroka 2. Paskudny, iTunesowy wygląd, brak takich opcji, jak powtarzanie, losowanie, korektor graficzny (!). Do tego bugi, jak nieprawidłowa obsługa Last.fm, brak obsługi podcastów czy urządzeń muzycznych, brak automatycznego tagowania za pomocą MusicBrainz. Nie da się tego po prostu używać. Ale mimo to, developerzy są pełni wiary w nowy, słuszniejszy produkt i:

We won’t add back amarok 1.4 on cooker.

OK, developerom Amaroka się poprzewracało w dupach, wydając stabilną, nową wersję programu, który nie ma nawet połowy funkcji poprzedniego. Ale to poprzewracanie widocznie się udziela.

Nie wiem, kiedy to się skończy, bo póki co jest coraz gorzej. I wiem, że zaraz zlecą desktopowcy mówiąc, że u nich wszystko działa. OK, ale umówmy się – na desktopie to mi wszystko działało od czasów Auroksa 10. I działa do tej pory, nawet na najtańszych płytach głównych i chińskich kościach do wszystkiego. Tyle, że nie o to tu chodzi, bo pojawia się wyzwanie, jak laptop i kilka funkcji więcej, i już są problemy.

Nie rozumiem też obrzydzenia developerów do poprzednich wersji aplikacji. Co jest złego w KDE3? I dlaczego mam używać KDE4.2, skoro poprzednia wersja działa u mnie dość szybko i bezproblemowo? OK, może i 4.2 jest zoptymalizowany, napisany od nowa, przyspieszony. Tyle, że ja tego zupełnie nie zauważam. Na KDE3 w Firefoksie potrafię mieć otwartych 15 kart z flashami i oglądać płynnie filmy na nich. W KDE4.2 otworzę sobie jeden teledysk + 2 inne karty, niezawierające flasha, i już zamiast filmu mam animację poklatkową.

Developerzy radzą na “starszych” sprzętach instalować jakieś śmieszne i biedne środowiska. OK, ale ja wcale nie mam zegara procesora taktowanego 233 MHz i 64 MB RAM-u. Mam te 1,73 GHz i 512 MB, więc dlaczego nie wystarcza to do swobodnej pracy? Nie da się? Widocznie się da, skoro KDE3 sobie radzi. No ale cóż, z obecnym myśleniem, życzę developerom powodzenia. Bo jeszcze trochę, a nie będzie się dało Mandrivy używać (nie, nie chcę zmieniać dystrybucji, za stary jestem).

Póki co, mam idealnie skonfigurowaną wersję 2008.1 oraz ściągnąłem na dysk repozytorium dla niej. Nagram na płyty i chyba zostanę, dopóki nie zmienię sprzętu (a nie zapowiada się, bo po prostu nie widzę potrzeby). A repozytorium się przyda, gdyby autorom z dnia na dzień przyszło na myśl, że 2008.1 to już wiekowa jest i “deprecated”.

Cursedsnake 0.5

UWAGA! Ten wpis ma już 9 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Niedawno wpadłem na pomysł napisania jakiejś gry. Dawno niczego “twórczego” nie pisałem, a i pomysł akurat się przyplątał – napisać snake`a pod konsolę, dla treningu.

Żeby maksymalnie uprościć sobie pracę, wybrałem bibliotekę ncurses do obsługi klawiatury i ekranu. Wszystkie metody i pola wrzuciłem w klasę, dla czytelności i prostszych ewentualnych modyfikacji. Jakichś specjalnych rozwiązań brak, bo w końcu temat oklepany, jednak starałem się pisać i wymyślać wszystko od zera. Główna pętla chodzi na liczniku z użyciem clock(), wąż jest reprezentowany w pamięci jako lista jednokierunkowa. Kodziłem sobie we wdzięcznym IDE o nazwie Code::Blocks.

From Snake

Obecna wersja to 0.5, lecz w zasadzie nie mam już pomysłów na jakieś nowe funkcje w tej grze. Różnice w stosunku do poprzednich wersji można śledzić w changelogu. Kod jest w miarę uporządkowany, można się przyczepić do mieszania języków w nazwach zmiennych, ale może to poprawię kiedyś.

Co do licencji, to nie myślałem nad tym dłużej, dlatego nie widzę problemu w wykorzystywaniu źródeł w różnych celach, jeżeli ktoś się będzie bawił w modyfikacje, to proszę tylko o umieszczenie informacji o autorze.

Oto link do pobrania źródeł (jest to paczka z projektem Code::Blocks, plik źródłowy to main.cpp):

CursedSnake 0.5

Z góry dziękuję za wszelkie sugestie w komentarzach. 😉

Google Chrome w oczach ortodoksyjnego firefoksiarza

UWAGA! Ten wpis ma już 9 lat. Pewne stwierdzenia i poglądy w nim zawarte mogą być nieaktualne.

Wczoraj na Joggerze panowała gorączka nowej przeglądarki Google o nazwie Chrome. Jako, iż jest to na razie wersja tylko pod Windows oraz przez to, że oczytałem się na ww. blogach o podobieństwie do Opery, to postanowiłem odpuścić. Jednak dzisiaj znalazłem chwilę czasu, żeby to przetestować. I oto moje spostrzeżenia:

  • nie zauważyłem zwiększenia prędkości w stosunku do Firefox 3 dla Windows (o linuksowym nie mówię, bo tam nadal jest problem z Flashem),
  • brak górnej belki menu oraz paska statusu na pierwszy rzut oka wydaje się być dobrym rozwiązaniem, kończy nim być gdy chcę się dowiedzieć, do czego prowadzi wskazany link oraz gdy chcę jakoś zarządzać zakładkami,
  • podoba mi się sygnalizacja wszelkich ostrzeżeń – np. przekreślone https czy ikony w pasku adresu,
  • imitacja AwesomeBar daje radę, ma też wpisanych wiele popularnych stron (np. po wpisaniu “da” pojawia mi się odnośnik do DeviantArt, chociaż nigdy tej strony nie odwiedzałem),
  • nie ma potwierdzenia zamknięcia przeglądarki przy otwartych wielu kartach (a ja z przyzwyczajenia do TabMix Plus klikam zawsze na ten brzegowy “x”),
  • po przejechaniu rolką na niektórych obiektach Flash (np. odtwarzaczu YouTube) następuje kilkunastosekundowe zatrzymanie przeglądarki,
  • taki sam efekt powoduje czasem przejście z jednej karty na drugą,
  • słownik nie działa poprawnie (np. podkreśla wszystkie wyrazy zakończone na “-em”),
  • gwiazdka zakładek mimo, iż zerżnięta żywcem z Firefoksa, lepiej spisuje się po lewej stronie,
  • brak wtyczek daje o sobie znać, zwłaszcza przy googlowej polityce minimalizmu (dosłownie trzy skromne zakładki w oknie konfiguracji!),
  • SpeedDial nigdy nie wygra u mnie z paskiem zakładek,
  • mam wrażenie, że każdą z opcji Chrome’a już gdzieś widziałem, i chyba nawet mam rację,
  • denerwuje mnie sposób ładowania strony – najpierw szkielet jest zmniejszony, a potem pod wpływem obrazków rozciąga się, a to na boki, a to w dół,

I to by było na tyle, i tak nie będę używał tej przeglądarki na codzień (chyba, że kiedyś na Windows, tak, jak to robię z Google Talkiem).