Skip to content

File syn_cose.c

File List > proto > syn_cose.c

Go to the documentation of this file

#include "syntropic/proto/syn_cose.h"

#if !defined(SYN_USE_COSE) || SYN_USE_COSE

#include "syntropic/util/syn_random.h"

#include <string.h>

/* Build serialized Sig_structure per RFC 9052 Section 4.4 */
static bool build_sig_structure(const uint8_t *body_protected, size_t body_protected_len,
                                const uint8_t *external_aad, size_t external_aad_len,
                                const uint8_t *payload, size_t payload_len, uint8_t *out_buf,
                                size_t out_buf_size, size_t *out_len)
{
    SYN_CborWriter w;
    syn_cbor_writer_init(&w, out_buf, out_buf_size);

    /* Sig_structure is an array of 4 elements */
    syn_cbor_write_array_begin(&w, 4);
    syn_cbor_write_text_cstr(&w, "Signature1");
    syn_cbor_write_bytes(&w, body_protected, body_protected_len);
    syn_cbor_write_bytes(&w, (external_aad != NULL) ? external_aad : (const uint8_t *)"",
                         external_aad_len);
    syn_cbor_write_bytes(&w, (payload != NULL) ? payload : (const uint8_t *)"", payload_len);

    if (!syn_cbor_writer_ok(&w)) {
        return false;
    }

    *out_len = syn_cbor_writer_len(&w);
    return true;
}

/* Build serialized Enc_structure per RFC 9052 Section 5.3 */
static bool build_enc_structure(const uint8_t *body_protected, size_t body_protected_len,
                                const uint8_t *external_aad, size_t external_aad_len,
                                uint8_t *out_buf, size_t out_buf_size, size_t *out_len)
{
    SYN_CborWriter w;
    syn_cbor_writer_init(&w, out_buf, out_buf_size);

    /* Enc_structure is an array of 3 elements */
    syn_cbor_write_array_begin(&w, 3);
    syn_cbor_write_text_cstr(&w, "Encrypt0");
    syn_cbor_write_bytes(&w, body_protected, body_protected_len);
    syn_cbor_write_bytes(&w, (external_aad != NULL) ? external_aad : (const uint8_t *)"",
                         external_aad_len);

    if (!syn_cbor_writer_ok(&w)) {
        return false;
    }

    *out_len = syn_cbor_writer_len(&w);
    return true;
}

static bool build_mac_structure(const uint8_t *body_protected, size_t body_protected_len,
                                const uint8_t *external_aad, size_t external_aad_len,
                                const uint8_t *payload, size_t payload_len, uint8_t *out_buf,
                                size_t out_buf_size, size_t *out_len)
{
    SYN_CborWriter w;
    syn_cbor_writer_init(&w, out_buf, out_buf_size);

    /* MAC_structure is an array of 4 elements */
    syn_cbor_write_array_begin(&w, 4);
    syn_cbor_write_text_cstr(&w, "MAC0");
    syn_cbor_write_bytes(&w, body_protected, body_protected_len);
    syn_cbor_write_bytes(&w, (external_aad != NULL) ? external_aad : (const uint8_t *)"",
                         external_aad_len);
    syn_cbor_write_bytes(&w, (payload != NULL) ? payload : (const uint8_t *)"", payload_len);

    if (!syn_cbor_writer_ok(&w)) {
        return false;
    }

    *out_len = syn_cbor_writer_len(&w);
    return true;
}

