Architektura PrivMX – serwer

Główne funkcje API serwera

Moduł serwerowy PrivMX udostępnia programom klienckim metody API pozwalające na realizację następujących funkcji:

  • przechowywanie danych – funkcje zapisu i odczytu bloków danych i deskryptorów;
  • obsługa skrzynek komunikacyjnych oraz funkcje przyjmowania i odczytywania wiadomości;
  • magazyn i weryfikacja kluczy publicznych – funkcje PrivMX PKI opisane są osobno w ostatnim rozdziale niniejszego dokumentu;
  • tworzenie kont, logowanie, proxy, profile użytkowników;
  • administracja – opis funkcji API związanych z zarządzaniem serwerem PrivMX jest w tym dokumencie pominięty.

W tym rozdziale skupiamy się na dwóch pierwszych zagadnieniach.

Klucze ECC – identyfikatory i prawa dostępu

Architektura PrivMX zakłada, iż identyfikatory dla nowo tworzonych obiektów nadawane są w większości przypadków przez program kliencki.

  • Identyfikatorami najważniejszych obiektów są (zserializowane) klucze publiczne ECC lub liczone z nich 160 bitowe adresy bitcoin. Powstają one na bazie kluczy prywatnych ECC losowanych przez program kliencki;
  • serwer NIE umożliwia programom klienckim enumeracji obiektów, które przechowuje.
  • dostęp do „zwykłych”, „nie chronionych” operacji na danych wymaga od programu klienckiego posiadania ich identyfikatora, czyli klucza publicznego ECC. Natomiast dostęp do określonych, „chronionych” operacji tj. modyfikacja obiektu lub jego usunięcie (a nawet czasem odczyt!) wymaga posiadania dodatkowo odpowiedniego klucza prywatnego ECC;

Tworzenie obiektów na serwerze PrivMX następuje zawsze tylko i wyłącznie na życzenie programu klienckiego. Ze względu na brak możliwości enumeracji obiektów, para kluczy ECC (privkey,pubkey) losowana przez moduł kliencki staje się jedyną drogą dostępu do obiektu. Przechowywanie tej pary to odpowiedzialność programu klienckiego – architektura PrivMX nie specyfikuje konkretnego sposobu przechowywania tych kluczy, jednak standardowa biblioteka kliencka zawiera pewne gotowe rozwiązania (p. dalsze rozdziały).

Bloki danych

Serwer PrivMX przyjmuje i serwuje dane podobnie jak sterowniki dysków w systemach plików – w postaci bloków o maksymalnej wielkości 128KB (wielkość ta może być konfigurowana).

Bloki to „porcje surowych danych” z przydzielonymi identyfikatorami – BIDs. Bloki tworzone są przez moduł kliencki, który przesyła je na serwer, gdzie są przechowywane bez modyfikacji zawartości.

  • serwer nie szyfruje danych – odpowiedzialny za to jest program kliencki.
  • Program kliencki ustala BID bloku tak, że jest on równy SHA256 zawartości przesyłanego bloku.

Ze względu na konstrukcję bloków (BID bloku = hash jego zawartości):

  • moduły kliencki i serwerowy mogą korzystać z naturalnej kontroli spójności przesyłanych danych – identyfikator bloku to zarazem suma kontrolna danych;
  • nie można stworzyć na serwerze dwóch bloków, dla których przesłane zostały te same dane;
  • bloki są niemodyfikowalne (immutable) – „zmiana danych bloku” może być zrealizowana jedynie poprzez stworzenie i przesłanie nowego bloku – nowe dane oznaczają nowy BID.

Bloki nie istnieją samodzielnie – mogą być one tworzone i czytane przez moduł kliencki zawsze w powiązaniu z operacją na obiekcie, który z tych bloków korzysta – p. deskryptory i wiadomości poniżej. Każdy blok może być użyty przez kilka takich obiektów. Architektura PrivMX zakłada, iż bloki, które nie są przypisane do żadnego obiektu powinny być automatycznie usuwane przez serwer.

Deskryptory

Deskryptory umożliwiają zapisanie na serwerze większych porcji danych niż 128KB – deskryptory „grupują bloki” oraz stanowią punkt dostępu do nich dla programu klienckiego. Są również obiektami realizującymi podstawową politykę praw dostępu do danych (zgodną z wspomnianym wcześniej sposobem wykorzystania publicznych i prywatnych kluczy ECC). Poniżej opisujemy to dokładniej.

