#include "base64.h"
#include "config.h"
#include "printf.h"
#include "sha256.h"
#include "tls.h"
#include "tls_aes128.h"
#include "tls_chacha20.h"
#include "tls_rsa.h"
#include "tls_uecc.h"
#include "tls_x25519.h"
#include "util.h"

#if MG_TLS == MG_TLS_BUILTIN

#ifndef MG_MAX_TLSOID_DEPTH
#define MG_MAX_TLSOID_DEPTH 15
#endif

// PKCS#8 algorithm OIDs
static const uint8_t mg_rsa_oid[] = {
    0x2a, 0x86, 0x48, 0x86, 0xf7,
    0x0d, 0x01, 0x01, 0x01  // 1.2.840.113549.1.1.1 rsaEncryption
};
static const uint8_t mg_ec_public_key_oid[] = {
    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01  // 1.2.840.10045.2.1 ecPublicKey
};
static const uint8_t mg_secp256r1_oid[] = {
    0x2a, 0x86, 0x48, 0xce,
    0x3d, 0x03, 0x01, 0x07  // 1.2.840.10045.3.1.7 secp256r1
};

/* TLS 1.3 Record Content Type (RFC8446 B.1) */
#define MG_TLS_CHANGE_CIPHER 20
#define MG_TLS_ALERT 21
#define MG_TLS_HANDSHAKE 22
#define MG_TLS_APP_DATA 23
#define MG_TLS_HEARTBEAT 24

/* TLS 1.3 Handshake Message Type (RFC8446 B.3) */
#define MG_TLS_CLIENT_HELLO 1
#define MG_TLS_SERVER_HELLO 2
#define MG_TLS_ENCRYPTED_EXTENSIONS 8
#define MG_TLS_CERTIFICATE 11
#define MG_TLS_CERTIFICATE_REQUEST 13
#define MG_TLS_CERTIFICATE_VERIFY 15
#define MG_TLS_FINISHED 20

#define MG_TLS_RSA_USE_CRT 1  // CRT instead of naive RSA

// handshake is re-entrant, so we need to keep track of its state state names
// refer to RFC8446#A.1
enum mg_tls_hs_state {
  // Client state machine:
  MG_TLS_STATE_CLIENT_START,        // Send ClientHello
  MG_TLS_STATE_CLIENT_WAIT_SH,      // Wait for ServerHello
  MG_TLS_STATE_CLIENT_WAIT_EE,      // Wait for EncryptedExtensions
  MG_TLS_STATE_CLIENT_WAIT_CERT,    // Wait for Certificate
  MG_TLS_STATE_CLIENT_WAIT_CV,      // Wait for CertificateVerify
  MG_TLS_STATE_CLIENT_WAIT_FINISH,  // Wait for Finish
  MG_TLS_STATE_CLIENT_CONNECTED,    // Done

  // Server state machine:
  MG_TLS_STATE_SERVER_START,       // Wait for ClientHello
  MG_TLS_STATE_SERVER_WAIT_CERT,   // Wait for Certificate
  MG_TLS_STATE_SERVER_WAIT_CV,     // Wait for CertificateVerify
  MG_TLS_STATE_SERVER_NEGOTIATED,  // Wait for Finish
  MG_TLS_STATE_SERVER_CONNECTED    // Done
};

// encryption keys for a TLS connection
struct tls_enc {
  uint32_t sseq;  // server sequence number, used in encryption
  uint32_t cseq;  // client sequence number, used in decryption
  // keys for AES encryption or ChaCha20
  uint8_t handshake_secret[32];
  uint8_t server_write_key[32];
  uint8_t server_write_iv[12];
  uint8_t server_finished_key[32];
  uint8_t client_write_key[32];
  uint8_t client_write_iv[12];
  uint8_t client_finished_key[32];
};

struct mg_rsa_key {
  struct mg_str n;     // modulus
  struct mg_str e;     // public exponent
  struct mg_str d;     // private exponent
  struct mg_str p;     // prime1
  struct mg_str q;     // prime2
  struct mg_str dP;    // exponent1 (d mod (p-1))
  struct mg_str dQ;    // exponent2 (d mod (q-1))
  struct mg_str qInv;  // coefficient ((inverse of q) mod p)
};

// per-connection TLS data
struct tls_data {
  enum mg_tls_hs_state state;  // keep track of connection handshake progress

  struct mg_iobuf send;  // For the receive path, we're reusing c->rtls
  size_t recv_offset;    // While c->rtls contains full records, reuse that
  size_t recv_len;       // buffer but point at individual decrypted messages

  uint8_t content_type;  // Last received record content type

  mg_sha256_ctx sha256;  // incremental SHA-256 hash for TLS handshake

  uint8_t random[32];      // client random from ClientHello
  uint8_t session_id[32];  // client session ID between the handshake states
  uint8_t x25519_cli[32];  // client X25519 key between the handshake states
  uint8_t x25519_sec[32];  // x25519 secret between the handshake states

  bool skip_verification;    // do not perform checks on server certificate
  bool cert_requested;       // client received a CertificateRequest
  bool is_twoway;            // server is configured to authenticate clients
  bool is_sntp_pending;      // TLS handshake is waiting for wall-clock time
  struct mg_connection *timec;  // SNTP connection for wall-clock time
  struct mg_str cert_der;    // certificate in DER format
  struct mg_str ca_der;      // current CA certificate
  struct mg_str *ca_bundle_der; // bundle of CA certificates
  size_t ca_bundle_len;         // number of certificates in bundle
  struct mg_str *chain_der;  // certificate chain (intermediate certs)
  size_t chain_len;          // number of certificates in chain
  uint8_t ec_key[32];        // EC private key
  struct mg_rsa_key rsa;
  struct mg_str rsa_key_der;  // RSA private key in DER format
  char hostname[254];         // matching hostname

  bool is_ec_pubkey;         // EC or RSA. TODO(): currently unused
  uint8_t pubkey[512 + 16];  // server EC (64) or RSA (512+exp) public key to
                             // verify cert
  size_t pubkeysz;           // size of the server public key
  uint8_t sighash[32];       // calculated signature verification hash

  struct tls_enc enc;       // actual keys in use at this time
  struct tls_enc app_keys;  // storage during two-way auth handshake
};

#define TLS_RECHDR_SIZE 5  // 1 byte type, 2 bytes version, 2 bytes length
#define TLS_MSGHDR_SIZE 4  // 1 byte type, 3 bytes length

#ifdef MG_TLS_SSLKEYLOGFILE
#include <stdio.h>
static void mg_ssl_key_log(const char *label, uint8_t client_random[32],
                           uint8_t *secret, size_t secretsz) {
  FILE *f;
  char *keylogfile = getenv("SSLKEYLOGFILE");
  if (keylogfile == NULL) return;
  MG_DEBUG(("Dumping key log into %s", keylogfile));
  f = fopen(keylogfile, "a");
  if (f != NULL) {
    size_t i;
    fprintf(f, "%s ", label);
    for (i = 0; i < 32; i++) {
      fprintf(f, "%02x", client_random[i]);
    }
    fprintf(f, " ");
    for (i = 0; i < secretsz; i++) {
      fprintf(f, "%02x", secret[i]);
    }
    fprintf(f, "\n");
    fclose(f);
  } else {
    MG_ERROR(("Cannot open %s", keylogfile));
  }
}
#endif

// for derived tls keys we need SHA256([0]*32)
static uint8_t zeros[32] = {0};
static uint8_t zeros_sha256_digest[32] = {
    0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
    0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
    0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};

// helper to hexdump buffers inline
static void mg_tls_hexdump(const char *msg, uint8_t *buf, size_t bufsz) {
  MG_VERBOSE(("%s: %M", msg, mg_print_hex, bufsz, buf));
}

// helper utilities to parse ASN.1 DER
struct mg_der_tlv {
  uint8_t type;
  uint32_t len;
  uint8_t *value;
};

// parse DER into a TLV record
static int mg_der_to_tlv(uint8_t *der, size_t dersz, struct mg_der_tlv *tlv) {
  uint32_t n = 0;
  if (dersz < 2) return -1;
  tlv->type = der[0];
  tlv->len = der[1];
  tlv->value = der + 2;
  if (tlv->len > 0x7f) {  // long-form length
    uint32_t i;
    n = tlv->len - 0x80;
    if (n == 0 || n > 4 || dersz < (2 + n)) return -1;
    tlv->len = 0;
    for (i = 0; i < n; i++) {
      tlv->len = (tlv->len << 8) | der[2 + i];
    }
    tlv->value += n;
  }
  if (tlv->len > dersz - (size_t) (tlv->value - der)) return -1;
  return (int) n;
}

static int mg_der_parse(uint8_t *der, size_t dersz, struct mg_der_tlv *tlv) {
  int n = mg_der_to_tlv(der, dersz, tlv);
  if (n < 0) return -1;
  if (tlv->len >= (uint32_t)((unsigned) -6)) return -1; // avoid overflow
  return 2 + n + (int) tlv->len; // 2: type, len; n = long form len bytes
}

static int mg_der_next(struct mg_der_tlv *parent, struct mg_der_tlv *child) {
  int consumed;
  if (parent->len == 0) return 0;
  consumed = mg_der_parse(parent->value, parent->len, child);
  if (consumed < 0) return -1;
  parent->value += consumed;
  parent->len -= (uint32_t) consumed;
  return 1;
}

static int der_find_oid(struct mg_der_tlv *tlv, const uint8_t *oid, size_t oid_len, struct mg_der_tlv *found, int depth) {
  struct mg_der_tlv parent, child;
  int r;
  parent = *tlv;
  while ((r = mg_der_next(&parent, &child)) > 0) {
    if (child.type == 0x06 && child.len == oid_len &&
        memcmp(child.value, oid, oid_len) == 0) {
      return mg_der_next(&parent, found);
    } else if (child.type & 0x20) {
      struct mg_der_tlv sub_parent = child;
      if (depth >= MG_MAX_TLSOID_DEPTH) {
        MG_ERROR(("too nested der"));
        return -1;
      }
      if ((r = der_find_oid(&sub_parent, oid, oid_len, found, depth + 1)) > 0)
        return 1;
      if (r < 0) return -1; // exit on error; r = 0 => not found, keep parsing
    }
  }
  if (r < 0) return -1;
  return 0;
}

static int mg_der_find_oid(struct mg_der_tlv *tlv, const uint8_t *oid,
                           size_t oid_len, struct mg_der_tlv *found) {
  return der_find_oid(tlv, oid, oid_len, found, 0);
}

#if 0
static void mg_der_debug(struct mg_der_tlv *tlv, int depth) {
  MG_DEBUG(("> %.*sd=%d Type: 0x%02X, Length: %u\n", depth * 4, " ", depth,
            tlv->type, tlv->len));

  if (tlv->type & 0x20) {  // Constructed: recurse into children
    struct mg_der_tlv child;
    struct mg_der_tlv parent = *tlv;
    while (mg_der_next(&parent, &child) > 0) {
      mg_der_debug(&child, depth + 1);
    }
  }
}
#endif

// Did we receive a full TLS record in the c->rtls buffer?
static bool mg_tls_got_record(struct mg_connection *c) {
  return c->rtls.len >= (size_t) TLS_RECHDR_SIZE &&
         c->rtls.len >=
             (size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3));
}

// Remove a single TLS record from the recv buffer
static void mg_tls_drop_record(struct mg_connection *c) {
  struct mg_iobuf *rio = &c->rtls;
  uint16_t n = MG_LOAD_BE16(rio->buf + 3) + TLS_RECHDR_SIZE;
  mg_iobuf_del(rio, 0, n);
}

// Remove a single TLS message from decrypted buffer, remove the wrapping
// record if it was the last message within a record
static void mg_tls_drop_message(struct mg_connection *c) {
  uint32_t len;
  struct tls_data *tls = (struct tls_data *) c->tls;
  unsigned char *recv_buf = &c->rtls.buf[tls->recv_offset];
  if (tls->recv_len == 0) return;
  len = MG_LOAD_BE24(recv_buf + 1) + TLS_MSGHDR_SIZE;
  if (tls->recv_len < len) {
    mg_error(c, "wrong size");
    return;
  }
  mg_sha256_update(&tls->sha256, recv_buf, len);
  tls->recv_offset += len;
  tls->recv_len -= len;
  if (tls->recv_len == 0) {
    mg_tls_drop_record(c);
  }
}

// TLS1.3 secret derivation based on the key label
static void mg_tls_derive_secret(const char *label, uint8_t *key, size_t keysz,
                                 uint8_t *data, size_t datasz, uint8_t *hash,
                                 size_t hashsz) {
  size_t labelsz = strlen(label);
  uint8_t secret[32];
  uint8_t packed[256] = {0, (uint8_t) hashsz, (uint8_t) labelsz};
  // TODO: assert lengths of label, key, data and hash
  if (labelsz > 0) memmove(packed + 3, label, labelsz);
  packed[3 + labelsz] = (uint8_t) datasz;
  if (datasz > 0) memmove(packed + labelsz + 4, data, datasz);
  packed[4 + labelsz + datasz] = 1;

  mg_hmac_sha256(secret, key, keysz, packed, 5 + labelsz + datasz);
  memmove(hash, secret, hashsz);
}

// at this point we have x25519 shared secret, we can generate a set of derived
// handshake encryption keys
static void mg_tls_generate_handshake_keys(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;

  mg_sha256_ctx sha256;
  uint8_t early_secret[32];
  uint8_t pre_extract_secret[32];
  uint8_t hello_hash[32];
  uint8_t server_hs_secret[32];
  uint8_t client_hs_secret[32];
#if MG_ENABLE_CHACHA20
  const size_t keysz = 32;
#else
  const size_t keysz = 16;
#endif

  mg_hmac_sha256(early_secret, NULL, 0, zeros, sizeof(zeros));
  mg_tls_derive_secret("tls13 derived", early_secret, 32, zeros_sha256_digest,
                       32, pre_extract_secret, 32);
  mg_hmac_sha256(tls->enc.handshake_secret, pre_extract_secret,
                 sizeof(pre_extract_secret), tls->x25519_sec,
                 sizeof(tls->x25519_sec));
  mg_tls_hexdump("hs secret", tls->enc.handshake_secret, 32);

  // mg_sha256_final is not idempotent, need to copy sha256 context to calculate
  // the digest
  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
  mg_sha256_final(hello_hash, &sha256);

  mg_tls_hexdump("hello hash", hello_hash, 32);
  // derive keys needed for the rest of the handshake
  mg_tls_derive_secret("tls13 s hs traffic", tls->enc.handshake_secret, 32,
                       hello_hash, 32, server_hs_secret, 32);
  mg_tls_derive_secret("tls13 c hs traffic", tls->enc.handshake_secret, 32,
                       hello_hash, 32, client_hs_secret, 32);

  mg_tls_derive_secret("tls13 key", server_hs_secret, 32, NULL, 0,
                       tls->enc.server_write_key, keysz);
  mg_tls_derive_secret("tls13 iv", server_hs_secret, 32, NULL, 0,
                       tls->enc.server_write_iv, 12);
  mg_tls_derive_secret("tls13 finished", server_hs_secret, 32, NULL, 0,
                       tls->enc.server_finished_key, 32);

  mg_tls_derive_secret("tls13 key", client_hs_secret, 32, NULL, 0,
                       tls->enc.client_write_key, keysz);
  mg_tls_derive_secret("tls13 iv", client_hs_secret, 32, NULL, 0,
                       tls->enc.client_write_iv, 12);
  mg_tls_derive_secret("tls13 finished", client_hs_secret, 32, NULL, 0,
                       tls->enc.client_finished_key, 32);

  mg_tls_hexdump("s hs traffic", server_hs_secret, 32);
  mg_tls_hexdump("s key", tls->enc.server_write_key, keysz);
  mg_tls_hexdump("s iv", tls->enc.server_write_iv, 12);
  mg_tls_hexdump("s finished", tls->enc.server_finished_key, 32);
  mg_tls_hexdump("c hs traffic", client_hs_secret, 32);
  mg_tls_hexdump("c key", tls->enc.client_write_key, keysz);
  mg_tls_hexdump("c iv", tls->enc.client_write_iv, 12);
  mg_tls_hexdump("c finished", tls->enc.client_finished_key, 32);

#ifdef MG_TLS_SSLKEYLOGFILE
  mg_ssl_key_log("SERVER_HANDSHAKE_TRAFFIC_SECRET", tls->random,
                 server_hs_secret, 32);
  mg_ssl_key_log("CLIENT_HANDSHAKE_TRAFFIC_SECRET", tls->random,
                 client_hs_secret, 32);
#endif
}