SYN_Status syn_cose_sign1_create(SYN_COSE_Algorithm alg, const uint8_t *secret_key,
                                 const uint8_t *public_key, const uint8_t *kid, size_t kid_len,
                                 const uint8_t *payload, size_t payload_len,
                                 const uint8_t *external_aad, size_t external_aad_len,
                                 uint8_t *out_buf, size_t out_buf_size, size_t *out_len)
{
    if (secret_key == NULL || out_buf == NULL || out_len == NULL ||
        (payload == NULL && payload_len > 0U)) {
        return SYN_ERROR;
    }

    if (alg != SYN_COSE_ALGO_EDDSA && alg != SYN_COSE_ALGO_ES256) {
        return SYN_ERROR;
    }

    /* 1. Build protected header: { 1: alg } */
    uint8_t protected_raw[16];
    SYN_CborWriter pw;
    syn_cbor_writer_init(&pw, protected_raw, sizeof(protected_raw));
    syn_cbor_write_map_begin(&pw, 1);
    syn_cbor_write_uint(&pw, SYN_COSE_HEADER_ALG);
    syn_cbor_write_int(&pw, (int64_t)alg);
    size_t protected_raw_len = syn_cbor_writer_len(&pw);

    /* 2. Build Sig_structure for signature generation */
    uint8_t sig_structure[1024];
    size_t sig_struct_len = 0;
    if (!build_sig_structure(protected_raw, protected_raw_len, external_aad, external_aad_len,
                             payload, payload_len, sig_structure, sizeof(sig_structure),
                             &sig_struct_len)) {
        return SYN_ERROR;
    }

    /* 3. Compute signature */
    uint8_t signature[SYN_COSE_MAX_SIG_LEN];
    size_t sig_len = 64U;

    if (alg == SYN_COSE_ALGO_EDDSA) {
        (void)syn_ed25519_sign(sig_structure, sig_struct_len, secret_key, public_key, signature);
    } else {
        /* ES256: Hash Sig_structure with SHA-256 then sign with P-256 ECDSA */
        uint8_t hash[SYN_SHA256_DIGEST_SIZE];
        syn_sha256(sig_structure, sig_struct_len, hash);

        uint8_t nonce_k[32];
        (void)syn_random_fill(nonce_k, sizeof(nonce_k));
        (void)syn_p256_sign_ecdsa(secret_key, nonce_k, hash, signature, signature + 32);
    }

    /* 4. Encode final COSE_Sign1 message */
    SYN_CborWriter w;
    syn_cbor_writer_init(&w, out_buf, out_buf_size);

    syn_cbor_write_array_begin(&w, 4);
    syn_cbor_write_bytes(&w, protected_raw, protected_raw_len);

    /* Unprotected header map */
    if (kid != NULL && kid_len > 0U) {
        syn_cbor_write_map_begin(&w, 1);
        syn_cbor_write_uint(&w, SYN_COSE_HEADER_KID);
        syn_cbor_write_bytes(&w, kid, kid_len);
    } else {
        syn_cbor_write_map_begin(&w, 0);
    }

    /* Payload */
    syn_cbor_write_bytes(&w, (payload != NULL) ? payload : (const uint8_t *)"", payload_len);

    /* Signature */
    syn_cbor_write_bytes(&w, signature, sig_len);

    if (!syn_cbor_writer_ok(&w)) {
        return SYN_ERROR;
    }

    *out_len = syn_cbor_writer_len(&w);
    return SYN_OK;
}

SYN_Status syn_cose_sign1_verify(const uint8_t *msg, size_t msg_len, const uint8_t *public_key,
                                 size_t public_key_len, const uint8_t *external_aad,
                                 size_t external_aad_len, SYN_COSE_Sign1Message *parsed_out)
{
    if (msg == NULL || msg_len == 0U || public_key == NULL) {
        return SYN_ERROR;
    }

    SYN_CborReader r;
    syn_cbor_reader_init(&r, msg, msg_len);

    size_t array_items = syn_cbor_read_array_begin(&r);
    if (!syn_cbor_reader_ok(&r) || array_items != 4U) {
        return SYN_ERROR;
    }

    /* 1. Read protected header bstr */
    uint8_t protected_buf[SYN_COSE_MAX_PROTECTED_LEN];
    size_t protected_len = syn_cbor_read_bytes(&r, protected_buf, sizeof(protected_buf));
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }

    /* Parse protected header map */
    SYN_COSE_Algorithm parsed_alg = SYN_COSE_ALGO_UNKNOWN;
    if (protected_len > 0U) {
        SYN_CborReader pr;
        syn_cbor_reader_init(&pr, protected_buf, protected_len);
        size_t pairs = syn_cbor_read_map_begin(&pr);
        for (size_t i = 0; i < pairs; i++) {
            uint64_t label = syn_cbor_read_uint(&pr);
            if (label == SYN_COSE_HEADER_ALG) {
                parsed_alg = (SYN_COSE_Algorithm)syn_cbor_read_int(&pr);
            } else {
                syn_cbor_skip(&pr);
            }
        }
    }

    if (parsed_alg != SYN_COSE_ALGO_EDDSA && parsed_alg != SYN_COSE_ALGO_ES256) {
        return SYN_ERROR;
    }

    /* 2. Read unprotected header map */
    uint8_t parsed_kid[SYN_COSE_MAX_KID_LEN] = {0};
    size_t parsed_kid_len = 0;

    size_t unprot_pairs = syn_cbor_read_map_begin(&r);
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }
    for (size_t i = 0; i < unprot_pairs; i++) {
        uint64_t label = syn_cbor_read_uint(&r);
        if (label == SYN_COSE_HEADER_KID) {
            parsed_kid_len = syn_cbor_read_bytes(&r, parsed_kid, sizeof(parsed_kid));
        } else {
            syn_cbor_skip(&r);
        }
    }

    /* 3. Read payload */
    uint8_t payload_buf[1024];
    size_t payload_len = syn_cbor_read_bytes(&r, payload_buf, sizeof(payload_buf));
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }

    /* 4. Read signature */
    uint8_t sig_buf[SYN_COSE_MAX_SIG_LEN];
    size_t sig_len = syn_cbor_read_bytes(&r, sig_buf, sizeof(sig_buf));
    if (!syn_cbor_reader_ok(&r) || sig_len != 64U) {
        return SYN_ERROR;
    }

    /* Populate optional output struct */
    if (parsed_out != NULL) {
        parsed_out->alg = parsed_alg;
        (void)memcpy(parsed_out->kid, parsed_kid, parsed_kid_len);
        parsed_out->kid_len = parsed_kid_len;
        parsed_out->payload = payload_buf;
        parsed_out->payload_len = payload_len;
        parsed_out->signature = sig_buf;
        parsed_out->signature_len = sig_len;
        (void)memcpy(parsed_out->protected_hdr, protected_buf, protected_len);
        parsed_out->protected_hdr_len = protected_len;
    }

    /* 5. Reconstruct Sig_structure */
    uint8_t sig_structure[1024];
    size_t sig_struct_len = 0;
    if (!build_sig_structure(protected_buf, protected_len, external_aad, external_aad_len,
                             payload_buf, payload_len, sig_structure, sizeof(sig_structure),
                             &sig_struct_len)) {
        return SYN_ERROR;
    }

    /* 6. Verify signature */
    if (parsed_alg == SYN_COSE_ALGO_EDDSA) {
        if (public_key_len != SYN_ED25519_PUBLIC_KEY_SIZE) {
            return SYN_ERROR;
        }
        if (!syn_ed25519_verify(sig_buf, sig_structure, sig_struct_len, public_key)) {
            return SYN_ERROR;
        }
    } else {
        /* ES256 */
        const uint8_t *px = public_key;
        const uint8_t *py = public_key + 32;
        if (public_key_len == 65U && public_key[0] == 0x04U) {
            px = public_key + 1;
            py = public_key + 33;
        } else if (public_key_len != 64U) {
            return SYN_ERROR;
        }

        uint8_t hash[SYN_SHA256_DIGEST_SIZE];
        syn_sha256(sig_structure, sig_struct_len, hash);

        if (!syn_p256_verify_ecdsa(hash, sig_buf, sig_buf + 32, px, py)) {
            return SYN_ERROR;
        }
    }

    return SYN_OK;
}