Tworzenie deskryptorów (API.descriptorCreate*) i ich modyfikacja (API.descriptorUpdate*) wymaga zazwyczaj przesyłania jednego lub wielu bloków danych poprzez jedno- lub wielorazowe wykorzystanie odpowiedniej metody API. W tym celu wykorzystany jest mechanizm „sesji transferowych” – tworzony jest tymczasowy unikalny identyfikator transferu danych wykorzystywany w kolejnych wywołaniach API.blockCreate:

  1. Program kliencki (zalogowany użytkownik) woła API.descriptorCreateInit i uzyskuje TransferID.
  2. Następnie wysyła bloki używając API.blockCreate – podając TransferID oraz BID i dane każdego bloku. Jeśli program kliencki chce użyć bloków, które na serwerze już istnieją, to używa innej funkcji – API.blockUseExisting.
  3. Losuje nowy klucz prywatny dpriv i generuje dla niego klucz publiczny dpub.
  4. Na koniec tworzy na serwerze nowy deskryptor (API.descriptorCreateFinish). W tym celu przesyła mu:
    • DID (160bitów) – adres bitcoinowy (sieć „main”) dla dpub;
    • TransferID, Blocks – wykorzystany TransferID oraz lista BIDs bloków przesłanych w trakcie sesji;
    • Extra – miejsce na dodatkowe, dowolne dane deskryptora, do wykorzystania przez aplikację kliencką.
    • dpub – klucz publiczny deskryptora;
    • podpis powyższych danych kluczem dpriv – żeby serwer wiedział, że klient ma odpowiedni klucz prywatny, tzn. że jest właścicielem deskryptora.

Deskryptory oraz bloki, w połączeniu z szyfrowaniem danych po stronie klienckiej (szyfrowanie danych bloków oraz pól Extra) umożliwiają stworzenie mechanizmów przechowywania danych, które ukrywają przed serwerem treść plików oraz strukturę katalogów – p. rozdział dot. Modułu klienckiego.

Odczytywanie deskryptora możliwe jest dla każdego programu klienckiego, który posiada odpowiedni DID. Wówczas wywołaniem API.descriptorGet klient uzyskać może pełen zestaw powyższych informacji (Blocks, Extra, dpub itd.) i kolejnymi wywołaniami API.blockGet pobrać może wszystkie bloki danych. Wywołanie API.blockGet wymaga podania BID oraz DID – nie wystarczy znajomość samego identyfikatora bloku, aby uzyskać dostęp do jego danych.

Modyfikacja deskryptora (API.descriptorUpdate) jest operacją podobną do tworzenia deskryptora – wymaga, aby żądanie było podpisane kluczem dpriv oraz wymaga przesłania nowych bloków (lub użycia istniejących). Żądanie usunięcia deskryptora (API.descriptorDelete) także wymaga podpisu kluczem prywatnym. Usunięcie deskryptora nie powoduje automatycznie usunięcia bloków – powinno się to dziać tylko wtedy, gdy nie są one używane przez inne deskryptory lub wiadomości.

Skrzynki pocztowe

Mechanizm działania skrzynek i przesyłania wiadomości pomiędzy programami klienckimi PrivMX (użytkownikami) można ogólnie opisać następująco:

  1. aby wysłać wiadomość, program kliencki nadawcy musi ją podpisać kluczem nadawcy oraz nawiązać standardowe połączenie PrivMX TLS z serwerem adresata (może zrobić to korzystając z proxy). Uruchamia na tym serwerze metodę API.messagePut, aby wrzucić wiadomość do określonej skrzynki pocztowej. Skrzynki identyfikowane są poprzez klucze publiczne.
  2. Adresat odbiera wiadomość ze swojego serwera, gdy program kliencki sprawdza stan swoich skrzynek (API.sinkGetMessages, API.messageGet). Odpowiednie żądania podpisać musi kluczami prywatnymi skrzynek.

