From 1cb3eda95465cf82dbf07be109de6db064e2587d Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Thu, 28 Mar 2013 10:23:12 +0200 Subject: [PATCH] HS 2.0R2: Add wrapper functions for libcurl Signed-off-by: Jouni Malinen --- src/utils/http-utils.h | 62 ++ src/utils/http_curl.c | 1383 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1445 insertions(+) create mode 100644 src/utils/http-utils.h create mode 100644 src/utils/http_curl.c diff --git a/src/utils/http-utils.h b/src/utils/http-utils.h new file mode 100644 index 000000000..4f5593d3f --- /dev/null +++ b/src/utils/http-utils.h @@ -0,0 +1,62 @@ +/* + * HTTP wrapper + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef HTTP_UTILS_H +#define HTTP_UTILS_H + +struct http_ctx; + +struct http_othername { + char *oid; + u8 *data; + size_t len; +}; + +#define HTTP_MAX_CERT_LOGO_HASH 32 + +struct http_logo { + char *alg_oid; + u8 *hash; + size_t hash_len; + char *uri; +}; + +struct http_cert { + char **dnsname; + unsigned int num_dnsname; + struct http_othername *othername; + unsigned int num_othername; + struct http_logo *logo; + unsigned int num_logo; +}; + +int soap_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key); +int soap_reinit_client(struct http_ctx *ctx); +xml_node_t * soap_send_receive(struct http_ctx *ctx, xml_node_t *node); + +struct http_ctx * http_init_ctx(void *upper_ctx, struct xml_node_ctx *xml_ctx); +void http_ocsp_set(struct http_ctx *ctx, int val); +void http_deinit_ctx(struct http_ctx *ctx); + +int http_download_file(struct http_ctx *ctx, const char *url, + const char *fname, const char *ca_fname); +char * http_post(struct http_ctx *ctx, const char *url, const char *data, + const char *content_type, const char *ext_hdr, + const char *ca_fname, + const char *username, const char *password, + const char *client_cert, const char *client_key, + size_t *resp_len); +void http_set_cert_cb(struct http_ctx *ctx, + int (*cb)(void *ctx, struct http_cert *cert), + void *cb_ctx); +const char * http_get_err(struct http_ctx *ctx); + +#endif /* HTTP_UTILS_H */ diff --git a/src/utils/http_curl.c b/src/utils/http_curl.c new file mode 100644 index 000000000..34595655d --- /dev/null +++ b/src/utils/http_curl.c @@ -0,0 +1,1383 @@ +/* + * HTTP wrapper for libcurl + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#include +#ifdef EAP_TLS_OPENSSL +#include +#include +#include +#include + +#ifdef SSL_set_tlsext_status_type +#ifndef OPENSSL_NO_TLSEXT +#define HAVE_OCSP +#include +#include +#endif /* OPENSSL_NO_TLSEXT */ +#endif /* SSL_set_tlsext_status_type */ +#endif /* EAP_TLS_OPENSSL */ + +#include "common.h" +#include "xml-utils.h" +#include "http-utils.h" + + +struct http_ctx { + void *ctx; + struct xml_node_ctx *xml; + CURL *curl; + struct curl_slist *curl_hdr; + char *svc_address; + char *svc_ca_fname; + char *svc_username; + char *svc_password; + char *svc_client_cert; + char *svc_client_key; + char *curl_buf; + size_t curl_buf_len; + + int (*cert_cb)(void *ctx, struct http_cert *cert); + void *cert_cb_ctx; + + enum { + NO_OCSP, OPTIONAL_OCSP, MANDATORY_OCSP + } ocsp; + X509 *peer_cert; + X509 *peer_issuer; + X509 *peer_issuer_issuer; + + const char *last_err; +}; + + +static void clear_curl(struct http_ctx *ctx) +{ + if (ctx->curl) { + curl_easy_cleanup(ctx->curl); + ctx->curl = NULL; + } + if (ctx->curl_hdr) { + curl_slist_free_all(ctx->curl_hdr); + ctx->curl_hdr = NULL; + } +} + + +static void clone_str(char **dst, const char *src) +{ + os_free(*dst); + if (src) + *dst = os_strdup(src); + else + *dst = NULL; +} + + +static void debug_dump(struct http_ctx *ctx, const char *title, + const char *buf, size_t len) +{ + char *txt = os_malloc(len + 1); + if (txt == NULL) + return; + os_memcpy(txt, buf, len); + txt[len] = '\0'; + while (len > 0) { + len--; + if (txt[len] == '\r' || txt[len] == '\n') + txt[len] = '\0'; + } + wpa_printf(MSG_MSGDUMP, "%s[%s]", title, txt); + os_free(txt); +} + + +static int curl_cb_debug(CURL *curl, curl_infotype info, char *buf, size_t len, + void *userdata) +{ + struct http_ctx *ctx = userdata; + switch (info) { + case CURLINFO_TEXT: + debug_dump(ctx, "CURLINFO_TEXT", buf, len); + break; + case CURLINFO_HEADER_IN: + debug_dump(ctx, "CURLINFO_HEADER_IN", buf, len); + break; + case CURLINFO_HEADER_OUT: + debug_dump(ctx, "CURLINFO_HEADER_OUT", buf, len); + break; + case CURLINFO_DATA_IN: + debug_dump(ctx, "CURLINFO_DATA_IN", buf, len); + break; + case CURLINFO_DATA_OUT: + debug_dump(ctx, "CURLINFO_DATA_OUT", buf, len); + break; + case CURLINFO_SSL_DATA_IN: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_SSL_DATA_IN - %d", + (int) len); + break; + case CURLINFO_SSL_DATA_OUT: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_SSL_DATA_OUT - %d", + (int) len); + break; + case CURLINFO_END: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_END - %d", + (int) len); + break; + } + return 0; +} + + +static size_t curl_cb_header(void *ptr, size_t size, size_t nmemb, + void *userdata) +{ + struct http_ctx *ctx = userdata; + debug_dump(ctx, "curl header", ptr, size * nmemb); + return size * nmemb; +} + + +static size_t curl_cb_write(void *ptr, size_t size, size_t nmemb, + void *userdata) +{ + struct http_ctx *ctx = userdata; + char *n; + debug_dump(ctx, "curl write", ptr, size * nmemb); + n = os_realloc(ctx->curl_buf, ctx->curl_buf_len + size * nmemb + 1); + if (n == NULL) + return 0; + ctx->curl_buf = n; + os_memcpy(n + ctx->curl_buf_len, ptr, size * nmemb); + n[ctx->curl_buf_len + size * nmemb] = '\0'; + ctx->curl_buf_len += size * nmemb; + return size * nmemb; +} + + +#ifdef EAP_TLS_OPENSSL + +static void debug_dump_cert(const char *title, X509 *cert) +{ + BIO *out; + char *txt; + size_t rlen; + + out = BIO_new(BIO_s_mem()); + if (!out) + return; + + X509_print_ex(out, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT); + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (txt) { + int res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_MSGDUMP, "%s:\n%s", title, txt); + } + os_free(txt); + } + BIO_free(out); +} + + +static void add_alt_name_othername(struct http_ctx *ctx, struct http_cert *cert, + OTHERNAME *o) +{ + char txt[100]; + int res; + struct http_othername *on; + ASN1_TYPE *val; + + on = os_realloc_array(cert->othername, cert->num_othername + 1, + sizeof(struct http_othername)); + if (on == NULL) + return; + cert->othername = on; + on = &on[cert->num_othername]; + os_memset(on, 0, sizeof(*on)); + + res = OBJ_obj2txt(txt, sizeof(txt), o->type_id, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + on->oid = os_strdup(txt); + if (on->oid == NULL) + return; + + val = o->value; + on->data = val->value.octet_string->data; + on->len = val->value.octet_string->length; + + cert->num_othername++; +} + + +static void add_alt_name_dns(struct http_ctx *ctx, struct http_cert *cert, + ASN1_STRING *name) +{ + char *buf; + char **n; + + buf = NULL; + if (ASN1_STRING_to_UTF8((unsigned char **) &buf, name) < 0) + return; + + n = os_realloc_array(cert->dnsname, cert->num_dnsname + 1, + sizeof(char *)); + if (n == NULL) + return; + + cert->dnsname = n; + n[cert->num_dnsname] = buf; + cert->num_dnsname++; +} + + +static void add_alt_name(struct http_ctx *ctx, struct http_cert *cert, + const GENERAL_NAME *name) +{ + switch (name->type) { + case GEN_OTHERNAME: + add_alt_name_othername(ctx, cert, name->d.otherName); + break; + case GEN_DNS: + add_alt_name_dns(ctx, cert, name->d.dNSName); + break; + } +} + + +static void add_alt_names(struct http_ctx *ctx, struct http_cert *cert, + GENERAL_NAMES *names) +{ + int num, i; + + num = sk_GENERAL_NAME_num(names); + for (i = 0; i < num; i++) { + const GENERAL_NAME *name; + name = sk_GENERAL_NAME_value(names, i); + add_alt_name(ctx, cert, name); + } +} + + +/* RFC 3709 */ + +typedef struct { + X509_ALGOR *hashAlg; + ASN1_OCTET_STRING *hashValue; +} HashAlgAndValue; + +typedef struct { + STACK_OF(HashAlgAndValue) *refStructHash; + STACK_OF(ASN1_IA5STRING) *refStructURI; +} LogotypeReference; + +typedef struct { + ASN1_IA5STRING *mediaType; + STACK_OF(HashAlgAndValue) *logotypeHash; + STACK_OF(ASN1_IA5STRING) *logotypeURI; +} LogotypeDetails; + +typedef struct { + int type; + union { + ASN1_INTEGER *numBits; + ASN1_INTEGER *tableSize; + } d; +} LogotypeImageResolution; + +typedef struct { + ASN1_INTEGER *type; /* LogotypeImageType ::= INTEGER */ + ASN1_INTEGER *fileSize; + ASN1_INTEGER *xSize; + ASN1_INTEGER *ySize; + LogotypeImageResolution *resolution; + ASN1_IA5STRING *language; +} LogotypeImageInfo; + +typedef struct { + LogotypeDetails *imageDetails; + LogotypeImageInfo *imageInfo; +} LogotypeImage; + +typedef struct { + ASN1_INTEGER *fileSize; + ASN1_INTEGER *playTime; + ASN1_INTEGER *channels; + ASN1_INTEGER *sampleRate; + ASN1_IA5STRING *language; +} LogotypeAudioInfo; + +typedef struct { + LogotypeDetails *audioDetails; + LogotypeAudioInfo *audioInfo; +} LogotypeAudio; + +typedef struct { + STACK_OF(LogotypeImage) *image; + STACK_OF(LogotypeAudio) *audio; +} LogotypeData; + +typedef struct { + int type; + union { + LogotypeData *direct; + LogotypeReference *indirect; + } d; +} LogotypeInfo; + +typedef struct { + ASN1_OBJECT *logotypeType; + LogotypeInfo *info; +} OtherLogotypeInfo; + +typedef struct { + STACK_OF(LogotypeInfo) *communityLogos; + LogotypeInfo *issuerLogo; + LogotypeInfo *subjectLogo; + STACK_OF(OtherLogotypeInfo) *otherLogos; +} LogotypeExtn; + +ASN1_SEQUENCE(HashAlgAndValue) = { + ASN1_SIMPLE(HashAlgAndValue, hashAlg, X509_ALGOR), + ASN1_SIMPLE(HashAlgAndValue, hashValue, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(HashAlgAndValue); + +ASN1_SEQUENCE(LogotypeReference) = { + ASN1_SEQUENCE_OF(LogotypeReference, refStructHash, HashAlgAndValue), + ASN1_SEQUENCE_OF(LogotypeReference, refStructURI, ASN1_IA5STRING) +} ASN1_SEQUENCE_END(LogotypeReference); + +ASN1_SEQUENCE(LogotypeDetails) = { + ASN1_SIMPLE(LogotypeDetails, mediaType, ASN1_IA5STRING), + ASN1_SEQUENCE_OF(LogotypeDetails, logotypeHash, HashAlgAndValue), + ASN1_SEQUENCE_OF(LogotypeDetails, logotypeURI, ASN1_IA5STRING) +} ASN1_SEQUENCE_END(LogotypeDetails); + +ASN1_CHOICE(LogotypeImageResolution) = { + ASN1_IMP(LogotypeImageResolution, d.numBits, ASN1_INTEGER, 1), + ASN1_IMP(LogotypeImageResolution, d.tableSize, ASN1_INTEGER, 2) +} ASN1_CHOICE_END(LogotypeImageResolution); + +ASN1_SEQUENCE(LogotypeImageInfo) = { + ASN1_IMP_OPT(LogotypeImageInfo, type, ASN1_INTEGER, 0), + ASN1_SIMPLE(LogotypeImageInfo, fileSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeImageInfo, xSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeImageInfo, ySize, ASN1_INTEGER), + ASN1_OPT(LogotypeImageInfo, resolution, LogotypeImageResolution), + ASN1_IMP_OPT(LogotypeImageInfo, language, ASN1_IA5STRING, 4), +} ASN1_SEQUENCE_END(LogotypeImageInfo); + +ASN1_SEQUENCE(LogotypeImage) = { + ASN1_SIMPLE(LogotypeImage, imageDetails, LogotypeDetails), + ASN1_OPT(LogotypeImage, imageInfo, LogotypeImageInfo) +} ASN1_SEQUENCE_END(LogotypeImage); + +ASN1_SEQUENCE(LogotypeAudioInfo) = { + ASN1_SIMPLE(LogotypeAudioInfo, fileSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeAudioInfo, playTime, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeAudioInfo, channels, ASN1_INTEGER), + ASN1_IMP_OPT(LogotypeAudioInfo, sampleRate, ASN1_INTEGER, 3), + ASN1_IMP_OPT(LogotypeAudioInfo, language, ASN1_IA5STRING, 4) +} ASN1_SEQUENCE_END(LogotypeAudioInfo); + +ASN1_SEQUENCE(LogotypeAudio) = { + ASN1_SIMPLE(LogotypeAudio, audioDetails, LogotypeDetails), + ASN1_OPT(LogotypeAudio, audioInfo, LogotypeAudioInfo) +} ASN1_SEQUENCE_END(LogotypeAudio); + +ASN1_SEQUENCE(LogotypeData) = { + ASN1_SEQUENCE_OF_OPT(LogotypeData, image, LogotypeImage), + ASN1_IMP_SEQUENCE_OF_OPT(LogotypeData, audio, LogotypeAudio, 1) +} ASN1_SEQUENCE_END(LogotypeData); + +ASN1_CHOICE(LogotypeInfo) = { + ASN1_IMP(LogotypeInfo, d.direct, LogotypeData, 0), + ASN1_IMP(LogotypeInfo, d.indirect, LogotypeReference, 1) +} ASN1_CHOICE_END(LogotypeInfo); + +ASN1_SEQUENCE(OtherLogotypeInfo) = { + ASN1_SIMPLE(OtherLogotypeInfo, logotypeType, ASN1_OBJECT), + ASN1_SIMPLE(OtherLogotypeInfo, info, LogotypeInfo) +} ASN1_SEQUENCE_END(OtherLogotypeInfo); + +ASN1_SEQUENCE(LogotypeExtn) = { + ASN1_EXP_SEQUENCE_OF_OPT(LogotypeExtn, communityLogos, LogotypeInfo, 0), + ASN1_EXP_OPT(LogotypeExtn, issuerLogo, LogotypeInfo, 1), + ASN1_EXP_OPT(LogotypeExtn, issuerLogo, LogotypeInfo, 2), + ASN1_EXP_SEQUENCE_OF_OPT(LogotypeExtn, otherLogos, OtherLogotypeInfo, 3) +} ASN1_SEQUENCE_END(LogotypeExtn); + +IMPLEMENT_ASN1_FUNCTIONS(LogotypeExtn); + +#define sk_LogotypeInfo_num(st) SKM_sk_num(LogotypeInfo, (st)) +#define sk_LogotypeInfo_value(st, i) SKM_sk_value(LogotypeInfo, (st), (i)) +#define sk_LogotypeImage_num(st) SKM_sk_num(LogotypeImage, (st)) +#define sk_LogotypeImage_value(st, i) SKM_sk_value(LogotypeImage, (st), (i)) +#define sk_HashAlgAndValue_num(st) SKM_sk_num(HashAlgAndValue, (st)) +#define sk_HashAlgAndValue_value(st, i) SKM_sk_value(HashAlgAndValue, (st), (i)) +#define sk_ASN1_IA5STRING_num(st) SKM_sk_num(ASN1_IA5STRING, (st)) +#define sk_ASN1_IA5STRING_value(st, i) SKM_sk_value(ASN1_IA5STRING, (st), (i)) + + +static void add_logo(struct http_ctx *ctx, struct http_cert *hcert, + HashAlgAndValue *hash, ASN1_IA5STRING *uri) +{ + char txt[100]; + int res, len; + struct http_logo *n; + + if (hash == NULL || uri == NULL) + return; + + res = OBJ_obj2txt(txt, sizeof(txt), hash->hashAlg->algorithm, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + n = os_realloc_array(hcert->logo, hcert->num_logo + 1, + sizeof(struct http_logo)); + if (n == NULL) + return; + hcert->logo = n; + n = &hcert->logo[hcert->num_logo]; + os_memset(n, 0, sizeof(*n)); + + n->alg_oid = os_strdup(txt); + if (n->alg_oid == NULL) + return; + + n->hash_len = ASN1_STRING_length(hash->hashValue); + n->hash = os_malloc(n->hash_len); + if (n->hash == NULL) { + os_free(n->alg_oid); + return; + } + os_memcpy(n->hash, ASN1_STRING_data(hash->hashValue), n->hash_len); + + len = ASN1_STRING_length(uri); + n->uri = os_malloc(len + 1); + if (n->uri == NULL) { + os_free(n->alg_oid); + os_free(n->hash); + return; + } + os_memcpy(n->uri, ASN1_STRING_data(uri), len); + n->uri[len] = '\0'; + + hcert->num_logo++; +} + + +static void add_logo_direct(struct http_ctx *ctx, struct http_cert *hcert, + LogotypeData *data) +{ + int i, num; + + if (data->image == NULL) + return; + + num = sk_LogotypeImage_num(data->image); + for (i = 0; i < num; i++) { + LogotypeImage *image; + LogotypeDetails *details; + int j, hash_num, uri_num; + HashAlgAndValue *found_hash = NULL; + + image = sk_LogotypeImage_value(data->image, i); + if (image == NULL) + continue; + + details = image->imageDetails; + if (details == NULL) + continue; + + hash_num = sk_HashAlgAndValue_num(details->logotypeHash); + for (j = 0; j < hash_num; j++) { + HashAlgAndValue *hash; + char txt[100]; + int res; + hash = sk_HashAlgAndValue_value(details->logotypeHash, + j); + if (hash == NULL) + continue; + res = OBJ_obj2txt(txt, sizeof(txt), + hash->hashAlg->algorithm, 1); + if (res < 0 || res >= (int) sizeof(txt)) + continue; + if (os_strcmp(txt, "2.16.840.1.101.3.4.2.1") == 0) { + found_hash = hash; + break; + } + } + + if (!found_hash) { + wpa_printf(MSG_DEBUG, "OpenSSL: No SHA256 hash found for the logo"); + continue; + } + + uri_num = sk_ASN1_IA5STRING_num(details->logotypeURI); + for (j = 0; j < uri_num; j++) { + ASN1_IA5STRING *uri; + uri = sk_ASN1_IA5STRING_value(details->logotypeURI, j); + add_logo(ctx, hcert, found_hash, uri); + } + } +} + + +static void add_logo_indirect(struct http_ctx *ctx, struct http_cert *hcert, + LogotypeReference *ref) +{ + int j, hash_num, uri_num; + + hash_num = sk_HashAlgAndValue_num(ref->refStructHash); + uri_num = sk_ASN1_IA5STRING_num(ref->refStructURI); + if (hash_num != uri_num) { + wpa_printf(MSG_INFO, "Unexpected LogotypeReference array size difference %d != %d", + hash_num, uri_num); + return; + } + + for (j = 0; j < hash_num; j++) { + HashAlgAndValue *hash; + ASN1_IA5STRING *uri; + hash = sk_HashAlgAndValue_value(ref->refStructHash, j); + uri = sk_ASN1_IA5STRING_value(ref->refStructURI, j); + add_logo(ctx, hcert, hash, uri); + } +} + + +static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert, + X509 *cert) +{ + ASN1_OBJECT *obj; + int pos; + X509_EXTENSION *ext; + ASN1_OCTET_STRING *os; + LogotypeExtn *logo; + const unsigned char *data; + int i, num; + + obj = OBJ_txt2obj("1.3.6.1.5.5.7.1.12", 0); + if (obj == NULL) + return; + + pos = X509_get_ext_by_OBJ(cert, obj, -1); + if (pos < 0) { + wpa_printf(MSG_INFO, "No logotype extension included"); + return; + } + + wpa_printf(MSG_INFO, "Parsing logotype extension"); + ext = X509_get_ext(cert, pos); + if (!ext) { + wpa_printf(MSG_INFO, "Could not get logotype extension"); + return; + } + + os = X509_EXTENSION_get_data(ext); + if (os == NULL) { + wpa_printf(MSG_INFO, "Could not get logotype extension data"); + return; + } + + wpa_hexdump(MSG_DEBUG, "logotypeExtn", + ASN1_STRING_data(os), ASN1_STRING_length(os)); + + data = ASN1_STRING_data(os); + logo = d2i_LogotypeExtn(NULL, &data, ASN1_STRING_length(os)); + if (logo == NULL) { + wpa_printf(MSG_INFO, "Failed to parse logotypeExtn"); + return; + } + + if (!logo->communityLogos) { + wpa_printf(MSG_INFO, "No communityLogos included"); + LogotypeExtn_free(logo); + return; + } + + num = sk_LogotypeInfo_num(logo->communityLogos); + for (i = 0; i < num; i++) { + LogotypeInfo *info; + info = sk_LogotypeInfo_value(logo->communityLogos, i); + switch (info->type) { + case 0: + add_logo_direct(ctx, hcert, info->d.direct); + break; + case 1: + add_logo_indirect(ctx, hcert, info->d.indirect); + break; + } + } + + LogotypeExtn_free(logo); +} + + +static int validate_server_cert(struct http_ctx *ctx, X509 *cert) +{ + GENERAL_NAMES *names; + struct http_cert hcert; + unsigned int i; + int ret; + + if (ctx->cert_cb == NULL) + return 0; + + os_memset(&hcert, 0, sizeof(hcert)); + + if (0) { + BIO *out; + out = BIO_new_fp(stdout, BIO_NOCLOSE); + X509_print_ex(out, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT); + BIO_free(out); + } + + names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (names) + add_alt_names(ctx, &hcert, names); + + add_logotype_ext(ctx, &hcert, cert); + + ret = ctx->cert_cb(ctx->cert_cb_ctx, &hcert); + + for (i = 0; i < hcert.num_dnsname; i++) + OPENSSL_free(hcert.dnsname[i]); + os_free(hcert.dnsname); + + for (i = 0; i < hcert.num_othername; i++) + os_free(hcert.othername[i].oid); + os_free(hcert.othername); + + for (i = 0; i < hcert.num_logo; i++) { + os_free(hcert.logo[i].alg_oid); + os_free(hcert.logo[i].hash); + os_free(hcert.logo[i].uri); + } + os_free(hcert.logo); + + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); + + return ret; +} + + +static int curl_cb_ssl_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + struct http_ctx *ctx; + X509 *cert; + int err, depth; + char buf[256]; + X509_NAME *name; + const char *err_str; + SSL *ssl; + SSL_CTX *ssl_ctx; + + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + ssl_ctx = ssl->ctx; + ctx = SSL_CTX_get_app_data(ssl_ctx); + + wpa_printf(MSG_DEBUG, "curl_cb_ssl_verify"); + + err = X509_STORE_CTX_get_error(x509_ctx); + err_str = X509_verify_cert_error_string(err); + depth = X509_STORE_CTX_get_error_depth(x509_ctx); + cert = X509_STORE_CTX_get_current_cert(x509_ctx); + if (!cert) { + wpa_printf(MSG_INFO, "No server certificate available"); + ctx->last_err = "No server certificate available"; + return 0; + } + + if (depth == 0) + ctx->peer_cert = cert; + else if (depth == 1) + ctx->peer_issuer = cert; + else if (depth == 2) + ctx->peer_issuer_issuer = cert; + + name = X509_get_subject_name(cert); + X509_NAME_oneline(name, buf, sizeof(buf)); + wpa_printf(MSG_INFO, "Server certificate chain - depth=%d err=%d (%s) subject=%s", + depth, err, err_str, buf); + debug_dump_cert("Server certificate chain - certificate", cert); + + if (depth == 0 && preverify_ok && validate_server_cert(ctx, cert) < 0) + return 0; + + if (!preverify_ok) + ctx->last_err = "TLS validation failed"; + + return preverify_ok; +} + + +#ifdef HAVE_OCSP + +static void ocsp_debug_print_resp(OCSP_RESPONSE *rsp) +{ + BIO *out; + size_t rlen; + char *txt; + int res; + + out = BIO_new(BIO_s_mem()); + if (!out) + return; + + OCSP_RESPONSE_print(out, rsp, 0); + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (!txt) { + BIO_free(out); + return; + } + + res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_MSGDUMP, "OpenSSL: OCSP Response\n%s", txt); + } + os_free(txt); + BIO_free(out); +} + + +static void tls_show_errors(const char *func, const char *txt) +{ + unsigned long err; + + wpa_printf(MSG_DEBUG, "OpenSSL: %s - %s %s", + func, txt, ERR_error_string(ERR_get_error(), NULL)); + + while ((err = ERR_get_error())) { + wpa_printf(MSG_DEBUG, "OpenSSL: pending error: %s", + ERR_error_string(err, NULL)); + } +} + + +static int ocsp_resp_cb(SSL *s, void *arg) +{ + struct http_ctx *ctx = arg; + const unsigned char *p; + int len, status, reason; + OCSP_RESPONSE *rsp; + OCSP_BASICRESP *basic; + OCSP_CERTID *id; + ASN1_GENERALIZEDTIME *produced_at, *this_update, *next_update; + X509_STORE *store; + STACK_OF(X509) *certs = NULL; + + len = SSL_get_tlsext_status_ocsp_resp(s, &p); + if (!p) { + wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP response received"); + if (ctx->ocsp == MANDATORY_OCSP) + ctx->last_err = "No OCSP response received"; + return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1; + } + + wpa_hexdump(MSG_DEBUG, "OpenSSL: OCSP response", p, len); + + rsp = d2i_OCSP_RESPONSE(NULL, &p, len); + if (!rsp) { + wpa_printf(MSG_INFO, "OpenSSL: Failed to parse OCSP response"); + ctx->last_err = "Failed to parse OCSP response"; + return 0; + } + + ocsp_debug_print_resp(rsp); + + status = OCSP_response_status(rsp); + if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + wpa_printf(MSG_INFO, "OpenSSL: OCSP responder error %d (%s)", + status, OCSP_response_status_str(status)); + ctx->last_err = "OCSP responder error"; + return 0; + } + + basic = OCSP_response_get1_basic(rsp); + if (!basic) { + wpa_printf(MSG_INFO, "OpenSSL: Could not find BasicOCSPResponse"); + ctx->last_err = "Could not find BasicOCSPResponse"; + return 0; + } + + store = SSL_CTX_get_cert_store(s->ctx); + if (ctx->peer_issuer) { + wpa_printf(MSG_DEBUG, "OpenSSL: Add issuer"); + debug_dump_cert("OpenSSL: Issuer certificate", + ctx->peer_issuer); + + if (X509_STORE_add_cert(store, ctx->peer_issuer) != 1) { + tls_show_errors(__func__, + "OpenSSL: Could not add issuer to certificate store\n"); + } + certs = sk_X509_new_null(); + if (certs) { + X509 *cert; + cert = X509_dup(ctx->peer_issuer); + if (cert && !sk_X509_push(certs, cert)) { + tls_show_errors( + __func__, + "OpenSSL: Could not add issuer to OCSP responder trust store\n"); + X509_free(cert); + sk_X509_free(certs); + certs = NULL; + } + if (ctx->peer_issuer_issuer) { + X509 *cert; + cert = X509_dup(ctx->peer_issuer_issuer); + if (cert && !sk_X509_push(certs, cert)) { + tls_show_errors( + __func__, + "OpenSSL: Could not add issuer to OCSP responder trust store\n"); + X509_free(cert); + } + } + } + } + + status = OCSP_basic_verify(basic, certs, store, OCSP_TRUSTOTHER); + sk_X509_pop_free(certs, X509_free); + if (status <= 0) { + tls_show_errors(__func__, + "OpenSSL: OCSP response failed verification"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "OCSP response failed verification"; + return 0; + } + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response verification succeeded"); + + if (!ctx->peer_cert) { + wpa_printf(MSG_DEBUG, "OpenSSL: Peer certificate not available for OCSP status check"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Peer certificate not available for OCSP status check"; + return 0; + } + + if (!ctx->peer_issuer) { + wpa_printf(MSG_DEBUG, "OpenSSL: Peer issuer certificate not available for OCSP status check"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Peer issuer certificate not available for OCSP status check"; + return 0; + } + + id = OCSP_cert_to_id(NULL, ctx->peer_cert, ctx->peer_issuer); + if (!id) { + wpa_printf(MSG_DEBUG, "OpenSSL: Could not create OCSP certificate identifier"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Could not create OCSP certificate identifier"; + return 0; + } + + if (!OCSP_resp_find_status(basic, id, &status, &reason, &produced_at, + &this_update, &next_update)) { + wpa_printf(MSG_INFO, "OpenSSL: Could not find current server certificate from OCSP response%s", + (ctx->ocsp == MANDATORY_OCSP) ? "" : + " (OCSP not required)"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + if (ctx->ocsp == MANDATORY_OCSP) + + ctx->last_err = "Could not find current server certificate from OCSP response"; + return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1; + } + + if (!OCSP_check_validity(this_update, next_update, 5 * 60, -1)) { + tls_show_errors(__func__, "OpenSSL: OCSP status times invalid"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "OCSP status times invalid"; + return 0; + } + + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status for server certificate: %s", + OCSP_cert_status_str(status)); + + if (status == V_OCSP_CERTSTATUS_GOOD) + return 1; + if (status == V_OCSP_CERTSTATUS_REVOKED) + ctx->last_err = "Server certificate has been revoked"; + return 0; + if (ctx->ocsp == MANDATORY_OCSP) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP required"); + ctx->last_err = "OCSP status unknown"; + return 0; + } + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP was not required, so allow connection to continue"); + return 1; +} + + +static SSL_METHOD patch_ssl_method; +static const SSL_METHOD *real_ssl_method; + +static int curl_patch_ssl_new(SSL *s) +{ + SSL_CTX *ssl = s->ctx; + int ret; + + ssl->method = real_ssl_method; + s->method = real_ssl_method; + + ret = s->method->ssl_new(s); + SSL_set_tlsext_status_type(s, TLSEXT_STATUSTYPE_ocsp); + + return ret; +} + +#endif /* HAVE_OCSP */ + + +static CURLcode curl_cb_ssl(CURL *curl, void *sslctx, void *parm) +{ + struct http_ctx *ctx = parm; + SSL_CTX *ssl = sslctx; + + wpa_printf(MSG_DEBUG, "curl_cb_ssl"); + SSL_CTX_set_app_data(ssl, ctx); + SSL_CTX_set_verify(ssl, SSL_VERIFY_PEER, curl_cb_ssl_verify); + +#ifdef HAVE_OCSP + if (ctx->ocsp != NO_OCSP) { + SSL_CTX_set_tlsext_status_cb(ssl, ocsp_resp_cb); + SSL_CTX_set_tlsext_status_arg(ssl, ctx); + + /* + * Use a temporary SSL_METHOD to get a callback on SSL_new() + * from libcurl since there is no proper callback registration + * available for this. + */ + os_memset(&patch_ssl_method, 0, sizeof(patch_ssl_method)); + patch_ssl_method.ssl_new = curl_patch_ssl_new; + real_ssl_method = ssl->method; + ssl->method = &patch_ssl_method; + } +#endif /* HAVE_OCSP */ + + return CURLE_OK; +} + +#endif /* EAP_TLS_OPENSSL */ + + +static CURL * setup_curl_post(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + CURL *curl; + + wpa_printf(MSG_DEBUG, "Start HTTP client: address=%s ca_fname=%s " + "username=%s", address, ca_fname, username); + + curl = curl_easy_init(); + if (curl == NULL) + return NULL; + + curl_easy_setopt(curl, CURLOPT_URL, address); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + if (ca_fname) { + curl_easy_setopt(curl, CURLOPT_CAINFO, ca_fname); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); +#ifdef EAP_TLS_OPENSSL + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, curl_cb_ssl); + curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, ctx); +#endif /* EAP_TLS_OPENSSL */ + } else { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + } + if (client_cert && client_key) { + curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert); + curl_easy_setopt(curl, CURLOPT_SSLKEY, client_key); + } + /* TODO: use curl_easy_getinfo() with CURLINFO_CERTINFO to fetch + * information about the server certificate */ + curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_cb_debug); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_cb_header); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, ctx); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_cb_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if (username) { + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE); + curl_easy_setopt(curl, CURLOPT_USERNAME, username); + curl_easy_setopt(curl, CURLOPT_PASSWORD, password); + } + + return curl; +} + + +static int post_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + char *pos; + int count; + + clone_str(&ctx->svc_address, address); + clone_str(&ctx->svc_ca_fname, ca_fname); + clone_str(&ctx->svc_username, username); + clone_str(&ctx->svc_password, password); + clone_str(&ctx->svc_client_cert, client_cert); + clone_str(&ctx->svc_client_key, client_key); + + /* + * Workaround for Apache "Hostname 'FOO' provided via SNI and hostname + * 'foo' provided via HTTP are different. + */ + for (count = 0, pos = ctx->svc_address; count < 3 && pos && *pos; + pos++) { + if (*pos == '/') + count++; + *pos = tolower(*pos); + } + + ctx->curl = setup_curl_post(ctx, ctx->svc_address, ca_fname, username, + password, client_cert, client_key); + if (ctx->curl == NULL) + return -1; + + return 0; +} + + +int soap_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + if (post_init_client(ctx, address, ca_fname, username, password, + client_cert, client_key) < 0) + return -1; + + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, + "Content-Type: application/soap+xml"); + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, "SOAPAction: "); + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, "Expect:"); + curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, ctx->curl_hdr); + + return 0; +} + + +int soap_reinit_client(struct http_ctx *ctx) +{ + char *address = NULL; + char *ca_fname = NULL; + char *username = NULL; + char *password = NULL; + char *client_cert = NULL; + char *client_key = NULL; + int ret; + + clear_curl(ctx); + + clone_str(&address, ctx->svc_address); + clone_str(&ca_fname, ctx->svc_ca_fname); + clone_str(&username, ctx->svc_username); + clone_str(&password, ctx->svc_password); + clone_str(&client_cert, ctx->svc_client_cert); + clone_str(&client_key, ctx->svc_client_key); + + ret = soap_init_client(ctx, address, ca_fname, username, password, + client_cert, client_key); + os_free(address); + os_free(ca_fname); + os_free(username); + os_free(password); + os_free(client_cert); + os_free(client_key); + return ret; +} + + +static void free_curl_buf(struct http_ctx *ctx) +{ + os_free(ctx->curl_buf); + ctx->curl_buf = NULL; + ctx->curl_buf_len = 0; +} + + +xml_node_t * soap_send_receive(struct http_ctx *ctx, xml_node_t *node) +{ + char *str; + xml_node_t *envelope, *ret, *resp, *n; + CURLcode res; + long http = 0; + + ctx->last_err = NULL; + + wpa_printf(MSG_DEBUG, "SOAP: Sending message"); + envelope = soap_build_envelope(ctx->xml, node); + str = xml_node_to_str(ctx->xml, envelope); + xml_node_free(ctx->xml, envelope); + wpa_printf(MSG_MSGDUMP, "SOAP[%s]", str); + + curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, str); + free_curl_buf(ctx); + + res = curl_easy_perform(ctx->curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + os_free(str); + free_curl_buf(ctx); + return NULL; + } + os_free(str); + + curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "SOAP: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP download failed"; + wpa_printf(MSG_INFO, "HTTP download failed - code %ld", http); + free_curl_buf(ctx); + return NULL; + } + + if (ctx->curl_buf == NULL) + return NULL; + + wpa_printf(MSG_MSGDUMP, "Server response:\n%s", ctx->curl_buf); + resp = xml_node_from_buf(ctx->xml, ctx->curl_buf); + free_curl_buf(ctx); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Could not parse SOAP response"); + ctx->last_err = "Could not parse SOAP response"; + return NULL; + } + + ret = soap_get_body(ctx->xml, resp); + if (ret == NULL) { + wpa_printf(MSG_INFO, "Could not get SOAP body"); + ctx->last_err = "Could not get SOAP body"; + return NULL; + } + + wpa_printf(MSG_DEBUG, "SOAP body localname: '%s'", + xml_node_get_localname(ctx->xml, ret)); + n = xml_node_copy(ctx->xml, ret); + xml_node_free(ctx->xml, resp); + + return n; +} + + +struct http_ctx * http_init_ctx(void *upper_ctx, struct xml_node_ctx *xml_ctx) +{ + struct http_ctx *ctx; + + ctx = os_zalloc(sizeof(*ctx)); + if (ctx == NULL) + return NULL; + ctx->ctx = upper_ctx; + ctx->xml = xml_ctx; + ctx->ocsp = OPTIONAL_OCSP; + + curl_global_init(CURL_GLOBAL_ALL); + + return ctx; +} + + +void http_ocsp_set(struct http_ctx *ctx, int val) +{ + if (val == 0) + ctx->ocsp = NO_OCSP; + else if (val == 1) + ctx->ocsp = OPTIONAL_OCSP; + if (val == 2) + ctx->ocsp = MANDATORY_OCSP; +} + + +void http_deinit_ctx(struct http_ctx *ctx) +{ + clear_curl(ctx); + os_free(ctx->curl_buf); + curl_global_cleanup(); + + os_free(ctx->svc_address); + os_free(ctx->svc_ca_fname); + os_free(ctx->svc_username); + os_free(ctx->svc_password); + os_free(ctx->svc_client_cert); + os_free(ctx->svc_client_key); + + os_free(ctx); +} + + +int http_download_file(struct http_ctx *ctx, const char *url, + const char *fname, const char *ca_fname) +{ + CURL *curl; + FILE *f; + CURLcode res; + long http = 0; + + ctx->last_err = NULL; + + wpa_printf(MSG_DEBUG, "curl: Download file from %s to %s (ca=%s)", + url, fname, ca_fname); + curl = curl_easy_init(); + if (curl == NULL) + return -1; + + f = fopen(fname, "wb"); + if (f == NULL) { + curl_easy_cleanup(curl); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + if (ca_fname) { + curl_easy_setopt(curl, CURLOPT_CAINFO, ca_fname); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); + } else { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + } + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_cb_debug); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_cb_header); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, ctx); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, f); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + curl_easy_cleanup(curl); + fclose(f); + return -1; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "curl: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP download failed"; + wpa_printf(MSG_INFO, "HTTP download failed - code %ld", http); + curl_easy_cleanup(curl); + fclose(f); + return -1; + } + + curl_easy_cleanup(curl); + fclose(f); + + return 0; +} + + +char * http_post(struct http_ctx *ctx, const char *url, const char *data, + const char *content_type, const char *ext_hdr, + const char *ca_fname, + const char *username, const char *password, + const char *client_cert, const char *client_key, + size_t *resp_len) +{ + long http = 0; + CURLcode res; + char *ret; + CURL *curl; + struct curl_slist *curl_hdr = NULL; + + ctx->last_err = NULL; + wpa_printf(MSG_DEBUG, "curl: HTTP POST to %s", url); + curl = setup_curl_post(ctx, url, ca_fname, username, password, + client_cert, client_key); + if (curl == NULL) + return NULL; + + if (content_type) { + char ct[200]; + snprintf(ct, sizeof(ct), "Content-Type: %s", content_type); + curl_hdr = curl_slist_append(curl_hdr, ct); + } + if (ext_hdr) + curl_hdr = curl_slist_append(curl_hdr, ext_hdr); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_hdr); + + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); + free_curl_buf(ctx); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + free_curl_buf(ctx); + return NULL; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "curl: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP POST failed"; + wpa_printf(MSG_INFO, "HTTP POST failed - code %ld", http); + free_curl_buf(ctx); + return NULL; + } + + if (ctx->curl_buf == NULL) + return NULL; + + ret = ctx->curl_buf; + if (resp_len) + *resp_len = ctx->curl_buf_len; + ctx->curl_buf = NULL; + ctx->curl_buf_len = 0; + + wpa_printf(MSG_MSGDUMP, "Server response:\n%s", ret); + + return ret; +} + + +void http_set_cert_cb(struct http_ctx *ctx, + int (*cb)(void *ctx, struct http_cert *cert), + void *cb_ctx) +{ + ctx->cert_cb = cb; + ctx->cert_cb_ctx = cb_ctx; +} + + +const char * http_get_err(struct http_ctx *ctx) +{ + return ctx->last_err; +}