static void mg_tls_generate_application_keys(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  uint8_t hash[32];
  uint8_t premaster_secret[32];
  uint8_t master_secret[32];
  uint8_t server_secret[32];
  uint8_t client_secret[32];
#if MG_ENABLE_CHACHA20
  const size_t keysz = 32;
#else
  const size_t keysz = 16;
#endif

  mg_sha256_ctx sha256;
  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
  mg_sha256_final(hash, &sha256);

  mg_tls_derive_secret("tls13 derived", tls->enc.handshake_secret, 32,
                       zeros_sha256_digest, 32, premaster_secret, 32);
  mg_hmac_sha256(master_secret, premaster_secret, 32, zeros, 32);

  mg_tls_derive_secret("tls13 s ap traffic", master_secret, 32, hash, 32,
                       server_secret, 32);
  mg_tls_derive_secret("tls13 key", server_secret, 32, NULL, 0,
                       tls->enc.server_write_key, keysz);
  mg_tls_derive_secret("tls13 iv", server_secret, 32, NULL, 0,
                       tls->enc.server_write_iv, 12);
  mg_tls_derive_secret("tls13 c ap traffic", master_secret, 32, hash, 32,
                       client_secret, 32);
  mg_tls_derive_secret("tls13 key", client_secret, 32, NULL, 0,
                       tls->enc.client_write_key, keysz);
  mg_tls_derive_secret("tls13 iv", client_secret, 32, NULL, 0,
                       tls->enc.client_write_iv, 12);

  mg_tls_hexdump("s ap traffic", server_secret, 32);
  mg_tls_hexdump("s key", tls->enc.server_write_key, keysz);
  mg_tls_hexdump("s iv", tls->enc.server_write_iv, 12);
  mg_tls_hexdump("s finished", tls->enc.server_finished_key, 32);
  mg_tls_hexdump("c ap traffic", client_secret, 32);
  mg_tls_hexdump("c key", tls->enc.client_write_key, keysz);
  mg_tls_hexdump("c iv", tls->enc.client_write_iv, 12);
  mg_tls_hexdump("c finished", tls->enc.client_finished_key, 32);
  tls->enc.sseq = tls->enc.cseq = 0;

#ifdef MG_TLS_SSLKEYLOGFILE
  mg_ssl_key_log("SERVER_TRAFFIC_SECRET_0", tls->random, server_secret, 32);
  mg_ssl_key_log("CLIENT_TRAFFIC_SECRET_0", tls->random, client_secret, 32);
#endif
}

// AES GCM encryption of the message + put encoded data into the write buffer
static bool mg_tls_encrypt(struct mg_connection *c, const uint8_t *msg,
                           size_t msgsz, uint8_t msgtype) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  struct mg_iobuf *wio = &tls->send;
  uint8_t *outmsg;
  uint8_t *tag;
  size_t encsz = msgsz + 16 + 1;
  uint8_t hdr[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
                    (uint8_t) ((encsz >> 8) & 0xff), (uint8_t) (encsz & 0xff)};
  uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
                                (uint8_t) ((encsz >> 8) & 0xff),
                                (uint8_t) (encsz & 0xff)};
  uint8_t nonce[12];

  uint32_t seq = c->is_client ? tls->enc.cseq : tls->enc.sseq;
  uint8_t *key =
      c->is_client ? tls->enc.client_write_key : tls->enc.server_write_key;
  uint8_t *iv =
      c->is_client ? tls->enc.client_write_iv : tls->enc.server_write_iv;

  if (msgsz > 16384) {
    MG_ERROR(("msg longer than recordsz"));
    return false;
  }

#if MG_ENABLE_CHACHA20
#else
  mg_gcm_initialize();
#endif

  memmove(nonce, iv, sizeof(nonce));
  nonce[8] ^= (uint8_t) ((seq >> 24) & 255U);
  nonce[9] ^= (uint8_t) ((seq >> 16) & 255U);
  nonce[10] ^= (uint8_t) ((seq >> 8) & 255U);
  nonce[11] ^= (uint8_t) ((seq) & 255U);

  if (mg_iobuf_add(wio, wio->len, hdr, sizeof(hdr)) == 0 ||
      !mg_iobuf_resize(wio, wio->len + encsz))
    return false;
  outmsg = wio->buf + wio->len;
  tag = wio->buf + wio->len + msgsz + 1;
  memmove(outmsg, msg, msgsz);
  outmsg[msgsz] = msgtype;
#if MG_ENABLE_CHACHA20
  (void) tag;  // tag is only used in aes gcm
  {
    size_t n;
    uint8_t *enc = (uint8_t *) mg_calloc(1, msgsz + 256 + 1);
    if (enc == NULL) return false;
    n = mg_chacha20_poly1305_encrypt(enc, key, nonce, associated_data,
                                     sizeof(associated_data), outmsg,
                                     msgsz + 1);
    memmove(outmsg, enc, n);
    mg_free(enc);
  }
#else
  mg_aes_gcm_encrypt(outmsg, outmsg, msgsz + 1, key, 16, nonce, sizeof(nonce),
                     associated_data, sizeof(associated_data), tag, 16);
#endif
  c->is_client ? tls->enc.cseq++ : tls->enc.sseq++;
  wio->len += encsz;
  return true;
}

static void verbose_alert(uint8_t *buf) {
  uint8_t level = buf[0], desc = buf[1];
  MG_INFO(("TLS ALERT received: level=%d, desc=%d (%s)", level, desc,
           desc == 0    ? "close_notify"
           : desc == 10 ? "unexpected_message"
           : desc == 20 ? "bad_record_mac"
           : desc == 21 ? "decryption_failed"
           : desc == 40 ? "handshake_failure"
           : desc == 42 ? "bad_certificate"
           : desc == 43 ? "unsupported_certificate"
           : desc == 44 ? "certificate_revoked"
           : desc == 45 ? "certificate_expired"
           : desc == 46 ? "certificate_unknown"
           : desc == 48 ? "unknown_ca"
                        : "unknown"));
}

// read an encrypted record, decrypt it in place
static int mg_tls_recv_record(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  struct mg_iobuf *rio = &c->rtls;
  uint16_t msgsz;
  uint8_t *msg;
  uint8_t nonce[12];
  int r;

  uint32_t seq = c->is_client ? tls->enc.sseq : tls->enc.cseq;
  uint8_t *key =
      c->is_client ? tls->enc.server_write_key : tls->enc.client_write_key;
  uint8_t *iv =
      c->is_client ? tls->enc.server_write_iv : tls->enc.client_write_iv;

  if (tls->recv_len > 0) {
    return 0; /* some data from previous record is still present */
  }
  for (;;) {
    if (!mg_tls_got_record(c)) {
      return MG_IO_WAIT;
    }
    if (rio->buf[0] == MG_TLS_APP_DATA) {
      break;
    } else if (rio->buf[0] == MG_TLS_CHANGE_CIPHER) {  // skip CCS
      mg_tls_drop_record(c);
    } else if (rio->buf[0] == MG_TLS_ALERT) {  // Skip Alerts
      if (rio->len >= 7) {
        verbose_alert(&rio->buf[5]);
      } else {
        MG_INFO(("TLS ALERT packet received (short)"));
      }
      mg_tls_drop_record(c);
    } else {
      mg_error(c, "unexpected packet");
      return -1;
    }
  }

  msgsz = MG_LOAD_BE16(rio->buf + 3);
  msg = rio->buf + 5;
  if (msgsz < 17) {
    mg_error(c, "wrong size");
    return -1;
  }

  memmove(nonce, iv, sizeof(nonce));
  nonce[8] ^= (uint8_t) ((seq >> 24) & 255U);
  nonce[9] ^= (uint8_t) ((seq >> 16) & 255U);
  nonce[10] ^= (uint8_t) ((seq >> 8) & 255U);
  nonce[11] ^= (uint8_t) ((seq) & 255U);
#if MG_ENABLE_CHACHA20
  {
    uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
                                  (uint8_t) ((msgsz >> 8) & 0xff),
                                  (uint8_t) (msgsz & 0xff)};
    uint8_t *dec = (uint8_t *) mg_calloc(1, msgsz);
    size_t n;
    if (dec == NULL) {
      mg_error(c, "TLS OOM");
      return -1;
    }
    n = mg_chacha20_poly1305_decrypt(dec, key, nonce, associated_data,
                                     sizeof(associated_data), msg, msgsz);
    if (n == (size_t) -1) {
      mg_free(dec);
      mg_error(c, "decryption error");
      return -1;
    }
    memmove(msg, dec, n);
    mg_free(dec);
  }
#else
  {
    uint8_t associated_data[5] = {MG_TLS_APP_DATA, 0x03, 0x03,
                                  (uint8_t) ((msgsz >> 8) & 0xff),
                                  (uint8_t) (msgsz & 0xff)};
    mg_gcm_initialize();
    if (mg_aes_gcm_decrypt(msg, msg, msgsz - 16, key, 16, nonce, sizeof(nonce),
                           associated_data, sizeof(associated_data),
                           msg + msgsz - 16, 16) != 0) {
      mg_error(c, "GCM tag verify failed");
      return -1;
    }
  }
#endif

  r = msgsz - 16 - 1;
  tls->content_type = msg[msgsz - 16 - 1];
  if (tls->content_type == MG_TLS_ALERT) {  // Process Alerts
    verbose_alert(msg);
    if (msg[0] == 2) {
      mg_error(c, "TLS Fatal alert");
      return -1;
    } else {
      mg_tls_drop_record(c);
      return MG_IO_WAIT;
    }
  }
  tls->recv_offset = (size_t) msg - (size_t) rio->buf;
  tls->recv_len = (size_t) msgsz - 16 - 1;
  c->is_client ? tls->enc.sseq++ : tls->enc.cseq++;
  return r;
}

static void mg_tls_calc_cert_verify_hash(struct mg_connection *c,
                                         uint8_t hash[32], bool is_client) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  uint8_t sig_content[130];
  mg_sha256_ctx sha256;

  memset(sig_content, 0x20, 64);
  if (is_client) {
    uint8_t client_context[34] = "TLS 1.3, client CertificateVerify";
    memcpy(sig_content + 64, client_context, sizeof(client_context));
  } else {
    uint8_t server_context[34] = "TLS 1.3, server CertificateVerify";
    memcpy(sig_content + 64, server_context, sizeof(server_context));
  }

  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
  mg_sha256_final(sig_content + 98, &sha256);

  mg_sha256_init(&sha256);
  mg_sha256_update(&sha256, sig_content, sizeof(sig_content));
  mg_sha256_final(hash, &sha256);
}

// read and parse ClientHello record
static int mg_tls_server_recv_hello(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  struct mg_iobuf *rio = &c->rtls;
  uint8_t session_id_len;
  uint16_t j;
  uint16_t cipher_suites_len;
  uint16_t ext_len;
  uint8_t *ext;
  uint16_t msgsz;

  if (!mg_tls_got_record(c)) {
    return MG_IO_WAIT;
  }
  if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->buf[5] != MG_TLS_CLIENT_HELLO) {
    mg_error(c, "not a client hello packet");
    return -1;
  }
  if (rio->len < 50) goto fail;
  msgsz = MG_LOAD_BE16(rio->buf + 3);
  if (((uint32_t) msgsz + 4) > rio->len) goto fail;
  mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);
  // store client random
  memmove(tls->random, rio->buf + 11, sizeof(tls->random));
  // store session_id
  session_id_len = rio->buf[43];
  if (session_id_len == sizeof(tls->session_id)) {
    if (rio->len < (size_t)(46 + session_id_len)) goto fail; // 2: ciphers len
    memmove(tls->session_id, rio->buf + 44, session_id_len);
  } else if (session_id_len != 0) {
    MG_ERROR(("bad session id len"));
    goto fail;
  } // session_id_len is either sizeof(tls->session_id) or 0
  if (((uint32_t) 44 + 2 + session_id_len) > rio->len) goto fail;
  cipher_suites_len = MG_LOAD_BE16(rio->buf + 44 + session_id_len);
  if (((uint32_t) cipher_suites_len + 46 + 2 + 2 + session_id_len) > rio->len)
    goto fail;
  ext_len = MG_LOAD_BE16(rio->buf + 48 + session_id_len + cipher_suites_len);
  ext = rio->buf + 50 + session_id_len + cipher_suites_len;
  if (((unsigned char *) ext + ext_len) > (rio->buf + rio->len)) goto fail;
  for (j = 0; j < ext_len;) {
    uint16_t k;
    uint16_t key_exchange_len;
    uint8_t *key_exchange;
    uint16_t n = MG_LOAD_BE16(ext + j + 2);
    if (((uint32_t) n + j + 4) > ext_len) goto fail;
    if (MG_LOAD_BE16(ext + j) != 0x0033) {  // not a key share extension, ignore
      j += (uint16_t) (n + 4);
      continue;
    }
    key_exchange_len = MG_LOAD_BE16(ext + j + 4);
    key_exchange = ext + j + 6;
    if ((key_exchange + key_exchange_len) > ((uint8_t *) rio->buf + rio->len))
      goto fail;
    for (k = 0; k < key_exchange_len;) {
      uint16_t m = MG_LOAD_BE16(key_exchange + k + 2);
      if (((uint32_t) m + k + 4) > key_exchange_len) goto fail;
      if (m == 32 && key_exchange[k] == 0x00 && key_exchange[k + 1] == 0x1d) {
        memmove(tls->x25519_cli, key_exchange + k + 4, m);
        mg_tls_drop_record(c);
        return 0;
      }
      k += (uint16_t) (m + 4);
    }
    j += (uint16_t) (n + 4);
  }
fail:
  mg_error(c, "bad client hello");
  return -1;
}

#define PLACEHOLDER_8B 'X', 'X', 'X', 'X', 'X', 'X', 'X', 'X'
#define PLACEHOLDER_16B PLACEHOLDER_8B, PLACEHOLDER_8B
#define PLACEHOLDER_32B PLACEHOLDER_16B, PLACEHOLDER_16B