Sposób przyjmowania wiadomości przez skrzynkę pocztową, określaną w terminologii PrivMX API jako „sink”, ustala się w momencie jej tworzenia. Wówczas program kliencki, po wylosowaniu nowej pary kluczy (spriv, spub), do metody API.sinkCreate musi przekazać:

  • SID, czyli zserializowany klucz publiczny spub.
  • WriteMode – wartość „private”, „public” lub „anonymous” określająca kto może wysyłać wiadomości do tej skrzynki:
    • private – tylko właściciel klucza prywatnego skrzynki może zostawiać wiadomości;
    • public – wiadomości zostawiać może dowolny użytkownik dowolnego serwera PrivMX. Podpis wiadomości jest wówczas weryfikowany poprzez wykorzystanie PrivMX PKI.
    • anonymous – skrzynka przyjmuje wiadomości podpisane dowolnym kluczem, np. takim, który jest wylosowany przed wysłaniem.
  • Extra – pole do dowolnego wykorzystania przez program kliencki.
  • Podpis powyższych danych wykonany kluczem spriv.

Pola WriteMode i Extra mogą być później przez program kliencki odczytywane (API.sinkGetInfo) i modyfikowane (API.sinkUpdate), a skrzynka jako całość może być usunięta poprzez wywołanie API.sinkDelete. Wszystkie wymienione tu operacje domyślnie dostępne są wyłącznie w połączeniu „zalogowanym” oraz muszą być podpisane odpowiednim kluczem prywatnym.

Wiadomości

Wysyłanie wiadomości (tzn. tworzenie i umieszczanie jej w określonej skrzynce docelowego serwera metodą API.messsagePut) podobne jest do tworzenia deskryptora – wiąże się z utworzeniem „sesji transferowej” i przesłaniem bloków danych. Jedynie wiadomości o wielkości do 1 MB można przesłać bez użycia bloków.

Zakładając, że program kliencki dysponuje parą kluczy nadawcy (cpriv,cpub):

  1. przesyłanie wiadomości rozpoczyna wywołanie API.messagePutInit z parametrami:
    • SID skrzynki docelowej.
    • SenderAddress – adres nadawcy w formacie „user#server.net”. To pole nie jest używane w przypadku skrzynek anonymous.
    • SenderPubKey – należący do nadawcy klucz publiczny cpub..
    • ExtraAuth – miejsce na opcjonalne dodatkowe dane do wykorzystania przez serwer do weryfikacji nadawcy. To pole przydatne jest zwłaszcza wtedy, gdy skrzynka działa w trybie anonymous – np. przyjmuje wiadomości z formularzy internetowych stosujących captcha.
    • Podpis całości żądania kluczem cpriv.
  2. Gdy klient nie zostanie odrzucony (p. poniżej), to otrzymuje nowy TransferID, który może wykorzystać, aby wysłać bloki danych wiadomości. Robi to poprzez odpowiednie, następujące tutaj wywołania API.blockCreate, tj. w przypadku deskryptorów.

  3. Przesyłanie wiadomości kończy wywołanie API.messagePutFinish z parametrami:
    • Extra – pole (max 1MB) do dowolnego wykorzystania przez klienta.
    • TransferID, Blocks – potwierdzenie sesji transferowej – wykorzystany TransferID i lista BIDs wysłanych bloków
    • Tags – lista dowolnych stringów ustawianych przez nadawcę. Serwer przechowuje je razem z wiadomoscią.
    • Podpis całości żądania kluczem cpriv.

Odrzucenie wiadomości nastąpić może z powodu złej weryfikacji adresu lub klucza nadawcy (PrivMX PKI) albo z powodu niepowodzenia opcjonalnej weryfikacji ExtraAuth. Serwer domyślnie nie sprawdza danych przesyłanych w polu ExtraAuth, ale mogą robić to rozszerzenia serwera – w zależności od konkretnej aplikacji.

Skrzynka numeruje przyjęte wiadomości i udostępnia numer ostatniej wiadomości (lastNumber) poprzez wspomniane już API.sinkGetInfo. Odczytywanie listy wiadomości znajdujących się w skrzynce dostępne jest dla posiadacza klucza prywatnego skrzynki poprzez odpowiednio podpisane wywołanie API.sinkGetMessages. Metoda ta pozwala klientowi uzyskać identyfikatory wiadomości (MIDs) z podanego przedziału numeracji oraz takich, które mają ustawione określone tagi.

