leo hendrickson / Mbed OS example-Ethernet-mbed-Cloud-connect
Revision:
0:8f0bb79ddd48
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/simple-mbed-cloud-client/mbed-cloud-client/certificate-enrollment-client/source/CertificateEnrollmentClient.cpp	Tue May 04 08:55:12 2021 +0000
@@ -0,0 +1,587 @@
+// ----------------------------------------------------------------------------
+// Copyright 2018 ARM Ltd.
+//  
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//  
+//     http://www.apache.org/licenses/LICENSE-2.0
+//  
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ----------------------------------------------------------------------------
+
+
+#include "CertificateEnrollmentClient.h"
+#include "mbed-client/m2mresource.h"
+#include "mbed-client/m2minterfacefactory.h"
+#include "mbed-client/m2minterface.h"
+#include "pal.h"
+#include "eventOS_scheduler.h"
+#include "eventOS_event.h"
+#include "ce_defs.h"
+#include "ce_tlv.h"
+#include "certificate_enrollment.h"
+#include <stdio.h>
+#include <string.h>
+#include "CertificateEnrollmentClient.h"
+#include "CertificateEnrollmentClientCommon.h"
+#include "CertificateRenewalData.h"
+#include "pv_error_handling.h"
+#include "pv_macros.h"
+
+#define RESOURCE_ID_CERTIFICATE_NAME "27002"
+#define OBJECT_LWM2M_CERTIFICATE "35011"
+
+#define NUMBER_OF_CONCURRENT_RENEWALS 1
+
+
+/************************************************************************/
+/* Different calls to update                                            */
+/************************************************************************/
+
+extern const char g_lwm2m_name[];
+
+namespace CertificateEnrollmentClient {
+
+    // Event type that is part of the arm_event_s structure.
+    enum event_type_e {
+        EVENT_TYPE_INIT, // Some initializer - nothing done currently, initialization called by mbed cloud client
+        EVENT_TYPE_RENEWAL_REQUEST, // Certificate renewal request. We can tell if it is initiated by the server or the device with the derived type of CertificateRenewalDataBase of the certificate descriptor / global variable
+        EVENT_TYPE_EST_RESPONDED, // Certificate arrived from EST service, or EST failure
+        EVENT_TYPE_MAX = 0xff // Must fit in a uint8_t (field in the arm_event_s struct)
+    };
+
+    // Pointer to the EST client object for dealing with the EST service.
+    extern const CERT_ENROLLMENT_EST_CLIENT *g_est_client;
+
+    // Data for certificate that is currently being renewed. Will change to list if we need to support multiple renewals
+    static CertificateRenewalDataBase *current_cert = NULL;
+
+    // ID of the handler we register to the MbedCloudClient event loop
+    static int8_t handler_id = -1;
+
+    // Flag that indicates whether the module is initialized
+    static bool is_initialized = false;
+
+    // Semaphore for enforcing that only NUMBER_OF_CONCURRENT_RENEWALS (currently 1) request at a time may update current_cert. Hold lock until process finished.
+    // Important: When pal_osSemaphoreWait called from within event loop - do not block, must set timeout to 0, and fail if failed to acquire lock
+    // Future: For supporting renewals of NUMBER_OF_CONCURRENT_RENEWALS certificates simultaneously - change to semaphore that counts to NUMBER_OF_CONCURRENT_RENEWALS
+    //         and maintain a list of certificates of size NUMBER_OF_CONCURRENT_RENEWALS inside the event loop.
+    static palSemaphoreID_t g_renewal_sem = 0;
+
+
+    /**
+    * \brief Finish the renewal process.
+    * Zero current_cert pointer, then release the semaphore. Note that when the semaphore is released - new device renewals may be made.
+    * Then call renewal_data->finish() and delete renewal_data.
+    *
+    * \param renewal_data the data of the certificate to be renewed. 
+    *        It is important that this is passed to the function because after releasing the semaphore - the global pointer may be replaced.
+    * \param exit_status the status of the renewal process
+    */
+    static void certificate_renewal_finish(CertificateRenewalDataBase *renewal_data, ce_status_e exit_status);
+
+    /**
+    * \brief The function that handles all the CertificateEnrollmentClient events
+    * Create an arm_event_s object and call eventOS_event_send()
+    * The event will have an application level priority, and will be executed when in the head of the event queue.
+    * In the future: an extra arg should be passed - some descriptor for the specific CertificateRenewalDataBase object.
+    *
+    * \param event_type An event identifier
+    */
+    static void event_handler(arm_event_s* event);
+
+    /**
+    * \brief Send a new event to the event loop queue.
+    * Create an arm_event_s object and call eventOS_event_send()
+    * The event will have an application level priority
+    *
+    * \param renewal_data A pointer to an object derived from CertificateRenewalDataBase
+    * \param event_type An event identifier
+    */
+    static ce_status_e schedule_event(event_type_e event_type);
+
+    /**
+    * \brief Callback that will be executed when an EST service response is available
+    * Event Context is probably network so this function will check if response is success, if so, allocate the data if needed in renewal_data->est_data. schedule a new event with type EVENT_TYPE_EST_RESPONDED.
+    *
+    * \param result Whether the EST client successfully received a certificate from the EST service
+    * \param cert_chain structure containing the certificate/chain received from the EST service
+    * \param Context passed when requesting a certificate via the EST client. Currently unused. In the future will be used to identify the relevant CertificateRenewalDataBase object.
+    */
+    static void est_cb(est_enrollment_result_e result,
+                       cert_chain_context_s *cert_chain,
+                       void *context);
+
+    /**
+    * \brief The function that handles the EST response.
+    *
+    * Called by event_handler(). renewal_data->est_data already exists and is valid.
+    * Will perform a safe replacement of the certificate with the new certificate received from EST
+    * Then it will free the EST chain and finish the renewal operation.
+    * \param renewal_data A pointer to an object derived from CertificateRenewalDataBase
+    */
+    static void est_response_process(CertificateRenewalDataBase *renewal_data);
+
+    /**
+    * \brief Create g_cert_enroll_lwm2m_obj, from the object create an object resource, and create the resources. Then push the object to the MCC object list
+    *
+    * Note that the pointers to the objects created by this function is owned by the CertificateEnrollmentClient Module and must be released in by CertificateEnrollmentClient::finalize()
+    * \param list A reference to the MbedCloudClient object list. MbedCloudClient will later set the resource
+    */
+    static ce_status_e init_objects(M2MBaseList& list);
+
+    /**
+    * \brief Release the objects created by init_objects()
+    *
+    */
+    static void release_objects();
+
+    // Callback is called when we get a POST message to g_cert_enroll_lwm2m_resource (runs in high priority context!)
+    /**
+    * \brief Callback is called when we get a POST message to g_cert_enroll_lwm2m_resource
+    * Runs in network context of the event loop.
+    * This function extracts the input data, creates a CertificateEnrollmentClient::CertificateRenewalDataFromServer object sets the global variable
+    *
+    * \param arg a M2MResource::M2MExecuteParameter argument.
+    */
+    static void certificate_renewal_post(void *arg);
+
+    /**
+    * \brief Start the renewal process.
+    * Parse the certificate name, generate keys and CSR. Then call the EST client so the new certificate may be retrieved
+    *
+    * \param renewal_data the data of the certificate to be renewed 
+    */
+    static void certificate_renewal_start(CertificateRenewalDataBase *renewal_data);
+
+    /**
+    * \brief Call the user callback and send a response to the server, when a CertificateRenewalDataFromServer object does not exist.
+    * Use only for server initiated renewal, since this sends a response to the server.
+    *
+    * \param tlv the raw data from the server - should be in the form of TLV
+    * \param tlv_size size of the TLV buffer
+    * \param ret_status The return status to return to the user callback and the server
+    */
+    static void call_user_cb_send_response(const uint8_t *tlv, uint16_t tlv_size, ce_status_e ret_status);
+
+#ifdef CERT_RENEWAL_TEST
+    void testonly_certificate_renewal_post(void *arg);
+#endif // CERT_RENEWAL_TEST
+
+}
+
+// FIXME: print error
+void CertificateEnrollmentClient::call_user_cb_send_response(const uint8_t *tlv, uint16_t tlv_size, ce_status_e ret_status)
+{
+    CertificateEnrollmentClient::CertificateRenewalDataFromServer temp_obj(tlv, tlv_size);
+    (void)temp_obj.parse();
+
+    // Call user callback with appropriate error
+    // In case of parsing error (malformed TLV), the provided ret_status will be returned and not 
+    call_user_cert_renewal_cb(temp_obj.cert_name, ret_status, CE_INITIATOR_SERVER);
+
+    // Send response to the server
+    SA_PV_LOG_INFO("sending delayed response\n");
+    g_cert_enroll_lwm2m_resource->set_value(ret_status);
+    g_cert_enroll_lwm2m_resource->send_delayed_post_response();
+}
+
+void CertificateEnrollmentClient::certificate_renewal_post(void *arg)
+{
+    palStatus_t pal_status;
+	ce_status_e status;
+    SA_PV_LOG_INFO_FUNC_ENTER_NO_ARGS();
+
+    M2MResource::M2MExecuteParameter *args = (M2MResource::M2MExecuteParameter *)arg;
+    const uint8_t *data = args->get_argument_value();
+    const uint16_t data_size = args->get_argument_value_length();
+
+    // If CEC module is not initialized - do not even take semaphore - exit with proper response
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((!is_initialized), call_user_cb_send_response(data, data_size, CE_STATUS_NOT_INITIALIZED), "Certificate Renewal module not initialized");
+
+    pal_status = pal_osSemaphoreWait(g_renewal_sem, 0, NULL);
+
+    if (pal_status == PAL_SUCCESS) {
+        CertificateEnrollmentClient::current_cert = new CertificateEnrollmentClient::CertificateRenewalDataFromServer(data, data_size);
+        if (!CertificateEnrollmentClient::current_cert) {
+            status = CE_STATUS_OUT_OF_MEMORY;
+            pal_status = pal_osSemaphoreRelease(g_renewal_sem);
+            if (PAL_SUCCESS != pal_status) { // Should never happen
+                status = CE_STATUS_ERROR;
+            }
+
+            call_user_cb_send_response(data, data_size, status);
+			return;
+        }
+
+        // Enqueue the event
+        status = schedule_event(CertificateEnrollmentClient::EVENT_TYPE_RENEWAL_REQUEST);
+        SA_PV_ERR_RECOVERABLE_RETURN_IF((status != CE_STATUS_SUCCESS), certificate_renewal_finish(CertificateEnrollmentClient::current_cert, status), "Error scheduling event");
+
+    } else {
+        SA_PV_LOG_ERR("Failed to take semaphore- device busy\n");
+
+        if (pal_status == PAL_ERR_RTOS_TIMEOUT) {
+            status = CE_STATUS_DEVICE_BUSY;
+        } else {
+            status = CE_STATUS_ERROR;
+        }
+
+        call_user_cb_send_response(data, data_size, status);
+        return;
+    }
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+}
+
+ce_status_e CertificateEnrollmentClient::certificate_renew(const char *cert_name)
+{
+    palStatus_t pal_status = PAL_SUCCESS;
+    ce_status_e status = CE_STATUS_SUCCESS;
+
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((!cert_name), CE_STATUS_INVALID_PARAMETER, "Provided NULL certificate name");
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((!is_initialized), CE_STATUS_NOT_INITIALIZED, "Certificate Renewal module not initialized");
+
+    SA_PV_LOG_INFO_FUNC_ENTER("cert_name = %s\n", cert_name);
+
+    pal_status = pal_osSemaphoreWait(g_renewal_sem, 0, NULL);
+
+    if (pal_status == PAL_SUCCESS) {
+        CertificateEnrollmentClient::current_cert = new CertificateEnrollmentClient::CertificateRenewalDataFromDevice(cert_name);
+        SA_PV_ERR_RECOVERABLE_GOTO_IF((!CertificateEnrollmentClient::current_cert), status = CE_STATUS_OUT_OF_MEMORY, ReleseSemReturn, "Allocation error");
+
+        // Enqueue the event
+        status = schedule_event(CertificateEnrollmentClient::EVENT_TYPE_RENEWAL_REQUEST);
+        SA_PV_ERR_RECOVERABLE_GOTO_IF((status != CE_STATUS_SUCCESS), status = status, ReleseSemReturn, "Error scheduling event");
+
+        // If some error synchronous error has occurred before scheduling the event - release the semaphore we had just taken, 
+        // and then return the error without calling the user callback 
+ReleseSemReturn:
+        if (status != CE_STATUS_SUCCESS) {
+            pal_status = pal_osSemaphoreRelease(g_renewal_sem);
+            if (PAL_SUCCESS != pal_status) { // Should never happen
+                status = CE_STATUS_ERROR;
+            }
+        }
+
+    } else {
+        // return with appropriate error
+        if (pal_status == PAL_ERR_RTOS_TIMEOUT) {
+            status = CE_STATUS_DEVICE_BUSY;
+        } else {
+            status = CE_STATUS_ERROR;
+        }
+        
+    }
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+    return status;
+}
+
+
+void CertificateEnrollmentClient::on_certificate_renewal(cert_renewal_cb_f user_cb)
+{
+    SA_PV_LOG_INFO_FUNC_ENTER_NO_ARGS();
+    CertificateEnrollmentClient::set_user_cert_renewal_cb(user_cb);
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+}
+
+
+ce_status_e CertificateEnrollmentClient::init_objects(M2MBaseList& list)
+{
+    M2MObjectInstance *cert_enroll_lwm2m_obj_instance;
+
+    ce_status_e ce_status = CE_STATUS_SUCCESS;
+    SA_PV_LOG_INFO_FUNC_ENTER_NO_ARGS();
+
+    // Create the certificate enrollment resource
+    g_cert_enroll_lwm2m_obj = M2MInterfaceFactory::create_object(OBJECT_LWM2M_CERTIFICATE);
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((!g_cert_enroll_lwm2m_obj), CE_STATUS_ERROR, "Error creating LWM2M object");
+
+    // Create the instance
+    cert_enroll_lwm2m_obj_instance = g_cert_enroll_lwm2m_obj->create_object_instance();
+    SA_PV_ERR_RECOVERABLE_GOTO_IF((!cert_enroll_lwm2m_obj_instance), ce_status = CE_STATUS_ERROR, Cleanup, "Error creating LWM2M object instance");
+
+    // Create the resource
+    g_cert_enroll_lwm2m_resource = cert_enroll_lwm2m_obj_instance->create_dynamic_resource(RESOURCE_ID_CERTIFICATE_NAME, "Enroll", M2MResourceInstance::INTEGER, false);
+    SA_PV_ERR_RECOVERABLE_GOTO_IF((!g_cert_enroll_lwm2m_resource), ce_status = CE_STATUS_ERROR, Cleanup, "Error creating LWM2M resource");
+
+    // Allow POST operations
+    g_cert_enroll_lwm2m_resource->set_operation(M2MBase::POST_ALLOWED);
+
+    // Set the resource callback
+    SA_PV_ERR_RECOVERABLE_GOTO_IF((!g_cert_enroll_lwm2m_resource->set_execute_function(CertificateEnrollmentClient::certificate_renewal_post)),
+                                  ce_status = CE_STATUS_ERROR, Cleanup, "Error resource callback");
+
+    // Enable sending of delayed responses
+    g_cert_enroll_lwm2m_resource->set_delayed_response(true);
+
+    // Push the object to the list
+    list.push_back(g_cert_enroll_lwm2m_obj);
+
+Cleanup:
+    if (ce_status != CE_STATUS_SUCCESS) {
+        // Destroying the object will destroy all instances and resources associated with it
+        delete g_cert_enroll_lwm2m_obj;
+        g_cert_enroll_lwm2m_resource = NULL;
+    }
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+    return ce_status;
+}
+
+void CertificateEnrollmentClient::release_objects()
+{
+    delete g_cert_enroll_lwm2m_obj;
+    g_cert_enroll_lwm2m_obj = NULL;
+}
+
+
+ce_status_e CertificateEnrollmentClient::init(M2MBaseList& list, const EstClient *est_client)
+{
+    ce_status_e ce_status = CE_STATUS_SUCCESS;
+    palStatus_t pal_status = PAL_SUCCESS;
+
+    SA_PV_LOG_INFO_FUNC_ENTER_NO_ARGS();
+
+    if (!is_initialized) {
+
+        // Init the LWM2M object and resource and push the object
+        ce_status = init_objects(list);
+        SA_PV_ERR_RECOVERABLE_RETURN_IF((ce_status != CE_STATUS_SUCCESS), ce_status, "Error initializing LWM2M object and resource");
+
+        // Put the handler creation in a critical code block for the case that this function is called after the start of the event loop
+        eventOS_scheduler_mutex_wait();
+        if (handler_id == -1) { // Register the handler only if it hadn't been registered before
+            handler_id = eventOS_event_handler_create(CertificateEnrollmentClient::event_handler, EVENT_TYPE_INIT);
+            SA_PV_ERR_RECOVERABLE_RETURN_IF((handler_id == -1), ce_status, "Error creating event handler");
+        }
+        eventOS_scheduler_mutex_release();
+
+        // Initialize the CE module
+        ce_status = ce_init();
+        SA_PV_ERR_RECOVERABLE_RETURN_IF((ce_status != CE_STATUS_SUCCESS), ce_status, "Error initializing CE module");
+
+        // Create the certificate renewal mutex
+        pal_status = pal_osSemaphoreCreate(NUMBER_OF_CONCURRENT_RENEWALS, &g_renewal_sem);
+        SA_PV_ERR_RECOVERABLE_RETURN_IF((pal_status != PAL_SUCCESS), CE_STATUS_ERROR, "Error creating semaphore");
+
+#ifdef CERT_ENROLLMENT_EST_MOCK
+        PV_UNUSED_PARAM(est_client);
+        g_est_client = new EstClientMock();        
+        SA_PV_ERR_RECOVERABLE_RETURN_IF((!g_est_client), CE_STATUS_ERROR, "Error creating mock EST");
+#else 
+        g_est_client = est_client;
+#endif
+
+        is_initialized = true;
+    }
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+    return CE_STATUS_SUCCESS;
+}
+
+void CertificateEnrollmentClient::finalize()
+{
+    palStatus_t pal_status;
+    // If module not initialized - do nothing
+    if (is_initialized) {
+        pal_status = pal_osSemaphoreDelete(&g_renewal_sem);
+        if (pal_status != PAL_SUCCESS) {
+            SA_PV_LOG_ERR("Error deleting semaphore");
+        }
+
+#ifdef CERT_ENROLLMENT_EST_MOCK
+        delete g_est_client;
+#endif
+        is_initialized = false;
+
+        // LWM2M objects, instances, and resources are deleted when MbedCloudClient is unregistered and ServiceClient::state_unregister() is called
+        // Currently nothing to finalize for CE core module except for KCM. However we do not wish to finalize it it may be used by other resources
+        
+        // Release our resources
+        release_objects();
+    }
+}
+
+void CertificateEnrollmentClient::event_handler(arm_event_s* event)
+{
+    SA_PV_LOG_INFO_FUNC_ENTER_NO_ARGS();
+
+    switch (event->event_type) {
+        case EVENT_TYPE_INIT:
+            // Nothing to do - ce module already initialized
+            break;
+        case EVENT_TYPE_RENEWAL_REQUEST:
+            certificate_renewal_start(current_cert);
+            break;
+        case EVENT_TYPE_EST_RESPONDED:
+            est_response_process(current_cert);
+            break;
+        default:
+            // Should never happen
+            SA_PV_LOG_ERR("Unsuupported event\n");
+    }
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+}
+
+// This is the entry point of the renewal request, inside the event loop
+void CertificateEnrollmentClient::certificate_renewal_start(CertificateRenewalDataBase *renewal_data)
+{
+    ce_status_e ce_status;
+    est_status_e est_status;
+    const char *cert_name;
+    size_t cert_name_size;
+    kcm_status_e kcm_status;
+    SA_PV_LOG_INFO_FUNC_ENTER_NO_ARGS();
+
+    // Parse the certificate name
+    ce_status = renewal_data->parse();
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((ce_status != CE_STATUS_SUCCESS), certificate_renewal_finish(renewal_data, ce_status), "Parse error");
+
+    // Create CSR's key handle
+    kcm_status = cs_ec_key_new(&renewal_data->key_handle);
+
+    // translate error to some CE native error
+    ce_status = ce_error_handler(kcm_status);
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((ce_status != CE_STATUS_SUCCESS), certificate_renewal_finish(renewal_data, ce_status), "Failed creating new key handle");
+
+    // key handle is initialized in the base constructor
+    ce_status = ce_generate_keys_and_create_csr_from_certificate(renewal_data->cert_name, renewal_data->key_handle, &renewal_data->csr, &renewal_data->csr_size);
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((ce_status != CE_STATUS_SUCCESS), certificate_renewal_finish(renewal_data, ce_status), "Keys/CSR generation error");
+
+    // Call the EST client
+
+    // If lwm2m device certificate - set cert name to NULL and request EST enrollment
+    if (pv_str_equals(g_lwm2m_name, renewal_data->cert_name,(uint32_t)(strlen(g_lwm2m_name) + 1))) {
+        SA_PV_LOG_INFO("Attempting to renew LwM2M device certificate\n");
+        cert_name = NULL;
+        cert_name_size = 0;
+    } else {
+        SA_PV_LOG_INFO("Attempting to renew a custom certificate\n");
+        cert_name = renewal_data->cert_name;
+        cert_name_size = strlen(renewal_data->cert_name);
+    }
+
+    // Request a certificate from a CSR via the EST service
+    est_status = g_est_client->est_request_enrollment(cert_name, cert_name_size, renewal_data->csr, renewal_data->csr_size, est_cb, NULL);
+    // FIXME: Currently commented out. If we find that the CSR must be persistent only during est_request_enrollment call - uncomment, and this should be the only place we free the CSR
+    //free(renewal_data->csr);
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((est_status != EST_STATUS_SUCCESS), certificate_renewal_finish(renewal_data, CE_STATUS_EST_ERROR), "EST request failed");
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+}
+
+ce_status_e CertificateEnrollmentClient::schedule_event(event_type_e event_type)
+{
+    int8_t event_status;
+
+    arm_event_s event = {
+        .receiver = handler_id, // ID we got when creating our handler
+        .sender = 0, // Which tasklet sent us the event is irrelevant to us 
+        .event_type = event_type, // Indicate event type 
+        .event_id = 0, // We currently do not need an ID for a specific event - event type is enough
+        .data_ptr = 0, // Not needed, data handled in internal structure
+        .priority = ARM_LIB_LOW_PRIORITY_EVENT, // Application level priority
+        .event_data = 0, // With one certificate this is irrelevant. If allow multiple certificates, This will be a certificate descriptor (index in a CertificateRenewalDataBase list)
+    };
+
+    SA_PV_LOG_INFO_FUNC_ENTER_NO_ARGS();
+
+    event_status = eventOS_event_send(&event);
+    SA_PV_ERR_RECOVERABLE_RETURN_IF((event_status < 0), CE_STATUS_OUT_OF_MEMORY, "Error scheduling event");
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+    return CE_STATUS_SUCCESS;
+}
+
+void CertificateEnrollmentClient::est_cb(est_enrollment_result_e result,
+                                         cert_chain_context_s *cert_chain,
+                                         void *context)
+{
+    ce_status_e status;
+    SA_PV_LOG_INFO_FUNC_ENTER("result = %d", result);
+
+    PV_UNUSED_PARAM(context);	
+    if (result != EST_ENROLLMENT_SUCCESS || cert_chain == NULL) {
+        return certificate_renewal_finish(current_cert, CE_STATUS_EST_ERROR);
+    }
+
+    // Cert chain remains persistent until g_est_client->free_cert_chain_context is called
+    current_cert->est_data = cert_chain;
+
+    status = schedule_event(CertificateEnrollmentClient::EVENT_TYPE_EST_RESPONDED);
+    if (status != CE_STATUS_SUCCESS) { // If event scheduling fails - free the chain context and finish the process
+        SA_PV_LOG_INFO("Error scheduling event");
+        g_est_client->free_cert_chain_context(current_cert->est_data);
+        
+        // Make sure we do not keep an invalid pointer
+        current_cert->est_data = NULL;
+        certificate_renewal_finish(current_cert, status);
+    }
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+}
+
+void CertificateEnrollmentClient::est_response_process(CertificateRenewalDataBase *renewal_data)
+{
+    ce_status_e ce_status;
+    ce_renewal_params_s params;
+    SA_PV_LOG_INFO_FUNC_ENTER_NO_ARGS();
+
+    // Fill params
+    params.cert_data = renewal_data->est_data;
+    params.crypto_handle = renewal_data->key_handle;
+
+    // Perform a safe renewal
+    ce_status = ce_safe_renewal(renewal_data->cert_name, &params);
+
+    // Free the est chain. Do not free in the destructor, we'd rather free it as soon as possible
+    g_est_client->free_cert_chain_context(renewal_data->est_data);
+    renewal_data->est_data = NULL;
+
+    // Done!
+    certificate_renewal_finish(renewal_data, ce_status);
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+}
+
+void CertificateEnrollmentClient::certificate_renewal_finish(CertificateRenewalDataBase *renewal_data, ce_status_e exit_status)
+{
+    palStatus_t pal_status;
+    SA_PV_LOG_INFO_FUNC_ENTER("exit_status = %d", exit_status);
+
+    // Don't leave an invalid global pointer
+    current_cert = NULL;
+
+    // Note: release of the mutex is before the deletion of the object (which holds the allocated cert_name)
+    // and before the user callback is invoked (so that the user may call the renewal API successfully from within his callback)
+    pal_status = pal_osSemaphoreRelease(g_renewal_sem);
+    if (PAL_SUCCESS != pal_status) { // 
+        exit_status = CE_STATUS_ERROR;
+    }
+
+    // At this point, new device requests may be made and the global pointer CertificateEnrollmentClient::current_cert may be changed.
+    // Therefore, we use the renewal_data pointer that was past as a parameter to this function
+    // New server requests will not be made until after this function returns since the response to the server is enqueued into the event loop by renewal_data->finish()
+    // and it is guaranteed that the server will not send another request until it receives a response.
+    renewal_data->finish(exit_status);
+
+    delete renewal_data;
+
+    SA_PV_LOG_INFO_FUNC_EXIT_NO_ARGS();
+}
+
+#ifdef CERT_RENEWAL_TEST
+void CertificateEnrollmentClient::testonly_certificate_renewal_post(void *arg)
+{
+    return certificate_renewal_post(arg);
+}
+
+
+#endif // CERT_RENEWAL_TEST