Build a Knox Security Simulator in C

Simulate Samsung Knox's layered security architecture using real crypto in C.

Introduction

30 Second Summary

Every time you unlock your work phone, layers of invisible security are verifying that nobody has tampered with the device since it left the factory. Samsung Knox is the platform that makes this possible, stacking cryptographic checks from the hardware up through the operating system.

In this project, you will build a C program that simulates Knox's layered security architecture using real cryptographic primitives you implement from scratch.

What You'll Build

A multi-file C program called knox_sim that walks through Samsung Knox's five security layers from power-on to runtime, using SHA-256 hashing and AES-256-CBC encryption you write yourself.

By the end of this project, you'll have:

  • A Trusted Boot chain that verifies each component via SHA-256 hash chains, halting on any tampered code.
  • An encrypted vault with password authentication and exponential-backoff brute-force protection, matching how Knox Vault and Samsung Weaver protect secrets.
  • A runtime integrity monitor and container isolation system that detects data tampering and blocks cross-container access.
  • Secret Mission: A Device Health Attestation report generator that cryptographically signs the device's security state.

Are there any prerequisites?

This project assumes you are comfortable writing C programs with structs, pointers, arrays, and file I/O. You also need a working C compiler (GCC via MinGW-w64 or similar) already installed on your machine.

Set Up the Project and Implement Cryptographic Primitives

Every layer of Samsung Knox's security architecture relies on cryptographic primitives. Hashing verifies that boot components haven't been tampered with. Encryption protects secrets stored in the vault.

Before you can build any of those layers, you need the primitives themselves. In this step, you'll implement SHA-256 hashing and AES-256-CBC encryption from scratch in C, following the NIST standards that Knox itself relies on.

In this step, get ready to:
  • Verify your C compiler by compiling and running a minimal program.
  • Implement SHA-256 hashing following the NIST FIPS 180-4 specification.
  • Implement AES-256-CBC encryption and decryption following NIST FIPS 197.
Verify your compiler with a minimal program

Your project files are already stubbed out. The first thing to do is confirm that your compiler can build the project by adding a minimal main function to knox_sim.c.

  • Open knox_sim.c in your editor and paste in this code:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crypto.h"
#include "boot_chain.h"
#include "vault.h"
#include "integrity.h"
#include "policy.h"

int main(void) {
    printf("Knox Security Simulator v1.0\n");
    return 0;
}

What does this code do?

The #include lines bring in all the module headers you'll build throughout this project. Right now those files are empty, so the compiler will find them but won't load anything from them.

The main() function just prints a banner. Once the project is complete, it will orchestrate all five Knox security layers in sequence.

  • Save knox_sim.c.
  • Compile the entire project by running this command in your terminal:
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c
  • Run the compiled program:
.\knox_sim.exe
  • You should see Knox Security Simulator v1.0 printed in your terminal.

Seeing 'gcc' is not recognized?

Make sure your MinGW-w64 installation directory is added to your system PATH. Open a new terminal window after updating PATH so the change takes effect.

If you see linker errors about missing functions, that's expected and is fine for now. The other .c files are empty, so no function bodies exist yet. As long as main() doesn't call any missing functions, the build should succeed.

help me fix my GCC compile error for the Knox simulator

Implement SHA-256 in the crypto module

SHA-256 is the hashing algorithm Knox uses to verify each component in the boot chain. If even a single bit of a bootloader changes, the hash changes completely, and the device knows something has been tampered with.

What is SHA-256?

SHA-256 takes any input (a file, a string, an entire bootloader binary) and produces a fixed 32-byte fingerprint. The algorithm processes input in 512-bit blocks through 64 rounds of compression, mixing the data with 64 constant values derived from the cube roots of the first 64 prime numbers.

The key property is collision resistance. It's computationally infeasible to find two different inputs that produce the same hash. This is what makes it useful for integrity verification.

  • Open crypto.h and paste in the header declarations:
#ifndef CRYPTO_H
#define CRYPTO_H

#include <stdint.h>
#include <stddef.h>

void sha256(const uint8_t *data, size_t len, uint8_t *hash_out);
void sha256_hex(const uint8_t *hash, char *hex_out);
void aes256_cbc_encrypt(const uint8_t *key, const uint8_t *iv,
                        const uint8_t *plaintext, uint8_t *ciphertext, size_t len);
void aes256_cbc_decrypt(const uint8_t *key, const uint8_t *iv,
                        const uint8_t *ciphertext, uint8_t *plaintext, size_t len);

#endif

What does this header declare?

  • sha256() takes raw bytes and produces a 32-byte hash digest.
  • sha256_hex() converts that 32-byte hash into a 64-character hex string for display.
  • aes256_cbc_encrypt() and aes256_cbc_decrypt() handle AES-256 encryption in CBC mode. You'll implement these in the next substep.
  • Save crypto.h.
  • Now open crypto.c and start with the includes and SHA-256 constants:
#include "crypto.h"
#include <string.h>
#include <stdio.h>

/* SHA-256 implementation following NIST FIPS 180-4 */

static const uint32_t K[64] = {
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

static uint32_t rotr(uint32_t x, int n) { return (x >> n) | (x << (32 - n)); }
static uint32_t ch(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); }
static uint32_t maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); }
static uint32_t sigma0(uint32_t x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); }
static uint32_t sigma1(uint32_t x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); }
static uint32_t gamma0(uint32_t x) { return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3); }
static uint32_t gamma1(uint32_t x) { return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); }

What are these constants and helpers?

The K[64] array contains the first 32 bits of the fractional parts of the cube roots of the first 64 prime numbers. These are defined by the NIST FIPS 180-4 standard and are used in each of the 64 compression rounds.

The helper functions implement the bitwise operations SHA-256 uses to mix data: rotr rotates bits right, ch (choose) selects bits based on the first argument, maj (majority) takes the majority vote of three inputs, and the sigma/gamma functions combine multiple rotations and shifts.

  • Below the helper functions in crypto.c, add the main sha256() function and the hex conversion helper:
void sha256(const uint8_t *data, size_t len, uint8_t *hash_out) {
    /* Initial hash values H0-H7 (fractional parts of square roots of first 8 primes) */
    uint32_t h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a;
    uint32_t h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19;

    /* Pad message to multiple of 512 bits (64 bytes) */
    size_t total_bits = len * 8;
    size_t padded_len = ((len + 9 + 63) / 64) * 64;
    uint8_t *msg = (uint8_t *)calloc(padded_len, 1);
    memcpy(msg, data, len);
    msg[len] = 0x80;
    for (int i = 0; i < 8; i++) {
        msg[padded_len - 1 - i] = (uint8_t)(total_bits >> (i * 8));
    }

    /* Process each 512-bit (64-byte) block */
    for (size_t offset = 0; offset < padded_len; offset += 64) {
        uint32_t w[64];
        /* Prepare message schedule */
        for (int i = 0; i < 16; i++) {
            w[i] = ((uint32_t)msg[offset + i*4] << 24) |
                   ((uint32_t)msg[offset + i*4+1] << 16) |
                   ((uint32_t)msg[offset + i*4+2] << 8) |
                   ((uint32_t)msg[offset + i*4+3]);
        }
        for (int i = 16; i < 64; i++) {
            w[i] = gamma1(w[i-2]) + w[i-7] + gamma0(w[i-15]) + w[i-16];
        }

        /* Initialize working variables */
        uint32_t a = h0, b = h1, c = h2, d = h3;
        uint32_t e = h4, f = h5, g = h6, h = h7;

        /* 64 rounds of compression */
        for (int i = 0; i < 64; i++) {
            uint32_t t1 = h + sigma1(e) + ch(e, f, g) + K[i] + w[i];
            uint32_t t2 = sigma0(a) + maj(a, b, c);
            h = g; g = f; f = e; e = d + t1;
            d = c; c = b; b = a; a = t1 + t2;
        }

        /* Add compressed chunk to hash values */
        h0 += a; h1 += b; h2 += c; h3 += d;
        h4 += e; h5 += f; h6 += g; h7 += h;
    }
    free(msg);

    /* Produce final 32-byte hash output */
    uint32_t hash[8] = {h0, h1, h2, h3, h4, h5, h6, h7};
    for (int i = 0; i < 8; i++) {
        hash_out[i*4]   = (hash[i] >> 24) & 0xff;
        hash_out[i*4+1] = (hash[i] >> 16) & 0xff;
        hash_out[i*4+2] = (hash[i] >> 8) & 0xff;
        hash_out[i*4+3] = hash[i] & 0xff;
    }
}

void sha256_hex(const uint8_t *hash, char *hex_out) {
    for (int i = 0; i < 32; i++) {
        sprintf(hex_out + i*2, "%02x", hash[i]);
    }
    hex_out[64] = '\0';
}

What does this code do?

  • The initial hash values h0 through h7 are the fractional parts of the square roots of the first 8 primes. These are the starting state defined by the NIST standard.
  • The message is padded to a multiple of 64 bytes: a 0x80 byte is appended, then zeros, then the original length in bits as a big-endian 64-bit integer.
  • Each 64-byte block is expanded into a 64-entry message schedule (w[64]), then compressed through 64 rounds that mix the schedule with the K constants and the working variables.
  • sha256_hex() converts the raw 32-byte hash into a 64-character hex string for human-readable display.
  • Save crypto.c.
  • To verify your SHA-256 implementation, temporarily update knox_sim.c main to hash the string "knox" and print the result:
int main(void) {
    printf("Knox Security Simulator v1.0\n");

    /* Test SHA-256 */
    uint8_t hash[32];
    char hex[65];
    sha256((const uint8_t *)"knox", 4, hash);
    sha256_hex(hash, hex);
    printf("SHA-256('knox') = %s\n", hex);

    return 0;
}
  • Compile and run:
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c
.\knox_sim.exe
  • You should see the output: 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3.

Keep this test in main temporarily

You'll remove this test code at the end of the step once you've also verified AES. For now it confirms your SHA-256 implementation matches the known correct output.

Hash output doesn't match?

Double-check that the K[64] array has all 64 values in the correct order. A single mistyped constant will produce a completely different hash.

Make sure the message padding appends the length in big-endian format. The loop msg[padded_len - 1 - i] must count i from 0 to 7.

help me debug my SHA-256 implementation that produces the wrong hash

Implement AES-256-CBC encryption

AES-256-CBC is the encryption algorithm Knox Vault uses to protect stored secrets. It operates on 16-byte blocks, using a 256-bit key expanded into 15 round keys through 14 rounds of transformation.

Why CBC mode?

CBC (Cipher Block Chaining) XORs each plaintext block with the previous ciphertext block before encrypting. This means identical plaintext blocks produce different ciphertext, hiding patterns in the data.

The chaining concept mirrors the chain of trust in Knox's boot process. Each block depends on the one before it, just like each boot component depends on the verification of the previous one.

  • In crypto.c, below the sha256_hex() function, add the AES lookup tables and the Galois Field multiplication helper:
/* AES-256-CBC implementation following NIST FIPS 197 */

static const uint8_t sbox[256] = {
    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
    0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
    0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
    0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
    0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
    0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
    0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
    0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
    0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
    0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
    0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
    0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
    0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
    0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
    0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
    0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
};

static const uint8_t inv_sbox[256] = {
    0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
    0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
    0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
    0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
    0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
    0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
    0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
    0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
    0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
    0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
    0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
    0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
    0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
    0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
    0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
    0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
};

static const uint8_t rcon[11] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36};

static uint8_t gmul(uint8_t a, uint8_t b) {
    uint8_t p = 0;
    for (int i = 0; i < 8; i++) {
        if (b & 1) p ^= a;
        uint8_t hi = a & 0x80;
        a <<= 1;
        if (hi) a ^= 0x1b;
        b >>= 1;
    }
    return p;
}

What are these tables?

  • sbox (Substitution Box) maps each byte to a different byte using a non-linear transformation. This is the core confusion step in AES that makes it resistant to linear attacks.
  • inv_sbox is the inverse lookup, used during decryption to reverse the substitution.
  • rcon (Round Constants) are powers of 2 in the Galois Field, used during key expansion to differentiate round keys.
  • gmul() performs multiplication in GF(2^8), the finite field AES operates in. The 0x1b value is the irreducible polynomial that keeps results within the field.
  • Below gmul(), add the key expansion function that turns the 32-byte key into 240 bytes of round keys:
static void key_expansion(const uint8_t *key, uint8_t *round_keys) {
    /* Copy original 32-byte key as first two round keys */
    memcpy(round_keys, key, 32);
    int bytes_generated = 32;
    int rcon_idx = 1;
    uint8_t temp[4];

    /* Generate remaining round key bytes */
    while (bytes_generated < 240) {
        memcpy(temp, round_keys + bytes_generated - 4, 4);
        if (bytes_generated % 32 == 0) {
            /* RotWord + SubWord + Rcon */
            uint8_t t = temp[0];
            temp[0] = sbox[temp[1]] ^ rcon[rcon_idx++];
            temp[1] = sbox[temp[2]];
            temp[2] = sbox[temp[3]];
            temp[3] = sbox[t];
        } else if (bytes_generated % 32 == 16) {
            /* Extra SubWord for AES-256 */
            for (int i = 0; i < 4; i++) temp[i] = sbox[temp[i]];
        }
        for (int i = 0; i < 4; i++) {
            round_keys[bytes_generated] = round_keys[bytes_generated - 32] ^ temp[i];
            bytes_generated++;
        }
    }
}

What does key expansion do?

AES-256 needs 15 round keys (one initial plus 14 rounds), each 16 bytes. That's 240 bytes total from a 32-byte input key. The expansion process mixes the key material through rotations, S-Box substitutions, and XOR with round constants to ensure each round key is cryptographically different from the others.

  • Below key_expansion(), add the AES transform functions that operate on the 16-byte state:
static void sub_bytes(uint8_t *state) {
    for (int i = 0; i < 16; i++) state[i] = sbox[state[i]];
}

static void inv_sub_bytes(uint8_t *state) {
    for (int i = 0; i < 16; i++) state[i] = inv_sbox[state[i]];
}

static void shift_rows(uint8_t *state) {
    uint8_t tmp;
    /* Row 1: shift left by 1 */
    tmp = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = tmp;
    /* Row 2: shift left by 2 */
    tmp = state[2]; state[2] = state[10]; state[10] = tmp;
    tmp = state[6]; state[6] = state[14]; state[14] = tmp;
    /* Row 3: shift left by 3 (same as shift right by 1) */
    tmp = state[15]; state[15] = state[11]; state[11] = state[7]; state[7] = state[3]; state[3] = tmp;
}

static void inv_shift_rows(uint8_t *state) {
    uint8_t tmp;
    /* Row 1: shift right by 1 */
    tmp = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = tmp;
    /* Row 2: shift right by 2 */
    tmp = state[2]; state[2] = state[10]; state[10] = tmp;
    tmp = state[6]; state[6] = state[14]; state[14] = tmp;
    /* Row 3: shift right by 3 (same as shift left by 1) */
    tmp = state[3]; state[3] = state[7]; state[7] = state[11]; state[11] = state[15]; state[15] = tmp;
}

static void mix_columns(uint8_t *state) {
    for (int i = 0; i < 4; i++) {
        int c = i * 4;
        uint8_t a0 = state[c], a1 = state[c+1], a2 = state[c+2], a3 = state[c+3];
        state[c]   = gmul(a0,2) ^ gmul(a1,3) ^ a2 ^ a3;
        state[c+1] = a0 ^ gmul(a1,2) ^ gmul(a2,3) ^ a3;
        state[c+2] = a0 ^ a1 ^ gmul(a2,2) ^ gmul(a3,3);
        state[c+3] = gmul(a0,3) ^ a1 ^ a2 ^ gmul(a3,2);
    }
}

static void inv_mix_columns(uint8_t *state) {
    for (int i = 0; i < 4; i++) {
        int c = i * 4;
        uint8_t a0 = state[c], a1 = state[c+1], a2 = state[c+2], a3 = state[c+3];
        state[c]   = gmul(a0,14) ^ gmul(a1,11) ^ gmul(a2,13) ^ gmul(a3,9);
        state[c+1] = gmul(a0,9) ^ gmul(a1,14) ^ gmul(a2,11) ^ gmul(a3,13);
        state[c+2] = gmul(a0,13) ^ gmul(a1,9) ^ gmul(a2,14) ^ gmul(a3,11);
        state[c+3] = gmul(a0,11) ^ gmul(a1,13) ^ gmul(a2,9) ^ gmul(a3,14);
    }
}

static void add_round_key(uint8_t *state, const uint8_t *round_key) {
    for (int i = 0; i < 16; i++) state[i] ^= round_key[i];
}

What do these transforms do?

Each AES round applies four operations to a 16-byte state (arranged as a 4x4 matrix). SubBytes substitutes each byte through the S-Box for non-linearity. ShiftRows rotates each row by a different offset for diffusion across columns.

MixColumns multiplies each column by a fixed polynomial in GF(2^8), spreading each byte's influence across the column. AddRoundKey XORs the state with the current round key. Together these four steps make it computationally infeasible to reverse the encryption without the key.

  • Below the transform functions, add the block-level encrypt/decrypt and the CBC mode wrappers:
static void aes256_encrypt_block(const uint8_t *round_keys, uint8_t *block) {
    add_round_key(block, round_keys);
    for (int round = 1; round <= 14; round++) {
        sub_bytes(block);
        shift_rows(block);
        if (round < 14) mix_columns(block);
        add_round_key(block, round_keys + round * 16);
    }
}

static void aes256_decrypt_block(const uint8_t *round_keys, uint8_t *block) {
    add_round_key(block, round_keys + 14 * 16);
    for (int round = 13; round >= 0; round--) {
        inv_shift_rows(block);
        inv_sub_bytes(block);
        add_round_key(block, round_keys + round * 16);
        if (round > 0) inv_mix_columns(block);
    }
}

void aes256_cbc_encrypt(const uint8_t *key, const uint8_t *iv,
                        const uint8_t *plaintext, uint8_t *ciphertext, size_t len) {
    uint8_t round_keys[240];
    key_expansion(key, round_keys);
    uint8_t prev[16];
    memcpy(prev, iv, 16);

    /* Encrypt each 16-byte block, chaining with previous ciphertext */
    for (size_t i = 0; i < len; i += 16) {
        uint8_t block[16];
        memcpy(block, plaintext + i, 16);
        for (int j = 0; j < 16; j++) block[j] ^= prev[j];
        aes256_encrypt_block(round_keys, block);
        memcpy(ciphertext + i, block, 16);
        memcpy(prev, block, 16);
    }
}

void aes256_cbc_decrypt(const uint8_t *key, const uint8_t *iv,
                        const uint8_t *ciphertext, uint8_t *plaintext, size_t len) {
    uint8_t round_keys[240];
    key_expansion(key, round_keys);
    uint8_t prev[16];
    memcpy(prev, iv, 16);

    /* Decrypt each 16-byte block, unchaining with previous ciphertext */
    for (size_t i = 0; i < len; i += 16) {
        uint8_t block[16];
        memcpy(block, ciphertext + i, 16);
        aes256_decrypt_block(round_keys, block);
        for (int j = 0; j < 16; j++) block[j] ^= prev[j];
        memcpy(plaintext + i, block, 16);
        memcpy(prev, ciphertext + i, 16);
    }
}

What does this code do?

  • aes256_encrypt_block() runs 14 rounds of SubBytes, ShiftRows, MixColumns (skipped in the final round), and AddRoundKey on a single 16-byte block.
  • aes256_decrypt_block() reverses the process by applying the inverse operations in reverse order.
  • The CBC wrappers handle multiple blocks by XORing each plaintext block with the previous ciphertext block (or the IV for the first block) before encrypting. This chaining ensures that identical plaintext blocks produce different ciphertext.
  • Save crypto.c.
  • To verify AES works, update your temporary test in knox_sim.c to encrypt and decrypt a 16-byte block:
int main(void) {
    printf("Knox Security Simulator v1.0\n");

    /* Test SHA-256 */
    uint8_t hash[32];
    char hex[65];
    sha256((const uint8_t *)"knox", 4, hash);
    sha256_hex(hash, hex);
    printf("SHA-256('knox') = %s\n", hex);

    /* Test AES-256-CBC */
    uint8_t key[32] = {0};
    uint8_t iv[16] = {0};
    uint8_t plain[16] = "KNOX_TEST_DATA!";
    uint8_t cipher[16], decrypted[16];

    aes256_cbc_encrypt(key, iv, plain, cipher, 16);
    aes256_cbc_decrypt(key, iv, cipher, decrypted, 16);
    printf("AES round-trip: %s\n",
           memcmp(plain, decrypted, 16) == 0 ? "PASS" : "FAIL");

    return 0;
}
  • Compile and run:
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c
.\knox_sim.exe
  • You should see AES round-trip: PASS in the output. This confirms that encrypting and then decrypting produces the original plaintext.

Seeing AES round-trip: FAIL?

The most common cause is a mismatch between shift_rows() and inv_shift_rows(). Double-check that the index swaps in inv_shift_rows() are the exact reverse of shift_rows().

Also verify that aes256_decrypt_block() starts from round key 14 (round_keys + 14 * 16) and counts down. If it starts from the wrong offset, decryption will silently produce garbage.

help me debug my AES-256-CBC implementation that fails the round-trip test

  • Now that both primitives are verified, revert knox_sim.c to the final form by removing the test code:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crypto.h"
#include "boot_chain.h"
#include "vault.h"
#include "integrity.h"
#include "policy.h"

int main(void) {
    printf("Knox Security Simulator v1.0\n");
    return 0;
}
  • Save knox_sim.c and recompile to confirm it still builds cleanly.
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c

✔️ Awesome, I've got everything!

Great. Double check you've saved both crypto.h and crypto.c, and that knox_sim.c compiles without errors.

ⓧ I'd like to double check the full code

Here is the full crypto.h:

#ifndef CRYPTO_H
#define CRYPTO_H

#include <stdint.h>
#include <stddef.h>

void sha256(const uint8_t *data, size_t len, uint8_t *hash_out);
void sha256_hex(const uint8_t *hash, char *hex_out);
void aes256_cbc_encrypt(const uint8_t *key, const uint8_t *iv,
                        const uint8_t *plaintext, uint8_t *ciphertext, size_t len);
void aes256_cbc_decrypt(const uint8_t *key, const uint8_t *iv,
                        const uint8_t *ciphertext, uint8_t *plaintext, size_t len);

#endif

Here is the full crypto.c:

#include "crypto.h"
#include <string.h>
#include <stdio.h>

/* SHA-256 implementation following NIST FIPS 180-4 */

static const uint32_t K[64] = {
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

static uint32_t rotr(uint32_t x, int n) { return (x >> n) | (x << (32 - n)); }
static uint32_t ch(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); }
static uint32_t maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); }
static uint32_t sigma0(uint32_t x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); }
static uint32_t sigma1(uint32_t x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); }
static uint32_t gamma0(uint32_t x) { return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3); }
static uint32_t gamma1(uint32_t x) { return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); }

void sha256(const uint8_t *data, size_t len, uint8_t *hash_out) {
    uint32_t h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a;
    uint32_t h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19;

    size_t total_bits = len * 8;
    size_t padded_len = ((len + 9 + 63) / 64) * 64;
    uint8_t *msg = (uint8_t *)calloc(padded_len, 1);
    memcpy(msg, data, len);
    msg[len] = 0x80;
    for (int i = 0; i < 8; i++) {
        msg[padded_len - 1 - i] = (uint8_t)(total_bits >> (i * 8));
    }

    for (size_t offset = 0; offset < padded_len; offset += 64) {
        uint32_t w[64];
        for (int i = 0; i < 16; i++) {
            w[i] = ((uint32_t)msg[offset + i*4] << 24) |
                   ((uint32_t)msg[offset + i*4+1] << 16) |
                   ((uint32_t)msg[offset + i*4+2] << 8) |
                   ((uint32_t)msg[offset + i*4+3]);
        }
        for (int i = 16; i < 64; i++) {
            w[i] = gamma1(w[i-2]) + w[i-7] + gamma0(w[i-15]) + w[i-16];
        }

        uint32_t a = h0, b = h1, c = h2, d = h3;
        uint32_t e = h4, f = h5, g = h6, h = h7;

        for (int i = 0; i < 64; i++) {
            uint32_t t1 = h + sigma1(e) + ch(e, f, g) + K[i] + w[i];
            uint32_t t2 = sigma0(a) + maj(a, b, c);
            h = g; g = f; f = e; e = d + t1;
            d = c; c = b; b = a; a = t1 + t2;
        }

        h0 += a; h1 += b; h2 += c; h3 += d;
        h4 += e; h5 += f; h6 += g; h7 += h;
    }
    free(msg);

    uint32_t hash[8] = {h0, h1, h2, h3, h4, h5, h6, h7};
    for (int i = 0; i < 8; i++) {
        hash_out[i*4]   = (hash[i] >> 24) & 0xff;
        hash_out[i*4+1] = (hash[i] >> 16) & 0xff;
        hash_out[i*4+2] = (hash[i] >> 8) & 0xff;
        hash_out[i*4+3] = hash[i] & 0xff;
    }
}