SYN_Status syn_cose_encrypt0_create(SYN_COSE_Algorithm alg, const uint8_t *key, const uint8_t *iv,
                                    size_t iv_len, const uint8_t *kid, size_t kid_len,
                                    const uint8_t *plaintext, size_t plaintext_len,
                                    const uint8_t *external_aad, size_t external_aad_len,
                                    uint8_t *out_buf, size_t out_buf_size, size_t *out_len)
{
    if (key == NULL || iv == NULL || out_buf == NULL || out_len == NULL ||
        (plaintext == NULL && plaintext_len > 0U)) {
        return SYN_ERROR;
    }

    size_t key_len = 0;
    size_t tag_len = 16U;
    if (alg == SYN_COSE_ALGO_CHACHA20_POLY1305) {
        key_len = 32U;
    } else if (alg == SYN_COSE_ALGO_A128GCM || alg == SYN_COSE_ALGO_AES_CCM_16_64_128 ||
               alg == SYN_COSE_ALGO_AES_CCM_16_128_128) {
        key_len = 16U;
        if (alg == SYN_COSE_ALGO_AES_CCM_16_64_128) {
            tag_len = 8U;
        }
    } else if (alg == SYN_COSE_ALGO_A192GCM) {
        key_len = 24U;
    } else if (alg == SYN_COSE_ALGO_A256GCM || alg == SYN_COSE_ALGO_AES_CCM_16_64_256 ||
               alg == SYN_COSE_ALGO_AES_CCM_16_128_256) {
        key_len = 32U;
        if (alg == SYN_COSE_ALGO_AES_CCM_16_64_256) {
            tag_len = 8U;
        }
    } else {
        return SYN_ERROR;
    }

    if (alg == SYN_COSE_ALGO_AES_CCM_16_64_128 || alg == SYN_COSE_ALGO_AES_CCM_16_64_256 ||
        alg == SYN_COSE_ALGO_AES_CCM_16_128_128 || alg == SYN_COSE_ALGO_AES_CCM_16_128_256) {
        if (iv_len < 7U || iv_len > 13U) {
            return SYN_ERROR;
        }
    } else {
        if (iv_len != 12U) {
            return SYN_ERROR;
        }
    }

    /* 1. Build protected header: { 1: alg } */
    uint8_t protected_raw[16];
    SYN_CborWriter pw;
    syn_cbor_writer_init(&pw, protected_raw, sizeof(protected_raw));
    syn_cbor_write_map_begin(&pw, 1);
    syn_cbor_write_uint(&pw, SYN_COSE_HEADER_ALG);
    syn_cbor_write_uint(&pw, (uint64_t)alg);
    size_t protected_raw_len = syn_cbor_writer_len(&pw);

    /* 2. Build Enc_structure for AAD */
    uint8_t enc_structure[512];
    size_t enc_struct_len = 0;
    if (!build_enc_structure(protected_raw, protected_raw_len, external_aad, external_aad_len,
                             enc_structure, sizeof(enc_structure), &enc_struct_len)) {
        return SYN_ERROR;
    }

    /* 3. Encrypt plaintext (Payload || Tag) */
    uint8_t ct_tag_buf[1024];
    if (plaintext_len + tag_len > sizeof(ct_tag_buf)) {
        return SYN_ERROR;
    }

    uint8_t tag[16];
    if (alg == SYN_COSE_ALGO_CHACHA20_POLY1305) {
        if (plaintext_len > 0U && plaintext != NULL) {
            syn_aead_encrypt(key, iv, enc_structure, enc_struct_len, plaintext, plaintext_len,
                             ct_tag_buf, tag);
        } else {
            syn_aead_encrypt(key, iv, enc_structure, enc_struct_len, NULL, 0U, NULL, tag);
        }
    } else if (alg == SYN_COSE_ALGO_AES_CCM_16_64_128 || alg == SYN_COSE_ALGO_AES_CCM_16_64_256 ||
               alg == SYN_COSE_ALGO_AES_CCM_16_128_128 || alg == SYN_COSE_ALGO_AES_CCM_16_128_256) {
        SYN_AES_Context aes_ctx;
        (void)syn_aes_init(&aes_ctx, key, key_len);
        (void)syn_aes_ccm_encrypt(&aes_ctx, iv, iv_len, enc_structure, enc_struct_len, plaintext,
                                  plaintext_len, ct_tag_buf, tag, tag_len);
    } else {
        SYN_AES_GCM_Context gcm_ctx;
        (void)syn_aes_gcm_init(&gcm_ctx, key, key_len);
        (void)syn_aes_gcm_encrypt(&gcm_ctx, iv, iv_len, enc_structure, enc_struct_len, plaintext,
                                  plaintext_len, ct_tag_buf, tag);
    }
    (void)memcpy(ct_tag_buf + plaintext_len, tag, tag_len);
    size_t total_ct_len = plaintext_len + tag_len;

    /* 4. Encode final COSE_Encrypt0 message */
    SYN_CborWriter w;
    syn_cbor_writer_init(&w, out_buf, out_buf_size);

    syn_cbor_write_array_begin(&w, 3);
    syn_cbor_write_bytes(&w, protected_raw, protected_raw_len);

    /* Unprotected header map: { 5: IV, [4: kid] } */
    size_t unprot_count = 1U + (size_t)(kid != NULL && kid_len > 0U);
    syn_cbor_write_map_begin(&w, unprot_count);
    syn_cbor_write_uint(&w, SYN_COSE_HEADER_IV);
    syn_cbor_write_bytes(&w, iv, iv_len);
    if (kid != NULL && kid_len > 0U) {
        syn_cbor_write_uint(&w, SYN_COSE_HEADER_KID);
        syn_cbor_write_bytes(&w, kid, kid_len);
    }

    /* Ciphertext + Tag */
    syn_cbor_write_bytes(&w, ct_tag_buf, total_ct_len);

    if (!syn_cbor_writer_ok(&w)) {
        return SYN_ERROR;
    }

    *out_len = syn_cbor_writer_len(&w);
    return SYN_OK;
}

