Skip to content

Commit

Permalink
Implemented enhanced authentication for MQTTAsync.
Browse files Browse the repository at this point in the history
Signed-off-by: Diego Dassie <[email protected]>
  • Loading branch information
ddassie-texa committed May 26, 2023
1 parent c2fb3cd commit 39a8672
Show file tree
Hide file tree
Showing 13 changed files with 591 additions and 26 deletions.
1 change: 1 addition & 0 deletions src/Clients.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ typedef struct
int sessionExpiry; /**< MQTT 5 session expiry */
char* httpProxy; /**< HTTP proxy */
char* httpsProxy; /**< HTTPS proxy */
char* authMethod; /**< MQTT 5 enhanced authentication method */
#if defined(OPENSSL)
MQTTClient_SSLOptions *sslopts; /**< the SSL/TLS connect options */
SSL_SESSION* session; /**< SSL session pointer for fast handhake */
Expand Down
75 changes: 73 additions & 2 deletions src/MQTTAsync.c
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options)
goto exit;
}

if (strncmp(options->struct_id, "MQTC", 4) != 0 || options->struct_version < 0 || options->struct_version > 8)
if (strncmp(options->struct_id, "MQTC", 4) != 0 || options->struct_version < 0 || options->struct_version > 9)
{
rc = MQTTASYNC_BAD_STRUCTURE;
goto exit;
Expand Down Expand Up @@ -693,6 +693,11 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options)
if (options->httpsProxy)
m->c->httpsProxy = MQTTStrdup(options->httpsProxy);
}
if (options->MQTTVersion >= MQTTVERSION_5 && options->struct_version >= 9)
{
if (options->authMethod)
m->c->authMethod = MQTTStrdup(options->authMethod);
}

if (m->c->will)
{
Expand Down Expand Up @@ -893,7 +898,6 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options)
if (MQTTProperties_hasProperty(options->connectProperties, MQTTPROPERTY_CODE_SESSION_EXPIRY_INTERVAL))
m->c->sessionExpiry = MQTTProperties_getNumericValue(options->connectProperties,
MQTTPROPERTY_CODE_SESSION_EXPIRY_INTERVAL);

}
if (options->willProperties)
{
Expand All @@ -909,6 +913,50 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options)
}
m->c->cleanstart = options->cleanstart;
}
if (options->struct_version >= 9)
{
if (m->c->authMethod)
{
MQTTAsync_authHandleData authData = MQTTAsync_authHandleData_initializer;
MQTTProperty property;
int authrc = 0;

if (!m->connectProps)
{
MQTTProperties initialized = MQTTProperties_initializer;

if ((m->connectProps = malloc(sizeof(MQTTProperties))) == NULL)
{
rc = PAHO_MEMORY_ERROR;
goto exit;
}

*m->connectProps = initialized;
}

property.identifier = MQTTPROPERTY_CODE_AUTHENTICATION_METHOD;
property.value.data.data = m->c->authMethod;
property.value.data.len = (int)strlen(m->c->authMethod);
rc = MQTTProperties_add(m->connectProps, &property);
if (rc)
goto exit;

if (m->auth_handle)
{
authrc = (*(m->auth_handle))(m->auth_handle_context, &authData);
if (authrc < 0)
goto exit;
}

property.identifier = MQTTPROPERTY_CODE_AUTHENTICATION_DATA;
property.value.data.data = authData.authDataOut.data;
property.value.data.len = authData.authDataOut.len;
rc = MQTTProperties_add(m->connectProps, &property);
free(authData.authDataOut.data);
if (rc)
goto exit;
}
}

/* Add connect request to operation queue */
if ((conn = malloc(sizeof(MQTTAsync_queuedCommand))) == NULL)
Expand Down Expand Up @@ -1711,6 +1759,29 @@ int MQTTAsync_setAfterPersistenceRead(MQTTAsync handle, void* context, MQTTPersi
}


