Commit 7ae3a3e5 authored by Yann Garcia's avatar Yann Garcia
Browse files

Validating integrity protection with 128 AES CMAC

parent 4864f43d
Loading
Loading
Loading
Loading
+0 −12
Original line number Diff line number Diff line
@@ -6,18 +6,6 @@
		{
			"path": "../../frameworks/titan/titan.core"
		},
		{
			"path": "../5G-AKA-simulation-in-C"
		},
		{
			"path": "../free5gc"
		},
		{
			"path": "../free5gc.util"
		},
		{
			"path": "../5G_ciphered_NAS_decipher_tool"
		},
		{
			"path": "../open5gs"
		},
+246 −268
Original line number Diff line number Diff line
@@ -19,8 +19,6 @@
#include "ia2_128.hh"
#include "ia3_128.hh"

#include "zuc.hh"

namespace NG__SecurityDefinitionsAndExternalFunctions {

   /**
@@ -56,10 +54,7 @@ OCTETSTRING fx__NG__NasIntegrityAlgorithm(const OCTETSTRING& p_encoded_nas_pdu,
  uint32_t mac_length;
  uint32_t nas_count = converter::get_instance().bytes_to_int(std::vector<uint8_t>(static_cast<const unsigned char*>(p_nas_count), static_cast<const unsigned char*>(p_nas_count) + p_nas_count.lengthof()));
  int result = -1;
   if (bit2int(p_integrity_algorithm) == 0) { // IA0/NEA0: No integrity protection
      loggers::get_instance().log("fx__NG__NasIntegrityAlgorithm: No integrity protection");
      return p_encoded_nas_pdu; // no integrity protection
   } else if (bit2int(p_integrity_algorithm) == 1) { // IA1_128/NEA1_128: Snow 3G based algorithm
  if (bit2int(p_integrity_algorithm) == 1) { // IA1_128/NEA1_128: Snow 3G based algorithm
  loggers::get_instance().log("fx__NG__NasIntegrityAlgorithm: ia1_128 selected");
  ia1_128 enc;
  result = enc.mac(
@@ -76,7 +71,7 @@ OCTETSTRING fx__NG__NasIntegrityAlgorithm(const OCTETSTRING& p_encoded_nas_pdu,
  } else if (bit2int(p_integrity_algorithm) == 2) { // IA2_128/NEA2_128: AES 128 CTR based algorithm
  loggers::get_instance().log("fx__NG__NasIntegrityAlgorithm: ia2_128 selected");
  ia2_128 enc;
      result = enc.encrypt(
  result = enc.mac(
                    bit2int(p_integrity_algorithm), 
                    static_cast<const unsigned char*>(bit2oct(p_knas_int)), 
                    nas_count, 
@@ -117,7 +112,7 @@ OCTETSTRING fx__NG__NasIntegrityAlgorithm(const OCTETSTRING& p_encoded_nas_pdu,
  return int2oct(0, 0);
  }
  OCTETSTRING os(mac_length, (const unsigned char*)mac);
   free(mac);
  std::free(mac);

  loggers::get_instance().log_msg("<<< fx__NG__NasIntegrityAlgorithm: ", os);
  return os;
@@ -180,14 +175,6 @@ OCTETSTRING fx__NG__NasCiphering(const OCTETSTRING& p_encoded_nas_pdu,const BIT
                          );
  } else if (bit2int(p_ciphering_algorithm) == 3) { // IA3_128/NEA3_128: ZUC based algorithm
     loggers::get_instance().log("fx__NG__NasCiphering: ia3_128 selected");
      // loggers::get_instance().log("fx__NG__NasCiphering: %d", p_encoded_nas_pdu.lengthof());
      // cyphered = (unsigned char*) std::malloc(p_encoded_nas_pdu.lengthof());
      // zuc_eea3(
      //   (unsigned char*)static_cast<const unsigned char*>(bit2oct(p_knas_enc)),
      //   nas_count, (uint32_t)bit2int(p_bearer_id), p_direction,
      //   p_encoded_nas_pdu.lengthof() * 8, (unsigned char*)static_cast<const unsigned char*>(p_encoded_nas_pdu), cyphered);
      // result = 0;
      // cyphered_length = p_encoded_nas_pdu.lengthof();
     ia3_128 enc;
     result = enc.encrypt(
                          bit2int(p_ciphering_algorithm), 
@@ -278,13 +265,6 @@ OCTETSTRING fx__NG__NasDeciphering(const OCTETSTRING& p_ciphered_nas_message, co
                          &payload_length
                          );
  } else if (bit2int(p_ciphering_algorithm) == 3) { // IA3_128/NEA3_128: ZUC based algorithm
      // loggers::get_instance().log("fx__NG__NasDeciphering: ia1_128 selected");
      // zuc_eea3(
      //   (unsigned char*)static_cast<const unsigned char*>(bit2oct(p_knas_enc)),
      //   nas_count, bit2int(p_bearer_id), p_direction,
      //   p_ciphered_nas_message.lengthof() * 8, (unsigned char*)static_cast<const unsigned char*>(p_ciphered_nas_message), payload);
      // result = 0;
      // payload_length = p_ciphered_nas_message.lengthof();
    ia3_128 enc;
    result = enc.decrypt(
                          bit2int(p_ciphering_algorithm), 
@@ -319,6 +299,4 @@ OCTETSTRING fx__NG__NasDeciphering(const OCTETSTRING& p_ciphered_nas_message, co
  return os;
}



} // namespace NG__SecurityDefinitionsAndExternalFunctions
+323 −199
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@

#include "ia2_128.hh"
#include <openssl/evp.h>
#include <openssl/cmac.h>
#include <openssl/aes.h>
#include <cstring>
#include <cstdlib>
@@ -18,6 +19,7 @@ namespace {
  constexpr size_t KEY_SIZE = 16;  // 128 bits = 16 bytes
  constexpr size_t BLOCK_SIZE = 16; // AES block size
  constexpr size_t COUNTER_SIZE = 16; // Counter block size
  constexpr size_t MAC_SIZE = 4; // MAC size in bytes
}

int ia2_128::encrypt(const uint8_t algo_id,
@@ -71,7 +73,7 @@ int ia2_128::encrypt(const uint8_t algo_id,
      return -1;
  }

    int ret = -1;
  int result = -1;
  int len = 0;
  int final_len = 0;

@@ -98,18 +100,18 @@ int ia2_128::encrypt(const uint8_t algo_id,
  *cyphered_length = static_cast<uint32_t>(len + final_len);
  loggers::get_instance().log("ia2_128::encrypt: cyphered_length: %d", *cyphered_length);
  loggers::get_instance().log_to_hexa("ia2_128::encrypt: cyphered: ", *cyphered, *cyphered_length);
    ret = 0;
  result = 0;

cleanup:
  EVP_CIPHER_CTX_free(ctx);
    if (ret != 0) {
  if (result != 0) {
      std::free(*cyphered);
      *cyphered = nullptr;
      *cyphered_length = 0;
  }

    loggers::get_instance().log("<<< ia2_128::encrypt: ret: %d", ret);
    return ret;
  loggers::get_instance().log("<<< ia2_128::encrypt: result: %d", result);
  return result;
}

int ia2_128::decrypt(const uint8_t algo_id,
@@ -163,7 +165,7 @@ int ia2_128::decrypt(const uint8_t algo_id,
      return -1;
  }

    int ret = -1;
  int result = -1;
  int len = 0;
  int final_len = 0;

@@ -190,18 +192,18 @@ int ia2_128::decrypt(const uint8_t algo_id,
  *payload_length = static_cast<uint32_t>(len + final_len);
  loggers::get_instance().log("ia2_128::decrypt: cyphered_length: %d", *payload_length);
  loggers::get_instance().log_to_hexa("ia2_128::decrypt: cyphered: ", *payload, *payload_length);
    ret = 0;
  result = 0;

cleanup:
  EVP_CIPHER_CTX_free(ctx);
    if (ret != 0) {
  if (result != 0) {
      std::free(*payload);
      *payload = nullptr;
      *payload_length = 0;
  }

    loggers::get_instance().log("<<< ia2_128::decrypt: ret: %d", ret);
    return ret;
  loggers::get_instance().log("<<< ia2_128::decrypt: result: %d", result);
  return result;
}

int ia2_128::mac(const uint8_t algo_id,
@@ -235,18 +237,73 @@ int ia2_128::mac(const uint8_t algo_id,
  }

  // Allocate memory for ciphertext
  *mac = static_cast<unsigned char*>(std::malloc(4));
  *mac = static_cast<unsigned char*>(std::malloc(MAC_SIZE));
  if (!*mac) {
    loggers::get_instance().error("ia2_128::mac: Failed to allocate memory");
    return -1;
  }
  *mac_length = 4;
  *mac_length = MAC_SIZE;

  // TODO: Implement AES CMAC computation according to 3GPP TS 33.401
  // Generate input block for CMAC
  unsigned char* input_block = nullptr;
  uint32_t input_block_length = 0;

  int result = generate_input_block(count, bearer, direction, payload, payload_length * 8, &input_block, &input_block_length);
  if (result != 0) {
    loggers::get_instance().error("ia2_128::mac: Failed to generate block for CMAC");
    return -1;
  }

  // Compute CMAC using OpenSSL
  // Note: CMAC functions are deprecated in OpenSSL 3.0+, but we use them for compatibility
  // with older versions. For OpenSSL 3.0+, consider using EVP_MAC API instead.
  
  // CMAC for AES-128 produces 16 bytes, but we need only 4 bytes (32 bits) for 128-EIA2
  unsigned char cmac_output[16];
  size_t cmac_len = sizeof(cmac_output);
  
  // Use deprecated CMAC API for compatibility with OpenSSL 1.x
  CMAC_CTX* ctx = CMAC_CTX_new();
  if (!ctx) {
    loggers::get_instance().error("ia2_128::mac: Failed to intialize CMAC context");
    std::free(input_block);
    return -1;
  }

  result = -1;

  // Initialize CMAC context with AES-128
  if (CMAC_Init(ctx, knas_int, KEY_SIZE, EVP_aes_128_cbc(), nullptr) != 1) {
    loggers::get_instance().error("ia2_128::mac: CMAC_Init failure");
    goto cleanup;
  }

  // Update CMAC with input block
  if (CMAC_Update(ctx, input_block, input_block_length) != 1) {
    loggers::get_instance().error("ia2_128::mac: CMAC_Update failure");
    goto cleanup;
  }

  // Finalize CMAC - get the MAC (16 bytes for AES-128)
  if (CMAC_Final(ctx, cmac_output, &cmac_len) != 1) {
    loggers::get_instance().error("ia2_128::mac: CMAC_Final failure");
    goto cleanup;
  }
  #pragma GCC diagnostic pop

  // 128-EIA2 produces 32-bit (4-byte) MAC
  // Truncate CMAC output to 4 bytes (take the first 4 bytes)
  std::memcpy(*mac, cmac_output, MAC_SIZE);
  loggers::get_instance().log_to_hexa("ia2_128::mac: mac: ", *mac, *mac_length);

  loggers::get_instance().log("<<< ia2_128::mac: ret: 0");
  cleanup:
    #pragma GCC diagnostic push
    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    CMAC_CTX_free(ctx);
    #pragma GCC diagnostic pop
    std::free(input_block);
  
  loggers::get_instance().log("<<< ia2_128::mac: result: 0");
  return 0;
}

@@ -264,3 +321,70 @@ void ia2_128::generate_counter_block(const uint32_t count,
  // Remaining bytes are zero
}

int ia2_128::generate_input_block(const uint32_t count,
                                  const uint8_t bearer,
                                  const uint8_t direction,
                                  const unsigned char* payload,
                                  const uint32_t payload_length,
                                  unsigned char** input_block,
                                  uint32_t* input_block_length) {
  loggers::get_instance().log(">>> ia2_128::generate_input_block: count: %u", count);
  loggers::get_instance().log(">>> ia2_128::generate_input_block: bearer: %d", bearer);
  loggers::get_instance().log(">>> ia2_128::generate_input_block: direction: %d", direction);
  loggers::get_instance().log(">>> ia2_128::generate_input_block: payload_length: %d", payload_length);
  loggers::get_instance().log_to_hexa(">>> ia2_128::generate_input_block: payload", payload, payload_length / 8);

  if (!payload || !input_block || !input_block_length) {
    loggers::get_instance().error("ia2_128::generate_input_block: Wrong input parameters");
    return -1;
  }

  // 3GPP TS 33.401 Section 5.1.4.2:
  // Input block = COUNT || (BEARER || DIRECTION || 0^26) || MESSAGE
  // COUNT: 4 bytes
  // BEARER + DIRECTION + padding: 4 bytes (32 bits total)
  // MESSAGE: (payload_length + 7) / 8 bytes (convert bits to bytes, round up)
  uint32_t payload_bytes = (payload_length + 7) / 8;
  uint32_t total_length = 4 + 4 + payload_bytes;
  loggers::get_instance().log("ia2_128::generate_input_block: payload_bytes: %u", payload_bytes);
  loggers::get_instance().log("ia2_128::generate_input_block: total_length: %u", total_length);

  // Allocate input block
  *input_block = static_cast<unsigned char*>(std::malloc(total_length));
  if (!*input_block) {
    loggers::get_instance().error("ia2_128::mac: Failed to allocate memory");
    return -1;
  }

  // Set COUNT (bytes 0-3, big-endian)
  unsigned char* ptr = *input_block;
  ptr[0] = static_cast<unsigned char>((count >> 24) & 0xFF);
  ptr[1] = static_cast<unsigned char>((count >> 16) & 0xFF);
  ptr[2] = static_cast<unsigned char>((count >> 8) & 0xFF);
  ptr[3] = static_cast<unsigned char>(count & 0xFF);
  ptr += 4;
  // Set BEARER and DIRECTION (byte 4)
  // Bits 0-4: BEARER (5 bits), Bit 5: DIRECTION (1 bit), Bits 6-31: padding (26 bits)
  ptr[0] = static_cast<unsigned char>((bearer << 3) | (direction << 2));
  ptr[1] = 0;
  ptr[2] = 0;
  ptr[3] = 0;
  ptr += 4;
  // Copy payload
  std::memcpy(ptr, payload, payload_bytes);
  loggers::get_instance().log_to_hexa("ia2_128::generate_input_block: ptr", ptr, total_length);

  // Handle partial bytes if payload_length is not a multiple of 8
  if (payload_length % 8 != 0) {
      // Clear the unused bits in the last byte
      uint32_t unused_bits = 8 - (payload_length % 8);
      uint8_t mask = static_cast<uint8_t>(0xFF << unused_bits);
      ptr[payload_bytes - 1] &= ~mask;
  }

  loggers::get_instance().log("ia2_128::generate_input_block: *input_block_length: %d", *input_block_length);
  *input_block_length = total_length;
  loggers::get_instance().log_to_hexa("<<< ia2_128::generate_input_block: ptr", static_cast<const uint8_t*>(*input_block), *input_block_length * sizeof(uint32_t));
  return 0;
}
+138 −117
Original line number Diff line number Diff line
@@ -8,8 +8,7 @@
 * as specified in 3GPP TS 33.401 for NAS encryption/decryption.
 */

#ifndef IA2_128_H
#define IA2_128_H
#pragma once

#include <cstdint>
#include <cstddef>
@@ -157,7 +156,29 @@ private:
                              const uint8_t bearer,
                              const uint8_t direction,
                              unsigned char* counter_block);
};

