/***************************************************************************************** * Authorization routines *****************************************************************************************/ int ossl_verify_cb (int ok, X509_STORE_CTX *ctx) { int cert_error = X509_STORE_CTX_get_error(ctx); X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx); EST_LOG_INFO("enter function: ok=%d cert_error=%d", ok, cert_error); if (!ok) { if (current_cert) { X509_NAME_print_ex_fp(stdout, X509_get_subject_name(current_cert), 0, XN_FLAG_ONELINE); printf("\n"); } EST_LOG_INFO("%serror %d at %d depth lookup: %s", X509_STORE_CTX_get0_parent_ctx(ctx) ? "[CRL path]" : "", cert_error, X509_STORE_CTX_get_error_depth(ctx), X509_verify_cert_error_string(cert_error)); switch (cert_error) { case X509_V_ERR_UNABLE_TO_GET_CRL: /* * We've enabled CRL checking in the TLS stack. If * the application hasn't loaded a CRL, then this * verify error can occur. The peer's cert is valid, * but we can't confirm if it was revoked. We'll * warn the application. */ EST_LOG_WARN("No CRL loaded, TLS peer will be allowed."); ok = 1; break; case X509_V_ERR_NO_EXPLICIT_POLICY: case X509_V_ERR_CERT_HAS_EXPIRED: /* since we are just checking the certificates, it is * ok if they are self signed. But we should still warn * the user. */ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: /* Continue after extension errors too */ case X509_V_ERR_INVALID_CA: case X509_V_ERR_INVALID_NON_CA: case X509_V_ERR_PATH_LENGTH_EXCEEDED: case X509_V_ERR_INVALID_PURPOSE: case X509_V_ERR_CRL_HAS_EXPIRED: case X509_V_ERR_CRL_NOT_YET_VALID: case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: case X509_V_ERR_CERT_REVOKED: default: EST_LOG_WARN("Certificate verify failed (reason=%d)", cert_error); break; } return ok; } return (ok); }
/* * 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); }
/* * This function should be called by the web server layer when * a HTTP request arrives on the listening port of the EST proxy. * It will determine the EST request type and dispatch the request * to the appropriate handler. * * Paramters: * ctx: Pointer to EST_CTX * http_ctx: Context pointer from web server * method: The HTML method in the request, should be either "GET" or "POST" * uri: pointer to HTTP URI * body: pointer to full HTML body contents * body_len: length of HTML body * ct: HTML content type header */ EST_ERROR est_proxy_http_request (EST_CTX *ctx, void *http_ctx, char *method, char *uri, char *body, int body_len, const char *ct) { SSL *ssl; EST_ERROR rc; if (!ctx) { return (EST_ERR_NO_CTX); } /* * Verify the context is for a proxy, not a client or server */ if (ctx->est_mode != EST_PROXY) { return (EST_ERR_BAD_MODE); } EST_LOG_INFO("Proxy started handling %s %s", method, uri); /* * See if this is a cacerts request */ if (strncmp(uri, EST_CACERTS_URI, EST_URI_MAX_LEN) == 0) { /* Only GET is allowed */ if (strcmp(method, "GET")) { rc = EST_ERR_WRONG_METHOD; } else { rc = est_handle_cacerts(ctx, http_ctx); } } /* * See if this is a simple (re-)enrollment request */ else if (strncmp(uri, EST_SIMPLE_ENROLL_URI, EST_URI_MAX_LEN) == 0 || strncmp(uri, EST_RE_ENROLL_URI, EST_URI_MAX_LEN) == 0) { /* Only POST is allowed */ if (strcmp(method, "POST")) { rc = EST_ERR_WRONG_METHOD; } else if (!ct) { EST_LOG_WARN("Incoming HTTP header has no Content-Type header"); rc = EST_ERR_BAD_CONTENT_TYPE; } else { /* * Get the SSL context, which is required for authenticating the client. */ ssl = (SSL*)mg_get_conn_ssl(http_ctx); if (!ssl) { rc = EST_ERR_NO_SSL_CTX; } else { rc = est_proxy_handle_simple_enroll(ctx, http_ctx, ssl, ct, body, body_len, strncmp(uri, EST_RE_ENROLL_URI, EST_URI_MAX_LEN) == 0); if (rc != EST_ERR_NONE && rc != EST_ERR_AUTH_PENDING && rc != EST_ERR_AUTH_FAIL) { #if 0 // I see no reason for hiding real cause of error from clients rc = EST_ERR_BAD_PKCS10; #endif } } } } #if 0 /* * See if this is a keygen request * FIXME: this is currently not implemented */ else if (strncmp(uri, EST_KEYGEN_URI, EST_URI_MAX_LEN) == 0) { /* Only POST is allowed */ if (strcmp(method, "POST")) { rc = EST_ERR_WRONG_METHOD; } else if (!ct) { EST_LOG_WARN("Incoming HTTP header has no Content-Type header"); rc = EST_ERR_BAD_CONTENT_TYPE; } else if (est_proxy_handle_keygen(ctx)) { rc = EST_ERR_HTTP_WRITE; //FIXME: need the appropriate return code } } #endif /* * See if this is a CSR attributes request */ else if (strncmp(uri, EST_CSR_ATTRS_URI, EST_URI_MAX_LEN) == 0) { /* Only GET is allowed */ if (strcmp(method, "GET")) { rc = EST_ERR_WRONG_METHOD; } else { rc = est_proxy_handle_csr_attrs(ctx, http_ctx); } } /* * Send a 404 error if the URI didn't match */ else { rc = EST_ERR_HTTP_NOT_FOUND; } EST_LOG_INFO("Proxy finished handling %s %s with rv = %d (%s)", method, uri, rc, EST_ERR_NUM_TO_STR(rc)); if (rc != EST_ERR_NONE) { est_send_http_error(ctx, http_ctx, rc); } return (rc); }
/* * 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 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 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; 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); }