SYN_Status syn_cose_encrypt0_decrypt(const uint8_t *msg, size_t msg_len, const uint8_t *key,
                                     const uint8_t *external_aad, size_t external_aad_len,
                                     uint8_t *out_plaintext, size_t out_plaintext_size,
                                     size_t *out_plaintext_len,
                                     SYN_COSE_Encrypt0Message *parsed_out)
{
    if (msg == NULL || msg_len == 0U || key == NULL || out_plaintext_len == NULL) {
        return SYN_ERROR;
    }

    SYN_CborReader r;
    syn_cbor_reader_init(&r, msg, msg_len);

    size_t array_items = syn_cbor_read_array_begin(&r);
    if (!syn_cbor_reader_ok(&r) || array_items != 3U) {
        return SYN_ERROR;
    }

    /* 1. Read protected header */
    uint8_t protected_buf[SYN_COSE_MAX_PROTECTED_LEN];
    size_t protected_len = syn_cbor_read_bytes(&r, protected_buf, sizeof(protected_buf));
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }

    SYN_COSE_Algorithm parsed_alg = SYN_COSE_ALGO_UNKNOWN;
    if (protected_len > 0U) {
        SYN_CborReader pr;
        syn_cbor_reader_init(&pr, protected_buf, protected_len);
        size_t pairs = syn_cbor_read_map_begin(&pr);
        for (size_t i = 0; i < pairs; i++) {
            uint64_t label = syn_cbor_read_uint(&pr);
            if (label == SYN_COSE_HEADER_ALG) {
                parsed_alg = (SYN_COSE_Algorithm)syn_cbor_read_int(&pr);
            } else {
                syn_cbor_skip(&pr);
            }
        }
    }

    size_t key_len = 0;
    size_t tag_len = 16U;
    if (parsed_alg == SYN_COSE_ALGO_CHACHA20_POLY1305) {
        key_len = 32U;
    } else if (parsed_alg == SYN_COSE_ALGO_A128GCM ||
               parsed_alg == SYN_COSE_ALGO_AES_CCM_16_64_128 ||
               parsed_alg == SYN_COSE_ALGO_AES_CCM_16_128_128) {
        key_len = 16U;
        if (parsed_alg == SYN_COSE_ALGO_AES_CCM_16_64_128) {
            tag_len = 8U;
        }
    } else if (parsed_alg == SYN_COSE_ALGO_A192GCM) {
        key_len = 24U;
    } else if (parsed_alg == SYN_COSE_ALGO_A256GCM ||
               parsed_alg == SYN_COSE_ALGO_AES_CCM_16_64_256 ||
               parsed_alg == SYN_COSE_ALGO_AES_CCM_16_128_256) {
        key_len = 32U;
        if (parsed_alg == SYN_COSE_ALGO_AES_CCM_16_64_256) {
            tag_len = 8U;
        }
    } else {
        return SYN_ERROR;
    }

    /* 2. Read unprotected header map */
    uint8_t parsed_iv[SYN_COSE_MAX_IV_LEN] = {0};
    size_t parsed_iv_len = 0;
    uint8_t parsed_kid[SYN_COSE_MAX_KID_LEN] = {0};
    size_t parsed_kid_len = 0;

    size_t unprot_pairs = syn_cbor_read_map_begin(&r);
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }
    for (size_t i = 0; i < unprot_pairs; i++) {
        uint64_t label = syn_cbor_read_uint(&r);
        if (label == SYN_COSE_HEADER_IV) {
            parsed_iv_len = syn_cbor_read_bytes(&r, parsed_iv, sizeof(parsed_iv));
        } else if (label == SYN_COSE_HEADER_KID) {
            parsed_kid_len = syn_cbor_read_bytes(&r, parsed_kid, sizeof(parsed_kid));
        } else {
            syn_cbor_skip(&r);
        }
    }

    if (parsed_alg == SYN_COSE_ALGO_AES_CCM_16_64_128 ||
        parsed_alg == SYN_COSE_ALGO_AES_CCM_16_64_256 ||
        parsed_alg == SYN_COSE_ALGO_AES_CCM_16_128_128 ||
        parsed_alg == SYN_COSE_ALGO_AES_CCM_16_128_256) {
        if (parsed_iv_len < 7U || parsed_iv_len > 13U) {
            return SYN_ERROR;
        }
    } else {
        if (parsed_iv_len != 12U) {
            return SYN_ERROR;
        }
    }

    /* 3. Read ciphertext + tag */
    uint8_t ct_tag_buf[1024];
    size_t ct_tag_len = syn_cbor_read_bytes(&r, ct_tag_buf, sizeof(ct_tag_buf));
    if (!syn_cbor_reader_ok(&r) || ct_tag_len < tag_len) {
        return SYN_ERROR;
    }

    size_t pt_len = ct_tag_len - tag_len;
    if (out_plaintext != NULL && pt_len > out_plaintext_size) {
        return SYN_ERROR;
    }

    /* Populate optional output descriptor */
    if (parsed_out != NULL) {
        parsed_out->alg = parsed_alg;
        (void)memcpy(parsed_out->iv, parsed_iv, parsed_iv_len);
        parsed_out->iv_len = parsed_iv_len;
        (void)memcpy(parsed_out->kid, parsed_kid, parsed_kid_len);
        parsed_out->kid_len = parsed_kid_len;
        parsed_out->ciphertext = ct_tag_buf;
        parsed_out->ciphertext_len = ct_tag_len;
        (void)memcpy(parsed_out->protected_hdr, protected_buf, protected_len);
        parsed_out->protected_hdr_len = protected_len;
    }

    /* 4. Reconstruct Enc_structure */
    uint8_t enc_structure[512];
    size_t enc_struct_len = 0;
    if (!build_enc_structure(protected_buf, protected_len, external_aad, external_aad_len,
                             enc_structure, sizeof(enc_structure), &enc_struct_len)) {
        return SYN_ERROR;
    }

    /* 5. Decrypt and verify tag */
    const uint8_t *tag = ct_tag_buf + pt_len;
    if (parsed_alg == SYN_COSE_ALGO_CHACHA20_POLY1305) {
        if (!syn_aead_decrypt(key, parsed_iv, enc_structure, enc_struct_len, ct_tag_buf, pt_len,
                              tag, out_plaintext)) {
            return SYN_ERROR;
        }
    } else if (parsed_alg == SYN_COSE_ALGO_AES_CCM_16_64_128 ||
               parsed_alg == SYN_COSE_ALGO_AES_CCM_16_64_256 ||
               parsed_alg == SYN_COSE_ALGO_AES_CCM_16_128_128 ||
               parsed_alg == SYN_COSE_ALGO_AES_CCM_16_128_256) {
        SYN_AES_Context aes_ctx;
        (void)syn_aes_init(&aes_ctx, key, key_len);
        if (syn_aes_ccm_decrypt(&aes_ctx, parsed_iv, parsed_iv_len, enc_structure, enc_struct_len,
                                ct_tag_buf, pt_len, tag, tag_len, out_plaintext) != SYN_OK) {
            return SYN_ERROR;
        }
    } else {
        SYN_AES_GCM_Context gcm_ctx;
        (void)syn_aes_gcm_init(&gcm_ctx, key, key_len);
        if (syn_aes_gcm_decrypt(&gcm_ctx, parsed_iv, parsed_iv_len, enc_structure, enc_struct_len,
                                ct_tag_buf, pt_len, out_plaintext, tag) != SYN_OK) {
            return SYN_ERROR;
        }
    }

    *out_plaintext_len = pt_len;
    return SYN_OK;
}