void sha256_hex(const uint8_t *hash, char *hex_out) {
    for (int i = 0; i < 32; i++) {
        sprintf(hex_out + i*2, "%02x", hash[i]);
    }
    hex_out[64] = '\0';
}

/* AES-256-CBC implementation following NIST FIPS 197 */

static const uint8_t sbox[256] = {
    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
    0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
    0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
    0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
    0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
    0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
    0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
    0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
    0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
    0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
    0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
    0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
    0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
    0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
    0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
    0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
};

static const uint8_t inv_sbox[256] = {
    0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
    0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
    0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
    0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
    0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
    0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
    0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
    0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
    0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
    0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
    0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
    0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
    0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
    0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
    0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
    0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
};

static const uint8_t rcon[11] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36};

static uint8_t gmul(uint8_t a, uint8_t b) {
    uint8_t p = 0;
    for (int i = 0; i < 8; i++) {
        if (b & 1) p ^= a;
        uint8_t hi = a & 0x80;
        a <<= 1;
        if (hi) a ^= 0x1b;
        b >>= 1;
    }
    return p;
}

static void key_expansion(const uint8_t *key, uint8_t *round_keys) {
    memcpy(round_keys, key, 32);
    int bytes_generated = 32;
    int rcon_idx = 1;
    uint8_t temp[4];

    while (bytes_generated < 240) {
        memcpy(temp, round_keys + bytes_generated - 4, 4);
        if (bytes_generated % 32 == 0) {
            uint8_t t = temp[0];
            temp[0] = sbox[temp[1]] ^ rcon[rcon_idx++];
            temp[1] = sbox[temp[2]];
            temp[2] = sbox[temp[3]];
            temp[3] = sbox[t];
        } else if (bytes_generated % 32 == 16) {
            for (int i = 0; i < 4; i++) temp[i] = sbox[temp[i]];
        }
        for (int i = 0; i < 4; i++) {
            round_keys[bytes_generated] = round_keys[bytes_generated - 32] ^ temp[i];
            bytes_generated++;
        }
    }
}

static void sub_bytes(uint8_t *state) {
    for (int i = 0; i < 16; i++) state[i] = sbox[state[i]];
}

static void inv_sub_bytes(uint8_t *state) {
    for (int i = 0; i < 16; i++) state[i] = inv_sbox[state[i]];
}

static void shift_rows(uint8_t *state) {
    uint8_t tmp;
    tmp = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = tmp;
    tmp = state[2]; state[2] = state[10]; state[10] = tmp;
    tmp = state[6]; state[6] = state[14]; state[14] = tmp;
    tmp = state[15]; state[15] = state[11]; state[11] = state[7]; state[7] = state[3]; state[3] = tmp;
}

static void inv_shift_rows(uint8_t *state) {
    uint8_t tmp;
    tmp = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = tmp;
    tmp = state[2]; state[2] = state[10]; state[10] = tmp;
    tmp = state[6]; state[6] = state[14]; state[14] = tmp;
    tmp = state[3]; state[3] = state[7]; state[7] = state[11]; state[11] = state[15]; state[15] = tmp;
}

static void mix_columns(uint8_t *state) {
    for (int i = 0; i < 4; i++) {
        int c = i * 4;
        uint8_t a0 = state[c], a1 = state[c+1], a2 = state[c+2], a3 = state[c+3];
        state[c]   = gmul(a0,2) ^ gmul(a1,3) ^ a2 ^ a3;
        state[c+1] = a0 ^ gmul(a1,2) ^ gmul(a2,3) ^ a3;
        state[c+2] = a0 ^ a1 ^ gmul(a2,2) ^ gmul(a3,3);
        state[c+3] = gmul(a0,3) ^ a1 ^ a2 ^ gmul(a3,2);
    }
}

static void inv_mix_columns(uint8_t *state) {
    for (int i = 0; i < 4; i++) {
        int c = i * 4;
        uint8_t a0 = state[c], a1 = state[c+1], a2 = state[c+2], a3 = state[c+3];
        state[c]   = gmul(a0,14) ^ gmul(a1,11) ^ gmul(a2,13) ^ gmul(a3,9);
        state[c+1] = gmul(a0,9) ^ gmul(a1,14) ^ gmul(a2,11) ^ gmul(a3,13);
        state[c+2] = gmul(a0,13) ^ gmul(a1,9) ^ gmul(a2,14) ^ gmul(a3,11);
        state[c+3] = gmul(a0,11) ^ gmul(a1,13) ^ gmul(a2,9) ^ gmul(a3,14);
    }
}

static void add_round_key(uint8_t *state, const uint8_t *round_key) {
    for (int i = 0; i < 16; i++) state[i] ^= round_key[i];
}

static void aes256_encrypt_block(const uint8_t *round_keys, uint8_t *block) {
    add_round_key(block, round_keys);
    for (int round = 1; round <= 14; round++) {
        sub_bytes(block);
        shift_rows(block);
        if (round < 14) mix_columns(block);
        add_round_key(block, round_keys + round * 16);
    }
}

static void aes256_decrypt_block(const uint8_t *round_keys, uint8_t *block) {
    add_round_key(block, round_keys + 14 * 16);
    for (int round = 13; round >= 0; round--) {
        inv_shift_rows(block);
        inv_sub_bytes(block);
        add_round_key(block, round_keys + round * 16);
        if (round > 0) inv_mix_columns(block);
    }
}

void aes256_cbc_encrypt(const uint8_t *key, const uint8_t *iv,
                        const uint8_t *plaintext, uint8_t *ciphertext, size_t len) {
    uint8_t round_keys[240];
    key_expansion(key, round_keys);
    uint8_t prev[16];
    memcpy(prev, iv, 16);

    for (size_t i = 0; i < len; i += 16) {
        uint8_t block[16];
        memcpy(block, plaintext + i, 16);
        for (int j = 0; j < 16; j++) block[j] ^= prev[j];
        aes256_encrypt_block(round_keys, block);
        memcpy(ciphertext + i, block, 16);
        memcpy(prev, block, 16);
    }
}

void aes256_cbc_decrypt(const uint8_t *key, const uint8_t *iv,
                        const uint8_t *ciphertext, uint8_t *plaintext, size_t len) {
    uint8_t round_keys[240];
    key_expansion(key, round_keys);
    uint8_t prev[16];
    memcpy(prev, iv, 16);

    for (size_t i = 0; i < len; i += 16) {
        uint8_t block[16];
        memcpy(block, ciphertext + i, 16);
        aes256_decrypt_block(round_keys, block);
        for (int j = 0; j < 16; j++) block[j] ^= prev[j];
        memcpy(plaintext + i, block, 16);
        memcpy(prev, ciphertext + i, 16);
    }
}

Here is the final knox_sim.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crypto.h"
#include "boot_chain.h"
#include "vault.h"
#include "integrity.h"
#include "policy.h"

int main(void) {
    printf("Knox Security Simulator v1.0\n");
    return 0;
}

Your cryptographic foundation is solid. You have working SHA-256 hashing and AES-256-CBC encryption. Next up, you'll use these primitives to build the Trusted Boot chain that verifies each component before allowing the device to start.

Build the Trusted Boot Chain

Your crypto primitives are ready. Now you need a way to guarantee that every piece of software running on the device is legitimate before it gets to execute.

Samsung Knox solves this with a Trusted Boot chain. A cryptographic key is burned into the hardware at the factory. That key verifies the bootloader, the bootloader verifies the kernel, and the kernel verifies the Knox framework. If any link in the chain fails its hash check, the device halts immediately. In this step, you will build that exact chain using your SHA-256 implementation.

In this step, get ready to:
  • Define the boot chain data structures in boot_chain.h.
  • Implement chain initialization and verification in boot_chain.c.
  • Wire the boot chain into knox_sim.c so the program halts on tampered components.
Define the boot chain header

Each component in the boot chain needs a name, a blob of data representing its "code", and the expected SHA-256 hash of the next component. When the chain runs, it recomputes the hash and compares it to the stored value.

  • Open boot_chain.h in your editor and add the following code:
#ifndef BOOT_CHAIN_H
#define BOOT_CHAIN_H

#include <stdint.h>
#include <stddef.h>

#define MAX_COMPONENT_NAME 64
#define MAX_COMPONENT_DATA 256

typedef struct {
    char name[MAX_COMPONENT_NAME];
    uint8_t data[MAX_COMPONENT_DATA];
    size_t data_len;
    uint8_t expected_hash[32];
} BootComponent;

void boot_chain_init(BootComponent *chain, int count);
int boot_verify_chain(BootComponent *chain, int count);

#endif

What does this code do?

The BootComponent struct holds everything about one link in the chain. name identifies the component (like "Primary Bootloader"). data holds the component's code payload. expected_hash stores the SHA-256 digest of the NEXT component's data.

This mirrors how real Knox hardware stores a measurement of the next boot stage before allowing it to execute.

  • Save boot_chain.h.
Implement the boot chain initialization and verification

The initialization function populates four components matching Knox's real boot sequence. The verification function walks the chain and halts if any hash mismatches.

  • Open boot_chain.c and add the includes and boot_chain_init() function:
#include "boot_chain.h"
#include "crypto.h"
#include <stdio.h>
#include <string.h>

void boot_chain_init(BootComponent *chain, int count) {
    /* Knox's real boot sequence: Hardware -> Bootloader -> Kernel -> Framework */
    const char *names[] = {
        "Hardware Root of Trust",
        "Primary Bootloader",
        "Secondary Bootloader",
        "Knox Framework"
    };
    const char *payloads[] = {
        "DRK_HARDWARE_KEY_SAMSUNG_KNOX",
        "BOOTLOADER_STAGE1_VERIFIED_CODE",
        "BOOTLOADER_STAGE2_KERNEL_INIT",
        "KNOX_FRAMEWORK_SECURITY_SERVICES"
    };

    /* Copy names and payload data into each component */
    for (int i = 0; i < count; i++) {
        strncpy(chain[i].name, names[i], MAX_COMPONENT_NAME - 1);
        chain[i].name[MAX_COMPONENT_NAME - 1] = '\0';
        size_t plen = strlen(payloads[i]);
        memcpy(chain[i].data, payloads[i], plen);
        chain[i].data_len = plen;
    }

    /* Each component stores the SHA-256 hash of the NEXT component's data */
    for (int i = 0; i < count - 1; i++) {
        sha256(chain[i + 1].data, chain[i + 1].data_len, chain[i].expected_hash);
    }
    /* Last component has no successor to verify */
    memset(chain[count - 1].expected_hash, 0, 32);
}

What does this code do?

The four components match Samsung Knox's actual boot sequence: the Hardware Root of Trust (a Device Root Key burned into silicon at the factory), the Primary Bootloader, the Secondary Bootloader (which loads the kernel), and the Knox Framework itself.

The hash chaining loop computes each component's expected_hash from the next component's raw data. This simulates the "measurement" step that happens during manufacturing. At boot time, the device recomputes these hashes and compares them to the stored values.

Why hash chaining instead of signing?

Real Knox uses digital signatures (asymmetric crypto) for verification. Hash chaining captures the same core concept: each layer must match a pre-computed reference before executing. If an attacker modifies any component's code, the hash changes and the chain breaks.

  • Now add the boot_verify_chain() function below boot_chain_init() in the same file:
int boot_verify_chain(BootComponent *chain, int count) {
    printf("\n--- TRUSTED BOOT CHAIN ---\n");
    for (int i = 0; i < count - 1; i++) {
        /* Recompute the hash of the next component's data */
        uint8_t computed_hash[32];
        sha256(chain[i + 1].data, chain[i + 1].data_len, computed_hash);

        /* Compare to the stored expected hash */
        if (memcmp(computed_hash, chain[i].expected_hash, 32) != 0) {
            printf("[FAIL] %s -> %s verification FAILED\n", chain[i].name, chain[i+1].name);
            printf("BOOT HALTED: %s verification failed - unauthorized code detected\n", chain[i+1].name);
            return 0;
        }
        printf("[OK] %s verified %s\n", chain[i].name, chain[i+1].name);
    }
    printf("[OK] Trusted Boot complete - all components verified\n");
    return 1;
}

What does this code do?

The function walks the chain from Hardware Root of Trust to Knox Framework. For each link, it recomputes the SHA-256 of the next component's data and compares it to the stored hash.

If any comparison fails, the function prints a halt message and returns 0 (failure). The caller treats this as a bricked device. This is exactly how Knox works: a tampered bootloader means the device refuses to start.

  • Save boot_chain.c.

Seeing a compile error about sha256 not declared?

Make sure your #include "crypto.h" line is at the top of boot_chain.c. The boot chain depends on the SHA-256 function you implemented in Step 1.

help me debug a compile error in boot_chain.c

Wire the boot chain into the main program

With the boot chain implemented, your main program needs to call it as the very first security layer. If boot verification fails, the program exits immediately, simulating a bricked device.

  • Open knox_sim.c and replace the entire contents with the following:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crypto.h"
#include "boot_chain.h"
#include "vault.h"
#include "integrity.h"
#include "policy.h"

int main(void) {
    printf("========================================\n");
    printf("    KNOX SECURITY SIMULATOR v1.0\n");
    printf("========================================\n");
    printf("Simulating Samsung Knox security layers\n");

    /* Layer 1: Trusted Boot */
    BootComponent chain[4];
    boot_chain_init(chain, 4);
    if (!boot_verify_chain(chain, 4)) {
        printf("\nDevice bricked. Exiting.\n");
        return 1;
    }

    return 0;
}

What does this code do?

The main function now prints a banner, initializes 4 boot components, and runs the verification chain. If boot_verify_chain() returns 0 (failure), the program prints "Device bricked" and exits with code 1. This mirrors how a real Knox device refuses to boot if any component has been modified.

  • Save knox_sim.c.
  • Compile the project by running:
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c
  • Run the program:
.\knox_sim.exe
  • You should see the banner followed by the boot chain output showing each component verified successfully:
========================================
    KNOX SECURITY SIMULATOR v1.0
========================================
Simulating Samsung Knox security layers

--- TRUSTED BOOT CHAIN ---
[OK] Hardware Root of Trust verified Primary Bootloader
[OK] Primary Bootloader verified Secondary Bootloader
[OK] Secondary Bootloader verified Knox Framework
[OK] Trusted Boot complete - all components verified

Seeing linker errors about undefined references?

Make sure you are compiling all six .c files together in one command. The other stub files (vault.c, integrity.c, policy.c) must exist even if they are empty. GCC needs all source files listed to produce the final executable.

help me fix linker errors when compiling the knox simulator

Now test tamper detection. This is the whole point of a Trusted Boot chain: if even one byte changes in a component, the verification catches it.

  • To test this, temporarily add a tamper line in knox_sim.c. Find this line:
    boot_chain_init(chain, 4);
  • Add this line directly below it (before the if statement):
    chain[1].data[0] ^= 0xFF;  /* Simulate tampering with the Primary Bootloader */
  • Recompile and run again:
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c
.\knox_sim.exe
  • You should now see the chain halt at the tampered component:
--- TRUSTED BOOT CHAIN ---
[FAIL] Hardware Root of Trust -> Primary Bootloader verification FAILED
BOOT HALTED: Primary Bootloader verification failed - unauthorized code detected

Device bricked. Exiting.

Why does flipping one byte break the chain?

SHA-256 is designed so that any change to the input, even a single bit, produces a completely different 32-byte output. The Hardware Root of Trust stored the hash of the original bootloader data at "manufacture time." When you flip a byte, the recomputed hash no longer matches the stored one.

This is why Knox's Hardware Root of Trust is so powerful. An attacker cannot modify the bootloader without the hash changing, and they cannot update the stored hash because it is burned into hardware.

  • Remove the tamper line you just added (chain[1].data[0] ^= 0xFF;) so the boot chain passes cleanly again.
  • Save knox_sim.c and recompile one more time to confirm the output shows all components verified.

✔️ Awesome, I've got everything!

Great. Make sure you have saved all three files and that the tamper test line has been removed.

ⓧ I'd like to double check the full code

Here is the full code for each file at this point.

#ifndef BOOT_CHAIN_H
#define BOOT_CHAIN_H

#include <stdint.h>
#include <stddef.h>

#define MAX_COMPONENT_NAME 64
#define MAX_COMPONENT_DATA 256

typedef struct {
    char name[MAX_COMPONENT_NAME];
    uint8_t data[MAX_COMPONENT_DATA];
    size_t data_len;
    uint8_t expected_hash[32];
} BootComponent;

void boot_chain_init(BootComponent *chain, int count);
int boot_verify_chain(BootComponent *chain, int count);

#endif
#include "boot_chain.h"
#include "crypto.h"
#include <stdio.h>
#include <string.h>

void boot_chain_init(BootComponent *chain, int count) {
    const char *names[] = {
        "Hardware Root of Trust",
        "Primary Bootloader",
        "Secondary Bootloader",
        "Knox Framework"
    };
    const char *payloads[] = {
        "DRK_HARDWARE_KEY_SAMSUNG_KNOX",
        "BOOTLOADER_STAGE1_VERIFIED_CODE",
        "BOOTLOADER_STAGE2_KERNEL_INIT",
        "KNOX_FRAMEWORK_SECURITY_SERVICES"
    };

    for (int i = 0; i < count; i++) {
        strncpy(chain[i].name, names[i], MAX_COMPONENT_NAME - 1);
        chain[i].name[MAX_COMPONENT_NAME - 1] = '\0';
        size_t plen = strlen(payloads[i]);
        memcpy(chain[i].data, payloads[i], plen);
        chain[i].data_len = plen;
    }

    for (int i = 0; i < count - 1; i++) {
        sha256(chain[i + 1].data, chain[i + 1].data_len, chain[i].expected_hash);
    }
    memset(chain[count - 1].expected_hash, 0, 32);
}