// put ServerHello record into wio buffer
static bool mg_tls_server_send_hello(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  struct mg_iobuf *wio = &tls->send;

  // clang-format off
  uint8_t msg_server_hello[122] = {
      // server hello, tls 1.2
      0x02, 0x00, 0x00, 0x76, 0x03, 0x03,
      // random (32 bytes)
      PLACEHOLDER_32B,
      // session ID length + session ID (32 bytes)
      0x20, PLACEHOLDER_32B,
#if MG_ENABLE_CHACHA20
      // TLS_CHACHA20_POLY1305_SHA256 + no compression
      0x13, 0x03, 0x00,
#else
      // TLS_AES_128_GCM_SHA256 + no compression
      0x13, 0x01, 0x00,
#endif
      // extensions + keyshare
      0x00, 0x2e, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20,
      // x25519 keyshare
      PLACEHOLDER_32B,
      // supported versions (tls1.3 == 0x304)
      0x00, 0x2b, 0x00, 0x02, 0x03, 0x04};
  // clang-format on

  // calculate keyshare
  uint8_t x25519_pub[X25519_BYTES];
  uint8_t x25519_prv[X25519_BYTES];
  if (!mg_random(x25519_prv, sizeof(x25519_prv))) mg_error(c, "RNG");
  if( mg_tls_x25519(x25519_pub, x25519_prv, X25519_BASE_POINT, 1) < 0 || mg_tls_x25519(tls->x25519_sec, x25519_prv, tls->x25519_cli, 1) < 0) {
    mg_error(c, "bad key");
    return false;
  }
  mg_tls_hexdump("s x25519 sec", tls->x25519_sec, sizeof(tls->x25519_sec));

  // fill in the gaps: random + session ID + keyshare
  memmove(msg_server_hello + 6, tls->random, sizeof(tls->random));
  memmove(msg_server_hello + 39, tls->session_id, sizeof(tls->session_id));
  memmove(msg_server_hello + 84, x25519_pub, sizeof(x25519_pub));

  // server hello message
  if (mg_iobuf_add(wio, wio->len, "\x16\x03\x03\x00\x7a", 5) == 0 ||
      mg_iobuf_add(wio, wio->len, msg_server_hello, sizeof(msg_server_hello)) ==
          0)
    return false;
  mg_sha256_update(&tls->sha256, msg_server_hello, sizeof(msg_server_hello));

  // change cipher message
  if (mg_iobuf_add(wio, wio->len, "\x14\x03\x03\x00\x01\x01", 6) == 0)
    return false;
  return true;
}

static bool mg_tls_server_send_ext(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  // server extensions
  uint8_t ext[6] = {0x08, 0, 0, 2, 0, 0};
  mg_sha256_update(&tls->sha256, ext, sizeof(ext));
  return mg_tls_encrypt(c, ext, sizeof(ext), MG_TLS_HANDSHAKE);
}

// signature algorithms we actually support:
// rsa_pkcs1_sha256, rsa_pss_rsae_sha256 and ecdsa_secp256r1_sha256
static const uint8_t secp256r1_sig_algs[12] = {
    0x00, 0x0d, 0x00, 0x08, 0x00, 0x06, 0x04, 0x03, 0x08, 0x04, 0x04, 0x01};

static bool mg_tls_server_send_cert_request(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  uint8_t req[13 + sizeof(secp256r1_sig_algs)];
  req[0] = MG_TLS_CERTIFICATE_REQUEST;  // handshake header
  MG_STORE_BE24(req + 1, 9 + sizeof(secp256r1_sig_algs));
  req[4] = 0;                                              // context length
  MG_STORE_BE16(req + 5, 6 + sizeof(secp256r1_sig_algs));  // extensions length
  MG_STORE_BE16(req + 7, 13);  // "signature algorithms"
  MG_STORE_BE16(req + 9, 2 + sizeof(secp256r1_sig_algs));  // length
  MG_STORE_BE16(
      req + 11,
      sizeof(secp256r1_sig_algs));  // signature hash algorithms length
  memcpy(req + 13, (uint8_t *) secp256r1_sig_algs, sizeof(secp256r1_sig_algs));
  mg_sha256_update(&tls->sha256, req, sizeof(req));
  return mg_tls_encrypt(c, req, sizeof(req), MG_TLS_HANDSHAKE);
}

static bool mg_tls_send_cert(struct mg_connection *c, bool is_client) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  int send_ca = !is_client && tls->ca_der.len > 0;
  // DER certificate + CA (server optional)
  size_t i, offset, total_size = tls->cert_der.len + 5;
  uint8_t *cert;
  bool res = false;
  for (i = 1; i < tls->chain_len; i++) {
    total_size += tls->chain_der[i].len + 5;
  }
  if (send_ca) {
    total_size += tls->ca_der.len + 5;
  }
  cert = (uint8_t *) mg_calloc(1, 13 + total_size);
  if (cert == NULL) return res;
  cert[0] = MG_TLS_CERTIFICATE;  // handshake header
  if (is_client && tls->cert_der.len == 0) total_size = 0; // empty list
  MG_STORE_BE24(cert + 1, total_size + 4);
  cert[4] = 0;                          // request context
  MG_STORE_BE24(cert + 5, total_size);  // 3 bytes: cert (s) length
  offset = 8;
  if (total_size > 0) { // handle empty list, RFC-8446 4.4.2
    MG_STORE_BE24(cert + offset, tls->cert_der.len);  // 3 bytes: 1st cert len
    offset += 3;
    // bytes 11+ are certificate in DER format
    memmove(cert + offset, tls->cert_der.buf, tls->cert_der.len);
    offset += tls->cert_der.len;
    MG_STORE_BE16(cert + offset, 0);  // certificate extensions (none)
    offset += 2;
    for (i = 1; i < tls->chain_len; i++) {
      MG_STORE_BE24(cert + offset, tls->chain_der[i].len);
      offset += 3;
      memmove(cert + offset, tls->chain_der[i].buf, tls->chain_der[i].len);
      offset += tls->chain_der[i].len;
      MG_STORE_BE16(cert + offset, 0);  // certificate extensions (none)
      offset += 2;
    }
    if (send_ca) {
      MG_STORE_BE24(cert + offset, tls->ca_der.len);  // 3 bytes: CA cert length
      offset += 3;
      memmove(cert + offset, tls->ca_der.buf,
              tls->ca_der.len);  // CA cert data
      offset += tls->ca_der.len;
      MG_STORE_BE16(cert + offset, 0);  // certificate extensions (none)
      offset += 2;
    }
  }
  mg_sha256_update(&tls->sha256, cert, offset);
  res = mg_tls_encrypt(c, cert, offset, MG_TLS_HANDSHAKE);
  mg_free(cert);
  return res;
}

// type adapter between uECC hash context and our sha256 implementation
typedef struct SHA256_HashContext {
  MG_UECC_HashContext uECC;
  mg_sha256_ctx ctx;
} SHA256_HashContext;

static void init_SHA256(const MG_UECC_HashContext *base) {
  SHA256_HashContext *c = (SHA256_HashContext *) base;
  mg_sha256_init(&c->ctx);
}

static void update_SHA256(const MG_UECC_HashContext *base,
                          const uint8_t *message, unsigned message_size) {
  SHA256_HashContext *c = (SHA256_HashContext *) base;
  mg_sha256_update(&c->ctx, message, message_size);
}
static void finish_SHA256(const MG_UECC_HashContext *base,
                          uint8_t *hash_result) {
  SHA256_HashContext *c = (SHA256_HashContext *) base;
  mg_sha256_final(hash_result, &c->ctx);
}

static void mg_tls_mgf1(uint8_t *mask, size_t mask_len, const uint8_t *seed,
                        size_t seed_len) {
  uint32_t counter = 0;
  size_t chunk, chunk_len, generated = 0;
  while (generated < mask_len) {
    mg_sha256_ctx ctx;
    uint8_t digest[32];
    uint8_t ctr[4];
    ctr[0] = (uint8_t) (counter >> 24);
    ctr[1] = (uint8_t) (counter >> 16);
    ctr[2] = (uint8_t) (counter >> 8);
    ctr[3] = (uint8_t) counter;
    mg_sha256_init(&ctx);
    mg_sha256_update(&ctx, seed, seed_len);
    mg_sha256_update(&ctx, ctr, sizeof(ctr));
    mg_sha256_final(digest, &ctx);
    chunk_len = mask_len - generated;
    chunk = (chunk_len < sizeof(digest) ? chunk_len : sizeof(digest));
    memmove(mask + generated, digest, chunk);
    generated += chunk;
    counter++;
  }
}

static unsigned int mg_tls_rsa_bits(const struct mg_str *n) {
  size_t i = 0;
  unsigned int bits = 0;
  while (i < n->len && n->buf[i] == 0) i++;
  if (i == n->len) return 0;
  bits = (unsigned int) ((n->len - i) * 8);
  {
    uint8_t byte = (uint8_t) n->buf[i];
    while ((byte & 0x80U) == 0) {
      bits--;
      byte = (uint8_t) (byte << 1);
    }
  }
  return bits;
}

static bool mg_tls_pss_encode(const uint8_t *hash, size_t hashlen,
                              const struct mg_str *n, uint8_t *em) {
  size_t emlen = n->len, saltlen = hashlen, dblen, pslen, i;
  uint8_t salt[64];   // Max salt size for any reasonable hash
  uint8_t m[136];     // 8 + max hash (64) + max salt (64) = 136
  uint8_t H[64];      // Max hash size
  uint8_t DB[512];    // Max for 4096-bit RSA
  uint8_t mask[512];  // Max for 4096-bit RSA
  mg_sha256_ctx ctx;

  // Check bounds
  if (saltlen > sizeof(salt) || (8 + hashlen + saltlen) > sizeof(m) ||
      hashlen > sizeof(H) || emlen > sizeof(DB)) {
    MG_ERROR(("RSA key too large for static buffers"));
    return false;
  }
  if (emlen < hashlen + saltlen + 2) {
    return false;
  }
  if (!mg_random(salt, saltlen)) {
    return false;
  }
  MG_VERBOSE(("PSS salt: %M", mg_print_hex, saltlen, salt));

  // Build m = 8 zero bytes || hash || salt
  memset(m, 0, 8);
  memcpy(m + 8, hash, hashlen);
  memcpy(m + 8 + hashlen, salt, saltlen);
  mg_sha256_init(&ctx);
  mg_sha256_update(&ctx, m, 8 + hashlen + saltlen);
  mg_sha256_final(H, &ctx);
  MG_VERBOSE(("PSS H: %M", mg_print_hex, hashlen, H));

  dblen = emlen - hashlen - 1;
  pslen = emlen - hashlen - saltlen - 2;

  // Build DB = PS || 0x01 || salt
  memset(DB, 0, pslen);
  DB[pslen] = 0x01;
  memcpy(DB + pslen + 1, salt, saltlen);

  // Generate mask and apply to DB
  mg_tls_mgf1(mask, dblen, H, hashlen);
  for (i = 0; i < dblen; i++) DB[i] ^= mask[i];

  {
    // PSS standard: emBits = modulus_bit_length - 1
    unsigned rsa_bits = mg_tls_rsa_bits(n);
    unsigned embits = rsa_bits - 1;
    unsigned unused = (unsigned) (8 * emlen - embits);
    MG_VERBOSE(("RSA modulus bits: %u, emBits: %u, emlen: %zu, unused: %u",
                rsa_bits, embits, emlen, unused));
    if (unused > 0 && unused < 8) {
      uint8_t mask_byte = (uint8_t) (0xff >> unused);
      MG_VERBOSE(("Applying mask 0x%02x to first byte (was 0x%02x)", mask_byte,
                  DB[0]));
      DB[0] &= mask_byte;
      MG_VERBOSE(("First byte after mask: 0x%02x", DB[0]));
    }
  }

  // Build final em = maskedDB || H || 0xbc
  memcpy(em, DB, dblen);
  memcpy(em + dblen, H, hashlen);
  em[emlen - 1] = 0xbc;
  return true;
}

static bool mg_tls_rsa_sign(struct tls_data *tls, const uint8_t *em,
                            size_t emlen, uint8_t *sig) {
  const uint8_t *n = (const uint8_t *) tls->rsa.n.buf;
  size_t nlen = tls->rsa.n.len;
  int crt_result;
  while (nlen > 0 && *n == 0) n++, nlen--;
  if (emlen != nlen) return false;
#if MG_TLS_RSA_USE_CRT
  // RSA CRT (Chinese Remainder Theorem) optimization:
  // s1 = em^dP mod p
  // s2 = em^dQ mod q
  // h = qInv * (s1 - s2) mod p
  // s = s2 + h * q

  if (tls->rsa.p.len == 0 || tls->rsa.q.len == 0 || tls->rsa.dP.len == 0 ||
      tls->rsa.dQ.len == 0 || tls->rsa.qInv.len == 0) {
    MG_ERROR(("CRT parameters missing, cannot use CRT optimization"));
    return false;
  }

  MG_VERBOSE(("Using RSA-CRT optimization"));

  crt_result = mg_rsa_crt_sign(
      em, emlen, (const uint8_t *) tls->rsa.dP.buf, tls->rsa.dP.len,
      (const uint8_t *) tls->rsa.dQ.buf, tls->rsa.dQ.len,
      (const uint8_t *) tls->rsa.p.buf, tls->rsa.p.len,
      (const uint8_t *) tls->rsa.q.buf, tls->rsa.q.len,
      (const uint8_t *) tls->rsa.qInv.buf, tls->rsa.qInv.len, sig, nlen);

  if (crt_result == 0) {
    uint8_t check[512];
    bool verified;
    if (nlen > sizeof(check) ||
        mg_rsa_mod_pow(n, nlen, (const uint8_t *) tls->rsa.e.buf,
                       tls->rsa.e.len, sig, nlen, check, nlen) != 0) {
      MG_ERROR(("CRT signature verification failed"));
      return false;
    }
    verified = memcmp(check, em, emlen) == 0;
    mg_bzero(check, sizeof(check));
    if (!verified) {
      MG_ERROR(("CRT signature verification failed"));
      return false;
    }
    MG_VERBOSE(("CRT signature successful (first 4 bytes): %02x %02x %02x %02x",
                sig[0], sig[1], sig[2], sig[3]));
    MG_VERBOSE(("CRT signature successful (last 4 bytes): %02x %02x %02x %02x",
                sig[nlen - 4], sig[nlen - 3], sig[nlen - 2], sig[nlen - 1]));
    return true;
  } else {
    MG_ERROR(("CRT signing failed"));
    return false;
  }
#else
  int ret;
  // Standard RSA: s = em^d mod n
  memset(sig, 0, nlen);
  ret = mg_rsa_mod_pow(n, nlen, (const uint8_t *) tls->rsa.d.buf,
                       tls->rsa.d.len, em, emlen, sig, nlen);
  if (ret == 0) {
    MG_VERBOSE(("RSA signature first 4 bytes: %02x %02x %02x %02x", sig[0],
                sig[1], sig[2], sig[3]));
    MG_VERBOSE(("RSA signature last 4 bytes: %02x %02x %02x %02x",
                sig[nlen - 4], sig[nlen - 3], sig[nlen - 2], sig[nlen - 1]));
  }
  return ret == 0;
#endif
}

static size_t mg_rsa_trim_len(const uint8_t **p, size_t n) {
  while (n > 0 && **p == 0) (*p)++, n--;
  return n;
}

