// SPDX-FileCopyrightText: © 2022—2026 Vladimir Zorin // SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later // Licensed under OWL v1.0+. See LICENSE. /* * LITLS -- Lilush TLS Stack * PKCS#10 CSR DER encoder for ACME use (ECDSA P-256 only). */ #include "../litls.h" #include "asn1.h" #include /* ── Well-known OIDs ──────────────────────────────────── */ /* 2.5.4.3 -- id-at-commonName */ static const uint8_t OID_CN[] = {0x55, 0x04, 0x03}; /* 1.2.840.10045.2.1 -- id-ecPublicKey */ static const uint8_t OID_EC_PUBKEY[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01}; /* 1.2.840.10045.3.1.7 -- prime256v1 (secp256r1) */ static const uint8_t OID_PRIME256V1[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}; /* 1.2.840.10045.4.3.2 -- ecdsa-with-SHA256 */ static const uint8_t OID_ECDSA_SHA256[] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02}; /* 1.2.840.113549.1.9.14 -- extensionRequest */ static const uint8_t OID_EXTENSION_REQUEST[] = {0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x0E}; /* 2.5.29.17 -- id-ce-subjectAltName */ static const uint8_t OID_SAN[] = {0x55, 0x1D, 0x11}; /* ── Raw r||s to DER ECDSA-Sig-Value conversion ──────── */ int litls_ecdsa_sig_raw_to_der(const uint8_t *raw, size_t raw_len, uint8_t *der, size_t der_cap, size_t *der_len) { if (raw_len != 64) return -1; litls_der_writer w; litls_der_writer_init(&w, der, der_cap); size_t seq = litls_der_begin_sequence(&w); litls_der_write_integer(&w, raw, 32); /* r */ litls_der_write_integer(&w, raw + 32, 32); /* s */ litls_der_end_constructed(&w, seq); if (w.err) return -1; *der_len = w.len; return 0; } /* ── CSR generation ───────────────────────────────────── */ int litls_csr_generate(const litls_csr_params *params, uint8_t *der_out, size_t der_cap) { uint8_t tbs_buf[LITLS_CSR_MAX_SIZE]; litls_der_writer tbs; litls_der_writer_init(&tbs, tbs_buf, sizeof(tbs_buf)); /* CertificationRequestInfo SEQUENCE */ size_t tbs_seq = litls_der_begin_sequence(&tbs); /* version INTEGER 0 */ { uint8_t zero = 0; litls_der_write_tlv(&tbs, ASN1_INTEGER, &zero, 1); } /* subject: SEQUENCE { SET { SEQUENCE { OID(CN), UTF8String(domain) } } } */ { size_t subj_seq = litls_der_begin_sequence(&tbs); size_t set = litls_der_begin_set(&tbs); size_t attr = litls_der_begin_sequence(&tbs); litls_der_write_oid(&tbs, OID_CN, sizeof(OID_CN)); litls_der_write_tlv(&tbs, ASN1_UTF8STRING, (const uint8_t *)params->domain, strlen(params->domain)); litls_der_end_constructed(&tbs, attr); litls_der_end_constructed(&tbs, set); litls_der_end_constructed(&tbs, subj_seq); } /* subjectPKInfo: SEQUENCE { AlgorithmIdentifier, BIT STRING } */ { size_t spki = litls_der_begin_sequence(&tbs); /* AlgorithmIdentifier: SEQUENCE { OID(ecPublicKey), OID(prime256v1) } */ size_t algo = litls_der_begin_sequence(&tbs); litls_der_write_oid(&tbs, OID_EC_PUBKEY, sizeof(OID_EC_PUBKEY)); litls_der_write_oid(&tbs, OID_PRIME256V1, sizeof(OID_PRIME256V1)); litls_der_end_constructed(&tbs, algo); /* BIT STRING wrapping the public key */ litls_der_write_bit_string(&tbs, params->public_key, 65); litls_der_end_constructed(&tbs, spki); } /* attributes [0] IMPLICIT: extensionRequest with SANs */ { size_t ctx0 = litls_der_begin_context(&tbs, 0); if (params->san_count > 0 || params->domain) { /* extensionRequest: SEQUENCE { OID, SET { SEQUENCE { ext... } } } */ size_t ext_req = litls_der_begin_sequence(&tbs); litls_der_write_oid(&tbs, OID_EXTENSION_REQUEST, sizeof(OID_EXTENSION_REQUEST)); size_t ext_set = litls_der_begin_set(&tbs); size_t exts = litls_der_begin_sequence(&tbs); /* SAN extension: SEQUENCE { OID, OCTET STRING { GeneralNames } } */ size_t san_ext = litls_der_begin_sequence(&tbs); litls_der_write_oid(&tbs, OID_SAN, sizeof(OID_SAN)); /* Build GeneralNames SEQUENCE in a sub-buffer */ uint8_t gn_buf[2048]; litls_der_writer gn; litls_der_writer_init(&gn, gn_buf, sizeof(gn_buf)); size_t gn_seq = litls_der_begin_sequence(&gn); /* Add primary domain as dNSName [2] */ { size_t dlen = strlen(params->domain); litls_der_write_tlv(&gn, 0x82, (const uint8_t *)params->domain, dlen); } /* Add additional SANs */ for (int i = 0; i < params->san_count; i++) { size_t slen = strlen(params->sans[i]); litls_der_write_tlv(&gn, 0x82, (const uint8_t *)params->sans[i], slen); } litls_der_end_constructed(&gn, gn_seq); if (gn.err) return -1; /* Wrap GeneralNames in OCTET STRING */ litls_der_write_tlv(&tbs, ASN1_OCTET_STRING, gn_buf, gn.len); litls_der_end_constructed(&tbs, san_ext); litls_der_end_constructed(&tbs, exts); litls_der_end_constructed(&tbs, ext_set); litls_der_end_constructed(&tbs, ext_req); } litls_der_end_constructed(&tbs, ctx0); } litls_der_end_constructed(&tbs, tbs_seq); if (tbs.err) return -1; /* Hash the TBS */ uint8_t hash[32]; litls_sha256(tbs_buf, tbs.len, hash); /* Sign */ uint8_t raw_sig[64]; size_t raw_sig_len; if (litls_p256_ecdsa_sign(hash, 32, params->private_key, raw_sig, &raw_sig_len) != 0) return -1; /* Convert raw sig to DER */ uint8_t der_sig[80]; size_t der_sig_len; if (litls_ecdsa_sig_raw_to_der(raw_sig, raw_sig_len, der_sig, sizeof(der_sig), &der_sig_len) != 0) return -1; /* Assemble final CSR: SEQUENCE { TBS, AlgorithmIdentifier, BIT STRING } */ litls_der_writer out; litls_der_writer_init(&out, der_out, der_cap); size_t csr_seq = litls_der_begin_sequence(&out); /* TBS bytes */ litls_der_write_raw(&out, tbs_buf, tbs.len); /* AlgorithmIdentifier: SEQUENCE { OID(ecdsa-with-SHA256) } */ { size_t algo = litls_der_begin_sequence(&out); litls_der_write_oid(&out, OID_ECDSA_SHA256, sizeof(OID_ECDSA_SHA256)); litls_der_end_constructed(&out, algo); } /* Signature as BIT STRING */ litls_der_write_bit_string(&out, der_sig, der_sig_len); litls_der_end_constructed(&out, csr_seq); if (out.err) return -1; return (int)out.len; }