/* ── COSE_Mac0 API ───────────────────────────────────────────────────────── */

SYN_Status syn_cose_mac0_create(SYN_COSE_Algorithm alg, const uint8_t *key, size_t key_len,
                                const uint8_t *kid, size_t kid_len, const uint8_t *payload,
                                size_t payload_len, const uint8_t *external_aad,
                                size_t external_aad_len, uint8_t *out_buf, size_t out_buf_size,
                                size_t *out_len)
{
    if (key == NULL || key_len == 0U || out_buf == NULL || out_len == NULL ||
        (payload == NULL && payload_len > 0U)) {
        return SYN_ERROR;
    }

    size_t tag_len = 0U;
    if (alg == SYN_COSE_ALGO_HMAC_256_256) {
        tag_len = 32U;
    } else if (alg == SYN_COSE_ALGO_HMAC_256_64) {
        tag_len = 8U;
    } else {
        return SYN_ERROR;
    }

    /* 1. Build protected header: { 1: alg } */
    uint8_t protected_raw[16];
    SYN_CborWriter pw;
    syn_cbor_writer_init(&pw, protected_raw, sizeof(protected_raw));
    syn_cbor_write_map_begin(&pw, 1);
    syn_cbor_write_uint(&pw, SYN_COSE_HEADER_ALG);
    syn_cbor_write_int(&pw, (int64_t)alg);
    size_t protected_raw_len = syn_cbor_writer_len(&pw);

    /* 2. Build MAC_structure */
    uint8_t mac_structure[1024];
    size_t mac_struct_len = 0;
    if (!build_mac_structure(protected_raw, protected_raw_len, external_aad, external_aad_len,
                             payload, payload_len, mac_structure, sizeof(mac_structure),
                             &mac_struct_len)) {
        return SYN_ERROR;
    }

    /* 3. Compute HMAC */
    uint8_t full_tag[32];
    syn_hmac_sha256(key, key_len, mac_structure, mac_struct_len, full_tag);

    /* 4. Encode COSE_Mac0 message (Array of 4 elements) */
    SYN_CborWriter w;
    syn_cbor_writer_init(&w, out_buf, out_buf_size);
    syn_cbor_write_array_begin(&w, 4);

    /* Element 0: protected header */
    syn_cbor_write_bytes(&w, protected_raw, protected_raw_len);

    /* Element 1: unprotected map { 4: kid } */
    if (kid != NULL && kid_len > 0U) {
        syn_cbor_write_map_begin(&w, 1);
        syn_cbor_write_uint(&w, SYN_COSE_HEADER_KID);
        syn_cbor_write_bytes(&w, kid, kid_len);
    } else {
        syn_cbor_write_map_begin(&w, 0);
    }

    /* Element 2: payload */
    syn_cbor_write_bytes(&w, (payload != NULL) ? payload : (const uint8_t *)"", payload_len);

    /* Element 3: tag */
    syn_cbor_write_bytes(&w, full_tag, tag_len);

    if (!syn_cbor_writer_ok(&w)) {
        return SYN_ERROR;
    }

    *out_len = syn_cbor_writer_len(&w);
    return SYN_OK;
}