int MQTTAsync_setHandleAuth(MQTTAsync handle, void *context,
MQTTAsync_authHandle *authenticate)
{
int rc = MQTTASYNC_SUCCESS;
MQTTAsyncs *m = handle;

FUNC_ENTRY;
MQTTAsync_lock_mutex(mqttasync_mutex);

if (m == NULL || m->c->connect_state != NOT_IN_PROGRESS)
rc = MQTTASYNC_FAILURE;
else
{
m->auth_handle_context = context;
m->auth_handle = authenticate;
}

MQTTAsync_unlock_mutex(mqttasync_mutex);
FUNC_EXIT_RC(rc);
return rc;
}


void MQTTAsync_setTraceLevel(enum MQTTASYNC_TRACE_LEVELS level)
{
Log_setTraceLevel((enum LOG_LEVELS)level);
Expand Down
71 changes: 63 additions & 8 deletions src/MQTTAsync.h
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,56 @@ LIBMQTT_API int MQTTAsync_setBeforePersistenceWrite(MQTTAsync handle, void* cont
*/
LIBMQTT_API int MQTTAsync_setAfterPersistenceRead(MQTTAsync handle, void* context, MQTTPersistence_afterRead* co);

/** The authentication data that is populated when MQTTv5 enhanced authentication
* is requested by an operation. */
typedef struct
{
/** The eyecatcher for this structure. Will be MQAD. */
char struct_id[4];
/** The version number of this structure. Will be 0 */
int struct_version;
/** The MQTT reason code received from the AUTH packet. */
enum MQTTReasonCodes reasonCode;
/**
* The data received from the MQTTv5 AUTH packet AUTHENTICATION_DATA property.
* Data is NULL if no AUTHENTICATION_DATA was received.
*/
struct {
int len; /**< binary input AUTHENTICATION_DATA length */
const void* data; /**< binary input AUTHENTICATION_DATA data */
} authDataIn;
/**
* The data to populate the MQTTv5 AUTH packet AUTHENTICATION_DATA property.
* Set data to NULL to remove. To change, allocate new
* storage with ::MQTTAsync_malloc - this will then be freed later by the library.
*/
struct {
int len; /**< binary output AUTHENTICATION_DATA length */
void* data; /**< binary output AUTHENTICATION_DATA data */
} authDataOut;
} MQTTAsync_authHandleData;

#define MQTTAsync_authHandleData_initializer {{'M', 'Q', 'A', 'D'}, 0, 0, {0, NULL}, {0, NULL}}

/**
* This is a callback function which will allow the client application to update the
* connection data.
* @param data The connection data which can be modified by the application.
* @return Return a zero or positive value to indicate sucess, a negative value on failure.
*/
typedef int MQTTAsync_authHandle(void* context, MQTTAsync_authHandleData* data);

/**
* Sets the MQTTAsync_authenticate() callback function for a client.
* @param handle A valid client handle from a successful call to MQTTAsync_create().
* @param context A pointer to any application-specific context. The
* the <i>context</i> pointer is passed to the callback function to
* provide access to the context information in the callback.
* @param co A pointer to an MQTTAsync_authHandle() callback
* function. NULL removes the callback setting.
*/
LIBMQTT_API int MQTTAsync_setHandleAuth(MQTTAsync handle, void* context, MQTTAsync_authHandle* authenticate);


/** The data returned on completion of an unsuccessful API call in the response callback onFailure. */
typedef struct
Expand Down Expand Up @@ -1208,6 +1258,7 @@ typedef struct
* 5 signifies no MQTTV5 properties
* 6 signifies no HTTP headers option
* 7 signifies no HTTP proxy and HTTPS proxy options
* 8 signifies no MQTTV5 enhanced authentication option
*/
int struct_version;
/** The "keep alive" interval, measured in seconds, defines the maximum time
Expand Down Expand Up @@ -1378,27 +1429,31 @@ typedef struct
* HTTPS proxy
*/
const char* httpsProxy;
/**
* MQTTv5 authentication method
*/
const char* authMethod;
} MQTTAsync_connectOptions;

/** Initializer for connect options for MQTT 3.1.1 non-WebSocket connections */
#define MQTTAsync_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 8, 60, 1, 65535, NULL, NULL, NULL, 30, 0,\
NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
#define MQTTAsync_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 9, 60, 1, 65535, NULL, NULL, NULL, 30, 0,\
NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}

