Skip to content

File syn_x509.c

File List > crypto > syn_x509.c

Go to the documentation of this file

#include "syntropic/crypto/syn_x509.h"

#include "syntropic/crypto/syn_ed25519.h"
#include "syntropic/crypto/syn_p256.h"
#include "syntropic/crypto/syn_sha256.h"

#include <string.h>

/* Standard OID Byte Sequences */
static const uint8_t OID_ED25519[] = {0x2B, 0x65, 0x70}; /* 1.3.101.112 ed25519 */
static const uint8_t OID_EC_PUBKEY[] = {0x2A, 0x86, 0x48, 0xCE,
                                        0x3D, 0x02, 0x01}; /* 1.2.840.10045.2.1 id-ecPublicKey */
static const uint8_t OID_ECDSA_SHA256[] = {0x2A, 0x86, 0x48, 0xCE,
                                           0x3D, 0x04, 0x03, 0x02}; /* 1.2.840.10045.4.3.2 */
static const uint8_t OID_COMMON_NAME[] = {0x55, 0x04, 0x03};        /* 2.5.4.3 id-at-commonName */

static void parse_rdn_cn(const SYN_ASN1_Element *name_container, char *cn_out, size_t max_len)
{
    cn_out[0] = '\0';

    const uint8_t *cur;
    size_t len;
    if (!syn_asn1_enter_container(name_container, &cur, &len)) {
        return; /* LCOV_EXCL_LINE: Defensive check if Issuer/Subject RDN container is not a
                   constructed DER sequence */
    }

    while (len > 0) {
        SYN_ASN1_Element rdn_set;
        if (!syn_asn1_step(&cur, &len, &rdn_set)) {
            break; /* LCOV_EXCL_LINE: Defensive check if RDN SET element iteration fails inside
                      Issuer/Subject sequence */
        }

        const uint8_t *set_cur;
        size_t set_len;
        if (syn_asn1_enter_container(&rdn_set, &set_cur, &set_len)) {
            SYN_ASN1_Element atv_seq;
            if (syn_asn1_step(&set_cur, &set_len, &atv_seq)) {
                const uint8_t *atv_cur;
                size_t atv_len;
                if (syn_asn1_enter_container(&atv_seq, &atv_cur, &atv_len)) {
                    SYN_ASN1_Element oid_elem, val_elem;
                    if (syn_asn1_step(&atv_cur, &atv_len, &oid_elem) &&
                        syn_asn1_step(&atv_cur, &atv_len, &val_elem)) {
                        if (syn_asn1_match_oid(&oid_elem, OID_COMMON_NAME,
                                               sizeof(OID_COMMON_NAME))) {
                            size_t copy_len = val_elem.length;
                            /* LCOV_EXCL_START: Defensive truncation clamp for CommonName strings
                             * exceeding max buffer size */
                            if (copy_len >= max_len) {
                                copy_len = max_len - 1;
                            }
                            /* LCOV_EXCL_STOP */
                            memcpy(cn_out, val_elem.value, copy_len);
                            cn_out[copy_len] = '\0';
                        }
                    }
                }
            }
        }
    }
}