SYN_Status syn_cose_mac0_verify(const uint8_t *msg, size_t msg_len, const uint8_t *key,
                                size_t key_len, const uint8_t *external_aad,
                                size_t external_aad_len, SYN_COSE_Mac0Message *parsed_out)
{
    if (msg == NULL || msg_len == 0U || key == NULL || key_len == 0U) {
        return SYN_ERROR;
    }

    SYN_CborReader r;
    syn_cbor_reader_init(&r, msg, msg_len);

    size_t array_items = syn_cbor_read_array_begin(&r);
    if (!syn_cbor_reader_ok(&r) || array_items != 4U) {
        return SYN_ERROR;
    }

    /* 0. Protected header */
    uint8_t protected_buf[SYN_COSE_MAX_PROTECTED_LEN];
    size_t protected_len = syn_cbor_read_bytes(&r, protected_buf, sizeof(protected_buf));
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }

    SYN_COSE_Algorithm parsed_alg = SYN_COSE_ALGO_UNKNOWN;
    if (protected_len > 0U) {
        SYN_CborReader pr;
        syn_cbor_reader_init(&pr, protected_buf, protected_len);
        size_t pairs = syn_cbor_read_map_begin(&pr);
        for (size_t i = 0; i < pairs; i++) {
            uint64_t label = syn_cbor_read_uint(&pr);
            if (label == SYN_COSE_HEADER_ALG) {
                parsed_alg = (SYN_COSE_Algorithm)syn_cbor_read_int(&pr);
            } else {
                syn_cbor_skip(&pr);
            }
        }
    }

    size_t exp_tag_len = 0U;
    if (parsed_alg == SYN_COSE_ALGO_HMAC_256_256) {
        exp_tag_len = 32U;
    } else if (parsed_alg == SYN_COSE_ALGO_HMAC_256_64) {
        exp_tag_len = 8U;
    } else {
        return SYN_ERROR;
    }

    /* 1. Unprotected header */
    uint8_t parsed_kid[SYN_COSE_MAX_KID_LEN] = {0};
    size_t parsed_kid_len = 0;
    size_t unprot_pairs = syn_cbor_read_map_begin(&r);
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }
    for (size_t i = 0; i < unprot_pairs; i++) {
        uint64_t label = syn_cbor_read_uint(&r);
        if (label == SYN_COSE_HEADER_KID) {
            parsed_kid_len = syn_cbor_read_bytes(&r, parsed_kid, sizeof(parsed_kid));
        } else {
            syn_cbor_skip(&r);
        }
    }

    /* 2. Payload */
    uint8_t payload_buf[1024];
    size_t payload_len = syn_cbor_read_bytes(&r, payload_buf, sizeof(payload_buf));
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }

    /* 3. Tag */
    uint8_t tag_buf[SYN_COSE_MAX_TAG_LEN];
    size_t tag_len = syn_cbor_read_bytes(&r, tag_buf, sizeof(tag_buf));
    if (!syn_cbor_reader_ok(&r) || tag_len != exp_tag_len) {
        return SYN_ERROR;
    }

    if (parsed_out != NULL) {
        parsed_out->alg = parsed_alg;
        (void)memcpy(parsed_out->kid, parsed_kid, parsed_kid_len);
        parsed_out->kid_len = parsed_kid_len;
        parsed_out->payload = payload_buf;
        parsed_out->payload_len = payload_len;
        parsed_out->tag = tag_buf;
        parsed_out->tag_len = tag_len;
        (void)memcpy(parsed_out->protected_hdr, protected_buf, protected_len);
        parsed_out->protected_hdr_len = protected_len;
    }

    /* 4. Reconstruct MAC_structure */
    uint8_t mac_structure[1024];
    size_t mac_struct_len = 0;
    if (!build_mac_structure(protected_buf, protected_len, external_aad, external_aad_len,
                             payload_buf, payload_len, mac_structure, sizeof(mac_structure),
                             &mac_struct_len)) {
        return SYN_ERROR;
    }

    /* 5. Compute HMAC and constant-time compare */
    uint8_t expected_tag[32];
    syn_hmac_sha256(key, key_len, mac_structure, mac_struct_len, expected_tag);

    uint8_t diff = 0U;
    for (size_t i = 0U; i < exp_tag_len; i++) {
        diff |= (uint8_t)(tag_buf[i] ^ expected_tag[i]);
    }

    return (diff == 0U) ? SYN_OK : SYN_ERROR;
}