/** Initializer for connect options for MQTT 5.0 non-WebSocket connections */
#define MQTTAsync_connectOptions_initializer5 { {'M', 'Q', 'T', 'C'}, 8, 60, 0, 65535, NULL, NULL, NULL, 30, 0,\
NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
#define MQTTAsync_connectOptions_initializer5 { {'M', 'Q', 'T', 'C'}, 9, 60, 0, 65535, NULL, NULL, NULL, 30, 0,\
NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}

/** Initializer for connect options for MQTT 3.1.1 WebSockets connections.
* The keepalive interval is set to 45 seconds to avoid webserver 60 second inactivity timeouts.
*/
#define MQTTAsync_connectOptions_initializer_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 1, 65535, NULL, NULL, NULL, 30, 0,\
NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
#define MQTTAsync_connectOptions_initializer_ws { {'M', 'Q', 'T', 'C'}, 9, 45, 1, 65535, NULL, NULL, NULL, 30, 0,\
NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_DEFAULT, 0, 1, 60, {0, NULL}, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}

/** Initializer for connect options for MQTT 5.0 WebSockets connections.
* The keepalive interval is set to 45 seconds to avoid webserver 60 second inactivity timeouts.
*/
#define MQTTAsync_connectOptions_initializer5_ws { {'M', 'Q', 'T', 'C'}, 8, 45, 0, 65535, NULL, NULL, NULL, 30, 0,\
NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL}
#define MQTTAsync_connectOptions_initializer5_ws { {'M', 'Q', 'T', 'C'}, 9, 45, 0, 65535, NULL, NULL, NULL, 30, 0,\
NULL, NULL, NULL, NULL, 0, NULL, MQTTVERSION_5, 0, 1, 60, {0, NULL}, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}


/**
Expand Down
127 changes: 127 additions & 0 deletions src/MQTTAsyncUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ static int cmdMessageIDCompare(void* a, void* b);
static void MQTTAsync_retry(void);
static MQTTPacket* MQTTAsync_cycle(SOCKET* sock, unsigned long timeout, int* rc);
static int MQTTAsync_connecting(MQTTAsyncs* m);
static enum MQTTReasonCodes MQTTAsync_processAuth(MQTTAsync_authHandle *func, void *context,
MQTTAsync_authHandleData *data);
static int MQTTAsync_verifyAuthMethod(const char* authMethod,
const char* data, int dataLen);

extern MQTTProtocol state; /* defined in MQTTAsync.c */
extern ClientStates* bstate; /* defined in MQTTAsync.c */
Expand Down Expand Up @@ -2133,6 +2137,44 @@ thread_return_type WINAPI MQTTAsync_receiveThread(void* n)
int sessionPresent = connack->flags.bits.sessionPresent;

rc = MQTTAsync_completeConnection(m, connack);
if (rc == MQTTASYNC_SUCCESS && m->c->authMethod)
{
MQTTAsync_authHandleData authHandleData = MQTTAsync_authHandleData_initializer;
MQTTProperty *authMethodProp = NULL;
char *authMethod = NULL;
int authMethodLen = 0;
MQTTProperty *authData = NULL;

authMethodProp = MQTTProperties_getProperty(&connack->properties,
MQTTPROPERTY_CODE_AUTHENTICATION_METHOD);
if (authMethodProp)
{
authMethod = authMethodProp->value.data.data;
authMethodLen = authMethodProp->value.data.len;
}

if ((connack->rc == 0 && authMethodProp == NULL) ||
MQTTAsync_verifyAuthMethod(m->c->authMethod, authMethod,
authMethodLen) == 0)
{
authData = MQTTProperties_getProperty(&connack->properties,
MQTTPROPERTY_CODE_AUTHENTICATION_DATA);

authHandleData.reasonCode = connack->rc;
if (authData && authMethod)
{
authHandleData.authDataIn.data = authData->value.data.data;
authHandleData.authDataIn.len = authData->value.data.len;
}

rc = MQTTAsync_processAuth(m->auth_handle,
m->auth_handle_context,
&authHandleData);
}
else
rc = MQTTREASONCODE_BAD_AUTHENTICATION_METHOD;
}