static bool mg_tls_send_cert_verify(struct mg_connection *c, bool is_client) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  uint8_t hash[32] = {0};

  mg_tls_calc_cert_verify_hash(c, (uint8_t *) hash, is_client);

  if (tls->rsa.n.len > 0 && tls->rsa.d.len > 0) {
    // RSA certificate verify packet
    const uint8_t *n = (const uint8_t *) tls->rsa.n.buf;
    size_t nlen = tls->rsa.n.len;
    struct mg_str rn;
    size_t emlen, verifysz;
    uint8_t em[512];      // Max for 4096-bit RSA
    uint8_t verify[520];  // 8 + 512 max
    nlen = mg_rsa_trim_len(&n, nlen);
    rn = mg_str_n((char *) n, nlen);
    emlen = nlen;
    verifysz = 8U + emlen;

    // Check bounds
    if (emlen > sizeof(em) || verifysz > sizeof(verify)) {
      MG_ERROR(("RSA key too large for static buffers"));
      return false;
    }

    if (!mg_tls_pss_encode(hash, sizeof(hash), &rn, em)) {
      MG_ERROR(("Failed PSS encode"));
      return false;
    }

    // Validate PSS encoded message format
    if (em[emlen - 1] != 0xbc) {
      MG_ERROR(("Invalid PSS encoding: last byte is 0x%02x, expected 0xbc",
                em[emlen - 1]));
      return false;
    }

    // Build verify packet header, then sign directly into the packet
    verify[0] = 0x0f;
    MG_STORE_BE24(verify + 1, emlen + 4);
    MG_STORE_BE16(verify + 4, 0x0804);
    MG_STORE_BE16(verify + 6, emlen);

    // Sign directly into the verify buffer (verify + 8 = signature location)
    memset(verify + 8, 0, emlen);  // Initialize signature area
    if (!mg_tls_rsa_sign(tls, em, emlen, verify + 8)) {
      MG_ERROR(("Failed RSA sign"));
      return false;
    }

    MG_VERBOSE(
        ("PSS EM first 4: %02x %02x %02x %02x", em[0], em[1], em[2], em[3]));
    MG_VERBOSE(("PSS EM last 4: %02x %02x %02x %02x", em[emlen - 4],
                em[emlen - 3], em[emlen - 2], em[emlen - 1]));

    mg_sha256_update(&tls->sha256, verify, verifysz);
    return mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE);
  } else {
    // EC certificate verify packet
    uint8_t verify[82] = {0x0f, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00};
    uint8_t tmp[2 * 32 + 64] = {0};
    struct SHA256_HashContext ctx = {
        {&init_SHA256, &update_SHA256, &finish_SHA256, 64, 32, tmp},
        {{0}, 0, 0, {0}}};
    size_t sigsz, verifysz = 0;
    int neg1, neg2;
    uint8_t sig[64] = {0};
    mg_uecc_sign_deterministic(tls->ec_key, hash, sizeof(hash), &ctx.uECC, sig,
                               mg_uecc_secp256r1());

    neg1 = !!(sig[0] & 0x80);
    neg2 = !!(sig[32] & 0x80);
    verify[8] = 0x30;  // ASN.1 SEQUENCE
    verify[9] = (uint8_t) (68 + neg1 + neg2);
    verify[10] = 0x02;  // ASN.1 INTEGER
    verify[11] = (uint8_t) (32 + neg1);
    memmove(verify + 12 + neg1, sig, 32);
    verify[12 + 32 + neg1] = 0x02;  // ASN.1 INTEGER
    verify[13 + 32 + neg1] = (uint8_t) (32 + neg2);
    memmove(verify + 14 + 32 + neg1 + neg2, sig + 32, 32);

    sigsz = (size_t) (70 + neg1 + neg2);
    verifysz = 8U + sigsz;
    verify[3] = (uint8_t) (sigsz + 4);
    verify[7] = (uint8_t) sigsz;

    mg_sha256_update(&tls->sha256, verify, verifysz);
    return mg_tls_encrypt(c, verify, verifysz, MG_TLS_HANDSHAKE);
  }
}

static bool mg_tls_server_send_finish(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  mg_sha256_ctx sha256;
  uint8_t hash[32];
  uint8_t finish[36] = {0x14, 0, 0, 32};
  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
  mg_sha256_final(hash, &sha256);
  mg_hmac_sha256(finish + 4, tls->enc.server_finished_key, 32, hash, 32);
  if (!mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE))
    return false;
  mg_sha256_update(&tls->sha256, finish, sizeof(finish));
  return true;
}

static int mg_tls_server_recv_finish(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  unsigned char *recv_buf;
  // we have to backup sha256 value to restore it later, since Finished record
  // is exceptional and is not supposed to be added to the rolling hash
  // calculation.
  mg_sha256_ctx sha256 = tls->sha256;
  if (mg_tls_recv_record(c) < 0) {
    return -1;
  }
  recv_buf = &c->rtls.buf[tls->recv_offset];
  if (recv_buf[0] != MG_TLS_FINISHED) {
    mg_error(c, "expected Finish but got msg 0x%02x", recv_buf[0]);
    return -1;
  }
  mg_tls_drop_message(c);

  // restore hash
  tls->sha256 = sha256;
  return 0;
}

static bool mg_tls_client_send_hello(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  struct mg_iobuf *wio = &tls->send;

  uint8_t x25519_pub[X25519_BYTES];

  // - "signature algorithms we actually support", see above
  //   uint8_t secp256r1_sig_algs[]
  // - all popular signature algorithms (if we don't care about verification)
  uint8_t all_sig_algs[34] = {
      0x00, 0x0d, 0x00, 0x1e, 0x00, 0x1c, 0x04, 0x03, 0x05, 0x03, 0x06, 0x03,
      0x08, 0x07, 0x08, 0x08, 0x08, 0x09, 0x08, 0x0a, 0x08, 0x0b, 0x08, 0x04,
      0x08, 0x05, 0x08, 0x06, 0x04, 0x01, 0x05, 0x01, 0x06, 0x01};
  uint8_t server_name_ext[9] = {0x00, 0x00, 0x00, 0xfe, 0x00,
                                0xfe, 0x00, 0x00, 0xfe};

  // clang-format off
  uint8_t msg_client_hello[145] = {
      // TLS Client Hello header reported as TLS1.2 (5)
      0x16, 0x03, 0x03, 0x00, 0xfe,
      // client hello, tls 1.2 (6)
      0x01, 0x00, 0x00, 0x8c, 0x03, 0x03,
      // random (32 bytes)
      PLACEHOLDER_32B,
      // session ID length + session ID (32 bytes)
      0x20, PLACEHOLDER_32B, 0x00,
      0x02,  // size = 2 bytes
#if MG_ENABLE_CHACHA20
      // TLS_CHACHA20_POLY1305_SHA256
      0x13, 0x03,
#else
      // TLS_AES_128_GCM_SHA256
      0x13, 0x01,
#endif
      // no compression
      0x01, 0x00,
      // extensions + keyshare
      0x00, 0xfe,
      // x25519 keyshare
      0x00, 0x33, 0x00, 0x26, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20,
      PLACEHOLDER_32B,
      // supported groups (x25519)
      0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x1d,
      // supported versions (tls1.3 == 0x304)
      0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04,
      // session ticket (none)
      0x00, 0x23, 0x00, 0x00, // 144 bytes till here
	};
  // clang-format on
  const char *hostname = tls->hostname;
  size_t hostnamesz = strlen(tls->hostname);
  size_t hostname_extsz = hostnamesz ? hostnamesz + 9 : 0;
  uint8_t *sig_alg =
      tls->skip_verification ? all_sig_algs : (uint8_t *) secp256r1_sig_algs;
  size_t sig_alg_sz = tls->skip_verification ? sizeof(all_sig_algs)
                                             : sizeof(secp256r1_sig_algs);

  // patch ClientHello with correct hostname ext length (if any)
  MG_STORE_BE16(msg_client_hello + 3,
                hostname_extsz + 183 - 9 - 34 + sig_alg_sz);
  MG_STORE_BE16(msg_client_hello + 7,
                hostname_extsz + 179 - 9 - 34 + sig_alg_sz);
  MG_STORE_BE16(msg_client_hello + 82,
                hostname_extsz + 104 - 9 - 34 + sig_alg_sz);

  if (hostnamesz > 0) {
    MG_STORE_BE16(server_name_ext + 2, hostnamesz + 5);
    MG_STORE_BE16(server_name_ext + 4, hostnamesz + 3);
    MG_STORE_BE16(server_name_ext + 7, hostnamesz);
  }

  // calculate keyshare
  if (!mg_random(tls->x25519_cli, sizeof(tls->x25519_cli))) mg_error(c, "RNG");
  mg_tls_x25519(x25519_pub, tls->x25519_cli, X25519_BASE_POINT, 1);

  // fill in the gaps: random + session ID + keyshare
  if (!mg_random(tls->session_id, sizeof(tls->session_id))) mg_error(c, "RNG");
  if (!mg_random(tls->random, sizeof(tls->random))) mg_error(c, "RNG");
  memmove(msg_client_hello + 11, tls->random, sizeof(tls->random));
  memmove(msg_client_hello + 44, tls->session_id, sizeof(tls->session_id));
  memmove(msg_client_hello + 94, x25519_pub, sizeof(x25519_pub));

  // client hello message
  if (mg_iobuf_add(wio, wio->len, msg_client_hello, sizeof(msg_client_hello)) ==
      0)
    return false;
  mg_sha256_update(&tls->sha256, msg_client_hello + 5,
                   sizeof(msg_client_hello) - 5);
  if (mg_iobuf_add(wio, wio->len, sig_alg, sig_alg_sz) == 0) return false;
  mg_sha256_update(&tls->sha256, sig_alg, sig_alg_sz);
  if (hostnamesz > 0) {
    if (mg_iobuf_add(wio, wio->len, server_name_ext, sizeof(server_name_ext)) ==
            0 ||
        mg_iobuf_add(wio, wio->len, hostname, hostnamesz) == 0)
      return false;
    mg_sha256_update(&tls->sha256, server_name_ext, sizeof(server_name_ext));
    mg_sha256_update(&tls->sha256, (uint8_t *) hostname, hostnamesz);
  }

  // change cipher message
  if (mg_iobuf_add(wio, wio->len, (const char *) "\x14\x03\x03\x00\x01\x01",
                   6) == 0)
    return false;
  return true;
}

static int mg_tls_client_recv_hello(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  struct mg_iobuf *rio = &c->rtls;
  uint16_t msgsz;
  uint8_t *ext;
  uint16_t ext_len;
  int j;

  if (!mg_tls_got_record(c)) {
    return MG_IO_WAIT;
  }
  if (rio->buf[0] != MG_TLS_HANDSHAKE || rio->len < 6 || rio->buf[5] != MG_TLS_SERVER_HELLO) {
    if (rio->buf[0] == MG_TLS_ALERT && rio->len >= 7) {
      verbose_alert(&rio->buf[5]);
      mg_error(c, "TLS error alert");
      return -1;
    }
    if (rio->len >= 6) MG_ERROR(("got packet type 0x%02x/0x%02x", rio->buf[0], rio->buf[5]));
    mg_error(c, "not a server hello packet");
    return -1;
  }

  if (rio->len < 5 + 39 + 32 + 3 + 2) goto fail;
  msgsz = MG_LOAD_BE16(rio->buf + 3);
  if (msgsz > rio->len - 5) goto fail;
  mg_sha256_update(&tls->sha256, rio->buf + 5, msgsz);

  ext_len = MG_LOAD_BE16(rio->buf + 5 + 39 + 32 + 3);
  ext = rio->buf + 5 + 39 + 32 + 3 + 2;
  if (ext_len > (rio->len - (5 + 39 + 32 + 3 + 2))) goto fail;

  for (j = 0; j < ext_len;) {
    uint16_t ext_type, ext_len2, group, key_exchange_len;
    uint8_t *key_exchange;
    if ((ext_len - j) < 4) goto fail;
    ext_type = MG_LOAD_BE16(ext + j);
    ext_len2 = MG_LOAD_BE16(ext + j + 2);
    if (ext_len2 > (ext_len - j - 4)) goto fail;
    if (ext_type != 0x0033) {  // not a key share extension, ignore
      j += (uint16_t) (ext_len2 + 4);
      continue;
    }
    if (ext_len2 < (2 + 2 + 32)) goto fail;
    group = MG_LOAD_BE16(ext + j + 4);
    if (group != 0x001d) {
      mg_error(c, "bad key exchange group");
      return -1;
    }
    key_exchange_len = MG_LOAD_BE16(ext + j + 6);
    key_exchange = ext + j + 8;
    if (key_exchange_len != 32 || mg_tls_x25519(tls->x25519_sec, tls->x25519_cli, key_exchange, 1) < 0) {
      mg_error(c, "bad key");
      return -1;
    }    
    mg_tls_hexdump("c x25519 sec", tls->x25519_sec, 32);
    mg_tls_drop_record(c);
    /* generate handshake keys */
    mg_tls_generate_handshake_keys(c);
    return 0;
  }
fail:
  mg_error(c, "bad server hello");
  return -1;
}

static int mg_tls_client_recv_ext(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  unsigned char *recv_buf;
  if (mg_tls_recv_record(c) < 0) {
    return -1;
  }
  recv_buf = &c->rtls.buf[tls->recv_offset];
  if (recv_buf[0] != MG_TLS_ENCRYPTED_EXTENSIONS) {
    mg_error(c, "expected server extensions but got msg 0x%02x", recv_buf[0]);
    return -1;
  }
  mg_tls_drop_message(c);
  return 0;
}

struct mg_tls_cert {
  bool is_ec_pubkey;
  bool is_ca;
  struct mg_str sn;
  struct mg_str pubkey;
  struct mg_der_tlv issuer;
  struct mg_der_tlv subj;
  struct mg_str sig;    // signature
  uint8_t tbshash[48];  // 32B for sha256/secp256, 48B for sha384/secp384
  size_t tbshashsz;     // actual TBS hash size
};

static void mg_der_debug_cert_name(const char *name, struct mg_der_tlv *tlv) {
  struct mg_der_tlv v;
  struct mg_str cn, c, o, ou;
  if (mg_log_level < MG_LL_VERBOSE) return; // skip recursive computations
  cn = c = o = ou = mg_str("");
  if (mg_der_find_oid(tlv, (uint8_t *) "\x55\x04\x03", 3, &v) > 0)
    cn = mg_str_n((const char *) v.value, v.len);
  if (mg_der_find_oid(tlv, (uint8_t *) "\x55\x04\x06", 3, &v) > 0)
    c = mg_str_n((const char *) v.value, v.len);
  if (mg_der_find_oid(tlv, (uint8_t *) "\x55\x04\x0a", 3, &v) > 0)
    o = mg_str_n((const char *) v.value, v.len);
  if (mg_der_find_oid(tlv, (uint8_t *) "\x55\x04\x0b", 3, &v) > 0)
    ou = mg_str_n((const char *) v.value, v.len);
  MG_VERBOSE(("%s: CN=%.*s, C=%.*s, O=%.*s, OU=%.*s", name, cn.len, cn.buf,
              c.len, c.buf, o.len, o.buf, ou.len, ou.buf));
}

