/* * Copyright (C) 2005-2010 Martin Willi * Copyright (C) 2010 revosec AG * Copyright (C) 2005 Jan Hutter * 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 "encryption_payload.h" #include #include #include #include #include typedef struct private_encryption_payload_t private_encryption_payload_t; /** * Private data of an encryption_payload_t' Object. * */ struct private_encryption_payload_t { /** * Public encryption_payload_t interface. */ encryption_payload_t public; /** * There is no next payload for an encryption payload, * since encryption payload MUST be the last one. * next_payload means here the first payload of the * contained, encrypted payload. */ u_int8_t next_payload; /** * Flags, including reserved bits */ u_int8_t flags; /** * Length of this payload */ u_int16_t payload_length; /** * Chunk containing the IV, plain, padding and ICV. */ chunk_t encrypted; /** * AEAD transform to use */ aead_t *aead; /** * Contained payloads */ linked_list_t *payloads; }; /** * Encoding rules to parse or generate a IKEv2-Encryption Payload. * * The defined offsets are the positions in a object of type * private_encryption_payload_t. */ encoding_rule_t encryption_payload_encodings[] = { /* 1 Byte next payload type, stored in the field next_payload */ { U_INT_8, offsetof(private_encryption_payload_t, next_payload) }, /* Critical and 7 reserved bits, all stored for reconstruction */ { U_INT_8, offsetof(private_encryption_payload_t, flags) }, /* Length of the whole encryption payload*/ { PAYLOAD_LENGTH, offsetof(private_encryption_payload_t, payload_length) }, /* encrypted data, stored in a chunk. contains iv, data, padding */ { ENCRYPTED_DATA, offsetof(private_encryption_payload_t, encrypted) }, }; /* 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ! Next Payload !C! RESERVED ! Payload Length ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ! Initialization Vector ! ! (length is block size for encryption algorithm) ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ! Encrypted IKE Payloads ! + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ! ! Padding (0-255 octets) ! +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ ! ! Pad Length ! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~ Integrity Checksum Data ~ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ METHOD(payload_t, verify, status_t, private_encryption_payload_t *this) { return SUCCESS; } METHOD(payload_t, get_encoding_rules, void, private_encryption_payload_t *this, encoding_rule_t **rules, size_t *count) { *rules = encryption_payload_encodings; *count = countof(encryption_payload_encodings); } METHOD(payload_t, get_type, payload_type_t, private_encryption_payload_t *this) { return ENCRYPTED; } METHOD(payload_t, get_next_type, payload_type_t, private_encryption_payload_t *this) { return this->next_payload; } METHOD(payload_t, set_next_type, void, private_encryption_payload_t *this, payload_type_t type) { /* the next payload is set during add */ } /** * Compute the lenght of the whole payload */ static void compute_length(private_encryption_payload_t *this) { enumerator_t *enumerator; payload_t *payload; size_t bs, length = 0; if (this->encrypted.len) { length = this->encrypted.len; } else { enumerator = this->payloads->create_enumerator(this->payloads); while (enumerator->enumerate(enumerator, &payload)) { length += payload->get_length(payload); } enumerator->destroy(enumerator); if (this->aead) { /* append padding */ bs = this->aead->get_block_size(this->aead); length += bs - (length % bs); /* add iv */ length += this->aead->get_iv_size(this->aead); /* add icv */ length += this->aead->get_icv_size(this->aead); } } length += ENCRYPTION_PAYLOAD_HEADER_LENGTH; this->payload_length = length; } METHOD2(payload_t, encryption_payload_t, get_length, size_t, private_encryption_payload_t *this) { compute_length(this); return this->payload_length; } METHOD(encryption_payload_t, add_payload, void, private_encryption_payload_t *this, payload_t *payload) { payload_t *last_payload; if (this->payloads->get_count(this->payloads) > 0) { this->payloads->get_last(this->payloads, (void **)&last_payload); last_payload->set_next_type(last_payload, payload->get_type(payload)); } else { this->next_payload = payload->get_type(payload); } payload->set_next_type(payload, NO_PAYLOAD); this->payloads->insert_last(this->payloads, payload); compute_length(this); } METHOD(encryption_payload_t, remove_payload, payload_t *, private_encryption_payload_t *this) { payload_t *payload; if (this->payloads->remove_first(this->payloads, (void**)&payload) == SUCCESS) { return payload; } return NULL; } /** * Generate payload before encryption */ static chunk_t generate(private_encryption_payload_t *this, generator_t *generator) { payload_t *current, *next; enumerator_t *enumerator; u_int32_t *lenpos; chunk_t chunk = chunk_empty; enumerator = this->payloads->create_enumerator(this->payloads); if (enumerator->enumerate(enumerator, ¤t)) { this->next_payload = current->get_type(current); while (enumerator->enumerate(enumerator, &next)) { current->set_next_type(current, next->get_type(next)); generator->generate_payload(generator, current); current = next; } current->set_next_type(current, NO_PAYLOAD); generator->generate_payload(generator, current); chunk = generator->get_chunk(generator, &lenpos); DBG2(DBG_ENC, "generated content in encryption payload"); } enumerator->destroy(enumerator); return chunk; } /** * Append the encryption payload header to the associated data */ static chunk_t append_header(private_encryption_payload_t *this, chunk_t assoc) { struct { u_int8_t next_payload; u_int8_t flags; u_int16_t length; } __attribute__((packed)) header = { .next_payload = this->next_payload, .flags = this->flags, .length = htons(get_length(this)), }; return chunk_cat("cc", assoc, chunk_from_thing(header)); } METHOD(encryption_payload_t, encrypt, bool, private_encryption_payload_t *this, chunk_t assoc) { chunk_t iv, plain, padding, icv, crypt; generator_t *generator; rng_t *rng; size_t bs; if (this->aead == NULL) { DBG1(DBG_ENC, "encrypting encryption payload failed, transform missing"); return FALSE; } rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); if (!rng) { DBG1(DBG_ENC, "encrypting encryption payload failed, no RNG found"); return FALSE; } assoc = append_header(this, assoc); generator = generator_create(); plain = generate(this, generator); bs = this->aead->get_block_size(this->aead); /* we need at least one byte padding to store the padding length */ padding.len = bs - (plain.len % bs); iv.len = this->aead->get_iv_size(this->aead); icv.len = this->aead->get_icv_size(this->aead); /* prepare data to authenticate-encrypt: * | IV | plain | padding | ICV | * \____crypt______/ ^ * | / * v / * assoc -> + ------->/ */ free(this->encrypted.ptr); this->encrypted = chunk_alloc(iv.len + plain.len + padding.len + icv.len); iv.ptr = this->encrypted.ptr; memcpy(iv.ptr + iv.len, plain.ptr, plain.len); plain.ptr = iv.ptr + iv.len; padding.ptr = plain.ptr + plain.len; icv.ptr = padding.ptr + padding.len; crypt = chunk_create(plain.ptr, plain.len + padding.len); generator->destroy(generator); rng->get_bytes(rng, iv.len, iv.ptr); rng->get_bytes(rng, padding.len - 1, padding.ptr); padding.ptr[padding.len - 1] = padding.len - 1; rng->destroy(rng); DBG3(DBG_ENC, "encryption payload encryption:"); DBG3(DBG_ENC, "IV %B", &iv); DBG3(DBG_ENC, "plain %B", &plain); DBG3(DBG_ENC, "padding %B", &padding); DBG3(DBG_ENC, "assoc %B", &assoc); this->aead->encrypt(this->aead, crypt, assoc, iv, NULL); DBG3(DBG_ENC, "encrypted %B", &crypt); DBG3(DBG_ENC, "ICV %B", &icv); free(assoc.ptr); return TRUE; } /** * Parse the payloads after decryption. */ static status_t parse(private_encryption_payload_t *this, chunk_t plain) { parser_t *parser; payload_type_t type; parser = parser_create(plain); type = this->next_payload; while (type != NO_PAYLOAD) { payload_t *payload; if (parser->parse_payload(parser, type, &payload) != SUCCESS) { parser->destroy(parser); return PARSE_ERROR; } if (payload->verify(payload) != SUCCESS) { DBG1(DBG_ENC, "%N verification failed", payload_type_names, payload->get_type(payload)); payload->destroy(payload); parser->destroy(parser); return VERIFY_ERROR; } type = payload->get_next_type(payload); this->payloads->insert_last(this->payloads, payload); } parser->destroy(parser); DBG2(DBG_ENC, "parsed content of encryption payload"); return SUCCESS; } METHOD(encryption_payload_t, decrypt, status_t, private_encryption_payload_t *this, chunk_t assoc) { chunk_t iv, plain, padding, icv, crypt; size_t bs; if (this->aead == NULL) { DBG1(DBG_ENC, "decrypting encryption payload failed, transform missing"); return INVALID_STATE; } /* prepare data to authenticate-decrypt: * | IV | plain | padding | ICV | * \____crypt______/ ^ * | / * v / * assoc -> + ------->/ */ bs = this->aead->get_block_size(this->aead); iv.len = this->aead->get_iv_size(this->aead); iv.ptr = this->encrypted.ptr; icv.len = this->aead->get_icv_size(this->aead); icv.ptr = this->encrypted.ptr + this->encrypted.len - icv.len; crypt.ptr = iv.ptr + iv.len; crypt.len = this->encrypted.len - iv.len; if (iv.len + icv.len > this->encrypted.len || (crypt.len - icv.len) % bs) { DBG1(DBG_ENC, "decrypting encryption payload failed, invalid length"); return FAILED; } assoc = append_header(this, assoc); DBG3(DBG_ENC, "encryption payload decryption:"); DBG3(DBG_ENC, "IV %B", &iv); DBG3(DBG_ENC, "encrypted %B", &crypt); DBG3(DBG_ENC, "ICV %B", &icv); DBG3(DBG_ENC, "assoc %B", &assoc); if (!this->aead->decrypt(this->aead, crypt, assoc, iv, NULL)) { DBG1(DBG_ENC, "verifying encryption payload integrity failed"); free(assoc.ptr); return FAILED; } free(assoc.ptr); plain = chunk_create(crypt.ptr, crypt.len - icv.len); padding.len = plain.ptr[plain.len - 1] + 1; if (padding.len > plain.len) { DBG1(DBG_ENC, "decrypting encryption payload failed, " "padding invalid %B", &crypt); return PARSE_ERROR; } plain.len -= padding.len; padding.ptr = plain.ptr + plain.len; DBG3(DBG_ENC, "plain %B", &plain); DBG3(DBG_ENC, "padding %B", &padding); return parse(this, plain); } METHOD(encryption_payload_t, set_transform, void, private_encryption_payload_t *this, aead_t* aead) { this->aead = aead; } METHOD2(payload_t, encryption_payload_t, destroy, void, private_encryption_payload_t *this) { this->payloads->destroy_offset(this->payloads, offsetof(payload_t, destroy)); free(this->encrypted.ptr); free(this); } /* * Described in header */ encryption_payload_t *encryption_payload_create() { private_encryption_payload_t *this; INIT(this, .public = { .payload_interface = { .verify = _verify, .get_encoding_rules = _get_encoding_rules, .get_length = _get_length, .get_next_type = _get_next_type, .set_next_type = _set_next_type, .get_type = _get_type, .destroy = _destroy, }, .get_length = _get_length, .add_payload = _add_payload, .remove_payload = _remove_payload, .set_transform = _set_transform, .encrypt = _encrypt, .decrypt = _decrypt, .destroy = _destroy, }, .next_payload = NO_PAYLOAD, .payload_length = ENCRYPTION_PAYLOAD_HEADER_LENGTH, .payloads = linked_list_create(), ); return &this->public; }