if (rc == MQTTASYNC_SUCCESS)
{
int onSuccess = 0;
Expand Down Expand Up @@ -2366,6 +2408,58 @@ thread_return_type WINAPI MQTTAsync_receiveThread(void* n)
m->c->connected = 0; /* don't send disconnect packet back */
nextOrClose(m, discrc, "Received disconnect");
}
else if (pack->header.bits.type == AUTH)
{
Auth *auth = (Auth *)pack;
enum MQTTReasonCodes authrc = MQTTREASONCODE_SUCCESS;
MQTTProperty *authMethodProp = NULL;
MQTTProperty *authData = NULL;
char* authMethod = NULL;
int authMethodLen = 0;
MQTTAsync_authHandleData authHandleData = MQTTAsync_authHandleData_initializer;

if (m->c->authMethod)
{
authMethodProp = MQTTProperties_getProperty(&auth->properties,
MQTTPROPERTY_CODE_AUTHENTICATION_METHOD);
if (authMethodProp)
{
authMethod = authMethodProp->value.data.data;
authMethodLen = authMethodProp->value.data.len;
}

if ((auth->rc == 0 && authMethodProp == NULL) ||
MQTTAsync_verifyAuthMethod(m->c->authMethod, authMethod,
authMethodLen) == 0)
{
authData = MQTTProperties_getProperty(&auth->properties,
MQTTPROPERTY_CODE_AUTHENTICATION_DATA);

authHandleData.reasonCode = auth->rc;
if (authData && authMethod)
{
authHandleData.authDataIn.data = authData->value.data.data;
authHandleData.authDataIn.len = authData->value.data.len;
}

authrc = MQTTAsync_processAuth(m->auth_handle,
m->auth_handle_context,
&authHandleData);
}
else
authrc = MQTTREASONCODE_BAD_AUTHENTICATION_METHOD;
}
else
authrc = MQTTREASONCODE_PROTOCOL_ERROR;

rc = MQTTProtocol_handleAuth(pack, m->c->net.socket, authrc,
authHandleData.authDataOut.data,
authHandleData.authDataOut.len);
free(authHandleData.authDataOut.data);
if (authrc != MQTTREASONCODE_SUCCESS &&
authrc != MQTTREASONCODE_CONTINUE_AUTHENTICATION)
nextOrClose(m, authrc, "Authentication failed");
}
}
}
}
Expand Down Expand Up @@ -3224,3 +3318,36 @@ int MQTTAsync_getNoBufferedMessages(MQTTAsyncs* m)
MQTTAsync_unlock_mutex(mqttcommand_mutex);
return count;
}


enum MQTTReasonCodes MQTTAsync_processAuth(MQTTAsync_authHandle *func, void *context,
MQTTAsync_authHandleData *data)
{
int rc;

if (func == NULL)
return MQTTREASONCODE_NOT_AUTHORIZED;

rc = (*(func))(context, data);
if (rc < 0)
return MQTTREASONCODE_NOT_AUTHORIZED;

if (rc > 0)
return MQTTREASONCODE_CONTINUE_AUTHENTICATION;

return MQTTREASONCODE_SUCCESS;
}


int MQTTAsync_verifyAuthMethod(const char *authMethod, const char *data, int dataLen)
{
if (authMethod && data && dataLen > 0)
{
if (strlen(authMethod) == dataLen && memcmp(authMethod, data, dataLen) == 0)
{
return 0;
}
}

return -1;
}
3 changes: 3 additions & 0 deletions src/MQTTAsyncUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ typedef struct MQTTAsync_struct
MQTTAsync_selectInterface* selectInterface;
void* selectInterface_context;

MQTTAsync_authHandle* auth_handle;
void* auth_handle_context;

/* Each time connect is called, we store the options that were used. These are reused in
any call to reconnect, or an automatic reconnect attempt */
MQTTAsync_command connect; /* Connect operation properties */
Expand Down
Loading

0 comments on commit 39a8672

Please sign in to comment.