static uint64_t asnt2t(uint8_t *v, uint8_t type) {
  unsigned int y, mo, d, h, mi, ss;
  y = 10U * (*v++ - '0'), y += (*v++ - '0');
  if (type == 0x17) {       // UTCTime, RFC-5280 4.1.2.5.1 YYMMDDHHMMSSZ
    if (y >= 50) return (uint64_t) 0;  // 19YY is in the past
    y += 2000;
  } else {  // GeneralizedTime, RFC-5280 4.1.2.5.2 YYYYMMDDHHMMSSZ
    y *= 100U, y += 10U * (*v++ - '0'), y += (*v++ - '0');
  }
  mo = 10U * (*v++ - '0'), mo += (*v++ - '0');
  d = 10U * (*v++ - '0'), d += (*v++ - '0');
  h = 10U * (*v++ - '0'), h += (*v++ - '0');
  mi = 10U * (*v++ - '0'), mi += (*v++ - '0');
  ss = 10U * (*v++ - '0'), ss += (*v++ - '0');
  if (*v != 'Z') return 0; // invalid
  return mg_timegm(y, mo, d, h, mi, ss);
}

static int mg_tls_parse_cert_der(void *buf, size_t dersz,
                                 struct mg_tls_cert *cert) {
  uint8_t *tbs, *der = (uint8_t *) buf;
  size_t tbssz;
  struct mg_der_tlv root, tbs_cert, field, algo;  // pubkey, signature;
  struct mg_der_tlv pki, pki_algo, pki_key, pki_curve, raw_sig;

  // Parse outermost SEQUENCE
  if (mg_der_parse(der, dersz, &root) <= 0 || root.type != 0x30) return -1;

  // Parse TBSCertificate SEQUENCE
  tbs = root.value;
  if (mg_der_next(&root, &tbs_cert) <= 0 || tbs_cert.type != 0x30) return -1;
  tbssz = (size_t) (tbs_cert.value + tbs_cert.len - tbs);

  // Parse Version (optional field)
  if (mg_der_next(&tbs_cert, &field) <= 0) return -1;
  if (field.type == 0xa0) {  // v3
    if (mg_der_parse(field.value, field.len, &field) <= 0 || field.len != 1 ||
        field.value[0] != 2)
      return -1;
    if (mg_der_next(&tbs_cert, &field) <= 0) return -1;
  }

  // Parse Serial Number
  if (field.type != 2) return -1;
  cert->sn = mg_str_n((char *) field.value, field.len);
  MG_VERBOSE(("cert s/n: %M", mg_print_hex, cert->sn.len, cert->sn.buf));

  // Parse signature algorithm (first occurrence)
  if (mg_der_next(&tbs_cert, &field) <= 0 || field.type != 0x30) return -1;
  if (mg_der_next(&field, &algo) <= 0 || algo.type != 0x06) return -1;

  MG_VERBOSE(("sig algo (oid): %M", mg_print_hex, algo.len, algo.value));
  // Signature algorithm OID mapping
  if (algo.len == 8 &&
      memcmp(algo.value, "\x2A\x86\x48\xCE\x3D\x04\x03\x02", 8) == 0) {
    MG_VERBOSE(("sig algo: ECDSA with SHA256"));
    mg_sha256(cert->tbshash, tbs, tbssz);
    cert->tbshashsz = 32;
  } else if (algo.len == 9 &&
             memcmp(algo.value, "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x0B", 9) ==
                 0) {
    MG_VERBOSE(("sig algo: RSA with SHA256"));
    mg_sha256(cert->tbshash, tbs, tbssz);
    cert->tbshashsz = 32;
  } else if (algo.len == 8 &&
             memcmp(algo.value, "\x2A\x86\x48\xCE\x3D\x04\x03\x03", 8) == 0) {
    MG_VERBOSE(("sig algo: ECDSA with SHA384"));
    mg_sha384(cert->tbshash, tbs, tbssz);
    cert->tbshashsz = 48;
  } else if (algo.len == 9 &&
             memcmp(algo.value, "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x0C", 9) ==
                 0) {
    MG_VERBOSE(("sig algo: RSA with SHA384"));
    mg_sha384(cert->tbshash, tbs, tbssz);
    cert->tbshashsz = 48;
  } else {
    MG_ERROR(
        ("sig algo: unsupported OID: %M", mg_print_hex, algo.len, algo.value));
    return -1;
  }
  MG_VERBOSE(("tbs hash: %M", mg_print_hex, cert->tbshashsz, cert->tbshash));

  // issuer
  if (mg_der_next(&tbs_cert, &field) <= 0 || field.type != 0x30) return -1;
  cert->issuer = field;
  mg_der_debug_cert_name("issuer", &field);

  // validity dates (before/after)
  if (mg_der_next(&tbs_cert, &field) <= 0 || field.type != 0x30) return -1;
  {
    struct mg_der_tlv before, after;
    uint64_t now = mg_now() / 1000U, t;
    if (mg_der_next(&field, &before) <= 0 || ((before.type != 0x17 || before.len != 13) && (before.type != 0x18 || before.len != 15))) return -1;
    if (now < (t = asnt2t(before.value, before.type))) {
      MG_ERROR(("cert is not yet valid: before=%.*s (%lu), now=%lu", before.len, before.value, t, now));
      return -1;
    }
    if (mg_der_next(&field, &after) <= 0 || ((after.type != 0x17 || after.len != 13) && (after.type != 0x18 || after.len != 15))) return -1;
    if (memcmp(after.value, "99991231235959Z", 15) == 0) { // RFC-5280 4.1.2.5
      MG_ERROR(("No well-defined expiration date"));
      return -1;
    }
    if (now > (t = asnt2t(after.value, after.type))) {
      MG_ERROR(("cert is no longer valid: after=%.*s (%lu), now=%lu", after.len, after.value, t, now));
      return -1;
    }
  }

  // subject
  if (mg_der_next(&tbs_cert, &field) <= 0 || field.type != 0x30) return -1;
  cert->subj = field;
  mg_der_debug_cert_name("subject", &field);

  // subject public key info
  if (mg_der_next(&tbs_cert, &field) <= 0 || field.type != 0x30) return -1;

  if (mg_der_next(&field, &pki) <= 0 || pki.type != 0x30) return -1;
  if (mg_der_next(&pki, &pki_algo) <= 0 || pki_algo.type != 0x06) return -1;

  // public key algorithm
  MG_VERBOSE(("pk algo (oid): %M", mg_print_hex, pki_algo.len, pki_algo.value));
  if (pki_algo.len == 8 &&
      memcmp(pki_algo.value, "\x2A\x86\x48\xCE\x3D\x03\x01\x07", 8) == 0) {
    cert->is_ec_pubkey = true;
    MG_VERBOSE(("pk algo: ECDSA secp256r1"));
  } else if (pki_algo.len == 8 &&
             memcmp(pki_algo.value, "\x2A\x86\x48\xCE\x3D\x03\x01\x08", 8) ==
                 0) {
    cert->is_ec_pubkey = true;
    MG_VERBOSE(("pk algo: ECDSA secp384r1"));
  } else if (pki_algo.len == 7 &&
             memcmp(pki_algo.value, "\x2A\x86\x48\xCE\x3D\x02\x01", 7) == 0) {
    cert->is_ec_pubkey = true;
    MG_VERBOSE(("pk algo: EC public key"));
  } else if (pki_algo.len == 9 &&
             memcmp(pki_algo.value, "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01",
                    9) == 0) {
    cert->is_ec_pubkey = false;
    MG_VERBOSE(("pk algo: RSA"));
  } else {
    MG_ERROR(("unsupported pk algo: %M", mg_print_hex, pki_algo.len,
              pki_algo.value));
    return -1;
  }

  // Parse public key
  if (cert->is_ec_pubkey) {
    if (mg_der_next(&pki, &pki_curve) <= 0 || pki_curve.type != 0x06) return -1;
  }
  if (mg_der_next(&field, &pki_key) <= 0 || pki_key.type != 0x03) return -1;

  if (cert->is_ec_pubkey) {  // Skip leading 0x00 and 0x04 (=uncompressed)
    if (pki_key.len < 2) return -1;
    cert->pubkey = mg_str_n((char *) pki_key.value + 2, pki_key.len - 2);
  } else {  // Skip leading 0x00 byte
    if (pki_key.len < 1) return -1;
    cert->pubkey = mg_str_n((char *) pki_key.value + 1, pki_key.len - 1);
  }

  { // parse optional fields
    int r; // unique ids
    while ((r = mg_der_next(&tbs_cert, &field)) > 0 && field.type == 0xa1);
    if (r > 0 && field.type == 0xa3) { // extensions
      bool ca = false, certsign = true;
      struct mg_der_tlv ext, e, i;  // ext[e(i, ...), ...]
      if (mg_der_next(&field, &ext) <= 0 || ext.type != 0x30) return -1;
      while (mg_der_next(&ext, &e) > 0) {
        if (mg_der_next(&e, &i) <= 0 || i.type != 0x06) return -1;
        if (i.len == 3 && memcmp(i.value, (uint8_t *) "\x55\x1d\x13", 3) == 0) {
          struct mg_der_tlv s, v; // basicConstraints
          MG_VERBOSE(("basicConstraints"));
          if (mg_der_next(&e, &i) <= 0 || i.type != 0x01 || i.len != 1 || *i.value != 0xff) return -1;
          if (mg_der_next(&e, &i) <= 0 || i.type != 0x04) return -1;
          if (mg_der_next(&i, &s) <= 0 || s.type != 0x30) return -1;
          if (s.len == 0) break;
          if (mg_der_next(&s, &v) <= 0) return -1;
          if (v.type == 0x01 && v.len == 1 && *v.value == 0xff) ca = true;
        } else if (i.len == 3 && memcmp(i.value, (uint8_t *) "\x55\x1d\x0f", 3) == 0) {
          struct mg_der_tlv b; // keyUsage, enforce when present
          MG_VERBOSE(("keyUsage"));
          if (mg_der_next(&e, &i) <= 0) return -1;
          if (i.type == 0x01) { // SHOULD, defaults to false
            if (i.len != 1) return -1;
            if (mg_der_next(&e, &i) <= 0) return -1;
          }
          if (i.type != 0x04) return -1;
          if (mg_der_next(&i, &b) <= 0 || b.type != 0x03) return -1;
          if (!(b.value[1] & MG_BIT(2))) certsign = false;
        }
      }
      if (ca && certsign) cert->is_ca = true;
    }
  }
  // Parse signature
  if (mg_der_next(&root, &field) <= 0 || field.type != 0x30) return -1;
  if (mg_der_next(&root, &raw_sig) <= 0 || raw_sig.type != 0x03) return -1;
  if (raw_sig.len < 1 || raw_sig.value[0] != 0x00) return -1;

  cert->sig = mg_str_n((char *) raw_sig.value + 1, raw_sig.len - 1);
  MG_VERBOSE(("sig: %M", mg_print_hex, cert->sig.len, cert->sig.buf));

  return 0;
}

static int countdots(struct mg_str s) {
  int count = 0;
  size_t len = s.len;
  char *p = s.buf;
  while (len--) {
    if (*(p++) == '.') ++count;
  }    
  return count;
}

static int mg_tls_verify_cert_san(const uint8_t *der, size_t dersz,
                                  const char *server_name,
                                  struct mg_addr *server_ip) {
  struct mg_der_tlv root, field, name;
  if (mg_der_parse((uint8_t *) der, dersz, &root) < 0) {
    MG_ERROR(("failed to parse certificate"));
    return -1;
  }
  if (mg_der_find_oid(&root, (uint8_t *) "\x55\x1d\x11", 3, &field) <= 0) {
    MG_ERROR(("failed to extract SAN"));
    return -1;
  }
  if (mg_der_parse(field.value, field.len, &field) < 0) {
    MG_ERROR(("SAN is not a constructed object"));
    return -1;
  }
  while (mg_der_next(&field, &name) > 0) {
    if (name.type == 0x87 && name.len == 4) {  // this is an IPv4 address
      MG_VERBOSE(("Found SAN, IP: %M", mg_print_ip4, name.value));
      if (!server_ip->is_ip6 &&
          *((uint32_t *) name.value) == server_ip->addr.ip4)
        return 1;  // and matches the one we're connected to
#if MG_ENABLE_IPV6
    } else if (name.type == 0x87 && name.len == 16) {  // is an IPv6 address
      MG_VERBOSE(("Found SAN, IPv6: %M", mg_print_ip6, name.value));
      if (server_ip->is_ip6 && memcmp(name.value, server_ip->addr.ip6, 16) == 0)
        return 1;  // and matches the one we're connected to
#endif
    } else {  // this is a text SAN
      struct mg_str sn, tn;
      MG_VERBOSE(("Found SAN, (%u): %.*s", name.type, name.len, name.value));
      sn = mg_str(server_name), tn = mg_str_n((char *) name.value, name.len);
      if (countdots(sn) == countdots(tn) && mg_match(sn, tn, NULL))
        return 1;  // and matches the host name
    }
  }
  return -1;
}

static int mg_tls_verify_cert_signature(const struct mg_tls_cert *cert,
                                        const struct mg_tls_cert *issuer) {
  if (issuer->is_ec_pubkey) {
    uint8_t sig[128];
    struct mg_der_tlv seq = {0, 0, 0}, a = {0, 0, 0}, b = {0, 0, 0};
    mg_der_parse((uint8_t *) cert->sig.buf, cert->sig.len, &seq);
    mg_der_next(&seq, &a);
    mg_der_next(&seq, &b);
    if (a.len == 0 || b.len == 0) {
      MG_ERROR(("cert verification error"));
      return 0;
    }
#if MG_UECC_SUPPORTS_secp256r1
    if (issuer->pubkey.len == 64) {
      const uint32_t N = 32;
      if (a.len > N) a.value += (a.len - N), a.len = N; // padding
      if (b.len > N) b.value += (b.len - N), b.len = N;
      memset(sig, 0, N - a.len); // short encoding
      memmove(sig + (N - a.len), a.value, a.len);
      memset(sig + N, 0, N - b.len);
      memmove(sig + N + (N - b.len), b.value, b.len);
      return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
                            (unsigned) cert->tbshashsz, sig,
                            mg_uecc_secp256r1());
    } else
#endif
#if MG_UECC_SUPPORTS_secp384r1
    if (issuer->pubkey.len == 96) {
      const uint32_t N = 48;
      if (a.len > N) a.value += (a.len - N), a.len = N;
      if (b.len > N) b.value += (b.len - N), b.len = N;
      memmove(sig, a.value, N);
      memmove(sig + N, b.value, N);
      return mg_uecc_verify((uint8_t *) issuer->pubkey.buf, cert->tbshash,
                            (unsigned) cert->tbshashsz, sig,
                            mg_uecc_secp384r1());
    } else
#endif
    {
      MG_ERROR(("unsupported public key length: %d", issuer->pubkey.len));
      return 0;
    }
  } else {
    const uint8_t *n;
    size_t nlen;
    uint8_t sig2[512];  // 4096 bits
    struct mg_der_tlv seq, modulus, exponent;
    if (mg_der_parse((uint8_t *) issuer->pubkey.buf, issuer->pubkey.len,
                     &seq) <= 0 ||
        mg_der_next(&seq, &modulus) <= 0 || modulus.type != 2 ||
        modulus.len == 0 || mg_der_next(&seq, &exponent) <= 0 ||
        exponent.type != 2 || exponent.len == 0) {
      return -1;
    }
    n = modulus.value, nlen = mg_rsa_trim_len(&n, modulus.len);
    if (nlen > sizeof(sig2) || cert->tbshashsz > nlen ||
        mg_rsa_mod_pow(modulus.value, modulus.len, exponent.value,
                       exponent.len, (uint8_t *) cert->sig.buf, cert->sig.len,
                       sig2, nlen) != 0) {
      return 0;
    }

    return mg_rsa_pkcs_verify(sig2, nlen, cert->tbshash, cert->tbshashsz);
  }
}

