[ Pobierz całość w formacie PDF ]
Tworzenie uwierzytelnionych bezpiecznych kanałów bez użycia SSL | 521
/* Jeżeli nie nasza kolej nadawania, anulujemy. */
if (ctx->nonce[0] != SPC_SERVER_DISTINGUISHER) abort();
/* Najpierw zapisujemy bajt stanu, pózniej identyfikator jednorazowy. */
spc_ssock_write(ctx->fd, &spc_msg_ok, sizeof(spc_msg_ok));
spc_ssock_write(ctx->fd, ctx->nonce, sizeof(ctx->nonce));
/* Następnie zapisujemy długość tekstu zaszyfrowanego, która będzie
* rozmiarem tekstu jawnego powiększonym o SPC_CWC_TAG_LEN bajtów
* zajmowanych przez znacznik. Anulujemy, jeżeli ciąg znaków liczy ponad
* 2^32-1 bajtów. Robimy to w sposób zwykle niezależny od rozmiaru słowa.
*/
if (mlen > (unsigned long)SPC_MAX_MLEN || mlen
for (i = 0; i
encoded_len[SPC_MLEN_FIELD_LEN - i - 1] = (mlen >> (8 * i)) & 0xff;
spc_ssock_write(ctx->fd, encoded_len, sizeof(encoded_len));
/* Teraz przeprowadzamy szyfrowanie CWC i przesyłamy wynik. Należy zauważyć,
* że jeżeli przesyłanie zakończy się niepowodzeniem i nie anuluje się działania,
* tak jak ma to miejsce w poniższym kodzie, trzeba pamiętać o zwolnieniu pamięci
* zajmowanej przez bufor komunikatów.
*/
mlen += SPC_CWC_TAG_LEN;
if (mlen
if (!(ct = (unsigned char *)malloc(mlen))) abort(); /* Brak pamięci. */
cwc_encrypt_message(&(ctx->cwc), &spc_msg_ok, sizeof(spc_msg_ok), msg,
mlen - SPC_CWC_TAG_LEN, ctx->nonce, ct);
spc_ssock_write(ctx->fd, ct, mlen);
free(ct);
}
static void spc_increment_counter(unsigned char *ctr, size_t len) {
while (len--) if (++ctr[len]) return;
abort(); /* Licznik przekręcony, co oznacza wystąpienie błędu! */
}
static void spc_ssock_write( int fd, unsigned char *msg, size_t mlen) {
ssize_t w;
while (mlen) {
if ((w = write(fd, msg, mlen)) == -1) {
switch (errno) {
case EINTR:
break;
default:
abort();
}
} else {
mlen -= w;
msg += w;
}
}
}
Teraz spójrzmy na resztę połączenia po stronie klienta, zanim skupimy uwagę na serwerze.
Kiedy klient chce zakończyć połączenie w sposób bezpieczny, przesyła komunikat pusty, ale
jako bajt stanu przekazuje wartość 0xff. Wciąż musi przesłać poprawny identyfikator jedno-
razowy oraz zaszyfrować komunikat o zerowej długości (co umożliwia schemat CWC). Można
tego dokonać przy użyciu kodu bardzo podobnego do przedstawionego powyżej, więc nie
będziemy marnować miejsca na jego powtarzanie.
522 | Rozdział 9. Komunikacja sieciowa
Teraz spójrzmy na zdarzenia zachodzące w momencie otrzymania przez klienta komunikatu.
Bajt stanu powinien mieć wartość 0x00. Identyfikator jednorazowy otrzymany od serwera
powinien być niezmieniony w porównaniu z przesłanym przez nas poza tym, że pierwszy
bajt powinien mieć wartość SPC_SERVER_DISTINGUISHER. Jeżeli identyfikator jednorazowy
jest niepoprawny, anulujemy po prostu dalsze działania, choć można by również odrzucić
komunikat (jest to jednak nieco problematyczne, ponieważ trzeba wówczas w pewien sposób
dokonać resynchronizacji połączenia).
Następnie odczytujemy wartość długości i dynamicznie przydzielamy bufor, który będzie
w stanie pomieścić tekst zaszyfrowany. Prezentowany kod nigdy nie przydziela więcej niż
232 1 bajtów pamięci. W praktyce należy określić maksymalną długość komunikatu i sprawdzać,
czy pole długości nie przekracza tej wartości. Takie sprawdzenie może zapobiec przeprowa-
dzeniu ataku zablokowania usług, kiedy to napastnik prowokuje przydzielenie takiej ilości
pamięci, która spowalnia działanie maszyny.
Wreszcie wywołujemy funkcję cwc_decrypt_message() i sprawdzamy, czy kod uwierzytelniający
komunikat jest poprawny. Jeśli tak, zwracamy komunikat. W przeciwnym wypadku anulujmy.
static void spc_ssock_read(int, unsigned char *, size_t);
static void spc_get_status_and_nonce(int, unsigned char *, unsigned char *);
static unsigned char *spc_finish_decryption(spc_ssock_t *, unsigned char,
unsigned char *, size_t *);
unsigned char *spc_client_read(spc_ssock_t *ctx, size_t *len, size_t *end) {
unsigned char status;
unsigned char nonce[SPC_CWC_NONCE_LEN];
/* Jeżeli kolej nadawania klienta, anulujemy. */
if (ctx->nonce[0] != SPC_CLIENT_DISTINGUISHER) abort();
ctx->nonce[0] = SPC_SERVER_DISTINGUISHER;
spc_get_status_and_nonce(ctx->fd, &status, nonce);
*end = status;
return spc_finish_decryption(ctx, status, nonce, len);
}
static void spc_get_status_and_nonce(int fd, unsigned char *status,
unsigned char *nonce) {
/* Odczytujemy bajt stanu. Jeżeli jego wartością jest 0x00 lub 0xff, musimy
* sprawdzić resztę komunikatu, w przeciwnym wypadku kończymy od razu.
*/
spc_ssock_read(fd, status, 1);
if (*status != spc_msg_ok && *status != spc_msg_end) abort( );
spc_ssock_read(fd, nonce, SPC_CWC_NONCE_LEN);
}
static unsigned char *spc_finish_decryption(spc_ssock_t *ctx, unsigned char status,
unsigned char *nonce, size_t *len) {
size_t ctlen = 0, i;
unsigned char *ct, encoded_len[SPC_MLEN_FIELD_LEN];
/* Sprawdzamy identyfikator jednorazowy. */
for (i = 0; i
if (nonce[i] != ctx->nonce[i]) abort();
/* Odczytujemy pole długości. */
spc_ssock_read(ctx->fd, encoded_len, SPC_MLEN_FIELD_LEN);
for (i = 0; i
ctlen
ctlen += encoded_len[i];
}
Tworzenie uwierzytelnionych bezpiecznych kanałów bez użycia SSL | 523
/* Odczytujemy tekst zaszyfrowany. */
if (!(ct = (unsigned char *)malloc(ctlen))) abort(); /* Brak pamięci. */
spc_ssock_read(ctx->fd, ct, ctlen);
/* Odszyfrowujemy szyfrogram i anulujemy, jeżeli proces ten kończy się.
* niepowodzeniem. Odszyfrowujemy do tego samego bufora, w którym już
* znajduje się tekst zaszyfrowany.
*/
if (!cwc_decrypt_message(&(ctx->cwc), &status, 1, ct, ctlen, nonce, ct)) {
free(ct);
abort();
}
*len = ctlen - SPC_CWC_TAG_LEN;
/* Unikamy konieczności pózniejszego wywołania funkcji realloc(),
* pozostawiając o SPC_CWC_TAG_LEN dodatkowych bajtów więcej na końcu bufora.
*/
return ct;
}
static void spc_ssock_read(int fd, unsigned char *msg, size_t mlen) {
ssize_t r;
while (mlen) {
if ((r = read(fd, msg, mlen)) == -1) {
switch (errno) {
case EINTR:
break;
default:
abort();
}
} else {
mlen -= r;
msg += r;
}
}
}
Klient jest odpowiedzialny za zwolnienie pamięci przydzielonej dla komunikatów.
Zaleca się wcześniejsze bezpieczne czyszczenie komunikatów, co omówiono w receptu-
rze 13.2. Ponadto należy w bezpieczny sposób zamazywać kontekst spc_ssock_t,
kiedy nie jest już potrzebny.
W przypadku klienta to wszystko. Teraz możemy skupić się na serwerze. Serwer może współ-
użytkować typ spc_ssock_t wykorzystywany przez klienta, jak również wszystkie funkcje
pomocnicze, takie jak spc_ssock_read() i spc_ssock_write(). Jednak interfejs API dla operacji
inicjalizacji, czytania oraz zapisu muszą ulec zmianie.
Poniżej przedstawiono funkcję inicjalizacji wykorzystywaną po stronie serwera, która po-
winna zostać wywołana po zakończeniu procedury wymiany kluczy, ale przed odczytaniem
pierwszego komunikatu od klienta.
void spc_init_server(spc_ssock_t *ctx, unsigned char *key, size_t klen, int fd) {
if (klen != 16 && klen != 24 && klen != 32) abort();
/* należy pamiętać, że funckja cwc_init() czyści przekazany klucz! */
cwc_init(&(ctx->cwc), key, klen * 8);
/* Musimy poczekać na losowy fragment identyfikatora jednorazowego od klienta.
* Fragment licznika można zainicjalizować wartością zero. Element wyróżniający
* ustawiamy na wartość SPC_SERVER_LACKS_NONCE, dzięki czemu będziemy wiedzieć,
524 | Rozdział 9. Komunikacja sieciowa
* że należy skopiować losowy fragment identyfikatora jednorazowego w momencie
* otrzymania komunikatu.
*/
ctx->nonce[0] = SPC_SERVER_LACKS_NONCE;
memset(ctx->nonce + SPC_CTR_IX, 0, SPC_CTR_LEN);
ctx->fd = fd;
}
Pierwszą rzeczą wykonywaną przez serwer jest odczytanie danych z gniazda klienta. W prak-
[ Pobierz całość w formacie PDF ]