Program kliencki może odczytać wiadomość poprzez wywołanie API.messageGet używając odpowiedniego MID oraz SID. Uzyskuje wówczas dostęp do pól wiadomości SenderAddress, SenderPubKey, ExtraAnon, Blocks, Extra, Tags. Żądanie odczytania wiadomości musi być podpisane kluczem prywatnym skrzynki. Metoda API.messageDelete wymaga podania podobnych danych i podpisu.

Publiczne i prywatne dane użytkowników

Oprócz obiektów opisanych w poprzednich rozdziałach, serwer przechowuje również dane powiązane z konkretnymi użytkownikami. Ich zakres zależny jest od konkretnych zastosowań.

W domyślnej konfiguracji serwer PrivMX dla każdego użytkownika przechowuje następujące dane prywatne generowane przez program kliencki:

  • dane związane z logowaniem – parametry hashowania (sól, ilość rund, nazwa algorytmu hashowania) wykorzystywane po stronie klienckiej oraz weryfikator SRP. Serwer nie przechowuje hasła użytkownika.
  • zaszyfrowany po stronie klienckiej niewielki rekord danych PrivData – są to dane nieczytelne dla serwera.

Korzystając z mechanizmu PrivMX PKI, serwer domyślnie udostępnia publicznie, tzn. dla dowolnych programów klienckich, następujące dane związane z użytkownikiem:

  • SID domyślnej skrzynki pocztowej – powiązanej z adresem użytkownika;
  • IdentityKeyPub, czyli klucz publiczny użytkownika;
  • Dane profilowe użytkownika – opcjonalne dane takie jak imię, opis, avatar, itp.

Znaczenie powyższych danych oraz sposób dostępu do nich opisane są w dalszym ciągu dokumentu.

Uwagi końcowe

W tym podrozdziale zgromadzone zostały różnorodne uwagi dot. konfiguracji, rozszerzeń i implementacji serwera PrivMX.

Przechowywanie bloków, deskryptorów, skrzynek i wiadomości zrealizować można najlepiej poprzez wykorzystanie prostych i szybkich baz typu key-value, baz dokumentowych.

Usuwanie bloków – wymóg automatycznego usuwania bloków, które nie są przypisane do żadnego obiektu (deskryptora, wiadomości) zrealizować można np. poprzez cykliczne uruchamianie garbage-collectora. Możliwe jest ponadto rozszerzenie API o metodę API.blockDelete i wówczas program kliencki mógłby przechowywać BIDs bloków w (zaszyfrowanym) polu Extra deskryptora.

Istnieje sporo możliwości konfiguracji serwera PrivMX w zakresie dopuszczalności połączeń – od zablokowania dostępu do konkretnych metod API w ramach połączenia standardowego, poprzez ograniczenie połączeń pomiędzy serwerami, aż po wyznaczenie konkretnych adresów IP i użytkowników, które mogą kontaktować się z serwerem.

Domyślna implementacja serwera zakłada, że wszystkie funkcje API związane ze skrzynkami pocztowymi dostępne są jedynie w połączeniu zalogowanym. Oznacza to możliwość rozszerzenia API o metody skrzynek pocztowych „automatycznie” wykorzystujące klucze zalogowanego użytkownika. Wówczas nie byłoby konieczności ciągłego podpisywania żądań i przekazywania klucza publicznego.

Skrzynki pocztowe numerują trafiające do nich wiadomości i udostępniają ich liczbę – lastNumber. Istnieje możliwość implementacji dodatkowych „liczników”, które mogą zoptymalizować proces sprawdzania skrzynek, odczytywania i modyfikowania wiadomości.

Serwer może zaznaczać sobie i przechowywać informację o tym kto utworzył (i jest pierwszym właścicielem) obiektów takich jak bloki, deskryptory, skrzynki.


Następny rozdział: Standardowa biblioteka kliencka PrivMX – rozszerzone klucze ECC, tworzenie kont, logowanie i inicjalizacja klienta, pliki i katalogi, wysyłanie i odbieranie wiadomości, udostępnianie danych.
Spis treści: Architektura PrivMX
Wersja PDF: Architektura PrivMX – opis techniczny (pdf, PL)english version