#endif // IA2_128_H

  /**
   * @brief Generates the input block for CMAC computation
   * 
   * According to 3GPP TS 33.401, the input to CMAC consists of:
   * - COUNT (32 bits, big-endian)
   * - BEARER (5 bits) + DIRECTION (1 bit) + padding (26 bits)
   * - MESSAGE (variable length, in bits)
   * 
   * @param count NAS count
   * @param bearer Bearer identifier
   * @param direction Direction bit
   * @param message Pointer to message data
   * @param message_length Length of message in bits
   * @param input_block Output buffer for the input block (will be allocated)
   * @param input_block_length Output variable for the input block length in bytes
   * @return 0 on success, negative on error
   */
  int generate_input_block(const uint32_t count,
                           const uint8_t bearer,
                           const uint8_t direction,
                           const unsigned char* message,
                           const uint32_t message_length,
                           unsigned char** input_block,
                           uint32_t* input_block_length);
};  
+0 −24
Original line number Diff line number Diff line
@@ -14,8 +14,6 @@
#include <cstring>
#include <cstdlib>

#include "zuc.hh"

#include "loggers.hh"

namespace {
@@ -23,28 +21,6 @@ namespace {
    constexpr size_t IV_SIZE = 16;   // Initialization vector size
}

// uint32_t ia3_128::lfsr_s0  = 0;
// uint32_t ia3_128::lfsr_s1  = 0;
// uint32_t ia3_128::lfsr_s2  = 0;
// uint32_t ia3_128::lfsr_s3  = 0;
// uint32_t ia3_128::lfsr_s4  = 0;
// uint32_t ia3_128::lfsr_s5  = 0;
// uint32_t ia3_128::lfsr_s6  = 0;
// uint32_t ia3_128::lfsr_s7  = 0;
// uint32_t ia3_128::lfsr_s8  = 0;
// uint32_t ia3_128::lfsr_s9  = 0;
// uint32_t ia3_128::lfsr_s10 = 0;
// uint32_t ia3_128::lfsr_s11 = 0;
// uint32_t ia3_128::lfsr_s12 = 0;
// uint32_t ia3_128::lfsr_s13 = 0;
// uint32_t ia3_128::lfsr_s14 = 0;
// uint32_t ia3_128::lfsr_s15 = 0;
// uint32_t ia3_128::f_r1     = 0;
// uint32_t ia3_128::f_r2     = 0;
// uint32_t ia3_128::brc_x0   = 0;
// uint32_t ia3_128::brc_x1   = 0;
// uint32_t ia3_128::brc_x2   = 0;
// uint32_t ia3_128::brc_x3   = 0;
void ia3_128::reset() {
  lfsr_s0  = 0;
  lfsr_s1  = 0;
Loading