Commit 4864f43d authored by Yann Garcia's avatar Yann Garcia
Browse files

Review SNOW 3G & ZUC code

parent 97a3fa35
Loading
Loading
Loading
Loading
+24 −24
Original line number Diff line number Diff line
@@ -61,32 +61,32 @@ OCTETSTRING fx__NG__NasIntegrityAlgorithm(const OCTETSTRING& p_encoded_nas_pdu,
      return p_encoded_nas_pdu; // no integrity protection
   } else 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(
      //                      bit2int(p_integrity_algorithm), 
      //                      static_cast<const unsigned char*>(bit2oct(p_knas_int)), 
      //                      nas_count, 
      //                      bit2int(p_bearer_id), 
      //                      p_direction, 
      //                      static_cast<const unsigned char*>(p_encoded_nas_pdu), 
      //                      p_encoded_nas_pdu.lengthof(),
      //                      &mac,
      //                      &mac_length
      //                      );
      ia1_128 enc;
      result = enc.mac(
                       bit2int(p_integrity_algorithm), 
                       static_cast<const unsigned char*>(bit2oct(p_knas_int)), 
                       nas_count, 
                       bit2int(p_bearer_id), 
                       p_direction, 
                       static_cast<const unsigned char*>(p_encoded_nas_pdu), 
                       p_encoded_nas_pdu.lengthof(),
                       &mac,
                       &mac_length
                       );
   } 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(
      //                      bit2int(p_integrity_algorithm), 
      //                      static_cast<const unsigned char*>(bit2oct(p_knas_int)), 
      //                      nas_count, 
      //                      bit2int(p_bearer_id), 
      //                      p_direction, 
      //                      static_cast<const unsigned char*>(p_encoded_nas_pdu), 
      //                      p_encoded_nas_pdu.lengthof(),
      //                      &mac,
      //                      &mac_length
      //                      );
      ia2_128 enc;
      result = enc.encrypt(
                           bit2int(p_integrity_algorithm), 
                           static_cast<const unsigned char*>(bit2oct(p_knas_int)), 
                           nas_count, 
                           bit2int(p_bearer_id), 
                           p_direction, 
                           static_cast<const unsigned char*>(p_encoded_nas_pdu), 
                           p_encoded_nas_pdu.lengthof(),
                           &mac,
                           &mac_length
                           );
   } else if (bit2int(p_integrity_algorithm) == 3) { // IA3_128/NEA3_128: ZUC based algorithm
      loggers::get_instance().log("fx__NG__NasIntegrityAlgorithm: ia3_128 selected");
      ia3_128 enc;
+153 −5
Original line number Diff line number Diff line
@@ -21,6 +21,11 @@ namespace {
  constexpr size_t IV_SIZE = 16;   // Initialization vector size
}

void ia1_128::reset() {
  loggers::get_instance().log("ia1_128::reset");
  std::memset(&_ctx, 0, sizeof(_ctx));
} 

int ia1_128::encrypt(const uint8_t algo_id,
                     const unsigned char* knas_enc,
                     const uint32_t count,
@@ -57,7 +62,8 @@ int ia1_128::encrypt(const uint8_t algo_id,
    return -1;
  }
  snow3g_context_t ctx;
  snow3g_initialize(count, bearer, direction, (const char *)knas_enc, &ctx);
  ctx.ciphering = true;
  snow3g_initialize(count, bearer, direction, knas_enc, &ctx);
  snow3g_generate(payload_length, payload, *cyphered, &ctx);
  *cyphered_length = payload_length;
  loggers::get_instance().log("ia1_128::encrypt: cyphered_length: %d", *cyphered_length);
@@ -88,7 +94,118 @@ int ia1_128::decrypt(const uint8_t algo_id,
                 cyphered, cyphered_length, payload, payload_length);
}

int ia1_128::snow3g_initialize(uint32_t count, uint8_t bearer, uint8_t direction, const char *knas_enc, snow3g_context_t *ctx) {
int ia1_128::mac(const uint8_t algo_id,
                 const unsigned char* knas_int,
                 const uint32_t count,
                 const uint8_t bearer,
                 const uint8_t direction,
                 const unsigned char* payload,
                 const uint32_t payload_length,
                 unsigned char** mac,
                 uint32_t* mac_length) {
  loggers::get_instance().log_to_hexa(">>> ia1_128::mac: knas_int", knas_int, KEY_SIZE);
  loggers::get_instance().log(">>> ia1_128::mac: payload_length: %d", payload_length);
  loggers::get_instance().log_to_hexa(">>> ia1_128::mac: payload", payload, payload_length);
  loggers::get_instance().log(">>> ia1_128::mac: algo_id: %d", algo_id);
  loggers::get_instance().log(">>> ia1_128::mac: count: %u", count);
  loggers::get_instance().log(">>> ia1_128::mac: bearer: %d", bearer);
  loggers::get_instance().log(">>> ia1_128::mac: direction: %d", direction);

  // Input validation
  if (!knas_int || !payload || !mac || !mac_length) {
    loggers::get_instance().error("ia1_128::mac: Wrong input parameters");
    return -1;
  }

  if (payload_length == 0) {
    loggers::get_instance().error("ia1_128::mac: Wrong payload length");
    *mac = nullptr;
    *mac_length = 0;
    return 0;
  }

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

  snow3g_context_t ctx;
  ctx.ciphering = false;
  snow3g_initialize(count, bearer, direction, knas_int, &ctx);

  uint32_t z[5] = {0};
  snow3g_generate(5, (uint32_t*)z, &ctx);
  loggers::get_instance().log_to_hexa("ia1_128::mac: z: ", (const unsigned char*)z, 5 * sizeof(uint32_t));

  uint64_t p = (uint64_t)z[0] << 32 | (uint64_t)z[1];
  uint64_t q = (uint64_t)z[2] << 32 | (uint64_t)z[3];
  
  uint32_t d;
  uint64_t length = payload_length * 8; // Length in bits
  loggers::get_instance().log("ia1_128::mac: length: %ld", length);
  if ((length % 64) == 0)
    d = (length>>6) + 1;
  else
    d = (length>>6) + 2;
  loggers::get_instance().log("ia1_128::mac: d: %u", d);
  uint64_t c = 0x1b;

  uint64_t eval = 0;
  uint64_t v = 0;
  for (uint64_t i = 0; i < d - 2; i++) {
    v = eval ^ ((uint64_t)payload[8*i  ]<<56 | (uint64_t)payload[8*i+1] << 48 | 
                (uint64_t)payload[8*i+2]<<40 | (uint64_t)payload[8*i+3] << 32 | 
                (uint64_t)payload[8*i+4]<<24 | (uint64_t)payload[8*i+5] << 16 | 
                (uint64_t)payload[8*i+6]<< 8 | (uint64_t)payload[8*i+7] 
                );
    eval = mul64(v, p, c);
  }
  loggers::get_instance().log_to_hexa("ia1_128::mac: eval: ", (const unsigned char*)&eval, sizeof(uint64_t));
  
  uint64_t rem = length % 64;
  if (rem == 0) {
    rem = 64;
  }
  loggers::get_instance().log("ia1_128::mac:rem: %d", rem);
  uint64_t m_d_2 = 0;
  uint8_t i = 0;
  while (rem > 7) {
    m_d_2 |= (uint64_t)payload[8*(d-2)+i] << (8*(7-i));
    rem -= 8;
    i++;
  }
  if (rem > 0) {
    m_d_2 |= (uint64_t)(payload[8*(d-2)+i] & mask8bit(rem)) << (8*(7-i));
  }
  loggers::get_instance().log_to_hexa("ia1_128::mac: m_d_2: ", (const unsigned char*)&m_d_2, sizeof(uint64_t));

  v = eval ^ m_d_2;
  eval = mul64(v, p, c);
  loggers::get_instance().log_to_hexa("ia1_128::mac: eval: ", (const unsigned char*)&eval, sizeof(uint64_t));
  
  /* for d-1 */
  eval ^= length;
  loggers::get_instance().log_to_hexa("ia1_128::mac: eval: ", (const unsigned char*)&eval, sizeof(uint64_t));
  
  /* Multiply by q */
  eval = mul64(eval, q, c);
  loggers::get_instance().log_to_hexa("ia1_128::mac: eval: ", (const unsigned char*)&eval, sizeof(uint64_t));
  
  /* XOR with z_5: this is a modification to the reference C code, 
     which forgot to XOR z[5] */
  for (i = 0; i < 4; i++) {
    (*mac)[i] = ((eval >> (56-(i*8))) ^ (z[4] >> (24-(i*8)))) & 0xff;
  }
  loggers::get_instance().log_to_hexa("ia1_128::mac: mac: ", *mac, *mac_length);

  loggers::get_instance().log("<<< ia1_128::mac: ret: 0");
  return 0;
}

int ia1_128::snow3g_initialize(const uint32_t count, const uint8_t bearer, const uint8_t direction, const unsigned char *knas_enc, snow3g_context_t *ctx) {
  loggers::get_instance().log_to_hexa(">>> ia1_128::snow3g_initialize: knas_enc", (const unsigned char*)knas_enc, 16);
  loggers::get_instance().log(">>> ia1_128::snow3g_initialize: count: %u", count);
  loggers::get_instance().log(">>> ia1_128::snow3g_initialize: bearer: %d", bearer);
@@ -108,9 +225,15 @@ int ia1_128::snow3g_initialize(uint32_t count, uint8_t bearer, uint8_t direction
  snow_key.key[0] = WORD_128(knas_enc, 3);

  snow_key.iv[3] = count;
  if (ctx->ciphering) {
    snow_key.iv[2] = ((bearer & 0x1F) << 27) | ((direction & 0x01) << 26);
    snow_key.iv[1] = snow_key.iv[3];
    snow_key.iv[0] = snow_key.iv[2];
  } else {
    snow_key.iv[2] = ((bearer & 0x1F) << 27);
    snow_key.iv[1] = count ^ ( direction << 31 );
    snow_key.iv[0] = ((bearer & 0x1F) << 27) ^ (direction << 15);
  }

  loggers::get_instance().log_to_hexa("ia1_128::snow3g_initialize: snow_key.key", (const unsigned char*)snow_key.key, 4 * sizeof(uint32_t));
  loggers::get_instance().log_to_hexa("ia1_128::snow3g_initialize: snow_key.iv", (const unsigned char*)snow_key.iv, 4 * sizeof(uint32_t));
@@ -162,3 +285,28 @@ int ia1_128::snow3g_generate(size_t nb_byte, const unsigned char *in, unsigned c
  loggers::get_instance().log_to_hexa("<<< SNOW: out", (const unsigned char*)out, nb_byte);
  return 0;
}

int ia1_128::snow3g_generate(size_t nb, uint32_t* out, snow3g_context_t* ctx) {
  loggers::get_instance().log(">>> snow3g_generate: nb: %u", nb);

  // Sanity checks
  if (ctx == NULL) {
    loggers::get_instance().error("snow3g_generate: Wrong parameters");
    return -1;
  }

  size_t i = 0;

  /* init */
  clock_fsm(ctx);
  lfsr_keystream(ctx);

  for (i = 0; i < nb; i++) {
    uint32_t f;
    out[i] = clock_fsm(ctx) ^ ctx->lfsr[0];
    lfsr_keystream(ctx);
  }

  loggers::get_instance().log_to_hexa("<<< snow3g_generate: out", (const unsigned char*)out, nb);
  return 0;
}
+68 −4
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ class ia1_128 {
  typedef struct snow_ctx_st {
    uint32_t lfsr[SNOW_KEY_SIZE];
    struct fsm_st fsm;
    bool ciphering;
  } snow3g_context_t;

public:
@@ -153,6 +154,35 @@ public:
              unsigned char** payload, 
              uint32_t* payload_length);

  /**
   * @brief Compute NAS integrity protection using SNOW 3G algorithm
   * 
   * This method computes the  integrity protection using the SNOW 3G algorithm.
   * 
   * @param algo_id Algorithm identifier (should be 3 for SNOW 3G)
   * @param knas_int Pointer to the 16-byte NAS integrity key
   * @param count 32-bit NAS count value
   * @param bearer 5-bit bearer identifier (only lower 5 bits are used)
   * @param direction Direction bit (0 = uplink, 1 = downlink)
   * @param payload Pointer to pointer that will receive the decrypted data (caller must free)
   * @param payload_length Pointer to variable that will receive the decrypted data length
   * @param mac Pointer to the mac to decrypt
   * @param mac_length Length of the mac in bytes
   * @return 0 on success, negative value on error
   * 
   * @note The caller is responsible for freeing the memory allocated for payload
   * @note payload_length will be equal to cyphered_length for stream ciphers
   */
  int mac(const uint8_t algo_id, 
          const unsigned char* knas_int, 
          const uint32_t count, 
          const uint8_t bearer, 
          const uint8_t direction, 
          const unsigned char* payload, 
          const uint32_t payload_length, 
          unsigned char** mac, 
          uint32_t* mac_length);

private:
  const uint32_t S1_T0[256] = {
  0xa56363c6U, 0x847c7cf8U, 0x997777eeU, 0x8d7b7bf6U,
@@ -824,9 +854,41 @@ private:
  0x9EE2651CU, 0x86ED25D1U, 0xAEFCE52FU, 0xB6F3A5E2U,
  };

  inline uint32_t snow_s1(uint32_t in) { return S1_T0[BYTE32(in, 3)] ^ S1_T1[BYTE32(in, 2)] ^ S1_T2[BYTE32(in, 1)] ^ S1_T3[BYTE32(in, 0)]; };
  snow3g_context_t _ctx;
  
  void reset();

  inline uint32_t snow_s1(const uint32_t in) { return S1_T0[BYTE32(in, 3)] ^ S1_T1[BYTE32(in, 2)] ^ S1_T2[BYTE32(in, 1)] ^ S1_T3[BYTE32(in, 0)]; };

  inline uint32_t snow_s2(const uint32_t in) { return S2_T0[BYTE32(in, 3)] ^ S2_T1[BYTE32(in, 2)] ^ S2_T2[BYTE32(in, 1)] ^ S2_T3[BYTE32(in, 0)]; };

  inline uint64_t mul64(uint64_t V, uint64_t P, uint64_t c) {
    uint64_t result = 0;
    for (int8_t i = 0; i < 64; i++) {
      if ((P >> i ) & 0x1) {
        result ^= mul64x_pow(V, i, c);
      }
    }
    return result;
  };

  inline uint64_t mul64x_pow(uint64_t V, uint8_t i, uint64_t c) {
    if (i == 0) {
      return V; 
    } else {
      return mul64x(mul64x_pow(V, i-1, c) , c);
    }
  };

  inline uint64_t mul64x(uint64_t V, uint64_t c) {
    if (V & 0x8000000000000000) {
      return (V << 1) ^ c;
    } else {
      return V << 1;
    }
  };

  inline uint32_t snow_s2(uint32_t in) { return S2_T0[BYTE32(in, 3)] ^ S2_T1[BYTE32(in, 2)] ^ S2_T2[BYTE32(in, 1)] ^ S2_T3[BYTE32(in, 0)]; };
  inline uint8_t mask8bit(int n) { return 0xFF ^ ((1<<(8-n)) - 1); };

  /* Clocking operations */
  inline void lfsr_init(uint32_t f, snow3g_context_t *ctx) {
@@ -953,7 +1015,7 @@ private:
   * @param key 16-byte encryption key
   * @param iv 16-byte initialization vector
   */
  int snow3g_initialize(uint32_t count, uint8_t bearer, uint8_t direction, const char *knas_enc, snow3g_context_t *ctx);
  int snow3g_initialize(const uint32_t count, const uint8_t bearer, const uint8_t direction, const unsigned char *knas_enc, snow3g_context_t *ctx);

  /**
   * @brief Generate keystream using SNOW 3G
@@ -963,4 +1025,6 @@ private:
   * @param length Length of keystream needed in bytes
   */
  int snow3g_generate(size_t nb_byte, const unsigned char* in, unsigned char* out, snow3g_context_t *ctx);

  int snow3g_generate(size_t nb_byte, uint32_t* out, snow3g_context_t *ctx);
};
+46 −0
Original line number Diff line number Diff line
@@ -204,6 +204,52 @@ cleanup:
    return ret;
}

int ia2_128::mac(const uint8_t algo_id,
                 const unsigned char* knas_int,
                 const uint32_t count,
                 const uint8_t bearer,
                 const uint8_t direction,
                 const unsigned char* payload,
                 const uint32_t payload_length,
                 unsigned char** mac,
                 uint32_t* mac_length) {
  loggers::get_instance().log_to_hexa(">>> ia2_128::mac: knas_int", knas_int, KEY_SIZE);
  loggers::get_instance().log(">>> ia2_128::mac: payload_length: %d", payload_length);
  loggers::get_instance().log_to_hexa(">>> ia2_128::mac: payload", payload, payload_length);
  loggers::get_instance().log(">>> ia2_128::mac: algo_id: %d", algo_id);
  loggers::get_instance().log(">>> ia2_128::mac: count: %u", count);
  loggers::get_instance().log(">>> ia2_128::mac: bearer: %d", bearer);
  loggers::get_instance().log(">>> ia2_128::mac: direction: %d", direction);

  // Input validation
  if (!knas_int || !payload || !mac || !mac_length) {
    loggers::get_instance().error("ia2_128::mac: Wrong input parameters");
    return -1;
  }

  if (payload_length == 0) {
    loggers::get_instance().error("ia2_128::mac: Wrong payload length");
    *mac = nullptr;
    *mac_length = 0;
    return 0;
  }

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

  // TODO: Implement AES CMAC computation according to 3GPP TS 33.401

  loggers::get_instance().log_to_hexa("ia2_128::mac: mac: ", *mac, *mac_length);

  loggers::get_instance().log("<<< ia2_128::mac: ret: 0");
  return 0;
}

// ETSI TS 133 401 V18.3.0 (2025-04) Annex B.1.3 128-EEA2
void ia2_128::generate_counter_block(const uint32_t count,
                                     const uint8_t bearer,
+28 −0
Original line number Diff line number Diff line
@@ -116,6 +116,34 @@ public:
                unsigned char** payload, 
                uint32_t* payload_length);

  /**
   * @brief Compute NAS integrity protection using AES 128 CRT algorithm
   * 
   * This method computes the  integrity protection using the AES 128 CRT algorithm.
   * 
   * @param algo_id Algorithm identifier (should be 3 for AES 128 CRT)
   * @param knas_int Pointer to the 16-byte NAS integrity key
   * @param count 32-bit NAS count value
   * @param bearer 5-bit bearer identifier (only lower 5 bits are used)
   * @param direction Direction bit (0 = uplink, 1 = downlink)
   * @param payload Pointer to pointer that will receive the decrypted data (caller must free)
   * @param payload_length Pointer to variable that will receive the decrypted data length
   * @param mac Pointer to the mac to decrypt
   * @param mac_length Length of the mac in bytes
   * @return 0 on success, negative value on error
   * 
   * @note The caller is responsible for freeing the memory allocated for payload
   * @note payload_length will be equal to cyphered_length for stream ciphers
   */
  int mac(const uint8_t algo_id, 
          const unsigned char* knas_int, 
          const uint32_t count, 
          const uint8_t bearer, 
          const uint8_t direction, 
          const unsigned char* payload, 
          const uint32_t payload_length, 
          unsigned char** mac, 
          uint32_t* mac_length);
private:
    /**
     * @brief Generates the initial counter block for AES-CTR
Loading