/* * Copyright (C) 2012-2013 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include #include #include "esp_context.h" #include #include /** * Should be a multiple of 8 */ #define ESP_DEFAULT_WINDOW_SIZE 128 typedef struct private_esp_context_t private_esp_context_t; /** * Private additions to esp_context_t. */ struct private_esp_context_t { /** * Public members */ esp_context_t public; /** * AEAD wrapper or method to encrypt/decrypt/authenticate ESP packets */ aead_t *aead; /** * The highest sequence number that was successfully verified * and authenticated, or assigned in an outbound context */ uint32_t last_seqno; /** * The bit in the window of the highest authenticated sequence number */ u_int seqno_index; /** * The size of the anti-replay window (in bits) */ u_int window_size; /** * The anti-replay window buffer */ chunk_t window; /** * TRUE in case of an inbound ESP context */ bool inbound; }; /** * Set or unset a bit in the window. */ static inline void set_window_bit(private_esp_context_t *this, u_int index, bool set) { u_int i = index / CHAR_BIT; if (set) { this->window.ptr[i] |= 1 << (index % CHAR_BIT); } else { this->window.ptr[i] &= ~(1 << (index % CHAR_BIT)); } } /** * Get a bit from the window. */ static inline bool get_window_bit(private_esp_context_t *this, u_int index) { u_int i = index / CHAR_BIT; return this->window.ptr[i] & (1 << index % CHAR_BIT); } /** * Returns TRUE if the supplied seqno is not already marked in the window */ static bool check_window(private_esp_context_t *this, uint32_t seqno) { u_int offset; offset = this->last_seqno - seqno; offset = (this->seqno_index - offset) % this->window_size; return !get_window_bit(this, offset); } METHOD(esp_context_t, verify_seqno, bool, private_esp_context_t *this, uint32_t seqno) { if (!this->inbound) { return FALSE; } if (seqno > this->last_seqno) { /* |----------------------------------------| * <---------^ ^ or <---------^ ^ * WIN H S WIN H S */ return TRUE; } else if (seqno > 0 && this->window_size > this->last_seqno - seqno) { /* |----------------------------------------| * <---------^ or <---------^ * WIN ^ H WIN ^ H * S S */ return check_window(this, seqno); } else { /* |----------------------------------------| * ^ <---------^ * S WIN H */ return FALSE; } } METHOD(esp_context_t, set_authenticated_seqno, void, private_esp_context_t *this, uint32_t seqno) { u_int i, shift; if (!this->inbound) { return; } if (seqno > this->last_seqno) { /* shift the window to the new highest authenticated seqno */ shift = seqno - this->last_seqno; shift = shift < this->window_size ? shift : this->window_size; for (i = 0; i < shift; ++i) { this->seqno_index = (this->seqno_index + 1) % this->window_size; set_window_bit(this, this->seqno_index, FALSE); } set_window_bit(this, this->seqno_index, TRUE); this->last_seqno = seqno; } else { /* seqno is inside the window, set the corresponding window bit */ i = this->last_seqno - seqno; set_window_bit(this, (this->seqno_index - i) % this->window_size, TRUE); } } METHOD(esp_context_t, get_seqno, uint32_t, private_esp_context_t *this) { return this->last_seqno; } METHOD(esp_context_t, next_seqno, bool, private_esp_context_t *this, uint32_t *seqno) { if (this->inbound || this->last_seqno == UINT32_MAX) { /* inbound or segno would cycle */ return FALSE; } *seqno = ++this->last_seqno; return TRUE; } METHOD(esp_context_t, get_aead, aead_t*, private_esp_context_t *this) { return this->aead; } METHOD(esp_context_t, destroy, void, private_esp_context_t *this) { chunk_free(&this->window); DESTROY_IF(this->aead); free(this); } /** * Create an AEAD algorithm */ static bool create_aead(private_esp_context_t *this, int alg, chunk_t key) { size_t salt = 0; switch (alg) { case ENCR_AES_GCM_ICV8: case ENCR_AES_GCM_ICV12: case ENCR_AES_GCM_ICV16: case ENCR_CHACHA20_POLY1305: salt = 4; break; case ENCR_AES_CCM_ICV8: case ENCR_AES_CCM_ICV12: case ENCR_AES_CCM_ICV16: case ENCR_CAMELLIA_CCM_ICV8: case ENCR_CAMELLIA_CCM_ICV12: case ENCR_CAMELLIA_CCM_ICV16: salt = 3; break; default: break; } if (salt) { this->aead = lib->crypto->create_aead(lib->crypto, alg, key.len - salt, salt); } if (!this->aead) { DBG1(DBG_ESP, "failed to create ESP context: unsupported AEAD " "algorithm %N", encryption_algorithm_names, alg); return FALSE; } if (!this->aead->set_key(this->aead, key)) { DBG1(DBG_ESP, "failed to create ESP context: setting AEAD key failed"); return FALSE; } return TRUE; } /** * Create AEAD wrapper around traditional encryption/integrity algorithms */ static bool create_traditional(private_esp_context_t *this, int enc_alg, chunk_t enc_key, int int_alg, chunk_t int_key) { crypter_t *crypter = NULL; signer_t *signer = NULL; iv_gen_t *ivg; switch (enc_alg) { case ENCR_AES_CTR: case ENCR_CAMELLIA_CTR: /* the key includes a 4 byte salt */ crypter = lib->crypto->create_crypter(lib->crypto, enc_alg, enc_key.len - 4); break; default: crypter = lib->crypto->create_crypter(lib->crypto, enc_alg, enc_key.len); break; } if (!crypter) { DBG1(DBG_ESP, "failed to create ESP context: unsupported encryption " "algorithm %N", encryption_algorithm_names, enc_alg); goto failed; } if (!crypter->set_key(crypter, enc_key)) { DBG1(DBG_ESP, "failed to create ESP context: setting encryption key " "failed"); goto failed; } signer = lib->crypto->create_signer(lib->crypto, int_alg); if (!signer) { DBG1(DBG_ESP, "failed to create ESP context: unsupported integrity " "algorithm %N", integrity_algorithm_names, int_alg); goto failed; } if (!signer->set_key(signer, int_key)) { DBG1(DBG_ESP, "failed to create ESP context: setting signature key " "failed"); goto failed; } ivg = iv_gen_create_for_alg(enc_alg); if (!ivg) { DBG1(DBG_ESP, "failed to create ESP context: creating iv gen failed"); goto failed; } this->aead = aead_create(crypter, signer, ivg); return TRUE; failed: DESTROY_IF(crypter); DESTROY_IF(signer); return FALSE; } /** * Described in header. */ esp_context_t *esp_context_create(int enc_alg, chunk_t enc_key, int int_alg, chunk_t int_key, bool inbound) { private_esp_context_t *this; INIT(this, .public = { .get_aead = _get_aead, .get_seqno = _get_seqno, .next_seqno = _next_seqno, .verify_seqno = _verify_seqno, .set_authenticated_seqno = _set_authenticated_seqno, .destroy = _destroy, }, .inbound = inbound, .window_size = ESP_DEFAULT_WINDOW_SIZE, ); if (encryption_algorithm_is_aead(enc_alg)) { if (!create_aead(this, enc_alg, enc_key)) { destroy(this); return NULL; } } else { if (!create_traditional(this, enc_alg, enc_key, int_alg, int_key)) { destroy(this); return NULL; } } if (inbound) { this->window = chunk_alloc(this->window_size / CHAR_BIT + 1); memset(this->window.ptr, 0, this->window.len); } return &this->public; }