Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement MQTTv5 Enhanced Authentication #1363

Open
wants to merge 2 commits into
base: 1.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
76 changes: 74 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,51 @@ 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);
if(authData.authDataOut.data)
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 +1760,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
72 changes: 64 additions & 8 deletions src/MQTTAsync.h
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,57 @@ 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 a negative value to indicate not-authorized, a value of 0 to indicate success,
* a positive value to indicate continue.
*/
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 +1259,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 +1430,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
84 changes: 84 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(MQTTAsyncs *m, int rc,
MQTTProperties *props, MQTTAsync_authHandleData *out);
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,15 @@ 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;
rc = MQTTAsync_processAuth(m, connack->rc, &connack->properties,
&authHandleData);
if (authHandleData.authDataOut.data)
free(authHandleData.authDataOut.data);
}

if (rc == MQTTASYNC_SUCCESS)
{
int onSuccess = 0;
Expand Down Expand Up @@ -2366,6 +2379,24 @@ 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;
MQTTAsync_authHandleData authHandleData = MQTTAsync_authHandleData_initializer;

authrc = MQTTAsync_processAuth(m, auth->rc, &auth->properties,
&authHandleData);

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


enum MQTTReasonCodes MQTTAsync_processAuth(MQTTAsyncs *m, int rc, MQTTProperties *props,
MQTTAsync_authHandleData *out)
{
MQTTProperty *authMethodProp = NULL;
char *authMethodIn = NULL;
int authMethodInLen = 0;
MQTTProperty *authData = NULL;

if (!m->c->authMethod)
{
return MQTTREASONCODE_PROTOCOL_ERROR;
}

authMethodProp = MQTTProperties_getProperty(props,
MQTTPROPERTY_CODE_AUTHENTICATION_METHOD);
if (authMethodProp)
{
authMethodIn = authMethodProp->value.data.data;
authMethodInLen = authMethodProp->value.data.len;
}

if ((rc == 0 && authMethodProp == NULL) ||
(m->c->authMethod && authMethodIn && authMethodInLen > 0 &&
strlen(m->c->authMethod) == authMethodInLen &&
memcmp(m->c->authMethod, authMethodIn, authMethodInLen) == 0))
{
if (m->auth_handle == NULL)
return MQTTREASONCODE_NOT_AUTHORIZED;

authData = MQTTProperties_getProperty(props,
MQTTPROPERTY_CODE_AUTHENTICATION_DATA);

out->reasonCode = rc;
if (authData && authMethodIn)
{
out->authDataIn.data = authData->value.data.data;
out->authDataIn.len = authData->value.data.len;
}

rc = (*(m->auth_handle))(m->auth_handle_context, out);
if (rc < 0)
return MQTTREASONCODE_NOT_AUTHORIZED;

if (rc > 0)
return MQTTREASONCODE_CONTINUE_AUTHENTICATION;

return MQTTREASONCODE_SUCCESS;
}

return MQTTREASONCODE_BAD_AUTHENTICATION_METHOD;
}
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