static int est_ssl_read (SSL *ssl, unsigned char *buf, int buf_max, int sock_read_timeout) { int timeout; int read_fd; int rv; struct pollfd pfd; /* * load up the timeval struct to be passed to the select */ timeout = sock_read_timeout * 1000; read_fd = SSL_get_fd(ssl); pfd.fd = read_fd; pfd.events = POLLIN; pfd.revents = 0; errno = 0; rv = POLL(&pfd, 1, timeout); if (rv == 0) { EST_LOG_ERR("Socket poll timeout. No data received from server."); return -1; } else if ( rv == -1) { EST_LOG_ERR("Socket read failure. errno = %d", errno); return -1; } else { return (SSL_read(ssl, buf, buf_max)); } }
/* * This function verifies the content type header and also * returns the length of the content header. The * content type is important. For example, the content * type is expected to be pkcs7 on a simple enrollment. */ static int est_io_check_http_hdrs (HTTP_HEADER *hdrs, int hdr_cnt, EST_OPERATION op) { int i; int cl = 0; int content_type_present = 0, content_length_present = 0; int cmp_result; /* * Traverse all the http headers and process the ones that need to be * checked */ for (i = 0; i < hdr_cnt; i++) { /* * Content type */ memcmp_s(hdrs[i].name, sizeof(EST_HTTP_HDR_CT), EST_HTTP_HDR_CT, sizeof(EST_HTTP_HDR_CT), &cmp_result); if (!cmp_result) { content_type_present = 1; /* * Verify content is pkcs7 data */ memcmp_s(hdrs[i].value, strnlen_s(est_op_map[op].content_type, est_op_map[op].length), est_op_map[op].content_type, strnlen_s(est_op_map[op].content_type, est_op_map[op].length), &cmp_result); if (cmp_result) { EST_LOG_ERR("HTTP content type is %s", hdrs[i].value); return 0; } } else { /* * Content Length */ memcmp_s(hdrs[i].name, sizeof(EST_HTTP_HDR_CL), EST_HTTP_HDR_CL, sizeof(EST_HTTP_HDR_CL), &cmp_result); if (!cmp_result) { content_length_present = 1; cl = atoi(hdrs[i].value); } } } /* * Make sure all the necessary headers were present. */ if (content_type_present == 0 ) { EST_LOG_ERR("Missing HTTP content type header"); return 0; } else if (content_length_present == 0 ) { EST_LOG_ERR("Missing HTTP content length header"); return 0; } return cl; }
/* * This function searches for and processes the WWW-Authenticate header from * the server. The result is the setting of the auth_mode value in the * context. If there is no WWW-Authenticate header, or the values in the * header are invalid, it will set the auth_mode to a failure setting. If * there are multiple Authenticate headers, only the first one will be * processed. */ static void est_io_parse_http_auth_request (EST_CTX *ctx, HTTP_HEADER *hdrs, int hdr_cnt) { int i; EST_ERROR rv; int auth_found = 0; /* * Walk the headers looking for the WWW-Authenticate. We'll * only process the first one. If an erroneous second one * is included, it will be ignored. */ for (i = 0; i < hdr_cnt; i++) { if (!strncmp(hdrs[i].name, EST_HTTP_HDR_AUTH, 16)) { auth_found = 1; if (!strncmp(hdrs[i].value, "Basic", 5)) { ctx->auth_mode = AUTH_BASIC; /* Parse the realm */ rv = est_io_parse_auth_tokens(ctx, hdrs[i].value); if (rv != EST_ERR_NONE) { ctx->auth_mode = AUTH_FAIL; } } else if (!strncmp(hdrs[i].value, "Digest", 6)) { ctx->auth_mode = AUTH_DIGEST; /* Parse the realm and nonce */ rv = est_io_parse_auth_tokens(ctx, hdrs[i].value); if (rv != EST_ERR_NONE) { ctx->auth_mode = AUTH_FAIL; } } else if (!strncmp(hdrs[i].value, "Bearer", 6)) { ctx->auth_mode = AUTH_TOKEN; /* Parse the realm and possible token error fields */ rv = est_io_parse_auth_tokens(ctx, hdrs[i].value); if (rv != EST_ERR_NONE) { ctx->auth_mode = AUTH_FAIL; } } else { EST_LOG_ERR("Unsupported WWW-Authenticate method"); ctx->auth_mode = AUTH_FAIL; } break; } } if (!auth_found) { EST_LOG_ERR("No WWW-Authenticate header found"); ctx->auth_mode = AUTH_FAIL; } return; }
/* * This function parses the HTTP status code * in the first header. Only a handful of codes are * handled by EST. We are not a full HTTP stack. Any * unrecognized codes will result in an error. * Note that HTTP 1.1 is expected. */ static int est_io_parse_response_status_code (unsigned char *buf) { if (!strncmp((const char *)buf, EST_HTTP_HDR_200, strnlen(EST_HTTP_HDR_200, EST_HTTP_HDR_MAX))) { return 200; } else if (!strncmp((const char *)buf, EST_HTTP_HDR_202, strnlen(EST_HTTP_HDR_202, EST_HTTP_HDR_MAX))) { return 202; } else if (!strncmp((const char *)buf, EST_HTTP_HDR_204, strnlen(EST_HTTP_HDR_204, EST_HTTP_HDR_MAX))) { return 204; } else if (!strncmp((const char *)buf, EST_HTTP_HDR_400, strnlen(EST_HTTP_HDR_400, EST_HTTP_HDR_MAX))) { return 400; } else if (!strncmp((const char *)buf, EST_HTTP_HDR_401, strnlen(EST_HTTP_HDR_401, EST_HTTP_HDR_MAX))) { return 401; } else if (!strncmp((const char *)buf, EST_HTTP_HDR_404, strnlen(EST_HTTP_HDR_404, EST_HTTP_HDR_MAX))) { return 404; } else { EST_LOG_ERR("Unhandled HTTP response %s", buf); return -1; } }
/* * Take care of the blocking IO aspect of ssl_read. Make sure there's * something waiting to be read from the socket before calling ssl_read. */ static int est_ssl_read (SSL *ssl, unsigned char *buf, int buf_max, int sock_read_timeout) { struct timeval timeout; fd_set set; int read_fd; int rv; /* * load up the timeval struct to be passed to the select */ timeout.tv_sec = sock_read_timeout; timeout.tv_usec = 0; read_fd = SSL_get_fd(ssl); FD_ZERO(&set); FD_SET(read_fd, &set); rv = select(read_fd + 1, &set, NULL, NULL, &timeout); if (rv == 0) { EST_LOG_ERR("Socket read timeout. No data received from server."); return -1; } return (SSL_read(ssl, buf, buf_max)); }
/* * This routine will send a PKCS7 encoded certificate to * the EST client via HTTP. */ static EST_ERROR est_proxy_propagate_pkcs7 (void *http_ctx, unsigned char *pkcs7, int pkcs7_len) { char http_hdr[EST_HTTP_HDR_MAX]; int hdrlen; /* * Send HTTP header */ snprintf(http_hdr, EST_HTTP_HDR_MAX, "%s%s%s%s", EST_HTTP_HDR_200, EST_HTTP_HDR_EOL, EST_HTTP_HDR_STAT_200, EST_HTTP_HDR_EOL); hdrlen = strnlen(http_hdr, EST_HTTP_HDR_MAX); snprintf(http_hdr + hdrlen, EST_HTTP_HDR_MAX, "%s: %s%s", EST_HTTP_HDR_CT, EST_HTTP_CT_PKCS7_CO, EST_HTTP_HDR_EOL); hdrlen = strnlen(http_hdr, EST_HTTP_HDR_MAX); snprintf(http_hdr + hdrlen, EST_HTTP_HDR_MAX, "%s: %s%s", EST_HTTP_HDR_CE, EST_HTTP_CE_BASE64, EST_HTTP_HDR_EOL); hdrlen = strnlen(http_hdr, EST_HTTP_HDR_MAX); snprintf(http_hdr + hdrlen, EST_HTTP_HDR_MAX, "%s: %d%s%s", EST_HTTP_HDR_CL, pkcs7_len, EST_HTTP_HDR_EOL, EST_HTTP_HDR_EOL); if (!mg_write(http_ctx, http_hdr, strnlen(http_hdr, EST_HTTP_HDR_MAX))) { return (EST_ERR_HTTP_WRITE); } /* * Send the signed PKCS7 certificate in the body */ if (!mg_write(http_ctx, pkcs7, pkcs7_len)) { EST_LOG_ERR("HTTP write error while propagating pkcs7"); return (EST_ERR_HTTP_WRITE); } return (EST_ERR_NONE); }
/* * This function is used to load an X509_STORE using raw * data from a buffer. The data is expected to be PEM * encoded. * * Returns the number of certs added to the store */ static int ossl_init_cert_store_from_raw (X509_STORE *store, unsigned char *raw, int size) { STACK_OF(X509_INFO) * sk = NULL; X509_INFO *xi; BIO *in; int cert_cnt = 0; in = BIO_new_mem_buf(raw, size); if (in == NULL) { EST_LOG_ERR("Unable to open the raw CA cert buffer"); return 0; } /* This loads from a file, a stack of x509/crl/pkey sets */ sk = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL); if (sk == NULL) { EST_LOG_ERR("Unable to read PEM encoded certs from BIO"); BIO_free(in); return 0; } BIO_free(in); /* scan over it and pull out the CRL's */ while (sk_X509_INFO_num(sk)) { xi = sk_X509_INFO_shift(sk); if (xi->x509 != NULL) { EST_LOG_INFO("Adding cert to store (%s)", xi->x509->name); X509_STORE_add_cert(store, xi->x509); cert_cnt++; } if (xi->crl != NULL) { EST_LOG_INFO("Adding CRL to store"); X509_STORE_add_crl(store, xi->crl); } X509_INFO_free(xi); } if (sk != NULL) { sk_X509_INFO_pop_free(sk, X509_INFO_free); } return (cert_cnt); }
/* * This function extracts data from the SSL context and puts * it into a buffer. */ static int est_io_read_raw (SSL *ssl, unsigned char *buf, int buf_max, int *read_cnt, int sock_read_timeout) { int cur_cnt; char peek_read_buf; *read_cnt = 0; cur_cnt = est_ssl_read(ssl, buf, buf_max, sock_read_timeout); if (cur_cnt < 0) { EST_LOG_ERR("TLS read error"); ossl_dump_ssl_errors(); return (EST_ERR_SSL_READ); } *read_cnt += cur_cnt; /* * Multiple calls to SSL_read may be required to get the full * HTTP payload. */ while (cur_cnt > 0 && *read_cnt < buf_max) { cur_cnt = est_ssl_read(ssl, (buf + *read_cnt), (buf_max - *read_cnt), sock_read_timeout); if (cur_cnt < 0) { EST_LOG_ERR("TLS read error"); ossl_dump_ssl_errors(); return (EST_ERR_SSL_READ); } *read_cnt += cur_cnt; } if ((*read_cnt == buf_max) && SSL_peek(ssl, &peek_read_buf, 1)) { EST_LOG_ERR("Buffer too small for received message"); return(EST_ERR_READ_BUFFER_TOO_SMALL); } return (EST_ERR_NONE); }
/* * This function is used to populate an X509_STORE structure, * which can be used by the OpenSSL TLS stack to verifying * a TLS peer. The X509_STORE should already have been allocated. * * Parameters: * store - Pointer to X509_STORE structure to hold the certs * raw1 - char array containing PEM encoded certs to put * into the store. * size1 - Length of the raw1 char array */ EST_ERROR ossl_init_cert_store (X509_STORE *store, unsigned char *raw1, int size1) { X509_STORE_set_flags(store, 0); int cnt; if (raw1) { cnt = ossl_init_cert_store_from_raw(store, raw1, size1); if (!cnt) { EST_LOG_ERR("Cert count is zero for store"); return (EST_ERR_NO_CERTS_FOUND); } } return (EST_ERR_NONE); }
/*! @brief est_proxy_stop() is used by an application to stop the EST proxy. This should be called prior to est_destroy(). @param ctx Pointer to the EST context libest uses HTTP code from the Mongoose HTTP server. This function allows the application to stop the HTTP services layer. @return EST_ERROR. */ EST_ERROR est_proxy_stop (EST_CTX *ctx) { EST_MG_CONTEXT *mgctx; if (!ctx) { EST_LOG_ERR("Null context"); return (EST_ERR_NO_CTX); } mgctx = (EST_MG_CONTEXT*)ctx->mg_ctx; if (mgctx) { mg_stop(mgctx); } return (EST_ERR_NONE); }
/* * This function can be used to output the OpenSSL * error buffer. This is useful when an OpenSSL * API call fails and you'd like to provide some * detail to the user regarding the cause of the * failure. */ void ossl_dump_ssl_errors () { BIO *e = NULL; BUF_MEM *bptr = NULL; e = BIO_new(BIO_s_mem()); if (!e) { EST_LOG_ERR("BIO_new failed"); return; } ERR_print_errors(e); (void)BIO_flush(e); BIO_get_mem_ptr(e, &bptr); EST_LOG_WARN("OSSL error: %s", bptr->data); BIO_free_all(e); }
unsigned char *est_ossl_BIO_copy_data(BIO *out, int *data_lenp) { unsigned char *data, *tdata; int data_len; data_len = BIO_get_mem_data(out, &tdata); data = malloc(data_len+1); if (data) { memcpy(data, tdata, data_len); data[data_len]='\0'; // Make sure it's \0 terminated, in case used as string if (data_lenp) { *data_lenp = data_len; } } else { EST_LOG_ERR("malloc failed"); } return data; }
/*! @brief est_proxy_start() is used by an application to start the EST proxy after est_proxy_init() and est_proxy_set_server() have been called and all the required callback functions have been provided by the application. @param ctx Pointer to the EST context libest uses HTTP code from the Mongoose HTTP server. This function allows the application to start the HTTP services layer, which is required by EST. @return EST_ERROR. */ EST_ERROR est_proxy_start (EST_CTX *ctx) { EST_MG_CONTEXT *mgctx; if (!ctx) { EST_LOG_ERR("Null context"); return (EST_ERR_NO_CTX); } mgctx = mg_start(ctx); if (mgctx) { ctx->mg_ctx = mgctx; return (EST_ERR_NONE); } else { return (EST_ERR_NO_SSL_CTX); } }
static HTTP_HEADER * parse_http_headers (unsigned char **buf, int *num_headers) { int i; HTTP_HEADER *hdrs; char *hdr_end; errno_t safec_rc; *num_headers = 0; hdrs = malloc(sizeof(HTTP_HEADER) * MAX_HEADERS); if (!hdrs) { EST_LOG_ERR("malloc failure"); return (NULL); } /* * Find offset of header deliminter */ safec_rc = strstr_s((char *) *buf, strnlen_s((char *) *buf, RSIZE_MAX_STR), "\r\n\r\n", MAX_HEADER_DELIMITER_LEN, &hdr_end); if (safec_rc != EOK) { EST_LOG_INFO("strstr_s error 0x%xO\n", safec_rc); } /* * Skip the first line */ skip((char **)buf, "\r\n"); for (i = 0; i < MAX_HEADERS; i++) { hdrs[i].name = skip_quoted((char **)buf, ":", " ", 0); hdrs[i].value = skip((char **)buf, "\r\n"); fflush(stdout); EST_LOG_INFO("Found HTTP header -> %s:%s", hdrs[i].name, hdrs[i].value); fflush(stdout); if (hdrs[i].name[0] == '\0') { break; } *num_headers = i + 1; if ((*buf) > (unsigned char *)hdr_end) { break; } } EST_LOG_INFO("Found %d HTTP headers\n", *num_headers); return (hdrs); }
/*! @brief est_proxy_set_server() is called by the application layer to specify the address/port of the EST server. It must be called after est_proxy_init() and prior to issuing any EST commands. @param ctx Pointer to EST context for a client session @param server Name of the EST server to connect to. The ASCII string representing the name of the server is limited to 254 characters @param port TCP port on the EST server to connect @return EST_ERROR EST_ERR_NONE - Success. EST_ERR_NO_CTX - NULL value passed for EST context EST_ERR_INVALID_SERVER_NAME - NULL value passed for EST server name, or server name string too long EST_ERR_INVALID_PORT_NUM - Invalid port number input, less than zero or greater than 65535 est_proxy_set_server error checks its input parameters and then stores both the hostname and port number into the EST context. */ EST_ERROR est_proxy_set_server (EST_CTX *ctx, const char *server, int port) { int rcvd_cacerts_len = 0; unsigned char *rcvd_cacerts = NULL; EST_ERROR rv; if (!ctx) { return EST_ERR_NO_CTX; } if (server == NULL) { return EST_ERR_INVALID_SERVER_NAME; } if (EST_MAX_SERVERNAME_LEN-1 < strnlen(server, EST_MAX_SERVERNAME_LEN)) { return EST_ERR_INVALID_SERVER_NAME; } if (port <= 0 || port > 65535) { return EST_ERR_INVALID_PORT_NUM; } strncpy(ctx->est_server, server, EST_MAX_SERVERNAME_LEN); ctx->est_port_num = port; /* * It's possible that the application did not provide the CA Certs chain * used to respond to Get CACerts requests. If this is the case, then get * them now and save them away in the context. */ if (ctx->ca_certs == NULL) { rv = est_proxy_retrieve_cacerts(ctx, &rcvd_cacerts, &rcvd_cacerts_len); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to retrieve CA Cert chain from server"); return (rv); } /* * This buffer will be freed normally with est_destroy() */ ctx->ca_certs = rcvd_cacerts; ctx->ca_certs_len = rcvd_cacerts_len; } return EST_ERR_NONE; }
/* * This routine will check the result code from an enroll * attempt and propagate the retry-after message to the * client if needed. */ static EST_ERROR est_proxy_propagate_retry (EST_CTX *ctx, void *http_ctx) { /* * The CA did not sign the request and has asked the * client to retry in the future. This may occur if * the CA is not configured for automatic enrollment. * Send the HTTP retry response to the client. * We need to propagate the retry-after response to * the client. */ EST_LOG_INFO("CA server requests retry, propagate this to the client (%d)", ctx->retry_after_delay); if (EST_ERR_NONE != est_server_send_http_retry_after(ctx, http_ctx, ctx->retry_after_delay)) { EST_LOG_ERR("HTTP write error while propagating retry-after"); return (EST_ERR_HTTP_WRITE); } return (EST_ERR_NONE); }
static HTTP_HEADER * parse_http_headers (unsigned char **buf, int *num_headers) { int i; HTTP_HEADER *hdrs; char *hdr_end; *num_headers = 0; hdrs = malloc(sizeof(HTTP_HEADER) * MAX_HEADERS); if (!hdrs) { EST_LOG_ERR("malloc failure"); return (NULL); } /* * Find offset of header delimiter */ hdr_end = strstr((const char *)*buf, "\r\n\r\n"); /* * Skip the first line */ skip((char **)buf, "\r\n"); for (i = 0; i < MAX_HEADERS; i++) { hdrs[i].name = skip_quoted((char **)buf, ":", " ", 0); hdrs[i].value = skip((char **)buf, "\r\n"); EST_LOG_INFO("Found HTTP header -> %s: %s", hdrs[i].name, hdrs[i].value); if (hdrs[i].name[0] == '\0') { break; } *num_headers = i + 1; if ((*buf) > (unsigned char *)hdr_end) { break; } } EST_LOG_INFO("Found %d HTTP headers", *num_headers); return (hdrs); }
/* * This function parses the authentication tokens from * the server when the server is requesting HTTP digest * authentication. The tokens are required to generate * a valid authentication response in future HTTP * requests. */ static EST_ERROR est_io_parse_auth_tokens (EST_CTX *ctx, char *hdr) { int rv = EST_ERR_NONE; char *p = hdr; char *token = NULL; char *value = NULL; int diff; errno_t safec_rc; /* * header will come in with the basic or digest field still on the front. * skip over it. */ token = HTNextField(&p); while ((token = HTNextField(&p))) { if (!est_strcasecmp_s(token, "realm")) { if ((value = HTNextField(&p))) { if (EOK != strncpy_s(ctx->realm, MAX_REALM, value, MAX_REALM)) { rv = EST_ERR_INVALID_TOKEN; } } else { rv = EST_ERR_INVALID_TOKEN; } } else if (!est_strcasecmp_s(token, "nonce")) { if ((value = HTNextField(&p))) { if (EOK != strncpy_s(ctx->s_nonce, MAX_NONCE, value, MAX_NONCE)) { rv = EST_ERR_INVALID_TOKEN; } } else { rv = EST_ERR_INVALID_TOKEN; } } else if (!est_strcasecmp_s(token, "qop")) { if ((value = HTNextField(&p))) { if (value[0] == '\0') { EST_LOG_WARN("Unsupported qop value: %s", value); } else { safec_rc = memcmp_s(value, sizeof("auth"), "auth", sizeof("auth"), &diff); if (safec_rc != EOK) { EST_LOG_INFO("memcmp_s error 0x%xO\n", safec_rc); } if (diff && (safec_rc == EOK)) { EST_LOG_WARN("Unsupported qop value: %s", value); } } } else { rv = EST_ERR_INVALID_TOKEN; } } else if (!est_strcasecmp_s(token, "algorithm")) { if ((value = HTNextField(&p)) && est_strcasecmp_s(value, "md5")) { EST_LOG_ERR("Unsupported digest algorithm: %s", value); /* ** We only support MD5 for the moment */ rv = EST_ERR_INVALID_TOKEN; } } else if (!est_strcasecmp_s(token, "error")) { if ((value = HTNextField(&p))) { if (EOK != strncpy_s(ctx->token_error, MAX_TOKEN_ERROR, value, MAX_TOKEN_ERROR)) { rv = EST_ERR_INVALID_TOKEN; } } else { rv = EST_ERR_INVALID_TOKEN; } } else if (!est_strcasecmp_s(token, "error_description")) { if ((value = HTNextField(&p))) { if (EOK != strncpy_s(ctx->token_error_desc, MAX_TOKEN_ERROR_DESC, value, MAX_TOKEN_ERROR_DESC)) { rv = EST_ERR_INVALID_TOKEN; } } else { rv = EST_ERR_INVALID_TOKEN; } } else { EST_LOG_WARN("Unsupported auth token ignored: %s", token); } if (rv == EST_ERR_INVALID_TOKEN) { memzero_s(ctx->s_nonce, MAX_NONCE+1); break; } } return (rv); }
/*! @brief est_proxy_init() is used by an application to create a context in the EST library. This context is used when invoking other functions in the API while in Proxy mode. @param ca_chain Char array containing PEM encoded CA certs & CRL entries. This chain of certificates is used as the trust anchor when establishing a TLS connection. @param ca_chain_len Length of ca_chain char array. @param cacerts_resp_chain Char array containing PEM encoded CA certs to include in the /cacerts response. This is an optional parameter. If it set, it contains the chain of certificates used by the proxy to respond to GET CA Certs requests from EST Clients. If this parameter is not included, then the proxy will obtain the CA certificate chain from the configured upstream EST server. If this parameter is not NULL, then the correct length of this buffer must be specified in cacerts_resp_chain_len. @param cacerts_resp_chain_len Length of cacerts_resp_chain char array @param cert_format Specifies the encoding of the local and external certificate chains (PEM/DER). @param http_realm Char array containing HTTP realm name for HTTP auth @param tls_id_cert Pointer to X509 that contains the proxy's certificate for the TLS layer. @param tls_id_key Pointer to EVP_PKEY that contains the private key associated with the proxy's certificate. @param uid User ID to use for authenticating with server @param pwd Password to use for authenticating with server This function allows an application to initialize an EST server context for proxy mode operation, which is used when operating as an RA. The application must provide the trusted CA certificates to use for server operation using the ca_chain parameter. This certificate set should include the explicit trust anchor certificate, any number of implicit trust anchor certificates, and any intermediate sub-CA certificates required to complete the chain of trust between the identity certificate passed into the tls_id_cert parameter and the root certificate for that identity certificate. The CA certificates should be encoded using the format specified in the cert_format parameter (e.g. PEM) and may contain CRL entries that will be used when authenticating EST clients connecting to the server. The applications must also provide the HTTP realm to use for HTTP authentication and the server cerificate/private key to use for TLS. Warning: Including additional intermediate sub-CA certificates that are not needed to complete the chain of trust may result in a potential MITM attack. @return EST_CTX. */ EST_CTX * est_proxy_init (unsigned char *ca_chain, int ca_chain_len, unsigned char *cacerts_resp_chain, int cacerts_resp_chain_len, EST_CERT_FORMAT cert_format, char *http_realm, X509 *tls_id_cert, EVP_PKEY *tls_id_key, char *uid, char *pwd) { EST_CTX *ctx; int len; est_log_version(); /* * Sanity check the input */ if (ca_chain == NULL) { EST_LOG_ERR("Trusted CA certificate set is empty"); return NULL; } if (tls_id_cert == NULL) { EST_LOG_ERR("TLS cert is empty"); return NULL; } if (tls_id_key == NULL) { EST_LOG_ERR("TLS private key is empty"); return NULL; } if (http_realm == NULL) { EST_LOG_ERR("EST HTTP realm is NULL"); return NULL; } if (cert_format != EST_CERT_FORMAT_PEM) { EST_LOG_ERR("Only PEM encoding of certificates is supported."); return NULL; } /* * Verify the lengths of the cert chains */ len = (int) strnlen((char *)ca_chain, EST_CA_MAX); if (len != ca_chain_len) { EST_LOG_ERR("Length of ca_chain doesn't match ca_chain_len"); return NULL; } if (cacerts_resp_chain) { len = (int) strnlen((char *)cacerts_resp_chain, EST_CA_MAX); if (len != cacerts_resp_chain_len) { EST_LOG_ERR("Length of cacerts_resp_chain doesn't match cacerts_resp_chain_len"); return NULL; } } /* * Allocate and set up the Proxy based EST Context. This context will be * use when operating as the Server to the downstream clients. EST Proxy mode * is basically a server function that requires client capabilities to * communicate to the upstream server when needed. */ ctx = malloc(sizeof(EST_CTX)); if (!ctx) { EST_LOG_ERR("malloc failed"); return NULL; } memset(ctx, 0, sizeof(EST_CTX)); ctx->est_mode = EST_PROXY; ctx->retry_period = EST_RETRY_PERIOD_DEF; ctx->server_enable_pop = 1; ctx->require_http_auth = HTTP_AUTH_REQUIRED; if (est_client_set_uid_pw(ctx, uid, pwd) != EST_ERR_NONE) { EST_LOG_ERR("Failed to store the userid and password during proxy initialization"); free(ctx); return NULL; } /* * Load the CA certificates into local memory and retain * for future use. This will be used for /cacerts requests. */ if (cacerts_resp_chain) { if (est_load_ca_certs(ctx, cacerts_resp_chain, cacerts_resp_chain_len)) { EST_LOG_ERR("Failed to load CA certificates response buffer"); free(ctx); return NULL; } } /* * Load the CA certificate chain into an X509 store structure * This will be used in verifying incoming certs during TLS * establishement. * Also save a way a raw copy of the ca_chain buffer so that * it can be used when creating client contexts used to communincate * to the upstream server. */ if (est_load_trusted_certs(ctx, ca_chain, ca_chain_len)) { EST_LOG_ERR("Failed to load trusted certificate store"); est_destroy(ctx); return NULL; } ctx->ca_chain_raw = malloc(ca_chain_len+1); if (!ctx->ca_chain_raw) { EST_LOG_ERR("malloc failed"); est_destroy(ctx); return NULL; } memcpy((char *)ctx->ca_chain_raw, (char *)ca_chain, ca_chain_len); ctx->ca_chain_raw[ca_chain_len] = '\0'; ctx->ca_chain_raw_len = ca_chain_len; strncpy(ctx->realm, http_realm, MAX_REALM); ctx->server_cert = tls_id_cert; ctx->server_priv_key = tls_id_key; ctx->auth_mode = AUTH_BASIC; ctx->read_timeout = EST_SSL_READ_TIMEOUT_DEF; ctx->retry_after_delay = 0; ctx->retry_after_date = 0; ctx->client_ctx_array = (CLIENT_CTX_LU_NODE_T *) malloc( sizeof(CLIENT_CTX_LU_NODE_T)*cur_max_ctx_array); memset(ctx->client_ctx_array, 0, sizeof(CLIENT_CTX_LU_NODE_T)*cur_max_ctx_array); return (ctx); }
/* * get_client_ctx() performs a search through a ordered array. * The key for the search is the current thread id and the value returned * is the client context that's been created for this thread. If no * entry exists in the array for this thread id, a new one is created. */ static EST_CTX *get_client_ctx (EST_CTX *p_ctx) { EST_CTX *c_ctx = NULL; EST_ERROR rv; unsigned long cur_threadid = 0; unsigned long cur_pid = getpid(); CLIENT_CTX_LU_NODE_T *found_node; unsigned long zero_threadid = 0x0; CLIENT_CTX_LU_NODE_T *empty_node; int empty_index; /* * TODO: This is really returning a pointer to an opaque value, so * what's being used here is typically a pointer in pthread based * environments and not the actual pthread id. The only helper API to * access the actual id is pthread_equal(). If this must be used, then * the array search would best be changed to a linear search. * We mix in the PID of the current process with the thread ID in * case the application is forking new processes (e.g. NGINX). */ #ifndef DISABLE_PTHREADS #ifdef __MINGW32__ cur_threadid = (unsigned long) GetCurrentThreadId(); #else cur_threadid = (unsigned long) pthread_self(); #endif #endif cur_threadid += cur_pid; found_node = (CLIENT_CTX_LU_NODE_T *) bsearch(&cur_threadid, p_ctx->client_ctx_array, cur_max_ctx_array, sizeof(CLIENT_CTX_LU_NODE_T), bsearch_compare); if (found_node == NULL) { /* * need to allocate a context and get it ready to be used. */ c_ctx = est_client_init(p_ctx->ca_chain_raw, p_ctx->ca_chain_raw_len, EST_CERT_FORMAT_PEM, NULL); if (c_ctx == NULL) { EST_LOG_ERR("Unable to allocate and initialize EST client context for proxy use"); return (NULL); } /* * The name is a bit misleading. The identity cert and private * key used for proxy mode are the ones stored in the server_cert and * server_priv_key, however they are used in both directions, so here * when setting up the client side, it looks mixed up. Might want to * change the name in context to hold these. */ rv = est_client_set_auth(c_ctx, p_ctx->userid, p_ctx->password, p_ctx->server_cert, p_ctx->server_priv_key); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to set authentication configuration in the client context for proxy use"); est_destroy(c_ctx); return (NULL); } rv = est_client_set_auth_cred_cb(c_ctx, p_ctx->auth_credentials_cb); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to register authentication credential callback."); return (NULL); } rv = est_client_set_server(c_ctx, p_ctx->est_server, p_ctx->est_port_num); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to set the upstream server configuration in the client context for proxy use"); est_destroy(c_ctx); return (NULL); } rv = est_client_set_read_timeout(c_ctx, p_ctx->read_timeout); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to set the SSL read timeout in the client context"); est_destroy(c_ctx); return (NULL); } /* * make sure there's room for another entry */ empty_node = (CLIENT_CTX_LU_NODE_T *) bsearch(&zero_threadid, p_ctx->client_ctx_array, cur_max_ctx_array, sizeof(CLIENT_CTX_LU_NODE_T), bsearch_compare); if (empty_node == NULL) { /* * we're out of space. allocate a new array and copy over what's * already there. Double the size of the current one. */ CLIENT_CTX_LU_NODE_T *temp_array; cur_max_ctx_array *= 2; temp_array = (CLIENT_CTX_LU_NODE_T *) malloc(sizeof(CLIENT_CTX_LU_NODE_T)*cur_max_ctx_array); memset(temp_array, 0, sizeof(CLIENT_CTX_LU_NODE_T)*cur_max_ctx_array); memcpy(temp_array, p_ctx->client_ctx_array, sizeof(CLIENT_CTX_LU_NODE_T)*cur_max_ctx_array/2); free(p_ctx->client_ctx_array); p_ctx->client_ctx_array = temp_array; qsort(p_ctx->client_ctx_array, cur_max_ctx_array, sizeof(CLIENT_CTX_LU_NODE_T), bsearch_compare); empty_node = (CLIENT_CTX_LU_NODE_T *) bsearch(&zero_threadid, p_ctx->client_ctx_array, cur_max_ctx_array, sizeof(CLIENT_CTX_LU_NODE_T), bsearch_compare); } empty_index = (int) (empty_node - p_ctx->client_ctx_array); /* * add to the array and sort it into its proper place */ p_ctx->client_ctx_array[empty_index].threadid = cur_threadid; p_ctx->client_ctx_array[empty_index].client_ctx = c_ctx; qsort(p_ctx->client_ctx_array, cur_max_ctx_array, sizeof(CLIENT_CTX_LU_NODE_T), bsearch_compare); } else { /* * the entry was found in the tree, return the client context for this pid */ c_ctx = found_node->client_ctx; } return(c_ctx); }
/*! @brief est_convert_p7b64_to_pem() converts the base64 encoded PKCS7 response from the EST server into PEM format. @param certs_p7 Points to a buffer containing the base64 encoded pkcs7 data. @param certs_len Indicates the size of the *certs_p7 buffer. @param pem Double pointer that will receive the PEM encoded data. Several of the EST message return data that contains base64 encoded PKCS7 certificates. This function is used to convert the data to PEM format. This function will allocate memory pointed to by the **pem argument. The caller is responsible for releasing this memory. The return value is the length of the PEM buffer, or -1 on error. @return int. */ int est_convert_p7b64_to_pem (unsigned char *certs_p7, int certs_len, unsigned char **pem) { X509 *x; STACK_OF(X509) *certs = NULL; BIO *b64, *in, *out; unsigned char *cacerts_decoded = NULL; int cacerts_decoded_len = 0; BIO *p7bio_in = NULL; PKCS7 *p7=NULL; int i, nid; unsigned char *pem_data; int pem_len; /* * Base64 decode the incoming ca certs buffer. Decoding will * always take up no more than the original buffer. */ b64 = BIO_new(BIO_f_base64()); if (!b64) { EST_LOG_ERR("BIO_new failed"); return (-1); } in = BIO_new_mem_buf(certs_p7, certs_len); if (!in) { EST_LOG_ERR("BIO_new failed"); return (-1); } in = BIO_push(b64, in); cacerts_decoded = malloc(certs_len); if (!cacerts_decoded) { EST_LOG_ERR("malloc failed"); return (-1); } cacerts_decoded_len = BIO_read(in, cacerts_decoded, certs_len); BIO_free_all(in); /* * Now get the PKCS7 formatted buffer of certificates read into a stack of * X509 certs */ p7bio_in = BIO_new_mem_buf(cacerts_decoded, cacerts_decoded_len); p7 = d2i_PKCS7_bio(p7bio_in, NULL); if (!p7) { EST_LOG_ERR("PEM_read_bio_PKCS7 failed"); ossl_dump_ssl_errors(); free(cacerts_decoded); return (-1); } BIO_free_all(p7bio_in); free(cacerts_decoded); /* * Now that we've decoded the certs, get a reference * the the stack of certs */ nid=OBJ_obj2nid(p7->type); switch (nid) { case NID_pkcs7_signed: certs = p7->d.sign->cert; break; case NID_pkcs7_signedAndEnveloped: certs = p7->d.signed_and_enveloped->cert; break; default: EST_LOG_ERR("Invalid NID value on PKCS7 structure"); PKCS7_free(p7); return (-1); break; } if (!certs) { EST_LOG_ERR("Failed to attain X509 cert stack from PKCS7 data"); PKCS7_free(p7); return (-1); } /* * Output the certs to a new BIO using the PEM format */ out = BIO_new(BIO_s_mem()); if (!out) { EST_LOG_ERR("BIO_new failed"); PKCS7_free(p7); return (-1); } for (i=0; i<sk_X509_num(certs); i++) { x=sk_X509_value(certs, i); PEM_write_bio_X509(out, x); BIO_puts(out, "\n"); } (void)BIO_flush(out); /* * Now convert the BIO to char* */ pem_len = (int) BIO_get_mem_data(out, (char**)&pem_data); if (pem_len <= 0) { EST_LOG_ERR("BIO_get_mem_data failed"); PKCS7_free(p7); return (-1); } *pem = malloc(pem_len + 1); if (!*pem) { EST_LOG_ERR("malloc failed"); PKCS7_free(p7); return (-1); } memcpy(*pem, pem_data, pem_len); (*pem)[pem_len] = 0; //Make sure it's null termianted BIO_free_all(out); PKCS7_free(p7); return (pem_len); }
/* * This function is used by the server side of the EST proxy to respond to an * incoming Simple Enroll request. This function is similar to the Client API * function, est_client_enroll_req(), except it bypasses some things that are * not done when functioning as a proxy, such as signing the CSR, not * inserting the TLS unique id and instead including the id-kp-cmcRA usage * extension. */ static EST_ERROR est_proxy_handle_simple_enroll (EST_CTX *ctx, void *http_ctx, SSL *ssl, const char *ct, char *body, int body_len, int reenroll) { EST_ERROR rv; BUF_MEM *pkcs10; unsigned char *pkcs7; int pkcs7_len = 0; X509_REQ *csr = NULL; EST_CTX *client_ctx; /* * Make sure the client has sent us a PKCS10 CSR request */ if (strcmp(ct, "application/pkcs10")) { return (EST_ERR_BAD_CONTENT_TYPE); } /* * Authenticate the client */ switch (est_enroll_auth(ctx, http_ctx, ssl, reenroll)) { case EST_HTTP_AUTH: case EST_SRP_AUTH: case EST_CERT_AUTH: break; case EST_HTTP_AUTH_PENDING: return (EST_ERR_AUTH_PENDING); break; case EST_UNAUTHORIZED: default: return (EST_ERR_AUTH_FAIL); break; } /* * Parse the PKCS10 CSR from the client */ csr = est_server_parse_csr((unsigned char*)body, body_len); if (!csr) { EST_LOG_ERR("Unable to parse the PKCS10 CSR sent by the client"); return (EST_ERR_BAD_PKCS10); } /* * Perform a sanity check on the CSR */ if (est_server_check_csr(csr)) { EST_LOG_ERR("PKCS10 CSR sent by the client failed sanity check"); X509_REQ_free(csr); return (EST_ERR_BAD_PKCS10); } /* * Do the PoP check (Proof of Possession). The challenge password * in the pkcs10 request should match the TLS unique ID. */ rv = est_tls_uid_auth(ctx, ssl, csr); X509_REQ_free(csr); if (rv != EST_ERR_NONE) { return (EST_ERR_AUTH_FAIL_TLSUID); } /* * body now points to the pkcs10 data, pass * this to the enrollment routine. Need to hi-jack * a BUF_MEM. Attach the body to a new BUF_MEM */ pkcs10 = BUF_MEM_new(); pkcs10->data = body; pkcs10->length = body_len; pkcs10->max = body_len; /* * get the client context for this thread */ client_ctx = get_client_ctx(ctx); if (!client_ctx) { EST_LOG_ERR("Unable to obtain client context for proxy operation"); est_proxy_free_ossl_bufmem(pkcs10); return (EST_ERR_NO_CTX); } /* * Allocate some space to hold the cert that we * expect to receive from the EST server. */ pkcs7 = malloc(EST_CA_MAX); /* * Attempt to enroll the CSR from the client */ rv = est_proxy_send_enroll_request(client_ctx, pkcs10, pkcs7, &pkcs7_len, reenroll); /* * Handle any errors that likely occurred */ switch (rv) { case EST_ERR_AUTH_FAIL: /* Try one more time if we're doing Digest auth */ if ((ctx->auth_mode == AUTH_DIGEST || ctx->auth_mode == AUTH_BASIC || ctx->auth_mode == AUTH_TOKEN)) { EST_LOG_INFO("HTTP Auth failed, trying again with digest/basic parameters"); rv = est_proxy_send_enroll_request(client_ctx, pkcs10, pkcs7, &pkcs7_len, reenroll); if (rv == EST_ERR_CA_ENROLL_RETRY) { rv = est_proxy_propagate_retry(client_ctx, http_ctx); } else if (rv != EST_ERR_NONE) { EST_LOG_WARN("EST enrollment failed, error code is %d", rv); } } break; case EST_ERR_CA_ENROLL_RETRY: rv = est_proxy_propagate_retry(client_ctx, http_ctx); break; default: EST_LOG_WARN("Initial EST enrollment request error code is %d", rv); break; } client_ctx->auth_mode = AUTH_NONE; /* * Prevent OpenSSL from freeing our data */ est_proxy_free_ossl_bufmem(pkcs10); /* * If we have a cert response from the EST server, let's forward * it back to the EST client */ if (pkcs7_len > 0) { rv = est_proxy_propagate_pkcs7(http_ctx, pkcs7, pkcs7_len); } free(pkcs7); return (rv); }
/* * This function is used by the server side of the EST proxy to respond to an * incoming CSR Attributes request. This function is similar to the Client API * function, est_client_get_csrattrs(). */ static int est_proxy_handle_csr_attrs (EST_CTX *ctx, void *http_ctx) { int rv = EST_ERR_NONE; int pop_present; char *csr_data, *csr_data_pop; int csr_len, csr_pop_len; EST_CTX *client_ctx; /* * get the client context for this thread */ client_ctx = get_client_ctx(ctx); if (!client_ctx) { EST_LOG_ERR("Unable to obtain client context for proxy operation"); return (EST_ERR_NO_CTX); } /* * Invoke client code to retrieve the CSR attributes. * Note: there is no need to authenticate the client (see sec 4.5) */ EST_LOG_INFO("Proxy get csr attributes"); rv = est_client_get_csrattrs(client_ctx, (unsigned char **)&csr_data, &csr_len); /* * csr_data points to the memory allocated to hold the csr attributes, * which will be freed in this call stack. To prevent a double-free * we null the to pointer on the client context. */ client_ctx->retrieved_csrattrs = NULL; client_ctx->retrieved_csrattrs_len = 0; if (rv == EST_ERR_NONE) { ctx->csr_pop_present = 0; if (ctx->server_enable_pop) { rv = est_is_challengePassword_present(csr_data, csr_len, &pop_present); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Error during PoP/sanity check"); est_send_http_error(ctx, http_ctx, EST_ERR_HTTP_NO_CONTENT); return (EST_ERR_NONE); } ctx->csr_pop_present = pop_present; if (!ctx->csr_pop_present) { if (csr_len == 0) { csr_data = malloc(EST_CSRATTRS_POP_LEN + 1); if (!csr_data) { return (EST_ERR_MALLOC); } strncpy(csr_data, EST_CSRATTRS_POP, EST_CSRATTRS_POP_LEN); csr_data[EST_CSRATTRS_POP_LEN] = 0; csr_len = EST_CSRATTRS_POP_LEN; return (est_send_csrattr_data(ctx, csr_data, csr_len, http_ctx)); } rv = est_add_challengePassword(csr_data, csr_len, &csr_data_pop, &csr_pop_len); if (rv != EST_ERR_NONE) { if (csr_data) { free(csr_data); } EST_LOG_ERR("Error during add PoP"); est_send_http_error(ctx, http_ctx, EST_ERR_HTTP_NO_CONTENT); return (EST_ERR_NONE); } if (csr_data) { free(csr_data); } csr_data = csr_data_pop; csr_len = csr_pop_len; } } } else { EST_LOG_ERR("Server not reachable or sent corrupt attributes"); est_send_http_error(ctx, http_ctx, EST_ERR_HTTP_NO_CONTENT); return (EST_ERR_NONE); } return (est_send_csrattr_data(ctx, csr_data, csr_len, http_ctx)); }
/* * est_proxy_retrieve_cacerts() issues a request to the server to obtain the * CA Certs chain to be used for Get CA Certs requests from clients. * The CA Cert chain returned from the server are passed back to the caller. * * It's the responsibility of the caller to free up this buffer. */ EST_ERROR est_proxy_retrieve_cacerts (EST_CTX *ctx, unsigned char **cacerts_rtn, int *cacerts_rtn_len) { EST_CTX *client_ctx; EST_ERROR rv; int rcvd_cacerts_len; unsigned char *rcvd_cacerts; if (ctx == NULL) { EST_LOG_ERR("Ctx not passed to %s", __FUNCTION__); return (EST_ERR_NO_CTX); } if (cacerts_rtn == NULL || cacerts_rtn_len == NULL) { EST_LOG_ERR("Ctx not passed to %s", __FUNCTION__); return (EST_ERR_INVALID_PARAMETERS); } *cacerts_rtn = NULL; *cacerts_rtn_len = 0; /* * Get the client context for this thread */ client_ctx = get_client_ctx(ctx); if (!client_ctx) { EST_LOG_ERR("Unable to obtain client context for proxy operation"); return (EST_ERR_NO_CTX); } rv = est_client_get_cacerts(client_ctx, &rcvd_cacerts_len); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to retrieve CA Certs from upstream server RC = %s", EST_ERR_NUM_TO_STR(rv)); return (rv); } /* * Allocate a buffer to retrieve the CA certs * and get them copied in */ rcvd_cacerts = malloc(rcvd_cacerts_len); if (rcvd_cacerts == NULL) { EST_LOG_ERR("Unable to malloc buffer for cacerts received from server"); return (EST_ERR_MALLOC); } rv = est_client_copy_cacerts(client_ctx, rcvd_cacerts); if (rv != EST_ERR_NONE) { EST_LOG_ERR("Unable to copy CA Certs from upstream server RC = %s", EST_ERR_NUM_TO_STR(rv)); free(rcvd_cacerts); return (rv); } /* * The retrieving of the CA certs through the normal client * interface causes the client to go back into an uninitialized state. * In this case though, we're getting it just for passing it back * to the downstream clients, so we're going to put this client * context back into the initialized state */ client_ctx->est_client_initialized = 1; *cacerts_rtn = rcvd_cacerts; *cacerts_rtn_len = rcvd_cacerts_len; return (EST_ERR_NONE); }
/* * This function verifies the content type header and also * returns the length of the content header, or -EST_ERROR. * The content type is important. For example, the content * type is expected to be pkcs7 on a simple enrollment. */ static int est_io_check_http_hdrs (HTTP_HEADER *hdrs, int hdr_cnt, EST_OPERATION op) { int i; int cl = 0; int status_present = 0, content_type_present = 0, content_length_present = 0; int cmp_result; /* * Traverse all the http headers and process the ones that need to be * checked */ for (i = 0; i < hdr_cnt; i++) { /* * status header */ cmp_result = strncmp(hdrs[i].name, "Status", 6); if (!cmp_result) { status_present = 1; if (strncmp(hdrs[i].value, "200 OK", 6)) { EST_LOG_ERR("HTTP status is not 200"); return -EST_ERR_UNKNOWN; } } else { /* * Content type */ cmp_result = strncmp(hdrs[i].name, EST_HTTP_HDR_CT, sizeof(EST_HTTP_HDR_CT)); if (!cmp_result) { content_type_present = 1; /* * Verify content is pkcs7 data */ cmp_result = strncmp(hdrs[i].value, est_op_map[op].content_type, strnlen(est_op_map[op].content_type, est_op_map[op].length)); if (cmp_result) { EST_LOG_ERR("HTTP content type is %s", hdrs[i].value); return -EST_ERR_BAD_CONTENT_TYPE; } } else { /* * Content Length */ cmp_result = strncmp(hdrs[i].name, EST_HTTP_HDR_CL, sizeof(EST_HTTP_HDR_CL)); if (!cmp_result) { content_length_present = 1; cl = atoi(hdrs[i].value); } } } } /* * Make sure all the necessary headers were present. */ if (op != EST_GET_CSRATTRS) { if (status_present == 0 ) { EST_LOG_ERR("Missing HTTP status header"); return -EST_ERR_UNKNOWN; } if (content_type_present == 0 ) { EST_LOG_ERR("Missing HTTP content type header"); return -EST_ERR_BAD_CONTENT_TYPE; } } if (content_length_present == 0 ) { EST_LOG_ERR("Missing HTTP content length header"); return -EST_ERR_BAD_CONTENT_LEN; } return cl; }
/* * This function provides the primary entry point into * this module. It's used by the EST client to read the * HTTP response from the server. The data is read from * the SSL context and HTTP parsing is invoked. * * If EST_ERR_NONE is returned then the raw_buf buffer must * be freed by the caller, otherwise, it is freed here. */ EST_ERROR est_io_get_response (EST_CTX *ctx, SSL *ssl, EST_OPERATION op, unsigned char **buf, int *payload_len) { int rv = EST_ERR_NONE; HTTP_HEADER *hdrs; int hdr_cnt; int http_status; unsigned char *raw_buf, *payload_buf, *payload; int raw_len = 0; raw_buf = malloc(EST_CA_MAX); if (raw_buf == NULL) { EST_LOG_ERR("Unable to allocate memory"); return EST_ERR_MALLOC; } memset(raw_buf, 0, EST_CA_MAX); payload = raw_buf; /* * Read the raw data from the SSL connection */ rv = est_io_read_raw(ssl, raw_buf, EST_CA_MAX, &raw_len, ctx->read_timeout); if (rv != EST_ERR_NONE) { EST_LOG_INFO("No valid response to process"); free(raw_buf); return (rv); } if (raw_len <= 0) { EST_LOG_WARN("Received empty HTTP response from server"); free(raw_buf); return (EST_ERR_HTTP_NOT_FOUND); } EST_LOG_INFO("Read %d bytes of HTTP data", raw_len); /* * Parse the HTTP header to get the status * Look for status 200 for success */ http_status = est_io_parse_response_status_code(raw_buf); hdrs = parse_http_headers(&payload, &hdr_cnt); EST_LOG_INFO("HTTP status %d received", http_status); /* * Check the Status header first to see * if the server accepted our request. */ switch (http_status) { case 200: /* Server reported OK, nothing to do */ break; case 204: case 404: EST_LOG_ERR("Server responded with 204/404, no content or not found"); if (op == EST_GET_CSRATTRS) { rv = EST_ERR_NONE; } else if (http_status == 404) { rv = EST_ERR_HTTP_NOT_FOUND; } else { rv = EST_ERR_HTTP_NO_CONTENT; } break; case 202: /* Server is asking for a retry */ EST_LOG_INFO("EST server responded with retry-after"); rv = est_io_parse_http_retry_after_resp(ctx, hdrs, hdr_cnt); break; case 400: EST_LOG_ERR("HTTP response from EST server was BAD REQUEST"); rv = EST_ERR_HTTP_BAD_REQ; break; case 401: /* Server is requesting user auth credentials */ EST_LOG_INFO("EST server requesting user authentication"); /* Check if we've already tried authenticating, if so, then bail * First time through, auth_mode will be set to NONE */ if (ctx->auth_mode == AUTH_DIGEST || ctx->auth_mode == AUTH_BASIC || ctx->auth_mode == AUTH_TOKEN) { ctx->auth_mode = AUTH_FAIL; rv = EST_ERR_AUTH_FAIL; break; } est_io_parse_http_auth_request(ctx, hdrs, hdr_cnt); rv = EST_ERR_AUTH_FAIL; break; case -1: /* Unsupported HTTP response */ EST_LOG_ERR("Unsupported HTTP response from EST server (%d)", http_status); rv = EST_ERR_UNKNOWN; break; default: /* Some other HTTP response was given, do we want to handle these? */ EST_LOG_ERR("HTTP response from EST server was %d", http_status); rv = EST_ERR_HTTP_UNSUPPORTED; break; } if (rv == EST_ERR_NONE) { /* * Get the Content-Type and Content-Length headers * and verify the HTTP response contains the correct amount * of data. */ *payload_len = est_io_check_http_hdrs(hdrs, hdr_cnt, op); if (*payload_len < 0) { rv = -*payload_len; } else { EST_LOG_INFO("HTTP Content len=%d", *payload_len); } if (*payload_len > EST_CA_MAX) { EST_LOG_ERR("Content Length larger than maximum value of %d.", EST_CA_MAX); rv = EST_ERR_UNKNOWN; *payload_len = 0; *buf = NULL; } else if (*payload_len <= 0) { *payload_len = 0; *buf = NULL; } else { /* * Allocate the buffer to hold the payload to be passed back */ payload_buf = malloc(*payload_len); if (!payload_buf) { EST_LOG_ERR("Unable to allocate memory"); free(raw_buf); free(hdrs); return EST_ERR_MALLOC; } memcpy(payload_buf, payload, *payload_len); *buf = payload_buf; } } if (raw_buf) { free(raw_buf); } if (hdrs) { free(hdrs); } return (rv); }
/* * This function parses the authentication tokens from * the server when the server is requesting HTTP digest * authentication. The tokens are required to generate * a valid authentication response in future HTTP * requests. */ static EST_ERROR est_io_parse_auth_tokens (EST_CTX *ctx, char *hdr) { int rv = EST_ERR_NONE; char *p = hdr; char *token = NULL; char *value = NULL; /* * header will come in with the basic or digest field still on the front. * skip over it. */ token = HTNextField(&p); while ((token = HTNextField(&p))) { if (!strcasecmp(token, "realm")) { if ((value = HTNextField(&p))) { strncpy(ctx->realm, value, MAX_REALM); } else { rv = EST_ERR_INVALID_TOKEN; } } else if (!strcasecmp(token, "nonce")) { if ((value = HTNextField(&p))) { strncpy(ctx->s_nonce, value, MAX_NONCE); } else { rv = EST_ERR_INVALID_TOKEN; } } else if (!strcasecmp(token, "qop")) { if ((value = HTNextField(&p))) { if (strcmp(value, "auth")) { EST_LOG_WARN("Unsupported qop value: %s", value); } } else { rv = EST_ERR_INVALID_TOKEN; } } else if (!strcasecmp(token, "algorithm")) { if ((value = HTNextField(&p)) && strcasecmp(value, "md5")) { EST_LOG_ERR("Unsupported digest algorithm: %s", value); /* ** We only support MD5 for the moment */ rv = EST_ERR_INVALID_TOKEN; } } else if (!strcasecmp(token, "error")) { if ((value = HTNextField(&p))) { if (!strncpy(ctx->token_error, value, MAX_TOKEN_ERROR)) { rv = EST_ERR_INVALID_TOKEN; } } else { rv = EST_ERR_INVALID_TOKEN; } } else if (!strcasecmp(token, "error_description")) { if ((value = HTNextField(&p))) { if (!strncpy(ctx->token_error_desc, value, MAX_TOKEN_ERROR_DESC)) { rv = EST_ERR_INVALID_TOKEN; } } else { rv = EST_ERR_INVALID_TOKEN; } } else { EST_LOG_WARN("Unsupported auth token ignored: %s", token); } if (rv == EST_ERR_INVALID_TOKEN) { memset(ctx->s_nonce, 0, MAX_NONCE); break; } } return (rv); }
/* * This function takes in the list of headers that were in the server's * response, it walks through the headers looking for a Retry-After response * header. If one is found, the value is parsed and saved away in the EST * context. This value can be in one of two formats, both are represented as * an ASCII string. The first format can be a count of the number of seconds * the client should wait before retrying the request. The second format is a * time/date stamp of the point in time at which the client should retry the * request. The result of this function is the setting of the retry_after * values in the context. If no retry-after header was received, or was * received and could not be parsed, the values will be zero, otherwise, they * are set to the value received. * * NOTE: The EST client currently does not support the time/date format * response and will not process a response in this format. */ static EST_ERROR est_io_parse_http_retry_after_resp (EST_CTX *ctx, HTTP_HEADER *hdrs, int hdr_cnt) { EST_ERROR rv = EST_ERR_INVALID_RETRY_VALUE; int i; int cmp_result; int rc; long long int temp_ll; int found = 0; /* * Initialize assuming there was no retry-after header. */ ctx->retry_after_delay = 0; ctx->retry_after_date = 0; for (i = 0; i < hdr_cnt; i++) { cmp_result = strncasecmp(hdrs[i].name, EST_HTTP_HDR_RETRY_AFTER, sizeof(EST_HTTP_HDR_RETRY_AFTER)); if (!cmp_result) { EST_LOG_INFO("Retry-After value = %s", hdrs[i].value); found = 1; /* * Determine whether or not the value is a date/time string * or is an integer representing the number of seconds * that the client must wait. */ if (isalpha((int)*(hdrs[i].value))) { #ifdef RETRY_AFTER_DELAY_TIME_SUPPORT int rc; /* * Convert the date/time string into a time_t */ rc = parsedate(hdrs[i].value, &ctx->retry_after_date); if (rc != PARSEDATE_OK) { EST_LOG_ERR("Retry-After value could not be parsed"); } #else /* * This format is not currently supported. */ EST_LOG_ERR("Retry-After value not in the correct format"); #endif } else { /* * make sure it's all digits, make sure it's no larger than a * four byte integer, and cache away the value returned for * the retry delay. */ rc = strisdigit(hdrs[i].value, 10); // max of 10 decimal places if (rc) { temp_ll = atoll(hdrs[i].value); if (temp_ll <= INT_MAX) { ctx->retry_after_delay = (int) temp_ll; rv = EST_ERR_CA_ENROLL_RETRY; } else { EST_LOG_ERR("Retry-After value too large"); } } else { EST_LOG_ERR("Retry-After value could not be parsed"); } } } } if (found == 0) { EST_LOG_ERR("Retry-After header missing"); } return rv; }