static int mg_tls_verify_cert_cn(struct mg_der_tlv *subj, const char *host) {
  struct mg_der_tlv v;
  int matched = 0;
  if (mg_der_find_oid(subj, (uint8_t *) "\x55\x04\x03", 3, &v) > 0) {
    struct mg_str hn, cn;
    MG_VERBOSE(("using CN: %.*s <-> %s", v.len, v.value, host));
    hn = mg_str(host), cn = mg_str_n((char *) v.value, v.len);
    matched = (int) (countdots(hn) == countdots(cn) && mg_match(hn, cn, NULL));
  }
  return matched;
}

static int tls_bundle_find(struct tls_data *tls, struct mg_der_tlv *name,
                           struct mg_tls_cert *cert) {
  size_t i;
  struct mg_der_tlv v;
  struct mg_str *p = tls->ca_bundle_der, tgt;
  if (!mg_der_find_oid(name, (uint8_t *) "\x55\x04\x03", 3, &v)) return false;
  tgt = mg_str_n((const char *) v.value, v.len);
  for (i = 0; i < tls->ca_bundle_len; i++, p++) {
    struct mg_str subj;
    if (mg_tls_parse_cert_der(p->buf, p->len, cert) < 0 ||
        !mg_der_find_oid(&cert->subj, (uint8_t *) "\x55\x04\x03", 3, &v)) {
      MG_ERROR(("failed to parse certificate #%u in bundle", i + 1));
      continue; // skip this certificate but don't halt the process
    }
    subj = mg_str_n((const char *) v.value, v.len);
    MG_VERBOSE(("#%u: %.*s (%.*s)", i + 1, subj.len, subj.buf, tgt.len, tgt.buf));
    if (mg_strcasecmp(subj, tgt) == 0) return 1;
  }
  return 0;
}

static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  unsigned char *recv_buf;

  if (mg_tls_recv_record(c) < 0) {
    return -1;
  }

  recv_buf = &c->rtls.buf[tls->recv_offset];

  if (recv_buf[0] == MG_TLS_CERTIFICATE_REQUEST) {
    MG_VERBOSE(("got certificate request"));
    mg_tls_drop_message(c);
    tls->cert_requested = 1;
    return -1;
  }

  if (recv_buf[0] != MG_TLS_CERTIFICATE) {
    mg_error(c, "expected %s certificate but got msg 0x%02x",
             is_client ? "server" : "client", recv_buf[0]);
    return -1;
  }

  if (tls->recv_len < 13) { // 8 + 3 + 2, chain len + cert len + ext len
    mg_error(c, "certificate list too short");
    return -1;
  }

  {
    // Normally, there are 2-3 certs in a chain (when is_client)
    struct mg_tls_cert certs[8];
    int certnum = 0;
    uint32_t full_cert_chain_len = MG_LOAD_BE24(recv_buf + 1);
    uint32_t cert_chain_len = MG_LOAD_BE24(recv_buf + 5);
    uint8_t *p = recv_buf + 8;
    uint8_t *endp = recv_buf + cert_chain_len + 8;
    bool found_ca = false;
    struct mg_tls_cert ca;

    if (cert_chain_len != full_cert_chain_len - 4 || cert_chain_len > (tls->recv_len - 8)) {
      MG_ERROR(("full chain length: %d, chain length: %d, msg length: %d", full_cert_chain_len, cert_chain_len, tls->recv_len));
      mg_error(c, "invalid certificate chain length");
      return -1;
    }

    memset(certs, 0, sizeof(certs));
    memset(&ca, 0, sizeof(ca));

    if (tls->ca_der.len > 0) { // single cert or server
      if (mg_tls_parse_cert_der(tls->ca_der.buf, tls->ca_der.len, &ca) < 0) {
        mg_error(c, "failed to parse CA certificate");
        return -1;
      }
      MG_VERBOSE(("CA serial: %M", mg_print_hex, ca.sn.len, ca.sn.buf));
    }

    while (p < endp) {
      struct mg_tls_cert *ci = &certs[certnum++];
      uint32_t certsz;
      uint16_t certext;
      uint8_t *cert = p + 3;

      if ((endp - p) < 5) { // 3 + 2, cert len + ext len fields
        mg_error(c, "truncated certificate in chain");
        return -1;
      }
      certsz = MG_LOAD_BE24(p);
      p = cert + certsz + 2; // skip cert extensions (size only, not supported)
      if (p > endp) {
        mg_error(c, "invalid certificate length");
        return -1;
      }
      certext = MG_LOAD_BE16(cert + certsz);
      if (certext != 0) {
        mg_error(c, "certificate extensions are not supported");
        return -1;
      }

      if (mg_tls_parse_cert_der(cert, certsz, ci) < 0) {
        mg_error(c, "failed to parse certificate");
        return -1;
      }

      if (ci == certs) {
        // First certificate in the chain is peer cert, check SAN if requested,
        // and store public key for further CertVerify step
        if (tls->hostname[0] != '\0' &&
            mg_tls_verify_cert_san(cert, certsz, tls->hostname, &c->rem) <= 0 &&
            mg_tls_verify_cert_cn(&ci->subj, tls->hostname) <= 0) {
          mg_error(c, "failed to verify hostname");
          return -1;
        }
        if (ci->pubkey.len > sizeof(tls->pubkey)) {
          mg_error(c, "peer public key too large");
          return -1;
        }
        memmove(tls->pubkey, ci->pubkey.buf, ci->pubkey.len);
        tls->pubkeysz = ci->pubkey.len;
      } else {
        if (!ci->is_ca || !mg_tls_verify_cert_signature(ci - 1, ci)) {
          mg_error(c, "failed to verify certificate chain %s", ci->is_ca ? "" : "(not a true CA)");
          return -1;
        }
      }

      if (tls->ca_bundle_len > 0) {  // bundle, find subject and compare keys
        int r;
        MG_VERBOSE(("Search current cert in bundle"));
        r = tls_bundle_find(tls, &ci->subj, &ca);  // find current cert ci
        if (r < 0) {
          mg_error(c, "failed to parse CA bundle");
          return -1;
        } else if (r > 0 && ca.pubkey.len == ci->pubkey.len &&
                   memcmp(ca.pubkey.buf, ci->pubkey.buf, ca.pubkey.len) == 0) {
          found_ca = true;
          MG_VERBOSE(("CA serial: %M", mg_print_hex, ca.sn.len, ca.sn.buf));
          break;
        }  // else either r = 0 => subj not found or pubkey not matching
      }

      if (certnum == sizeof(certs) / sizeof(certs[0]) - 1) {
        mg_error(c, "too many certificates in the chain");
        return -1;
      }
    }

    if (!found_ca && certnum > 0 && tls->ca_bundle_len > 0) {  // bundle
      int r;
      MG_VERBOSE(("Search bundle for issuer of last cert in chain"));
      r = tls_bundle_find(tls, &certs[certnum - 1].issuer, &ca);
      if (r <= 0) {
        mg_error(c, r < 0 ? "failed to parse CA bundle"
                          : "failed to find issuing CA in bundle");
        return -1;
      } else if (!ca.is_ca ||  // candidate issuer found
                 !mg_tls_verify_cert_signature(&certs[certnum - 1], &ca)) {
        mg_error(c, "failed to verify CA");
        return -1;
      }  // issuer verified. NOTE(): may fail if two diff CAs share a CN
      MG_VERBOSE(("CA serial: %M", mg_print_hex, ca.sn.len, ca.sn.buf));
    }
    if (!found_ca && tls->ca_der.len > 0) {  // single cert or server
      if (certnum < 1 ||
          !mg_tls_verify_cert_signature(&certs[certnum - 1], &ca)) {
        mg_error(c, "failed to verify CA");
        return -1;
      } else if (is_client) {
        MG_VERBOSE(("no CA in chain; verification with builtin CA passed"));
      }
    }
  }
  mg_tls_drop_message(c);
  mg_tls_calc_cert_verify_hash(c, tls->sighash, !is_client);
  return 0;
}

static int mg_tls_recv_cert_verify(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  unsigned char *recv_buf;
  if (mg_tls_recv_record(c) < 0) {
    return -1;
  }
  recv_buf = &c->rtls.buf[tls->recv_offset];
  if (recv_buf[0] != MG_TLS_CERTIFICATE_VERIFY) {
    mg_error(c, "expected %s certificate verify but got msg 0x%02x",
             c->is_client ? "server" : "client", recv_buf[0]);
    return -1;
  }
  if (tls->recv_len < 8) {
    mg_error(c, "server certificate verify is too short: %d bytes",
             tls->recv_len);
    return -1;
  }

  // Ignore CertificateVerify if strict checks are not required
  if (tls->skip_verification) {
    mg_tls_drop_message(c);
    return 0;
  }

  {
    uint16_t sigalg = MG_LOAD_BE16(recv_buf + 4);
    uint16_t siglen = MG_LOAD_BE16(recv_buf + 6);
    uint8_t *sigbuf = recv_buf + 8;
    if (siglen > tls->recv_len - 8) {
      mg_error(c, "invalid certverify signature length: %d, expected %d",
               siglen, tls->recv_len - 8);
      return -1;
    }
    MG_VERBOSE(
        ("certificate verification, algo=%04x, siglen=%d", sigalg, siglen));

    if (sigalg == 0x0804) {  // rsa_pss_rsae_sha256
      uint8_t sig2[512];     // 2048 or 4096 bits
      const uint8_t *n;
      size_t nlen;
      struct mg_der_tlv seq, modulus, exponent;

      if (mg_der_parse(tls->pubkey, tls->pubkeysz, &seq) <= 0 ||
          mg_der_next(&seq, &modulus) <= 0 || modulus.type != 2 ||
          modulus.len == 0 || mg_der_next(&seq, &exponent) <= 0 ||
          exponent.type != 2 || exponent.len == 0) {
        mg_error(c, "invalid public key");
        return -1;
      }

      n = modulus.value, nlen = mg_rsa_trim_len(&n, modulus.len);
      if (nlen > sizeof(sig2) ||
          mg_rsa_mod_pow(modulus.value, modulus.len, exponent.value,
                         exponent.len, sigbuf, siglen, sig2, nlen) != 0 ||
          !mg_rsa_verify(sig2, nlen, tls->sighash)) {
        mg_error(c, "failed to verify RSA certificate (certverify)");
        return -1;
      }
      MG_VERBOSE(("certificate verification successful (RSA)"));
    } else if (sigalg == 0x0403) {  // ecdsa_secp256r1_sha256
      // Extract certificate signature and verify it using pubkey and sighash
      uint8_t sig[64];
      struct mg_der_tlv seq, r, s;
      memset(sig, 0, 64);
      if (mg_der_to_tlv(sigbuf, siglen, &seq) < 0) {
        mg_error(c, "verification message is not an ASN.1 DER sequence");
        return -1;
      }
      if (mg_der_to_tlv(seq.value, seq.len, &r) < 0) {
        mg_error(c, "missing first part of the signature");
        return -1;
      }
      if (mg_der_to_tlv(r.value + r.len, seq.len - r.len, &s) < 0) {
        mg_error(c, "missing second part of the signature");
        return -1;
      }
      // Integers may be padded with zeroes
      if (r.len > 32) r.value = r.value + (r.len - 32), r.len = 32;
      if (s.len > 32) s.value = s.value + (s.len - 32), s.len = 32;

      // r or s may be shorter than 32 bytes, "right-justify" (network order)
      memmove(sig + (32 - r.len), r.value, r.len);
      memmove(sig + 32 + (32 - s.len), s.value, s.len);

      if (mg_uecc_verify(tls->pubkey, tls->sighash, sizeof(tls->sighash), sig,
                         mg_uecc_secp256r1()) != 1) {
        mg_error(c, "failed to verify EC certificate (certverify)");
        return -1;
      }
      MG_VERBOSE(("certificate verification successful (EC)"));
    } else {
      // From
      // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml:
      //   0805 = rsa_pss_rsae_sha384
      //   0806 = rsa_pss_rsae_sha512
      //   0807 = ed25519
      //   0808 = ed448
      //   0809 = rsa_pss_pss_sha256
      //   080A = rsa_pss_pss_sha384
      //   080B = rsa_pss_pss_sha512
      MG_ERROR(("unsupported certverify signature scheme: %x of %d bytes",
                sigalg, siglen));
      return -1;
    }
  }
  mg_tls_drop_message(c);
  return 0;
}

static int mg_tls_client_recv_finish(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  unsigned char *recv_buf;
  if (mg_tls_recv_record(c) < 0) {
    return -1;
  }
  recv_buf = &c->rtls.buf[tls->recv_offset];
  if (recv_buf[0] != MG_TLS_FINISHED) {
    mg_error(c, "expected server finished but got msg 0x%02x", recv_buf[0]);
    return -1;
  }
  mg_tls_drop_message(c);
  return 0;
}

static bool mg_tls_client_send_finish(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  mg_sha256_ctx sha256;
  uint8_t hash[32];
  uint8_t finish[36] = {0x14, 0, 0, 32};
  memmove(&sha256, &tls->sha256, sizeof(mg_sha256_ctx));
  mg_sha256_final(hash, &sha256);
  mg_hmac_sha256(finish + 4, tls->enc.client_finished_key, 32, hash, 32);
  return mg_tls_encrypt(c, finish, sizeof(finish), MG_TLS_HANDSHAKE);
}

static bool mg_tls_client_handshake(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  switch (tls->state) {
    case MG_TLS_STATE_CLIENT_START:
      if (!mg_tls_client_send_hello(c)) return false;
      tls->state = MG_TLS_STATE_CLIENT_WAIT_SH;
      // Fallthrough
    case MG_TLS_STATE_CLIENT_WAIT_SH:
      if (mg_tls_client_recv_hello(c) < 0) break;
      tls->state = MG_TLS_STATE_CLIENT_WAIT_EE;
      // Fallthrough
    case MG_TLS_STATE_CLIENT_WAIT_EE:
      if (mg_tls_client_recv_ext(c) < 0) break;
      tls->state = MG_TLS_STATE_CLIENT_WAIT_CERT;
      // Fallthrough
    case MG_TLS_STATE_CLIENT_WAIT_CERT:
      if (mg_tls_recv_cert(c, true) < 0) break;
      tls->state = MG_TLS_STATE_CLIENT_WAIT_CV;
      // Fallthrough
    case MG_TLS_STATE_CLIENT_WAIT_CV:
      if (mg_tls_recv_cert_verify(c) < 0) break;
      tls->state = MG_TLS_STATE_CLIENT_WAIT_FINISH;
      // Fallthrough
    case MG_TLS_STATE_CLIENT_WAIT_FINISH:
      if (mg_tls_client_recv_finish(c) < 0) break;
      if (tls->cert_requested && tls->cert_der.len > 0) {  // two-way auth
        // generate application keys at this point, keep using handshake keys
        struct tls_enc hs_keys = tls->enc;
        mg_tls_generate_application_keys(c);
        tls->app_keys = tls->enc;
        tls->enc = hs_keys;
        if (!mg_tls_send_cert(c, true) || !mg_tls_send_cert_verify(c, true) ||
            !mg_tls_client_send_finish(c))
          return false;
        tls->enc = tls->app_keys;
      } else {
        if (!mg_tls_client_send_finish(c)) return false;
        mg_tls_generate_application_keys(c);
      }
      tls->state = MG_TLS_STATE_CLIENT_CONNECTED;
      c->is_tls_hs = 0;
      mg_call(c, MG_EV_TLS_HS, NULL);
      break;
    default:
      mg_error(c, "unexpected client state: %d", tls->state);
      break;
  }
  return true;
}