int boot_verify_chain(BootComponent *chain, int count) {
    printf("\n--- TRUSTED BOOT CHAIN ---\n");
    for (int i = 0; i < count - 1; i++) {
        uint8_t computed_hash[32];
        sha256(chain[i + 1].data, chain[i + 1].data_len, computed_hash);

        if (memcmp(computed_hash, chain[i].expected_hash, 32) != 0) {
            printf("[FAIL] %s -> %s verification FAILED\n", chain[i].name, chain[i+1].name);
            printf("BOOT HALTED: %s verification failed - unauthorized code detected\n", chain[i+1].name);
            return 0;
        }
        printf("[OK] %s verified %s\n", chain[i].name, chain[i+1].name);
    }
    printf("[OK] Trusted Boot complete - all components verified\n");
    return 1;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crypto.h"
#include "boot_chain.h"
#include "vault.h"
#include "integrity.h"
#include "policy.h"

int main(void) {
    printf("========================================\n");
    printf("    KNOX SECURITY SIMULATOR v1.0\n");
    printf("========================================\n");
    printf("Simulating Samsung Knox security layers\n");

    /* Layer 1: Trusted Boot */
    BootComponent chain[4];
    boot_chain_init(chain, 4);
    if (!boot_verify_chain(chain, 4)) {
        printf("\nDevice bricked. Exiting.\n");
        return 1;
    }

    return 0;
}

Your device now refuses to boot if anything has been tampered with. Next up, you will build the Knox Vault, an encrypted storage system that protects secrets with password authentication and brute-force lockout.

Build the Knox Vault with Encrypted Storage

Your boot chain now ensures only trusted code runs on the device. But once the device boots, it needs a secure place to store sensitive data like encryption keys, certificates, and credentials.

Samsung's Knox Vault solves this problem with an independent tamper-proof subsystem that encrypts secrets and guards them with brute-force protection. In this step, you will build your own vault that uses the AES-256-CBC encryption you already implemented to protect stored secrets, and add exponential-backoff lockouts modeled after Samsung Weaver.

In this step, get ready to:
  • Define the vault data structures and API in a header file.
  • Implement password-authenticated encrypted storage with brute-force protection.
  • Wire the vault into the main simulator so it runs after a successful boot.
Define the vault data structures and API

Knox Vault operates as an independent secure subsystem with its own processor and memory. Your simulator models this as a self-contained struct that holds an encryption key, an initialization vector, and an array of encrypted entries.

  • Open vault.h in your editor and replace its empty contents with the vault header definitions:
#ifndef VAULT_H
#define VAULT_H

#include <stdint.h>
#include <stddef.h>

#define VAULT_MAX_SECRETS 8
#define VAULT_MAX_KEY_NAME 32
#define VAULT_MAX_VALUE_LEN 128
#define VAULT_MAX_ATTEMPTS 10

typedef struct {
    char key_name[VAULT_MAX_KEY_NAME];
    uint8_t encrypted_value[VAULT_MAX_VALUE_LEN];
    size_t value_len;
    int occupied;
} VaultEntry;

typedef struct {
    uint8_t encryption_key[32];
    uint8_t iv[16];
    VaultEntry entries[VAULT_MAX_SECRETS];
    int failed_attempts;
    int locked;
    int authenticated;
} KnoxVault;

void vault_init(KnoxVault *vault, const char *master_password);
int vault_authenticate(KnoxVault *vault, const char *password);
int vault_store_secret(KnoxVault *vault, const char *key, const uint8_t *value, size_t len);
int vault_retrieve_secret(KnoxVault *vault, const char *key, uint8_t *value_out, size_t *len_out);

#endif

What does this code do?

The VaultEntry struct represents one secret stored in the vault. Each entry has a name, an encrypted byte buffer, its length, and a flag indicating whether the slot is in use.

The KnoxVault struct is the vault itself. It holds a 32-byte AES-256 encryption key derived from the master password, a 16-byte initialization vector for CBC mode, up to 8 secret entries, and state tracking for authentication and lockout.

  • Save vault.h.
  • Confirm the file exists by running:
ls vault.h

You should see vault.h listed in the output.

File not showing up?

Make sure you saved the file inside your knox_sim project folder, not in a parent or sibling directory.

Help me find where my vault.h file was saved

Now you need to implement the functions declared in the header. The first function initializes the vault by deriving an encryption key from the master password.

  • Open vault.c and add the includes and the vault_init function:
#include "vault.h"
#include "crypto.h"
#include <stdio.h>
#include <string.h>

void vault_init(KnoxVault *vault, const char *master_password) {
    /* Zero out all vault memory */
    memset(vault, 0, sizeof(KnoxVault));
    /* Derive encryption key from password via SHA-256 */
    sha256((const uint8_t *)master_password, strlen(master_password), vault->encryption_key);
    /* Use first 16 bytes of key as IV */
    memcpy(vault->iv, vault->encryption_key, 16);
    vault->authenticated = 0;
    vault->locked = 0;
    vault->failed_attempts = 0;
}

What does this code do?

The vault_init function zeroes out the vault struct, then derives a 32-byte AES-256 encryption key by hashing the master password with SHA-256. The first 16 bytes of that key double as the CBC initialization vector.

This mirrors how Knox Vault derives device-unique keys from hardware-embedded secrets. Your simulator uses a password instead of hardware, but the principle is the same: one master secret protects everything inside the vault.

  • Save vault.c.

You will not see a change yet. The next chunk adds the authentication function that uses this initialization.

Implement brute-force protection and encrypted storage

Samsung Weaver protects Knox Vault with a non-bypassable timer that uses binary exponential backoff to thwart brute-force attempts. After too many failures, the vault permanently wipes all stored secrets. Your vault_authenticate function replicates this behavior.

  • In vault.c, add the vault_authenticate function below vault_init:
int vault_authenticate(KnoxVault *vault, const char *password) {
    if (vault->locked) {
        printf("VAULT WIPED: Maximum attempts exceeded. All secrets destroyed.\n");
        return 0;
    }

    /* Hash the provided password and compare to stored key */
    uint8_t test_key[32];
    sha256((const uint8_t *)password, strlen(password), test_key);

    if (memcmp(test_key, vault->encryption_key, 32) == 0) {
        vault->authenticated = 1;
        vault->failed_attempts = 0;
        printf("[OK] Vault authenticated successfully\n");
        return 1;
    }

    /* Failed attempt - exponential backoff */
    vault->failed_attempts++;
    int lockout = 1 << (vault->failed_attempts - 1);
    printf("Authentication failed. Next attempt available in %d seconds.\n", lockout);

    /* Wipe after max attempts */
    if (vault->failed_attempts >= VAULT_MAX_ATTEMPTS) {
        vault->locked = 1;
        memset(vault->entries, 0, sizeof(vault->entries));
        printf("VAULT WIPED: Maximum attempts exceeded. All secrets destroyed.\n");
    }
    return 0;
}

How does exponential backoff work here?

The lockout delay doubles after each failure: 1 second, 2 seconds, 4 seconds, 8 seconds, and so on. The expression 1 << (failed_attempts - 1) computes 2 raised to the power of failures minus one using a bit shift.

After 10 consecutive failures (VAULT_MAX_ATTEMPTS), the vault permanently locks and zeroes all secret entries. This matches Samsung Weaver's behavior where brute-force attempts eventually destroy the protected data entirely.

  • Save vault.c.

You will not see a change yet. The next chunk adds the functions for storing and retrieving encrypted secrets.

Once a user authenticates, they can store secrets. Each secret is encrypted with AES-256-CBC before being written to the vault's entry array.

  • In vault.c, add the vault_store_secret function below vault_authenticate:
int vault_store_secret(KnoxVault *vault, const char *key, const uint8_t *value, size_t len) {
    if (!vault->authenticated) {
        printf("[DENIED] Vault not authenticated\n");
        return 0;
    }
    if (len > VAULT_MAX_VALUE_LEN - 16) {
        printf("[ERROR] Value too large for vault\n");
        return 0;
    }

    /* Find an empty slot and encrypt the value */
    for (int i = 0; i < VAULT_MAX_SECRETS; i++) {
        if (!vault->entries[i].occupied) {
            strncpy(vault->entries[i].key_name, key, VAULT_MAX_KEY_NAME - 1);
            vault->entries[i].key_name[VAULT_MAX_KEY_NAME - 1] = '\0';
            /* Pad to AES block boundary (16 bytes) */
            size_t padded_len = ((len + 15) / 16) * 16;
            uint8_t padded[VAULT_MAX_VALUE_LEN];
            memset(padded, 0, sizeof(padded));
            memcpy(padded, value, len);
            aes256_cbc_encrypt(vault->encryption_key, vault->iv,
                              padded, vault->entries[i].encrypted_value, padded_len);
            vault->entries[i].value_len = padded_len;
            vault->entries[i].occupied = 1;
            printf("[OK] Secret '%s' stored in vault (encrypted)\n", key);
            return 1;
        }
    }
    printf("[ERROR] Vault full\n");
    return 0;
}

What does this code do?

The function first checks authentication and size constraints. Then it finds an empty slot, pads the plaintext to a 16-byte boundary (AES block size), and encrypts it using the vault's key and IV.

The padding ensures AES-256-CBC can process the data in complete 16-byte blocks. Any bytes beyond the original length are zeros, which get discarded on retrieval.

  • Save vault.c.

You will not see a change yet. The retrieval function completes the vault and allows a compile check.

The retrieval function reverses the process. It finds the matching entry, decrypts it with the same key and IV, and returns the plaintext.

  • In vault.c, add the vault_retrieve_secret function below vault_store_secret:
int vault_retrieve_secret(KnoxVault *vault, const char *key, uint8_t *value_out, size_t *len_out) {
    if (!vault->authenticated) {
        printf("[DENIED] Vault not authenticated\n");
        return 0;
    }

    /* Search for the matching entry and decrypt */
    for (int i = 0; i < VAULT_MAX_SECRETS; i++) {
        if (vault->entries[i].occupied && strcmp(vault->entries[i].key_name, key) == 0) {
            aes256_cbc_decrypt(vault->encryption_key, vault->iv,
                              vault->entries[i].encrypted_value, value_out,
                              vault->entries[i].value_len);
            *len_out = vault->entries[i].value_len;
            printf("[OK] Secret '%s' retrieved from vault (decrypted)\n", key);
            return 1;
        }
    }
    printf("[ERROR] Secret '%s' not found\n", key);
    return 0;
}

What does this code do?

The function searches the vault entries by name. When it finds a match, it decrypts the stored ciphertext using aes256_cbc_decrypt with the vault's key and IV. The decrypted plaintext is written to value_out.

  • Save vault.c.
  • Verify the vault module compiles cleanly by running:
gcc -c vault.c

If no errors appear, the vault module compiled successfully. You should see a vault.o file in your directory.

Seeing a compilation error?

Check that your #include "vault.h" and #include "crypto.h" lines are at the top of vault.c. Missing includes cause undeclared-identifier errors.

Also confirm that vault.h and crypto.h are in the same directory as vault.c. GCC looks for quoted includes in the current directory first.

Help me fix a compile error in vault.c

✔️ Awesome, I've got everything!

Great. Double check you have saved both vault.h and vault.c.

ⓧ I'd like to double check the full code

Here is the complete vault.h:

#ifndef VAULT_H
#define VAULT_H

#include <stdint.h>
#include <stddef.h>

#define VAULT_MAX_SECRETS 8
#define VAULT_MAX_KEY_NAME 32
#define VAULT_MAX_VALUE_LEN 128
#define VAULT_MAX_ATTEMPTS 10

typedef struct {
    char key_name[VAULT_MAX_KEY_NAME];
    uint8_t encrypted_value[VAULT_MAX_VALUE_LEN];
    size_t value_len;
    int occupied;
} VaultEntry;

typedef struct {
    uint8_t encryption_key[32];
    uint8_t iv[16];
    VaultEntry entries[VAULT_MAX_SECRETS];
    int failed_attempts;
    int locked;
    int authenticated;
} KnoxVault;

void vault_init(KnoxVault *vault, const char *master_password);
int vault_authenticate(KnoxVault *vault, const char *password);
int vault_store_secret(KnoxVault *vault, const char *key, const uint8_t *value, size_t len);
int vault_retrieve_secret(KnoxVault *vault, const char *key, uint8_t *value_out, size_t *len_out);

#endif

Here is the complete vault.c:

#include "vault.h"
#include "crypto.h"
#include <stdio.h>
#include <string.h>

void vault_init(KnoxVault *vault, const char *master_password) {
    memset(vault, 0, sizeof(KnoxVault));
    sha256((const uint8_t *)master_password, strlen(master_password), vault->encryption_key);
    memcpy(vault->iv, vault->encryption_key, 16);
    vault->authenticated = 0;
    vault->locked = 0;
    vault->failed_attempts = 0;
}

int vault_authenticate(KnoxVault *vault, const char *password) {
    if (vault->locked) {
        printf("VAULT WIPED: Maximum attempts exceeded. All secrets destroyed.\n");
        return 0;
    }

    uint8_t test_key[32];
    sha256((const uint8_t *)password, strlen(password), test_key);

    if (memcmp(test_key, vault->encryption_key, 32) == 0) {
        vault->authenticated = 1;
        vault->failed_attempts = 0;
        printf("[OK] Vault authenticated successfully\n");
        return 1;
    }

    vault->failed_attempts++;
    int lockout = 1 << (vault->failed_attempts - 1);
    printf("Authentication failed. Next attempt available in %d seconds.\n", lockout);

    if (vault->failed_attempts >= VAULT_MAX_ATTEMPTS) {
        vault->locked = 1;
        memset(vault->entries, 0, sizeof(vault->entries));
        printf("VAULT WIPED: Maximum attempts exceeded. All secrets destroyed.\n");
    }
    return 0;
}

int vault_store_secret(KnoxVault *vault, const char *key, const uint8_t *value, size_t len) {
    if (!vault->authenticated) {
        printf("[DENIED] Vault not authenticated\n");
        return 0;
    }
    if (len > VAULT_MAX_VALUE_LEN - 16) {
        printf("[ERROR] Value too large for vault\n");
        return 0;
    }

    for (int i = 0; i < VAULT_MAX_SECRETS; i++) {
        if (!vault->entries[i].occupied) {
            strncpy(vault->entries[i].key_name, key, VAULT_MAX_KEY_NAME - 1);
            vault->entries[i].key_name[VAULT_MAX_KEY_NAME - 1] = '\0';
            size_t padded_len = ((len + 15) / 16) * 16;
            uint8_t padded[VAULT_MAX_VALUE_LEN];
            memset(padded, 0, sizeof(padded));
            memcpy(padded, value, len);
            aes256_cbc_encrypt(vault->encryption_key, vault->iv,
                              padded, vault->entries[i].encrypted_value, padded_len);
            vault->entries[i].value_len = padded_len;
            vault->entries[i].occupied = 1;
            printf("[OK] Secret '%s' stored in vault (encrypted)\n", key);
            return 1;
        }
    }
    printf("[ERROR] Vault full\n");
    return 0;
}

int vault_retrieve_secret(KnoxVault *vault, const char *key, uint8_t *value_out, size_t *len_out) {
    if (!vault->authenticated) {
        printf("[DENIED] Vault not authenticated\n");
        return 0;
    }

    for (int i = 0; i < VAULT_MAX_SECRETS; i++) {
        if (vault->entries[i].occupied && strcmp(vault->entries[i].key_name, key) == 0) {
            aes256_cbc_decrypt(vault->encryption_key, vault->iv,
                              vault->entries[i].encrypted_value, value_out,
                              vault->entries[i].value_len);
            *len_out = vault->entries[i].value_len;
            printf("[OK] Secret '%s' retrieved from vault (decrypted)\n", key);
            return 1;
        }
    }
    printf("[ERROR] Secret '%s' not found\n", key);
    return 0;
}
Wire the vault into the main simulator

The vault is fully implemented. Now you need to call it from knox_sim.c so that Layer 2 runs immediately after a successful boot. The demo will show a failed authentication attempt (triggering the lockout message), a successful authentication, and a store-then-retrieve cycle.

  • In knox_sim.c, find the line that says return 0; at the bottom of main.
  • Replace that return 0; with the following Layer 2 vault demonstration code:
    /* Layer 2: Knox Vault */
    printf("\n--- KNOX VAULT ---\n");
    KnoxVault vault;
    vault_init(&vault, "enterprise_master_key_2024");

    printf("\nAttempting authentication with wrong password:\n");
    vault_authenticate(&vault, "wrong_password");

    printf("\nAttempting authentication with correct password:\n");
    vault_authenticate(&vault, "enterprise_master_key_2024");

    const char *cert = "ENTERPRISE_ROOT_CA_CERT";
    vault_store_secret(&vault, "root_ca_cert", (const uint8_t *)cert, strlen(cert));

    uint8_t retrieved[128];
    size_t rlen;
    vault_retrieve_secret(&vault, "root_ca_cert", retrieved, &rlen);

    return 0;

What does this code do?

The demonstration initializes the vault with a master password, then shows two scenarios. First, a wrong password triggers the exponential backoff message. Second, the correct password unlocks the vault.

Once authenticated, it stores an enterprise root CA certificate (encrypted with AES-256-CBC) and immediately retrieves it (decrypted back to plaintext). This proves the full encrypt-store-retrieve cycle works end to end.

  • Save knox_sim.c.
  • Compile the full project by running:
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c
  • Run the simulator:
.\knox_sim.exe

You should see the boot chain verification from before, followed by the new Knox Vault output. Look for the failed authentication lockout message, the successful authentication, and the secret store and retrieve confirmations.

What should I see?

After the Trusted Boot output, you should see lines including:

  • Authentication failed. Next attempt available in 1 seconds.
  • [OK] Vault authenticated successfully
  • [OK] Secret 'root_ca_cert' stored in vault (encrypted)
  • [OK] Secret 'root_ca_cert' retrieved from vault (decrypted)

Seeing linker errors about undefined references?

Make sure you include all .c files in the compile command. Missing vault.c or crypto.c causes undefined reference errors for the vault and crypto functions.

If you see errors about integrity.c or policy.c, make sure those files exist (even if they are empty). GCC needs every file listed in the command to be present on disk.

Help me fix linker errors when compiling knox_sim

✔️ Awesome, I've got everything!

Great. Make sure all three files are saved: vault.h, vault.c, and knox_sim.c.

ⓧ I'd like to double check the full code

Here is the complete knox_sim.c at this point:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crypto.h"
#include "boot_chain.h"
#include "vault.h"
#include "integrity.h"
#include "policy.h"

int main(void) {
    printf("========================================\n");
    printf("    KNOX SECURITY SIMULATOR v1.0\n");
    printf("========================================\n");
    printf("Simulating Samsung Knox security layers\n");

    /* Layer 1: Trusted Boot */
    BootComponent chain[4];
    boot_chain_init(chain, 4);
    if (!boot_verify_chain(chain, 4)) {
        printf("\nDevice bricked. Exiting.\n");
        return 1;
    }

    /* Layer 2: Knox Vault */
    printf("\n--- KNOX VAULT ---\n");
    KnoxVault vault;
    vault_init(&vault, "enterprise_master_key_2024");

    printf("\nAttempting authentication with wrong password:\n");
    vault_authenticate(&vault, "wrong_password");

    printf("\nAttempting authentication with correct password:\n");
    vault_authenticate(&vault, "enterprise_master_key_2024");

    const char *cert = "ENTERPRISE_ROOT_CA_CERT";
    vault_store_secret(&vault, "root_ca_cert", (const uint8_t *)cert, strlen(cert));

    uint8_t retrieved[128];
    size_t rlen;
    vault_retrieve_secret(&vault, "root_ca_cert", retrieved, &rlen);

    return 0;
}

Your simulator now has two active security layers. The Trusted Boot chain verifies code integrity before execution, and the Knox Vault protects secrets with encryption and brute-force resistance. Next up, you will add a runtime integrity monitor that catches tampering after the device has booted.

Implement Runtime Integrity Monitoring and Container Isolation

Your simulator now boots securely and locks down secrets in an encrypted vault. But what happens after boot? A sophisticated attacker could modify kernel memory at runtime, bypassing everything you built so far.

Samsung Knox solves this with two runtime layers: Real-time Kernel Protection (RKP) that monitors critical memory for unauthorized changes, and Knox Workspace containers that isolate personal data from work data. In this step, you will implement both.

In this step, get ready to:
  • Build a runtime integrity monitor that detects memory tampering via SHA-256 baselines.
  • Create isolated containers that block cross-container data access.
  • Wire both layers into the simulator and demonstrate tamper detection and isolation.
Implement the runtime integrity monitor

Knox's RKP runs in an isolated execution environment and continuously checks that critical kernel data has not been modified. Your simulator approximates this by storing a SHA-256 baseline of each protected memory region at registration time, then recomputing and comparing on demand.

What is Real-time Kernel Protection?

RKP runs in the Secure World of ARM TrustZone (or a thin hypervisor). It prevents unauthorized privileged code from running, blocks direct user access to kernel data, and monitors critical kernel data structures to prevent credential escalation.

Your simulator captures the core idea: take a hash snapshot of protected data, then periodically verify nothing has changed.

  • Open integrity.h and add the following header guard, struct definition, and function declarations:
#ifndef INTEGRITY_H
#define INTEGRITY_H

#include <stdint.h>
#include <stddef.h>

#define MAX_REGION_NAME 64

/* A memory region monitored for unauthorized changes */
typedef struct {
    char name[MAX_REGION_NAME];
    uint8_t *data;           /* pointer to the monitored memory */
    size_t data_len;
    uint8_t baseline_hash[32]; /* SHA-256 snapshot taken at registration */
} ProtectedRegion;

void integrity_register(ProtectedRegion *region, const char *name, uint8_t *data, size_t len);
int integrity_check(ProtectedRegion *region);
int integrity_check_all(ProtectedRegion *regions, int count);

#endif

What does this code do?

The ProtectedRegion struct stores a pointer to monitored memory alongside the SHA-256 hash computed when the region was first registered. At any time, the monitor can recompute the hash and compare it to detect changes.

This is a simplified model of what RKP does in hardware. The real implementation runs at a higher privilege level so that even a compromised kernel cannot disable the checks.

  • Save integrity.h.
  • Open integrity.c and implement the three integrity functions:
#include "integrity.h"
#include "crypto.h"
#include <stdio.h>
#include <string.h>

void integrity_register(ProtectedRegion *region, const char *name, uint8_t *data, size_t len) {
    strncpy(region->name, name, MAX_REGION_NAME - 1);
    region->name[MAX_REGION_NAME - 1] = '\0';
    region->data = data;
    region->data_len = len;
    /* Compute and store the baseline SHA-256 hash */
    sha256(data, len, region->baseline_hash);
}

int integrity_check(ProtectedRegion *region) {
    uint8_t current_hash[32];
    sha256(region->data, region->data_len, current_hash);
    /* Compare current hash against the baseline */
    return memcmp(current_hash, region->baseline_hash, 32) == 0;
}

int integrity_check_all(ProtectedRegion *regions, int count) {
    printf("\n--- RUNTIME INTEGRITY CHECK (RKP) ---\n");
    int all_ok = 1;
    for (int i = 0; i < count; i++) {
        if (integrity_check(&regions[i])) {
            printf("[INTACT] %s - no tampering detected\n", regions[i].name);
        } else {
            printf("[TAMPERED] %s - INTEGRITY VIOLATION DETECTED\n", regions[i].name);
            all_ok = 0;
        }
    }
    return all_ok;
}

What does this code do?

  • integrity_register() stores the memory pointer and immediately computes a SHA-256 hash as the trusted baseline.
  • integrity_check() recomputes the hash and returns 1 (intact) or 0 (tampered) by comparing against the baseline.
  • integrity_check_all() iterates over all registered regions, printing a status line for each and returning whether all passed.
  • Save integrity.c.

Seeing undeclared identifier errors?

Make sure integrity.c includes both "integrity.h" and "crypto.h". The sha256 function comes from your crypto module.

help me debug undeclared identifier errors in integrity.c

Implement container isolation

Knox Workspace uses containers to separate personal and work data on the same device. Apps inside one container cannot communicate with apps in the other. The containers use Intent filtering to block cross-container access.

  • Open policy.h and add the full header with container structs, policy struct, and all function declarations:
#ifndef POLICY_H
#define POLICY_H

#include <stdint.h>
#include <stddef.h>

#define MAX_CONTAINER_NAME 32
#define MAX_FILES 8
#define MAX_FILENAME 32
#define MAX_FILE_DATA 128

/* A file stored inside a container */
typedef struct {
    char filename[MAX_FILENAME];
    uint8_t data[MAX_FILE_DATA];
    size_t data_len;
    int occupied;
} ContainerFile;

/* An isolated data container (Personal or Work) */
typedef struct {
    char name[MAX_CONTAINER_NAME];
    ContainerFile files[MAX_FILES];
} Container;

/* Enterprise security policy (Knox Manage) */
typedef struct {
    int allow_camera;
    int allow_usb_export;
    int allow_clipboard_cross_container;
    int require_auth_timeout_seconds;
    int min_password_length;
} SecurityPolicy;

void container_init(Container *c, const char *name);
int container_write(Container *c, const char *filename, const uint8_t *data, size_t len);
int container_read(Container *c, const char *filename, uint8_t *data_out, size_t *len_out);
int container_cross_access(Container *from, Container *to, const char *filename);

void policy_init_default(SecurityPolicy *policy);
int policy_check_action(SecurityPolicy *policy, const char *action);

#endif

What does this code do?

  • ContainerFile simulates a file stored inside an isolated container. Each container holds up to 8 files.
  • Container represents one isolated workspace (Personal or Work) with its own file storage.
  • SecurityPolicy defines enterprise restrictions that Knox Manage enforces. You will implement the policy engine in the next step.
  • Save policy.h.
  • Open policy.c and implement the container functions:
#include "policy.h"
#include <stdio.h>
#include <string.h>

void container_init(Container *c, const char *name) {
    memset(c, 0, sizeof(Container));
    strncpy(c->name, name, MAX_CONTAINER_NAME - 1);
    c->name[MAX_CONTAINER_NAME - 1] = '\0';
}

int container_write(Container *c, const char *filename, const uint8_t *data, size_t len) {
    if (len > MAX_FILE_DATA) return 0;
    for (int i = 0; i < MAX_FILES; i++) {
        if (!c->files[i].occupied) {
            strncpy(c->files[i].filename, filename, MAX_FILENAME - 1);
            c->files[i].filename[MAX_FILENAME - 1] = '\0';
            memcpy(c->files[i].data, data, len);
            c->files[i].data_len = len;
            c->files[i].occupied = 1;
            printf("[OK] Written '%s' to container '%s'\n", filename, c->name);
            return 1;
        }
    }
    return 0;
}

int container_read(Container *c, const char *filename, uint8_t *data_out, size_t *len_out) {
    for (int i = 0; i < MAX_FILES; i++) {
        if (c->files[i].occupied && strcmp(c->files[i].filename, filename) == 0) {
            memcpy(data_out, c->files[i].data, c->files[i].data_len);
            *len_out = c->files[i].data_len;
            return 1;
        }
    }
    return 0;
}

int container_cross_access(Container *from, Container *to, const char *filename) {
    /* Knox Intent filtering - always block cross-container access */
    printf("ACCESS DENIED: Container '%s' cannot access '%s' data. Intent blocked.\n",
           from->name, to->name);
    return 0;
}

What does this code do?

  • container_init() zeros out the container and sets its name. Each container starts empty.
  • container_write() finds the first empty slot and stores the file data. It simulates writing a file into an isolated workspace.
  • container_cross_access() always returns failure and prints an ACCESS DENIED message. This mirrors Knox's Intent filtering, which prevents apps in one container from reading data in another.
  • Save policy.c.

Why is container_cross_access() hardcoded to deny?

In real Knox Workspace, the container boundary is enforced at the OS level. There is no code path that allows cross-container communication. By hardcoding the denial, your simulator captures this architectural guarantee. It is not a permission check that could be bypassed. It is a structural impossibility.

Compiler warnings about unused functions?

You may see warnings about policy_init_default and policy_check_action being declared but not defined. This is expected. You declared them in the header but will implement them in the next step. The project still compiles because knox_sim.c does not call those functions yet.

help me fix linker errors related to policy functions

Wire layers 3 and 4 into the simulator

Now you will add the integrity monitoring and container isolation demos to your main function. The integrity monitor registers critical memory regions, then you will simulate a tampering attack to prove detection works.

  • In knox_sim.c, find this line near the bottom of main:
    vault_retrieve_secret(&vault, "root_ca_cert", retrieved, &rlen);
  • Directly below that line, add the Layer 3 runtime integrity monitor code:
    /* Layer 3: Runtime Integrity Monitor */
    ProtectedRegion regions[2];
    integrity_register(&regions[0], "Boot Chain Data", chain[0].data, chain[0].data_len);
    integrity_register(&regions[1], "Vault Encryption Key", vault.encryption_key, 32);

    integrity_check_all(regions, 2);

    printf("\nSimulating kernel tampering...\n");
    chain[0].data[0] ^= 0xFF;
    integrity_check_all(regions, 2);
    chain[0].data[0] ^= 0xFF;

What does this code do?

  • It registers two protected regions: the boot chain's Hardware Root of Trust data and the vault's encryption key. Both get SHA-256 baselines.
  • The first integrity_check_all() call verifies both regions are intact (they should be).
  • Then it flips a single byte (^= 0xFF) in the boot chain data to simulate a kernel-level attack. The second check detects this tampering.
  • Finally, it restores the original byte so the simulator can continue normally.
  • Below the Layer 3 code you just added, add the Layer 4 container isolation demo:
    /* Layer 4: Container Isolation */
    printf("\n--- CONTAINER ISOLATION (Knox Workspace) ---\n");
    Container personal, work;
    container_init(&personal, "Personal");
    container_init(&work, "Work");

    container_write(&personal, "vacation_photo.jpg", (const uint8_t *)"PHOTO_DATA", 10);
    container_write(&work, "quarterly_report.pdf", (const uint8_t *)"CONFIDENTIAL", 12);

    printf("\nAttempting cross-container access:\n");
    container_cross_access(&personal, &work, "quarterly_report.pdf");
    container_cross_access(&work, &personal, "vacation_photo.jpg");

What does this code do?

  • It creates two isolated containers: Personal (for personal files) and Work (for enterprise data).
  • Each container gets a file written to it. A vacation photo goes into Personal. A confidential report goes into Work.
  • The cross-access attempts demonstrate that Personal cannot read Work's data and vice versa. Both attempts are denied.
  • Make sure return 0; remains as the last line inside main(), below the Layer 4 code.
  • Save knox_sim.c.
  • Compile and run the full project by running this command:
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c
  • Run the simulator:
.\knox_sim.exe
  • You should see all previous layers (Trusted Boot and Knox Vault) followed by the new output. Look for two integrity checks (one passing, one detecting tampering) and the container isolation demo with ACCESS DENIED messages.

Don't see the tamper detection?

Check that your Layer 3 code uses chain[0].data (not chain[1].data). The first region registered points to chain[0].data, so you must tamper with that same array for the hash mismatch to trigger.

Also verify that the XOR line uses ^= 0xFF (XOR-assign), not = 0xFF (plain assign). XOR flips the bits, and a second XOR restores them.

help me debug why my integrity monitor is not detecting tampering

✔️ Awesome, I've got everything!

Great. Double check you have saved all four files: integrity.h, integrity.c, policy.h, and policy.c.

ⓧ I'd like to double check the full code

Here is the complete code for each file at this point.

#ifndef INTEGRITY_H
#define INTEGRITY_H

#include <stdint.h>
#include <stddef.h>

#define MAX_REGION_NAME 64

typedef struct {
    char name[MAX_REGION_NAME];
    uint8_t *data;
    size_t data_len;
    uint8_t baseline_hash[32];
} ProtectedRegion;

void integrity_register(ProtectedRegion *region, const char *name, uint8_t *data, size_t len);
int integrity_check(ProtectedRegion *region);
int integrity_check_all(ProtectedRegion *regions, int count);

#endif
#include "integrity.h"
#include "crypto.h"
#include <stdio.h>
#include <string.h>

void integrity_register(ProtectedRegion *region, const char *name, uint8_t *data, size_t len) {
    strncpy(region->name, name, MAX_REGION_NAME - 1);
    region->name[MAX_REGION_NAME - 1] = '\0';
    region->data = data;
    region->data_len = len;
    sha256(data, len, region->baseline_hash);
}

int integrity_check(ProtectedRegion *region) {
    uint8_t current_hash[32];
    sha256(region->data, region->data_len, current_hash);
    return memcmp(current_hash, region->baseline_hash, 32) == 0;
}

int integrity_check_all(ProtectedRegion *regions, int count) {
    printf("\n--- RUNTIME INTEGRITY CHECK (RKP) ---\n");
    int all_ok = 1;
    for (int i = 0; i < count; i++) {
        if (integrity_check(&regions[i])) {
            printf("[INTACT] %s - no tampering detected\n", regions[i].name);
        } else {
            printf("[TAMPERED] %s - INTEGRITY VIOLATION DETECTED\n", regions[i].name);
            all_ok = 0;
        }
    }
    return all_ok;
}
#ifndef POLICY_H
#define POLICY_H

#include <stdint.h>
#include <stddef.h>

#define MAX_CONTAINER_NAME 32
#define MAX_FILES 8
#define MAX_FILENAME 32
#define MAX_FILE_DATA 128

typedef struct {
    char filename[MAX_FILENAME];
    uint8_t data[MAX_FILE_DATA];
    size_t data_len;
    int occupied;
} ContainerFile;

typedef struct {
    char name[MAX_CONTAINER_NAME];
    ContainerFile files[MAX_FILES];
} Container;

typedef struct {
    int allow_camera;
    int allow_usb_export;
    int allow_clipboard_cross_container;
    int require_auth_timeout_seconds;
    int min_password_length;
} SecurityPolicy;

void container_init(Container *c, const char *name);
int container_write(Container *c, const char *filename, const uint8_t *data, size_t len);
int container_read(Container *c, const char *filename, uint8_t *data_out, size_t *len_out);
int container_cross_access(Container *from, Container *to, const char *filename);

void policy_init_default(SecurityPolicy *policy);
int policy_check_action(SecurityPolicy *policy, const char *action);

#endif
#include "policy.h"
#include <stdio.h>
#include <string.h>

void container_init(Container *c, const char *name) {
    memset(c, 0, sizeof(Container));
    strncpy(c->name, name, MAX_CONTAINER_NAME - 1);
    c->name[MAX_CONTAINER_NAME - 1] = '\0';
}

int container_write(Container *c, const char *filename, const uint8_t *data, size_t len) {
    if (len > MAX_FILE_DATA) return 0;
    for (int i = 0; i < MAX_FILES; i++) {
        if (!c->files[i].occupied) {
            strncpy(c->files[i].filename, filename, MAX_FILENAME - 1);
            c->files[i].filename[MAX_FILENAME - 1] = '\0';
            memcpy(c->files[i].data, data, len);
            c->files[i].data_len = len;
            c->files[i].occupied = 1;
            printf("[OK] Written '%s' to container '%s'\n", filename, c->name);
            return 1;
        }
    }
    return 0;
}

int container_read(Container *c, const char *filename, uint8_t *data_out, size_t *len_out) {
    for (int i = 0; i < MAX_FILES; i++) {
        if (c->files[i].occupied && strcmp(c->files[i].filename, filename) == 0) {
            memcpy(data_out, c->files[i].data, c->files[i].data_len);
            *len_out = c->files[i].data_len;
            return 1;
        }
    }
    return 0;
}

int container_cross_access(Container *from, Container *to, const char *filename) {
    printf("ACCESS DENIED: Container '%s' cannot access '%s' data. Intent blocked.\n",
           from->name, to->name);
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crypto.h"
#include "boot_chain.h"
#include "vault.h"
#include "integrity.h"
#include "policy.h"

int main(void) {
    printf("========================================\n");
    printf("    KNOX SECURITY SIMULATOR v1.0\n");
    printf("========================================\n");
    printf("Simulating Samsung Knox security layers\n");

    /* Layer 1: Trusted Boot */
    BootComponent chain[4];
    boot_chain_init(chain, 4);
    if (!boot_verify_chain(chain, 4)) {
        printf("\nDevice bricked. Exiting.\n");
        return 1;
    }

    /* Layer 2: Knox Vault */
    printf("\n--- KNOX VAULT ---\n");
    KnoxVault vault;
    vault_init(&vault, "enterprise_master_key_2024");

    printf("\nAttempting authentication with wrong password:\n");
    vault_authenticate(&vault, "wrong_password");

    printf("\nAttempting authentication with correct password:\n");
    vault_authenticate(&vault, "enterprise_master_key_2024");

    const char *cert = "ENTERPRISE_ROOT_CA_CERT";
    vault_store_secret(&vault, "root_ca_cert", (const uint8_t *)cert, strlen(cert));

    uint8_t retrieved[128];
    size_t rlen;
    vault_retrieve_secret(&vault, "root_ca_cert", retrieved, &rlen);

    /* Layer 3: Runtime Integrity Monitor */
    ProtectedRegion regions[2];
    integrity_register(&regions[0], "Boot Chain Data", chain[0].data, chain[0].data_len);
    integrity_register(&regions[1], "Vault Encryption Key", vault.encryption_key, 32);

    integrity_check_all(regions, 2);

    printf("\nSimulating kernel tampering...\n");
    chain[0].data[0] ^= 0xFF;
    integrity_check_all(regions, 2);
    chain[0].data[0] ^= 0xFF;

    /* Layer 4: Container Isolation */
    printf("\n--- CONTAINER ISOLATION (Knox Workspace) ---\n");
    Container personal, work;
    container_init(&personal, "Personal");
    container_init(&work, "Work");

    container_write(&personal, "vacation_photo.jpg", (const uint8_t *)"PHOTO_DATA", 10);
    container_write(&work, "quarterly_report.pdf", (const uint8_t *)"CONFIDENTIAL", 12);

    printf("\nAttempting cross-container access:\n");
    container_cross_access(&personal, &work, "quarterly_report.pdf");
    container_cross_access(&work, &personal, "vacation_photo.jpg");

    return 0;
}

Your simulator now detects runtime memory tampering and enforces data isolation between containers. In the next step, you will add the final layer: a policy engine that restricts device operations like camera access and USB file export.

Build the Policy Engine and Complete the Integration

Your simulator now boots securely, stores encrypted secrets, detects tampering in real time, and isolates data between containers. The one layer still missing is the one that ties it all together for IT administrators.

In enterprise environments, administrators push security policies to every managed device. These policies restrict specific operations (camera, USB export, clipboard sharing) at the OS level. This is the job of Knox Manage in Samsung's architecture. In this step, you will implement the policy engine and bring all five Knox layers together in one complete simulation.

In this step, get ready to:
  • Implement the policy engine functions that enforce enterprise security rules.
  • Wire the policy enforcement layer into the main simulator.
  • Compile and run the complete 5-layer Knox simulation end to end.
Implement the policy engine functions

The SecurityPolicy struct is already declared in policy.h with boolean flags for each restricted operation. Now you need to write the functions that initialize and enforce those flags.

  • In policy.c, find the end of the container_cross_access function.
  • Add the policy_init_default function below it by copying the following code:
void policy_init_default(SecurityPolicy *policy) {
    /* Set restrictive enterprise defaults */
    policy->allow_camera = 0;
    policy->allow_usb_export = 0;
    policy->allow_clipboard_cross_container = 0;
    policy->require_auth_timeout_seconds = 300;
    policy->min_password_length = 8;
}

What does this code do?

This function sets the default enterprise policy to be maximally restrictive. Camera, USB export, and cross-container clipboard are all disabled (set to 0).

The 300-second auth timeout means the device locks after 5 minutes of inactivity. The 8-character minimum password length enforces basic credential strength. These mirror the kind of defaults a real Knox Manage admin would push to company devices.

  • Now add the policy_check_action function directly below policy_init_default:
int policy_check_action(SecurityPolicy *policy, const char *action) {
    /* Check each action against the active policy */
    if (strcmp(action, "camera_capture") == 0) {
        if (!policy->allow_camera) {
            printf("[BLOCKED] camera_capture - Policy violation: camera disabled by admin\n");
            return 0;
        }
        printf("[ALLOWED] camera_capture\n");
        return 1;
    }
    if (strcmp(action, "usb_file_export") == 0) {
        if (!policy->allow_usb_export) {
            printf("[BLOCKED] usb_file_export - Policy violation: USB export disabled\n");
            return 0;
        }
        printf("[ALLOWED] usb_file_export\n");
        return 1;
    }
    if (strcmp(action, "clipboard_paste_to_personal") == 0) {
        if (!policy->allow_clipboard_cross_container) {
            printf("[BLOCKED] clipboard_paste_to_personal - Policy violation: cross-container clipboard disabled\n");
            return 0;
        }
        printf("[ALLOWED] clipboard_paste_to_personal\n");
        return 1;
    }
    if (strcmp(action, "open_work_app") == 0) {
        printf("[ALLOWED] open_work_app\n");
        return 1;
    }
    /* Default: allow actions with no explicit restriction */
    printf("[ALLOWED] %s - no policy restriction\n", action);
    return 1;
}

What does this code do?

This function acts as the runtime policy enforcement point. Every user action passes through it before executing.

  • Each if block maps an action string to a specific policy flag. If the flag is 0 (disabled), the action is blocked with a descriptive message.
  • The open_work_app action is always allowed because opening work apps is never restricted by enterprise policy.
  • Any unrecognized action falls through to the default case, which allows it. This matches how real policy engines work: only explicitly restricted actions are blocked.
  • Save policy.c.

Seeing a compiler error about strcmp?

Make sure #include <string.h> is at the top of your policy.c file. The strcmp function comes from the string library.

Help me debug my policy.c compiler errors

Add the policy enforcement layer to the main simulator

With the policy engine implemented, you need to wire it into the main simulation flow. This adds Layer 5 where the simulator tests a series of user actions against the enterprise policy.

  • In knox_sim.c, find the end of the Layer 4 section. Look for these two lines at the bottom of the container isolation code:
    container_cross_access(&personal, &work, "quarterly_report.pdf");
    container_cross_access(&work, &personal, "vacation_photo.jpg");
  • Add the Layer 5 policy enforcement code below those lines:
    /* Layer 5: Policy Enforcement */
    printf("\n--- POLICY ENFORCEMENT (Knox Manage) ---\n");
    SecurityPolicy policy;
    policy_init_default(&policy);
    printf("Enterprise policy loaded (camera:OFF, USB:OFF, clipboard:OFF)\n\n");

    /* Simulate a series of user actions against the policy */
    policy_check_action(&policy, "camera_capture");
    policy_check_action(&policy, "usb_file_export");
    policy_check_action(&policy, "clipboard_paste_to_personal");
    policy_check_action(&policy, "open_work_app");

What does this code do?

This section creates a SecurityPolicy struct, loads the restrictive enterprise defaults, and then simulates four user actions. Three of the four (camera, USB, clipboard) are blocked by policy. Only open_work_app passes.

This is how Knox Manage works in practice. An IT admin configures a policy on the management server. The device receives the policy and enforces it on every action going forward.

Add the summary banner and compile the complete project

The simulation now has all five layers. The last piece is a summary banner that reports the status of every layer, confirming the device is fully secured.

  • In knox_sim.c, directly below the policy_check_action calls you just added, replace the existing return 0; at the end of main with the following summary code:
    /* Summary */
    printf("\n========================================\n");
    printf("    ALL KNOX LAYERS ACTIVE\n");
    printf("========================================\n");
    printf("Layer 1: Trusted Boot      [VERIFIED]\n");
    printf("Layer 2: Knox Vault        [SEALED]\n");
    printf("Layer 3: Integrity Monitor [ACTIVE]\n");
    printf("Layer 4: Container Isolation [ENFORCED]\n");
    printf("Layer 5: Policy Engine     [ENFORCED]\n");
    printf("========================================\n");

    return 0;
}

What does this code do?

The summary banner prints the final state of all five security layers. In a real Knox device, this status would be reported during Device Health Attestation to prove the device booted correctly and all protections are active.

  • Save knox_sim.c.
  • Compile the entire project by running this command:
gcc -o knox_sim knox_sim.c crypto.c boot_chain.c vault.c integrity.c policy.c
  • Run the simulator:
.\knox_sim.exe
  • You should see all five layers execute in sequence. The output starts with the Trusted Boot chain, moves through vault authentication, integrity checks, container isolation, and ends with the policy enforcement results followed by the ALL KNOX LAYERS ACTIVE summary banner.

What should I see in the policy section?

The policy enforcement output should show three [BLOCKED] lines for camera, USB export, and clipboard. One [ALLOWED] line for open_work_app appears because opening work applications is never restricted.

Compile failing with undefined reference errors?

Make sure every .c file is included in the compile command. If you see errors about undefined references to sha256 or policy_check_action, it means one of the source files is missing from the gcc command.

Also check that each .c file includes its matching .h header at the top.

Help me fix my linker errors when compiling the Knox simulator

Why a policy engine on top of container isolation?

Containers prevent data from leaking between personal and work contexts. Policies go further by restricting what the user can do even within a single container. A blocked camera stops corporate espionage. A blocked USB port prevents data exfiltration to a thumb drive.

Together, containers and policies give IT admins fine-grained control over both data boundaries and device capabilities.

✔️ Awesome, I've got everything!

Great. Double check you have saved both policy.c and knox_sim.c.

ⓧ I'd like to double check the full code

Here is the complete policy.c:

#include "policy.h"
#include <stdio.h>
#include <string.h>

void container_init(Container *c, const char *name) {
    memset(c, 0, sizeof(Container));
    strncpy(c->name, name, MAX_CONTAINER_NAME - 1);
    c->name[MAX_CONTAINER_NAME - 1] = '\0';
}

int container_write(Container *c, const char *filename, const uint8_t *data, size_t len) {
    if (len > MAX_FILE_DATA) return 0;
    for (int i = 0; i < MAX_FILES; i++) {
        if (!c->files[i].occupied) {
            strncpy(c->files[i].filename, filename, MAX_FILENAME - 1);
            c->files[i].filename[MAX_FILENAME - 1] = '\0';
            memcpy(c->files[i].data, data, len);
            c->files[i].data_len = len;
            c->files[i].occupied = 1;
            printf("[OK] Written '%s' to container '%s'\n", filename, c->name);
            return 1;
        }
    }
    return 0;
}

int container_read(Container *c, const char *filename, uint8_t *data_out, size_t *len_out) {
    for (int i = 0; i < MAX_FILES; i++) {
        if (c->files[i].occupied && strcmp(c->files[i].filename, filename) == 0) {
            memcpy(data_out, c->files[i].data, c->files[i].data_len);
            *len_out = c->files[i].data_len;
            return 1;
        }
    }
    return 0;
}

int container_cross_access(Container *from, Container *to, const char *filename) {
    printf("ACCESS DENIED: Container '%s' cannot access '%s' data. Intent blocked.\n",
           from->name, to->name);
    return 0;
}

void policy_init_default(SecurityPolicy *policy) {
    policy->allow_camera = 0;
    policy->allow_usb_export = 0;
    policy->allow_clipboard_cross_container = 0;
    policy->require_auth_timeout_seconds = 300;
    policy->min_password_length = 8;
}

int policy_check_action(SecurityPolicy *policy, const char *action) {
    if (strcmp(action, "camera_capture") == 0) {
        if (!policy->allow_camera) {
            printf("[BLOCKED] camera_capture - Policy violation: camera disabled by admin\n");
            return 0;
        }
        printf("[ALLOWED] camera_capture\n");
        return 1;
    }
    if (strcmp(action, "usb_file_export") == 0) {
        if (!policy->allow_usb_export) {
            printf("[BLOCKED] usb_file_export - Policy violation: USB export disabled\n");
            return 0;
        }
        printf("[ALLOWED] usb_file_export\n");
        return 1;
    }
    if (strcmp(action, "clipboard_paste_to_personal") == 0) {
        if (!policy->allow_clipboard_cross_container) {
            printf("[BLOCKED] clipboard_paste_to_personal - Policy violation: cross-container clipboard disabled\n");
            return 0;
        }
        printf("[ALLOWED] clipboard_paste_to_personal\n");
        return 1;
    }
    if (strcmp(action, "open_work_app") == 0) {
        printf("[ALLOWED] open_work_app\n");
        return 1;
    }
    printf("[ALLOWED] %s - no policy restriction\n", action);
    return 1;
}

Here is the complete knox_sim.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crypto.h"
#include "boot_chain.h"
#include "vault.h"
#include "integrity.h"
#include "policy.h"

int main(void) {
    printf("========================================\n");
    printf("    KNOX SECURITY SIMULATOR v1.0\n");
    printf("========================================\n");
    printf("Simulating Samsung Knox security layers\n");

    /* Layer 1: Trusted Boot */
    BootComponent chain[4];
    boot_chain_init(chain, 4);
    if (!boot_verify_chain(chain, 4)) {
        printf("\nDevice bricked. Exiting.\n");
        return 1;
    }

    /* Layer 2: Knox Vault */
    printf("\n--- KNOX VAULT ---\n");
    KnoxVault vault;
    vault_init(&vault, "enterprise_master_key_2024");

    printf("\nAttempting authentication with wrong password:\n");
    vault_authenticate(&vault, "wrong_password");

    printf("\nAttempting authentication with correct password:\n");
    vault_authenticate(&vault, "enterprise_master_key_2024");

    const char *cert = "ENTERPRISE_ROOT_CA_CERT";
    vault_store_secret(&vault, "root_ca_cert", (const uint8_t *)cert, strlen(cert));

    uint8_t retrieved[128];
    size_t rlen;
    vault_retrieve_secret(&vault, "root_ca_cert", retrieved, &rlen);

    /* Layer 3: Runtime Integrity Monitor */
    ProtectedRegion regions[2];
    integrity_register(&regions[0], "Boot Chain Data", chain[0].data, chain[0].data_len);
    integrity_register(&regions[1], "Vault Encryption Key", vault.encryption_key, 32);

    integrity_check_all(regions, 2);

    printf("\nSimulating kernel tampering...\n");
    chain[0].data[0] ^= 0xFF;
    integrity_check_all(regions, 2);
    chain[0].data[0] ^= 0xFF;

    /* Layer 4: Container Isolation */
    printf("\n--- CONTAINER ISOLATION (Knox Workspace) ---\n");
    Container personal, work;
    container_init(&personal, "Personal");
    container_init(&work, "Work");

    container_write(&personal, "vacation_photo.jpg", (const uint8_t *)"PHOTO_DATA", 10);
    container_write(&work, "quarterly_report.pdf", (const uint8_t *)"CONFIDENTIAL", 12);

    printf("\nAttempting cross-container access:\n");
    container_cross_access(&personal, &work, "quarterly_report.pdf");
    container_cross_access(&work, &personal, "vacation_photo.jpg");

    /* Layer 5: Policy Enforcement */
    printf("\n--- POLICY ENFORCEMENT (Knox Manage) ---\n");
    SecurityPolicy policy;
    policy_init_default(&policy);
    printf("Enterprise policy loaded (camera:OFF, USB:OFF, clipboard:OFF)\n\n");

    policy_check_action(&policy, "camera_capture");
    policy_check_action(&policy, "usb_file_export");
    policy_check_action(&policy, "clipboard_paste_to_personal");
    policy_check_action(&policy, "open_work_app");

    /* Summary */
    printf("\n========================================\n");
    printf("    ALL KNOX LAYERS ACTIVE\n");
    printf("========================================\n");
    printf("Layer 1: Trusted Boot      [VERIFIED]\n");
    printf("Layer 2: Knox Vault        [SEALED]\n");
    printf("Layer 3: Integrity Monitor [ACTIVE]\n");
    printf("Layer 4: Container Isolation [ENFORCED]\n");
    printf("Layer 5: Policy Engine     [ENFORCED]\n");
    printf("========================================\n");

    return 0;
}

Your Knox Security Simulator is complete. All five layers are running together: hardware-rooted trust, encrypted vault storage, runtime integrity monitoring, container isolation, and policy enforcement. Next, take on the Secret Mission to add Device Health Attestation and generate a signed report of the entire device's security state.

Secret mission

Add Device Health Attestation

In enterprise deployments, a device doesn't just run security layers. It proves to a remote server that all layers are healthy. Can you add a Device Health Attestation report that aggregates every layer's status into a single signed document?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally on your machine, so there are no ongoing costs or cloud resources to worry about. Decide whether to keep your project files for reference or delete them to free up disk space.

Resources you used:

  • Source code files (knox_sim.c, crypto.c, crypto.h, boot_chain.c, boot_chain.h, vault.c, vault.h, integrity.c, integrity.h, policy.c, policy.h).
  • Compiled executable (knox_sim.exe on Windows, knox_sim on macOS/Linux).

Keep everything running

No action needed. Choose this if you want to keep your Knox simulator for reference or plan to extend it further.

  • Your source files and compiled executable remain on your Desktop. You can recompile at any time with the same gcc command.
  • There are no running processes or background services to manage.

Pause - I'll come back to this later

Since this project is entirely local with no running processes, there is nothing to pause. Your files sit on disk until you return.

  • No servers, containers, or background tasks are running.
  • When you come back, open your terminal, navigate to your project folder on the Desktop, and recompile with the same command.

Delete - I don't want to use this again

Remove your project folder and all its contents from your Desktop.

  • Open your terminal.
  • Navigate to your Desktop by running:
cd ~/Desktop
  • Delete the project folder (replace the folder name with whatever you named yours) by running:

macOS

rm -rf knox_sim

Windows

rmdir /s /q knox_sim
  • Confirm the folder is gone by running:
ls

You should no longer see your project folder in the output.

Nice Work!

Nice Work!

You just built a complete Samsung Knox security architecture simulator in C, implementing five interconnected layers from boot to runtime. Every layer uses real cryptographic primitives that you wrote from scratch.

You've learned how to:

  • Implement SHA-256 hashing and AES-256-CBC encryption from scratch in C following NIST standards, giving you a deep understanding of the cryptographic primitives that underpin enterprise device security.
  • Build a Trusted Boot chain that verifies each component via hash chains and halts on tampered code, replicating how Knox's Hardware Root of Trust protects devices from power-on.
  • Create a multi-layer security system combining an encrypted vault with brute-force protection, runtime integrity monitoring, container isolation that blocks cross-container access, and a policy engine that enforces enterprise restrictions.
  • Secret Mission: Generated a Device Health Attestation report signed with a cryptographic hash, proving that tampering with any layer changes the signature and exposes compromise.

Ready to quiz yourself?