/* ── COSE_Key API ────────────────────────────────────────────────────────── */

SYN_Status syn_cose_key_encode(const SYN_COSE_Key *key, uint8_t *out_buf, size_t out_buf_size,
                               size_t *out_len)
{
    if (key == NULL || out_buf == NULL || out_len == NULL) {
        return SYN_ERROR;
    }

    SYN_CborWriter w;
    syn_cbor_writer_init(&w, out_buf, out_buf_size);

    /* Count map entries */
    size_t count = 1; /* kty */
    if (key->kid_len > 0U) {
        count++;
    }
    if (key->alg != SYN_COSE_ALGO_UNKNOWN) {
        count++;
    }
    if (key->kty == SYN_COSE_KTY_SYMMETRIC) {
        if (key->priv_d_len > 0U) {
            count++; /* k = -1 */
        }
    } else if (key->kty == SYN_COSE_KTY_OKP) {
        if (key->crv != SYN_COSE_CRV_RESERVED) {
            count++; /* crv = -1 */
        }
        if (key->pub_x_len > 0U) {
            count++; /* x = -2 */
        }
        if (key->priv_d_len > 0U) {
            count++; /* d = -4 */
        }
    } else if (key->kty == SYN_COSE_KTY_EC2) {
        if (key->crv != SYN_COSE_CRV_RESERVED) {
            count++; /* crv = -1 */
        }
        if (key->pub_x_len > 0U) {
            count++; /* x = -2 */
        }
        if (key->pub_y_len > 0U) {
            count++; /* y = -3 */
        }
        if (key->priv_d_len > 0U) {
            count++; /* d = -4 */
        }
    } else {
        return SYN_ERROR;
    }

    syn_cbor_write_map_begin(&w, count);

    /* 1: kty */
    syn_cbor_write_int(&w, SYN_COSE_KEY_KTY);
    syn_cbor_write_int(&w, (int64_t)key->kty);

    /* 2: kid */
    if (key->kid_len > 0U) {
        syn_cbor_write_int(&w, SYN_COSE_KEY_KID);
        syn_cbor_write_bytes(&w, key->kid, key->kid_len);
    }

    /* 3: alg */
    if (key->alg != SYN_COSE_ALGO_UNKNOWN) {
        syn_cbor_write_int(&w, SYN_COSE_KEY_ALG);
        syn_cbor_write_int(&w, (int64_t)key->alg);
    }

    if (key->kty == SYN_COSE_KTY_SYMMETRIC) {
        if (key->priv_d_len > 0U) {
            syn_cbor_write_int(&w, SYN_COSE_KEY_K);
            syn_cbor_write_bytes(&w, key->priv_d, key->priv_d_len);
        }
    } else if (key->kty == SYN_COSE_KTY_OKP || key->kty == SYN_COSE_KTY_EC2) {
        if (key->crv != SYN_COSE_CRV_RESERVED) {
            syn_cbor_write_int(&w, SYN_COSE_KEY_CRV);
            syn_cbor_write_int(&w, (int64_t)key->crv);
        }
        if (key->pub_x_len > 0U) {
            syn_cbor_write_int(&w, SYN_COSE_KEY_X);
            syn_cbor_write_bytes(&w, key->pub_x, key->pub_x_len);
        }
        if (key->kty == SYN_COSE_KTY_EC2 && key->pub_y_len > 0U) {
            syn_cbor_write_int(&w, SYN_COSE_KEY_Y);
            syn_cbor_write_bytes(&w, key->pub_y, key->pub_y_len);
        }
        if (key->priv_d_len > 0U) {
            syn_cbor_write_int(&w, SYN_COSE_KEY_D);
            syn_cbor_write_bytes(&w, key->priv_d, key->priv_d_len);
        }
    }

    if (!syn_cbor_writer_ok(&w)) {
        return SYN_ERROR;
    }

    *out_len = syn_cbor_writer_len(&w);
    return SYN_OK;
}