static bool mg_tls_server_handshake(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  switch (tls->state) {
    case MG_TLS_STATE_SERVER_START:
      if (mg_tls_server_recv_hello(c) < 0) break;
      if (!mg_tls_server_send_hello(c)) return false;
      mg_tls_generate_handshake_keys(c);
      if (!mg_tls_server_send_ext(c)) return false;
      if (tls->is_twoway && !mg_tls_server_send_cert_request(c)) return false;
      if (!mg_tls_send_cert(c, false) || !mg_tls_send_cert_verify(c, false) ||
          !mg_tls_server_send_finish(c))
        return false;
      if (tls->is_twoway) {
        // generate application keys at this point, keep using handshake keys
        struct tls_enc hs_keys = tls->enc;
        mg_tls_generate_application_keys(c);
        tls->app_keys = tls->enc;
        tls->enc = hs_keys;
        tls->state = MG_TLS_STATE_SERVER_WAIT_CERT;
        break;
      }
      tls->state = MG_TLS_STATE_SERVER_NEGOTIATED;
      // fallthrough
    case MG_TLS_STATE_SERVER_NEGOTIATED:
      if (mg_tls_server_recv_finish(c) < 0) break;
      if (tls->is_twoway) {  // use previously generated keys
        tls->enc = tls->app_keys;
      } else {  // generate keys now
        mg_tls_generate_application_keys(c);
      }
      tls->state = MG_TLS_STATE_SERVER_CONNECTED;
      c->is_tls_hs = 0;
      break;
    case MG_TLS_STATE_SERVER_WAIT_CERT:
      if (mg_tls_recv_cert(c, false) < 0) break;
      tls->state = MG_TLS_STATE_SERVER_WAIT_CV;
      // Fallthrough
    case MG_TLS_STATE_SERVER_WAIT_CV:
      if (mg_tls_recv_cert_verify(c) < 0) break;
      tls->state = MG_TLS_STATE_SERVER_NEGOTIATED;
      break;
    default:
      mg_error(c, "unexpected server state: %d", tls->state);
      break;
  }
  return true;
}

void mg_tls_handshake(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  long n;
  bool res;
  if (c->is_closing) return;  // we don't clear rx buf, so ignore what's left
  if (tls->is_sntp_pending) return;
  if (c->is_client) {
    // will clear is_hs when sending last chunk
    res = mg_tls_client_handshake(c);
  } else {
    res = mg_tls_server_handshake(c);
  }
  if (!res) {
    mg_error(c, "TLS OOM");
    return;
  }
  while (tls->send.len > 0 &&
         (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
    mg_iobuf_del(&tls->send, 0, (size_t) n);
  }  // if last chunk fails to be sent, it will be sent with first app data,
     // otherwise, it needs to be flushed
}

static int mg_rsa_parse_der_int(const uint8_t **p, const uint8_t *end,
                                struct mg_str *out) {
  const uint8_t *start = *p, *value_start, *value_end;
  uint8_t i;
  uint32_t len;

  if (end - start < 2) {
    MG_VERBOSE(("DER INT: not enough bytes (%d < 2)", (int) (end - start)));
    return -1;
  }
  if (start[0] != 0x02) {
    MG_VERBOSE(("DER INT: expected 0x02, got 0x%02x", start[0]));
    return -1;
  }

  len = start[1];
  *p = start + 2;

  if (len > 0x7F) {
    // Long form length
    uint8_t len_bytes = len & 0x7F;
    MG_VERBOSE(("DER INT: long form, %d length bytes", len_bytes));
    if (len_bytes == 0 || len_bytes > 4 || (size_t) (end - *p) < len_bytes) {
      MG_VERBOSE(("DER INT: not enough bytes for length"));
      return -1;
    }
    len = 0;
    for (i = 0; i < len_bytes; i++) {
      len = (len << 8) | (*p)[i];
    }
    *p += len_bytes;
  }

  MG_VERBOSE(("DER INT: length=%u, remaining=%d", len, (int) (end - *p)));

  if ((size_t) (end - *p) < len) {
    MG_VERBOSE(("DER INT: length exceeds remaining bytes"));
    return -1;
  }

  // The encoded length tells us how many bytes to consume from the stream
  value_start = *p;
  value_end = *p + len;

  // Skip leading zero byte if present (for positive numbers)
  // This doesn't change how many bytes we consume, just what we expose
  if (len > 0 && (*p)[0] == 0x00) {
    (*p)++;
    len--;
  }

  out->buf = (char *) *p;
  out->len = len;

  // Advance pointer by the ORIGINAL encoded length, not the adjusted length
  *p = value_end;

  MG_VERBOSE(("DER INT: parsed %u bytes (skipped zero=%d)", len,
              (size_t) (value_end - value_start) != (size_t) len ? 1 : 0));
  return 0;
}

// RFC 5915 ECPrivateKey ::= SEQUENCE {
//   version INTEGER { ecPrivkeyVer1(1) },
//   privateKey OCTET STRING,
//   parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
//   publicKey [1] BIT STRING OPTIONAL
// }
static int mg_parse_ec_private_key(const uint8_t *der, size_t dersz,
                                   uint8_t *ec_key) {
  struct mg_der_tlv root, version, private_key_octets;

  if (mg_der_parse((uint8_t *) der, dersz, &root) < 0 || root.type != 0x30) {
    MG_ERROR(("EC private key: invalid SEQUENCE"));
    return -1;
  }

  if (mg_der_next(&root, &version) <= 0 || version.type != 0x02) {
    MG_ERROR(("EC private key: invalid version"));
    return -1;
  }

  if (mg_der_next(&root, &private_key_octets) <= 0 ||
      private_key_octets.type != 0x04) {
    MG_ERROR(("EC private key: invalid privateKey OCTET STRING"));
    return -1;
  }

  if (private_key_octets.len != 32) {
    MG_ERROR(
        ("EC private key: expected 32 bytes, got %u", private_key_octets.len));
    return -1;
  }

  memcpy(ec_key, private_key_octets.value, 32);
  return 0;
}

// Parse RSA private key from DER format
// RSAPrivateKey ::= SEQUENCE {
//   version           INTEGER (0),
//   modulus           INTEGER,  -- n
//   publicExponent    INTEGER,  -- e
//   privateExponent   INTEGER,  -- d
//   prime1            INTEGER,  -- p
//   prime2            INTEGER,  -- q
//   exponent1         INTEGER,  -- dP = d mod (p-1)
//   exponent2         INTEGER,  -- dQ = d mod (q-1)
//   coefficient       INTEGER,  -- qInv = (inverse of q) mod p
// }
static int mg_rsa_parse_key(const uint8_t *der, size_t dersz,
                            struct mg_rsa_key *key) {
  const uint8_t *p = der;
  const uint8_t *end = der + dersz;
  uint32_t seq_len;
  struct mg_str version;

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

  // Parse outer SEQUENCE
  if (end - p < 2) {
    MG_ERROR(("RSA key too short for SEQUENCE header"));
    return -1;
  }
  if (p[0] != 0x30) {
    MG_ERROR(("RSA key: expected SEQUENCE (0x30), got 0x%02x", p[0]));
    return -1;
  }

  seq_len = p[1];
  p += 2;

  if (seq_len > 0x7F) {
    // Long form length
    uint8_t i, len_bytes = seq_len & 0x7F;
    MG_VERBOSE(("Long form length: %d bytes", len_bytes));
    if (len_bytes == 0 || len_bytes > 4 || (size_t) (end - p) < len_bytes) {
      MG_ERROR(("Not enough bytes for long form length"));
      return -1;
    }
    seq_len = 0;
    for (i = 0; i < len_bytes; i++) {
      seq_len = (seq_len << 8) | p[i];
    }
    p += len_bytes;
  }

  MG_VERBOSE(
      ("SEQUENCE length: %u, total DER size: %u", seq_len, (unsigned) dersz));

  if ((size_t) (end - p) < seq_len) {
    MG_ERROR(("SEQUENCE length exceeds buffer"));
    return -1;
  }
  end = p + seq_len;  // Adjust end to sequence boundary

  // Parse version (should be 0)
  MG_VERBOSE(("Before version: offset=%d, bytes: %02x %02x %02x %02x",
              (int) (p - der), p < end ? p[0] : 0xFF,
              p + 1 < end ? p[1] : 0xFF, p + 2 < end ? p[2] : 0xFF,
              p + 3 < end ? p[3] : 0xFF));
  if (mg_rsa_parse_der_int(&p, end, &version) < 0) {
    MG_ERROR(("Failed to parse version"));
    return -1;
  }
  MG_DEBUG(("Version: %d byte(s), value=%d, offset now=%d", (int) version.len,
            version.len > 0 ? (int) (unsigned char) version.buf[0] : -1,
            (int) (p - der)));

  // Parse the 8 components: n, e, d, p, q, dP, dQ, qInv
  MG_VERBOSE(("Before n: offset=%d, bytes: %02x %02x %02x %02x %02x %02x",
              (int) (p - der), p < end ? p[0] : 0xFF,
              p + 1 < end ? p[1] : 0xFF, p + 2 < end ? p[2] : 0xFF,
              p + 3 < end ? p[3] : 0xFF, p + 4 < end ? p[4] : 0xFF,
              p + 5 < end ? p[5] : 0xFF));
  if (mg_rsa_parse_der_int(&p, end, &key->n) < 0) {
    MG_ERROR(("Failed to parse n (modulus)"));
    return -1;
  }
  MG_VERBOSE(("Parsed n: %d bytes, offset now=%d, consumed=%d bytes total",
              (int) key->n.len, (int) (p - der), (int) (p - der)));
  MG_VERBOSE(("  First 8 bytes of n: %02x %02x %02x %02x %02x %02x %02x %02x",
              key->n.len > 0 ? (unsigned char) key->n.buf[0] : 0xFF,
              key->n.len > 1 ? (unsigned char) key->n.buf[1] : 0xFF,
              key->n.len > 2 ? (unsigned char) key->n.buf[2] : 0xFF,
              key->n.len > 3 ? (unsigned char) key->n.buf[3] : 0xFF,
              key->n.len > 4 ? (unsigned char) key->n.buf[4] : 0xFF,
              key->n.len > 5 ? (unsigned char) key->n.buf[5] : 0xFF,
              key->n.len > 6 ? (unsigned char) key->n.buf[6] : 0xFF,
              key->n.len > 7 ? (unsigned char) key->n.buf[7] : 0xFF));
  MG_VERBOSE(("  Next bytes after n: %02x %02x %02x %02x %02x %02x",
              p < end ? p[0] : 0xFF, p + 1 < end ? p[1] : 0xFF,
              p + 2 < end ? p[2] : 0xFF, p + 3 < end ? p[3] : 0xFF,
              p + 4 < end ? p[4] : 0xFF, p + 5 < end ? p[5] : 0xFF));

  if (mg_rsa_parse_der_int(&p, end, &key->e) < 0) {
    MG_ERROR(("Failed to parse e (public exponent), bytes remaining: %d",
              (int) (end - p)));
    if (end - p >= 4) {
      MG_ERROR(("  Next 4 bytes: %02x %02x %02x %02x", p[0], p[1], p[2], p[3]));
    }
    return -1;
  }
  if (mg_rsa_parse_der_int(&p, end, &key->d) < 0) {
    MG_ERROR(("Failed to parse d (private exponent)"));
    return -1;
  }
  if (mg_rsa_parse_der_int(&p, end, &key->p) < 0) {
    MG_ERROR(("Failed to parse p (prime1)"));
    return -1;
  }
  if (mg_rsa_parse_der_int(&p, end, &key->q) < 0) {
    MG_ERROR(("Failed to parse q (prime2)"));
    return -1;
  }
  if (mg_rsa_parse_der_int(&p, end, &key->dP) < 0) {
    MG_ERROR(("Failed to parse dP (exponent1)"));
    return -1;
  }
  if (mg_rsa_parse_der_int(&p, end, &key->dQ) < 0) {
    MG_ERROR(("Failed to parse dQ (exponent2)"));
    return -1;
  }
  if (mg_rsa_parse_der_int(&p, end, &key->qInv) < 0) {
    MG_ERROR(("Failed to parse qInv (coefficient)"));
    return -1;
  }

  MG_VERBOSE(("Successfully parsed RSA key"));
  return 0;
}

// PKCS#8 PrivateKeyInfo ::= SEQUENCE {
//   version INTEGER,
//   privateKeyAlgorithm AlgorithmIdentifier,
//   privateKey OCTET STRING,
//   attributes [0] Attributes OPTIONAL
// }
// AlgorithmIdentifier ::= SEQUENCE {
//   algorithm OBJECT IDENTIFIER,
//   parameters ANY OPTIONAL
// }
static int mg_parse_pkcs8_key(const uint8_t *der, size_t dersz,
                              struct mg_rsa_key *rsa, uint8_t *ec_key) {
  struct mg_der_tlv root, version, alg_id, private_key_octets;
  struct mg_der_tlv alg_oid, alg_params;

  if (mg_der_parse((uint8_t *) der, dersz, &root) < 0 || root.type != 0x30) {
    MG_ERROR(("PKCS#8: invalid PrivateKeyInfo SEQUENCE"));
    return -1;
  }

  if (mg_der_next(&root, &version) <= 0 || version.type != 0x02) {
    MG_ERROR(("PKCS#8: invalid version"));
    return -1;
  }

  if (mg_der_next(&root, &alg_id) <= 0 || alg_id.type != 0x30) {
    MG_ERROR(("PKCS#8: invalid AlgorithmIdentifier SEQUENCE"));
    return -1;
  }

  if (mg_der_next(&alg_id, &alg_oid) <= 0 || alg_oid.type != 0x06) {
    MG_ERROR(("PKCS#8: invalid algorithm OID"));
    return -1;
  }

  if (mg_der_next(&root, &private_key_octets) <= 0 ||
      private_key_octets.type != 0x04) {
    MG_ERROR(("PKCS#8: invalid privateKey OCTET STRING"));
    return -1;
  }

  if (alg_oid.len == sizeof(mg_rsa_oid) &&
      memcmp(alg_oid.value, mg_rsa_oid, sizeof(mg_rsa_oid)) == 0) {
    struct mg_rsa_key rsa_key;
    if (rsa == NULL) return -1;
    if (mg_rsa_parse_key(private_key_octets.value, private_key_octets.len,
                         &rsa_key) < 0) {
      MG_ERROR(("PKCS#8: failed to parse inner RSA key"));
      return -1;
    }
    *rsa = rsa_key;
    return 0;

  } else if (alg_oid.len == sizeof(mg_ec_public_key_oid) &&
             memcmp(alg_oid.value, mg_ec_public_key_oid,
                    sizeof(mg_ec_public_key_oid)) == 0) {
    if (ec_key == NULL) return -1;
    if (mg_der_next(&alg_id, &alg_params) <= 0 || alg_params.type != 0x06) {
      MG_ERROR(("PKCS#8: invalid EC parameters OID"));
      return -1;
    }

    if (alg_params.len != sizeof(mg_secp256r1_oid) ||
        memcmp(alg_params.value, mg_secp256r1_oid, sizeof(mg_secp256r1_oid)) !=
            0) {
      MG_ERROR(("PKCS#8: unsupported EC curve (only secp256r1 supported)"));
      return -1;
    }

    return mg_parse_ec_private_key(private_key_octets.value,
                                   private_key_octets.len, ec_key);

  } else {
    MG_ERROR(("PKCS#8: unsupported algorithm"));
    return -1;
  }
}

static int mg_parse_pem(const struct mg_str pem, const struct mg_str label,
                        struct mg_str *der) {
  size_t n = 0, m = 0;
  char *s;
  const char *c;
  struct mg_str caps[6];  // number of wildcards + 1
  if (!mg_match(pem, mg_str("#-----BEGIN #-----#-----END #-----#"), caps)) {
    *der = mg_strdup(pem);
    return 0;
  }
  if (mg_strcmp(caps[1], label) != 0 || mg_strcmp(caps[3], label) != 0) {
    return -1;  // bad label
  }
  if ((s = (char *) mg_calloc(1, caps[2].len)) == NULL) {
    return -1;
  }

  for (c = caps[2].buf; c < caps[2].buf + caps[2].len; c++) {
    if (*c == ' ' || *c == '\n' || *c == '\r' || *c == '\t') {
      continue;
    }
    s[n++] = *c;
  }
  m = mg_base64_decode(s, n, s, n);
  if (m == 0) {
    mg_free(s);
    return -1;
  }
  der->buf = s;
  der->len = m;
  return 0;
}

static int mg_parse_pem_certs(const struct mg_str pem, struct mg_str **ders) {
  int count = 0;
  struct mg_str *certs = NULL;
  const char *p = pem.buf;
  const char *end = pem.buf + pem.len;
  const char *begin_marker = "-----BEGIN CERTIFICATE-----";
  const char *end_marker = "-----END CERTIFICATE-----";
  size_t begin_len = strlen(begin_marker);
  size_t end_len = strlen(end_marker);

  while (p < end) {
    const char *s, *begin = NULL, *finish = NULL;
    struct mg_str cert_pem, cert_der, *new_certs;
    int i;

    for (s = p; s <= end - (int) begin_len; s++) {
      if (memcmp(s, begin_marker, begin_len) == 0) {
        begin = s;
        break;
      }
    }
    if (begin == NULL) break;

    for (s = begin + begin_len; s <= end - (int) end_len; s++) {
      if (memcmp(s, end_marker, end_len) == 0) {
        finish = s + end_len;
        break;
      }
    }
    if (finish == NULL) {
      for (i = 0; i < count; i++) mg_free((void *) certs[i].buf);
      mg_free(certs);
      return -1;
    }

    cert_pem = mg_str_n(begin, (size_t) (finish - begin));
    if (mg_parse_pem(cert_pem, mg_str_s("CERTIFICATE"), &cert_der) < 0) {
      for (i = 0; i < count; i++) mg_free((void *) certs[i].buf);
      mg_free(certs);
      return -1;
    }

    new_certs =
        (struct mg_str *) mg_calloc((size_t) count + 1, sizeof(*new_certs));
    if (new_certs == NULL) {
      mg_free((void *) cert_der.buf);
      for (i = 0; i < count; i++) mg_free((void *) certs[i].buf);
      mg_free(certs);
      return -1;
    }
    if (count > 0) {
      memmove(new_certs, certs, (size_t) count * sizeof(struct mg_str));
      mg_free(certs);
    }
    certs = new_certs;

    certs[count++] = cert_der;
    p = finish;
  }

  *ders = certs;
  return count;
}

size_t mg_uecc_parse_private_key(struct mg_str key, uint8_t *buf, size_t len) {
  struct mg_str der = mg_str_n(NULL, 0);
  size_t n = 0;
  if (buf == NULL || len < 32) return 0;
  // current mg_parse_ec_private_key() only handles 32-byte keys
  if (mg_parse_pem(key, mg_str_s("EC PRIVATE KEY"), &der) == 0) {
    if (mg_parse_ec_private_key((uint8_t *) der.buf, der.len, buf) == 0) n = 32;
  } else if (mg_parse_pem(key, mg_str_s("PRIVATE KEY"), &der) == 0) {
    if (mg_parse_pkcs8_key((uint8_t *) der.buf, der.len, NULL, buf) == 0) n = 32;
  }
  mg_free((void *) der.buf);
  return n;
}

static void sync_time_cancel(struct mg_connection *c) {
  c->fn = NULL;
  c->is_closing = 1;
}

static void sync_time_cb(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_SNTP_TIME || (ev == MG_EV_CLOSE)) {
    struct mg_connection *tlsc = (struct mg_connection *) c->fn_data;
    struct tls_data *tls = (struct tls_data *) tlsc->tls;
    sync_time_cancel(c);  // c = tls->timec
    tls->timec = NULL;
    tls->is_sntp_pending = false;
    if (ev == MG_EV_SNTP_TIME) mg_tls_handshake(tlsc);
    if (ev == MG_EV_CLOSE) mg_error(tlsc, "time sync failed");
  }
  (void) ev_data;
}