bool syn_x509_parse(const uint8_t *der, size_t der_len, SYN_X509_Cert *cert_out)
{
    if (der == NULL || der_len == 0 || cert_out == NULL) {
        return false;
    }

    memset(cert_out, 0, sizeof(SYN_X509_Cert));

    SYN_ASN1_Element root_seq;
    const uint8_t *ptr = der;
    size_t rem = der_len;

    if (!syn_asn1_step(&ptr, &rem, &root_seq) || root_seq.tag != SYN_ASN1_TAG_SEQUENCE ||
        !root_seq.constructed) {
        return false;
    }

    const uint8_t *cert_cur = root_seq.value;
    size_t cert_len = root_seq.length;

    /* 1. TBSCertificate (To Be Signed) */
    SYN_ASN1_Element tbs_elem;
    if (!syn_asn1_step(&cert_cur, &cert_len, &tbs_elem) || tbs_elem.tag != SYN_ASN1_TAG_SEQUENCE ||
        !tbs_elem.constructed) {
        return false;
    }

    cert_out->tbs_bytes = tbs_elem.value - tbs_elem.header_len;
    cert_out->tbs_len = tbs_elem.header_len + tbs_elem.length;

    const uint8_t *tbs_cur = tbs_elem.value;
    size_t tbs_len = tbs_elem.length;

    /* Check Version (Optional [0] EXPLICIT INTEGER) */
    SYN_ASN1_Element first_elem;
    if (!syn_asn1_step(&tbs_cur, &tbs_len, &first_elem)) {
        return false;
    }

    if (first_elem.tag_class == SYN_ASN1_CLASS_CONTEXT_SPECIFIC && first_elem.tag_number == 0) {
        const uint8_t *v_cur;
        size_t v_len;
        if (syn_asn1_enter_container(&first_elem, &v_cur, &v_len)) {
            SYN_ASN1_Element ver_elem;
            if (syn_asn1_step(&v_cur, &v_len, &ver_elem)) {
                const uint8_t *v_bytes;
                size_t v_bytes_len;
                if (syn_asn1_get_integer(&ver_elem, &v_bytes, &v_bytes_len) && v_bytes_len > 0) {
                    cert_out->version = v_bytes[v_bytes_len - 1] + 1U; /* v1=0, v2=1, v3=2 */
                }
            }
        }
        /* Read next for Serial Number */
        if (!syn_asn1_step(&tbs_cur, &tbs_len, &first_elem)) {
            return false;
        }
    } else {
        cert_out->version = 1U; /* Default v1 */
    }

    /* Serial Number */
    if (!syn_asn1_get_integer(&first_elem, &cert_out->serial, &cert_out->serial_len)) {
        return false;
    }

    /* Signature Algorithm Identifier */
    SYN_ASN1_Element sig_algo_elem;
    if (!syn_asn1_step(&tbs_cur, &tbs_len, &sig_algo_elem)) {
        return false;
    }

    /* Issuer Name */
    SYN_ASN1_Element issuer_elem;
    if (!syn_asn1_step(&tbs_cur, &tbs_len, &issuer_elem)) {
        return false;
    }
    parse_rdn_cn(&issuer_elem, cert_out->issuer_cn, sizeof(cert_out->issuer_cn));

    /* Validity Period */
    SYN_ASN1_Element validity_elem;
    if (!syn_asn1_step(&tbs_cur, &tbs_len, &validity_elem)) {
        return false;
    }

    /* Subject Name */
    SYN_ASN1_Element subject_elem;
    if (!syn_asn1_step(&tbs_cur, &tbs_len, &subject_elem)) {
        return false;
    }
    parse_rdn_cn(&subject_elem, cert_out->subject_cn, sizeof(cert_out->subject_cn));

    /* SubjectPublicKeyInfo */
    SYN_ASN1_Element spki_elem;
    if (!syn_asn1_step(&tbs_cur, &tbs_len, &spki_elem)) {
        return false;
    }

    const uint8_t *spki_cur;
    size_t spki_len;
    if (syn_asn1_enter_container(&spki_elem, &spki_cur, &spki_len)) {
        SYN_ASN1_Element algo_seq, pub_bits_elem;
        if (syn_asn1_step(&spki_cur, &spki_len, &algo_seq) &&
            syn_asn1_step(&spki_cur, &spki_len, &pub_bits_elem)) {
            const uint8_t *algo_cur;
            size_t algo_len;
            if (syn_asn1_enter_container(&algo_seq, &algo_cur, &algo_len)) {
                SYN_ASN1_Element oid_elem;
                if (syn_asn1_step(&algo_cur, &algo_len, &oid_elem)) {
                    if (syn_asn1_match_oid(&oid_elem, OID_ED25519, sizeof(OID_ED25519))) {
                        cert_out->pubkey_algo = SYN_X509_ALGO_ED25519;
                    } else if (syn_asn1_match_oid(&oid_elem, OID_EC_PUBKEY,
                                                  sizeof(OID_EC_PUBKEY)) ||
                               syn_asn1_match_oid(&oid_elem, OID_ECDSA_SHA256,
                                                  sizeof(OID_ECDSA_SHA256))) {
                        cert_out->pubkey_algo = SYN_X509_ALGO_ECDSA_P256;
                    }
                }
            }
            const uint8_t *bits_ptr;
            size_t bit_len;
            if (syn_asn1_get_bit_string(&pub_bits_elem, &bits_ptr, &bit_len)) {
                size_t byte_len = bit_len / 8;
                if (byte_len <= sizeof(cert_out->pubkey)) {
                    memcpy(cert_out->pubkey, bits_ptr, byte_len);
                    cert_out->pubkey_len = byte_len;
                }
            }
        }
    }

    /* 2. SignatureAlgorithm outer Sequence */
    SYN_ASN1_Element outer_sig_algo;
    if (!syn_asn1_step(&cert_cur, &cert_len, &outer_sig_algo)) {
        return false;
    }

    /* 3. SignatureValue BIT STRING */
    SYN_ASN1_Element sig_bits_elem;
    if (!syn_asn1_step(&cert_cur, &cert_len, &sig_bits_elem)) {
        return false;
    }

    const uint8_t *sig_ptr;
    size_t sig_bit_len;
    if (syn_asn1_get_bit_string(&sig_bits_elem, &sig_ptr, &sig_bit_len)) {
        size_t byte_len = sig_bit_len / 8;
        if (byte_len <= sizeof(cert_out->signature)) {
            memcpy(cert_out->signature, sig_ptr, byte_len);
            cert_out->signature_len = byte_len;
        }
    }

    cert_out->sig_algo = cert_out->pubkey_algo;
    return true;
}