SYN_Status syn_cose_key_decode(const uint8_t *buf, size_t buf_len, SYN_COSE_Key *key_out)
{
    if (buf == NULL || buf_len == 0U || key_out == NULL) {
        return SYN_ERROR;
    }

    memset(key_out, 0, sizeof(*key_out));

    SYN_CborReader r;
    syn_cbor_reader_init(&r, buf, buf_len);

    size_t map_sz = syn_cbor_read_map_begin(&r);
    if (!syn_cbor_reader_ok(&r)) {
        return SYN_ERROR;
    }

    for (size_t i = 0; i < map_sz; i++) {
        int64_t label = syn_cbor_read_int(&r);

        switch (label) {
        case SYN_COSE_KEY_KTY: {
            int64_t val = syn_cbor_read_int(&r);
            key_out->kty = (SYN_COSE_KeyType)val;
            break;
        }
        case SYN_COSE_KEY_KID:
            key_out->kid_len = syn_cbor_read_bytes(&r, key_out->kid, sizeof(key_out->kid));
            break;
        case SYN_COSE_KEY_ALG: {
            int64_t val = syn_cbor_read_int(&r);
            key_out->alg = (SYN_COSE_Algorithm)val;
            break;
        }
        case SYN_COSE_KEY_CRV: { /* -1: crv or k */
            if (key_out->kty == SYN_COSE_KTY_SYMMETRIC ||
                syn_cbor_peek_type(&r) == SYN_CBOR_BYTES) {
                key_out->priv_d_len =
                    syn_cbor_read_bytes(&r, key_out->priv_d, sizeof(key_out->priv_d));
            } else {
                int64_t val = syn_cbor_read_int(&r);
                key_out->crv = (SYN_COSE_Curve)val;
            }
            break;
        }
        case SYN_COSE_KEY_X: /* -2 */
            key_out->pub_x_len = syn_cbor_read_bytes(&r, key_out->pub_x, sizeof(key_out->pub_x));
            break;
        case SYN_COSE_KEY_Y: /* -3 */
            key_out->pub_y_len = syn_cbor_read_bytes(&r, key_out->pub_y, sizeof(key_out->pub_y));
            break;
        case SYN_COSE_KEY_D: /* -4 */
            key_out->priv_d_len = syn_cbor_read_bytes(&r, key_out->priv_d, sizeof(key_out->priv_d));
            break;
        default:
            syn_cbor_skip(&r);
            break;
        }
    }

    return (key_out->kty != SYN_COSE_KTY_RESERVED) ? SYN_OK : SYN_ERROR;
}

#endif /* SYN_USE_COSE */