void mg_tls_init(struct mg_connection *c, const struct mg_tls_opts *opts) {
  struct mg_str key;
  struct tls_data *tls =
      (struct tls_data *) mg_calloc(1, sizeof(struct tls_data));
  if (tls == NULL) {
    mg_error(c, "tls oom");
    return;
  }

  tls->state =
      c->is_client ? MG_TLS_STATE_CLIENT_START : MG_TLS_STATE_SERVER_START;

  tls->skip_verification = opts->skip_verification;
  // tls->send.align = MG_IO_SIZE;

  c->tls = tls;
  c->is_tls = c->is_tls_hs = 1;
  mg_sha256_init(&tls->sha256);

  // save hostname (client extension)
  if (opts->name.len > 0) {
    if (opts->name.len >= sizeof(tls->hostname) - 1) {
      mg_error(c, "hostname too long");
      return;
    }
    strncpy((char *) tls->hostname, opts->name.buf, sizeof(tls->hostname) - 1);
    tls->hostname[opts->name.len] = 0;
  }

  // server CA certificate; parse PEM [bundle] or DER
  if (opts->ca.len > 0)  {
    struct mg_str *all_certs = NULL;
    int cert_count = mg_parse_pem_certs(opts->ca, &all_certs);

    if (cert_count > 1 && c->is_client) { // use bundle for clients only
      tls->ca_bundle_len = (size_t) cert_count;
      tls->ca_bundle_der = all_certs;
      MG_VERBOSE(("%d-cert bundle", cert_count));
    } else if (cert_count > 0) {
      tls->ca_der.buf = all_certs[0].buf;
      tls->ca_der.len = all_certs[0].len;
      mg_free(all_certs);
    } else { // parse again for a possible DER (or a truncated begin string)
      if (mg_parse_pem(opts->ca, mg_str_s("CERTIFICATE"), &tls->ca_der) < 0) {
        MG_ERROR(("Failed to load CA certificate"));
        goto xit;
      }
    } // ca_bundle_len != 0 && ca_der.len = 0 => bundle
    if (!c->is_client) tls->is_twoway = true;  // server + CA: two-way auth
  }

  if (opts->cert.buf == NULL) {
    MG_VERBOSE(("No certificate provided"));
    goto xit;
  }

  // parse PEM or DER certificate
  {
    struct mg_str *all_certs = NULL;
    int cert_count = mg_parse_pem_certs(opts->cert, &all_certs);

    if (cert_count > 0) {
      tls->cert_der.buf = all_certs[0].buf;
      tls->cert_der.len = all_certs[0].len;
      if (cert_count > 1) {
        tls->chain_len = (size_t) cert_count;
        tls->chain_der = all_certs;
      } else {
        mg_free(all_certs);
      }
    } else { // parse again for a possible DER (or a truncated begin string)
      if (mg_parse_pem(opts->cert, mg_str_s("CERTIFICATE"), &tls->cert_der) <
          0) {
        MG_ERROR(("Failed to load certificate"));
        return;
      }
    }
  }

  // parse PEM or DER EC key
  if (opts->key.buf == NULL) {
    mg_error(c, "Certificate provided without a private key");
    return;
  }

  if (mg_parse_pem(opts->key, mg_str_s("EC PRIVATE KEY"), &key) == 0) {
    // expect ASN.1 SEQUENCE=[INTEGER=1, BITSTRING of 32 bytes, ...]
    // 30 nn 02 01 01 04 20 [key] ...
    if (key.len < (2 + 5 + 32) || key.buf[0] != 0x30 ||
        (key.buf[1] & 0x80) != 0 ||
        memcmp(key.buf + 2, "\x02\x01\x01\x04\x20", 5) != 0) {
      mg_error(c, "EC private key: invalid ASN.1");
    } else {
      memmove(tls->ec_key, key.buf + 7, 32);
    }
    mg_free((void *) key.buf);
  } else if (mg_parse_pem(opts->key, mg_str_s("RSA PRIVATE KEY"), &key) == 0) {
    struct mg_rsa_key rsa_key;
    // RSA private key found, store it for later use
    tls->rsa_key_der = key;

    // parse and validate the key structure
    // we keep the DER buffer, rsa_key just points into it
    if (mg_rsa_parse_key((const uint8_t *) key.buf, key.len, &rsa_key) < 0) {
      MG_ERROR(("Failed to parse RSA private key structure"));
      mg_free((void *) key.buf);
      tls->rsa_key_der = mg_str_n(NULL, 0);
      mg_error(c, "Invalid RSA private key format");
      return;
    }

    MG_VERBOSE(("RSA key components:"));
    MG_VERBOSE(("  n (modulus):  %d bytes", (int) rsa_key.n.len));
    MG_VERBOSE(("  e (pubexp):   %d bytes", (int) rsa_key.e.len));
    MG_VERBOSE(("  d (privexp):  %d bytes", (int) rsa_key.d.len));
    MG_VERBOSE(("  p (prime1):   %d bytes", (int) rsa_key.p.len));
    MG_VERBOSE(("  q (prime2):   %d bytes", (int) rsa_key.q.len));
    MG_VERBOSE(("  dP:           %d bytes", (int) rsa_key.dP.len));
    MG_VERBOSE(("  dQ:           %d bytes", (int) rsa_key.dQ.len));
    MG_VERBOSE(("  qInv:         %d bytes", (int) rsa_key.qInv.len));

    // Copy parsed RSA key components to tls->rsa for signing operations
    tls->rsa = rsa_key;
  } else if (mg_parse_pem(opts->key, mg_str_s("PRIVATE KEY"), &key) == 0) {
    if (mg_parse_pkcs8_key((const uint8_t *) key.buf, key.len, &tls->rsa,
                           tls->ec_key) == 0) {
      if (tls->rsa.n.len > 0) {
        tls->rsa_key_der = key;
        MG_INFO(("Parsed PKCS#8 RSA private key: %d bytes", (int) key.len));
      } else {
        mg_free((void *) key.buf);
        MG_INFO(("Parsed PKCS#8 EC private key"));
      }
    } else {
      mg_free((void *) key.buf);
      mg_error(c, "Unsupported PKCS#8 private key format, algorithm, or curve");
      return;
    }
  } else {
    mg_error(
        c, "Expected EC PRIVATE KEY, RSA PRIVATE KEY, or PRIVATE KEY (PKCS#8)");
    return;
  }
xit:
  if (mg_boot_timestamp_ms == 0 && !tls->skip_verification &&
      (tls->ca_bundle_len > 0 || tls->ca_der.len > 0)) {
    struct mg_connection *timec;
    timec = mg_sntp_connect(c->mgr, NULL, sync_time_cb, c);
    if (timec == NULL) {
      mg_error(c, "time sync failed");
      return;
    }
    tls->is_sntp_pending = true;
    tls->timec = timec;
  }
}

void mg_tls_free(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  size_t i;
  if (tls != NULL) {
    if (tls->timec != NULL) {
      sync_time_cancel(tls->timec);
      tls->timec = NULL;
    }
    mg_iobuf_free(&tls->send);
    if (tls->chain_der != NULL) {
      for (i = 0; i < tls->chain_len; i++) {
        mg_free((void *) tls->chain_der[i].buf);
      }
      mg_free(tls->chain_der);
    } else {
      mg_free((void *) tls->cert_der.buf);
    }
    if (tls->ca_bundle_der != NULL) {
      for (i = 0; i < tls->ca_bundle_len; i++) {
        mg_free((void *) tls->ca_bundle_der[i].buf);
      }
      mg_free(tls->ca_bundle_der);
    }
    mg_free((void *) tls->ca_der.buf);
    mg_free((void *) tls->rsa_key_der.buf);
  }
  mg_free(c->tls);
  c->tls = NULL;
}

long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  long n = MG_IO_WAIT;
  bool was_throttled = c->is_tls_throttled;  // see #3074
  if (!was_throttled) {                      // encrypt new data
    if (len > MG_IO_SIZE) len = MG_IO_SIZE;
    if (len > 16384) len = 16384;
    if (!mg_tls_encrypt(c, (const uint8_t *) buf, len, MG_TLS_APP_DATA))
      return 0;  // returning 0 means an OOM condition (iobuf couldn't resize),
                 // yet this is so far recoverable, let the caller decide
  }  // else, resend outstanding encrypted data in tls->send
  while (tls->send.len > 0 &&
         (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
    mg_iobuf_del(&tls->send, 0, (size_t) n);
  }  // if last chunk fails to be sent, it needs to be flushed
  c->is_tls_throttled = (tls->send.len > 0 && n == MG_IO_WAIT);
  MG_VERBOSE(("%lu %ld %ld %ld %c %c", c->id, (long) len, (long) tls->send.len,
              n, was_throttled ? 'T' : 't', c->is_tls_throttled ? 'T' : 't'));
  if (n == MG_IO_ERR) return MG_IO_ERR;
  if (was_throttled) return MG_IO_WAIT;  // sent throttled data instead
  return (long) len;  // return len even when throttled, already encripted that
}

long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) {
  int r = 0;
  struct tls_data *tls = (struct tls_data *) c->tls;
  unsigned char *recv_buf;
  size_t minlen;

  for (;;) {
    r = mg_tls_recv_record(c);
    if (r < 0) return r;
    if (tls->content_type == MG_TLS_APP_DATA) break;
    tls->recv_len = 0;
    mg_tls_drop_record(c);
  }

  if (buf == NULL || len == 0) return 0L;
  recv_buf = &c->rtls.buf[tls->recv_offset];
  minlen = len < tls->recv_len ? len : tls->recv_len;
  memmove(buf, recv_buf, minlen);
  tls->recv_offset += minlen;
  tls->recv_len -= minlen;
  if (tls->recv_len == 0) mg_tls_drop_record(c);
  return (long) minlen;
}

size_t mg_tls_pending(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  return tls != NULL ? tls->recv_len : 0;
}

void mg_tls_flush(struct mg_connection *c) {
  struct tls_data *tls = (struct tls_data *) c->tls;
  long n;
  while (tls->send.len > 0 &&
         (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
    mg_iobuf_del(&tls->send, 0, (size_t) n);
  }
}

void mg_tls_ctx_init(struct mg_mgr *mgr) {
  (void) mgr;
}

void mg_tls_ctx_free(struct mg_mgr *mgr) {
  (void) mgr;
}
#endif