bool syn_x509_verify_signature(const SYN_X509_Cert *cert, const uint8_t *issuer_pubkey,
                               size_t issuer_pubkey_len, SYN_X509_Algo algo)
{
    if (cert == NULL || issuer_pubkey == NULL || cert->tbs_bytes == NULL || cert->tbs_len == 0) {
        return false;
    }

    if (algo == SYN_X509_ALGO_ED25519) {
        if (issuer_pubkey_len != SYN_ED25519_PUBLIC_KEY_SIZE ||
            cert->signature_len != SYN_ED25519_SIGNATURE_SIZE) {
            return false; /* LCOV_EXCL_LINE: Defensive check if Ed25519 public key or signature
                             length is invalid */
        }
        return syn_ed25519_verify(cert->signature, cert->tbs_bytes, cert->tbs_len, issuer_pubkey);
    }

    if (algo == SYN_X509_ALGO_ECDSA_P256) {
        const uint8_t *px = issuer_pubkey;
        const uint8_t *py = issuer_pubkey + 32;
        if (issuer_pubkey_len == 65 && issuer_pubkey[0] == 0x04) {
            px = issuer_pubkey + 1;
            py = issuer_pubkey + 33;
        } else if (issuer_pubkey_len != 64) {
            return false;
        }

        if (cert->signature_len != 64) {
            return false;
        }

        uint8_t hash[SYN_SHA256_DIGEST_SIZE];
        syn_sha256(cert->tbs_bytes, cert->tbs_len, hash);

        return syn_p256_verify_ecdsa(hash, cert->signature, cert->signature + 32, px, py);
    }

    return false;
}

bool syn_x509_validate_chain(const SYN_X509_Cert *cert, const SYN_X509_Cert *root_ca,
                             const char *expected_cn)
{
    if (cert == NULL || root_ca == NULL) {
        return false;
    }

    if (expected_cn != NULL) {
        if (strcmp(cert->subject_cn, expected_cn) != 0) {
            return false;
        }
    }

    return syn_x509_verify_signature(cert, root_ca->pubkey, root_ca->pubkey_len,
                                     root_ca->pubkey_algo);
}