/** * Parse the metaserver certificate information. * * @param server * Metaserver entry. * @return * True on success, false on failure. */ static bool parse_metaserver_cert (server_struct *server) { HARD_ASSERT(server != NULL); if (server->cert == NULL || server->cert_sig == NULL) { /* No certificate. */ return true; } /* Generate a SHA512 hash of the certificate's contents. */ unsigned char cert_digest[SHA512_DIGEST_LENGTH]; if (SHA512((unsigned char *) server->cert, strlen(server->cert), cert_digest) == NULL) { LOG(ERROR, "SHA512() failed: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } char cert_hash[SHA512_DIGEST_LENGTH * 2 + 1]; SOFT_ASSERT_RC(string_tohex(VS(cert_digest), VS(cert_hash), false) == sizeof(cert_hash) - 1, false, "string_tohex failed"); string_tolower(cert_hash); /* Verify the signature. */ if (!curl_verify(CURL_PKEY_TRUST_ULTIMATE, cert_hash, strlen(cert_hash), server->cert_sig, server->cert_sig_len)) { LOG(ERROR, "Failed to verify signature"); return false; } server_cert_info_t *info = ecalloc(1, sizeof(*info)); char buf[MAX_BUF]; size_t pos = 0; bool in_info = false; while (string_get_word(server->cert, &pos, '\n', VS(buf), 0)) { char *cp = buf; string_skip_whitespace(cp); string_strip_newline(cp); if (*cp == '\0') { continue; } if (strcmp(cp, cert_begin_str) == 0) { in_info = true; continue; } else if (!in_info) { continue; } else if (strcmp(cp, cert_end_str) == 0) { break; } char *cps[2]; if (string_split(cp, cps, arraysize(cps), ':') != arraysize(cps)) { LOG(ERROR, "Parsing error"); continue; } string_tolower(cps[0]); string_skip_whitespace(cps[1]); const char *key = cps[0]; const char *value = cps[1]; char **content = NULL; if (strcmp(key, "name") == 0) { content = &info->name; } else if (strcmp(key, "hostname") == 0) { content = &info->hostname; } else if (strcmp(key, "ipv4 address") == 0) { content = &info->ipv4_address; } else if (strcmp(key, "ipv6 address") == 0) { content = &info->ipv6_address; } else if (strcmp(key, "public key") == 0) { content = &info->pubkey; } else if (strcmp(key, "port") == 0) { info->port = atoi(value); } else if (strcmp(key, "crypto port") == 0) { info->port_crypto = atoi(value); } else { LOG(DEVEL, "Unrecognized key: %s", key); continue; } if (content != NULL) { StringBuffer *sb = stringbuffer_new(); if (*content != NULL) { stringbuffer_append_string(sb, *content); stringbuffer_append_char(sb, '\n'); efree(*content); } stringbuffer_append_string(sb, value); *content = stringbuffer_finish(sb); } } /* Ensure we got the data we need. */ if (info->name == NULL || info->hostname == NULL || info->pubkey == NULL || info->port_crypto <= 0 || (info->ipv4_address == NULL) != (info->ipv6_address == NULL)) { LOG(ERROR, "Certificate is missing required data."); goto error; } /* Ensure certificate attributes match the advertised ones. */ if (strcmp(info->hostname, server->hostname) != 0) { LOG(ERROR, "Certificate hostname does not match advertised hostname."); goto error; } if (strcmp(info->name, server->name) != 0) { LOG(ERROR, "Certificate name does not match advertised name."); goto error; } if (info->port != server->port) { LOG(ERROR, "Certificate port does not match advertised port."); goto error; } if (info->port_crypto != server->port_crypto) { LOG(ERROR, "Certificate crypto port does not match advertised crypto port."); goto error; } server->cert_info = info; return true; error: metaserver_cert_free(info); return false; }
QUVIcode verify_wrapper(_quvi_t q, _quvi_llst_node_t l) { static const char *scheme = "http://"; _quvi_media_url_t m; _quvi_net_t n; QUVIcode rc; char b[8]; rc = QUVI_OK; m = (_quvi_media_url_t) l->data; m->url = from_html_entities(m->url); memset(&b, 0, sizeof(b)); if (strcmp(strncpy(b, m->url, strlen(scheme)), scheme) != 0) return (QUVI_OK); /* Ignore non-HTTP URLs */ if (q->status_func) { if (q->status_func(_makelong(QUVISTATUS_VERIFY, 0), 0) != QUVI_OK) return (QUVI_ABORTEDBYCALLBACK); } n = new_net_handle(); if (!n) return (QUVI_MEM); freprintf(&n->url, "%s", m->url); if (q->verify_func) rc = q->verify_func(n); else rc = curl_verify(q, n); if (rc == QUVI_OK) { freprintf(&m->content_type, "%s", n->verify.content_type); m->length = n->verify.content_length; rc = run_lua_suffix_func(q, m); /* content-type -> suffix */ if (q->status_func) { const long param = _makelong(QUVISTATUS_VERIFY, QUVISTATUSTYPE_DONE); rc = q->status_func(param, 0); } } else { if (n->errmsg) freprintf(&q->errmsg, "%s", n->errmsg); } q->resp_code = n->resp_code; free_net_handle(&n); return (rc); }