Authenticators are invoked by security attributes. Therefore, how and when they are used is determined by the specific implementation of an attribute. One main usage of authenticators is for controlling access to queues in queue-based security. Authenticators can be used in queue-based security to control access to queues. WebSphere MQ Everyplace provides a certificate authenticator as part of its base code, com.ibm.mqe.attributes.MQeWTLSCertAuthenticator. There are some Java example authenticators, in the examples.attributes directory, which are based on user names and passwords. There is also a C example, WinCEAuthenticator, in the examples\src\WinCEAuthenticator directory. In addition to these, WebSphere MQ Everyplace allows you to write your own authenticator.
In queue-based security, authenticators are activated when a queue is first accessed and they can grant or deny access to the queue. When a queue is accessed from its local queue manager, the authenticator is activated when the first operation, for example put, get , or browse is performed on the queue. When a queue is accessed from a remote queue manager, WebSphere MQ Everyplace establishes a channel between the two queue managers and the authenticator is activated as part of establishing the channel.
All authenticators must extend the base authenticator class:
class MyAuthenticator extends com.ibm.mqe.MQeAuthenticator
The following methods in the base class can be overridden:
public byte[] activateMaster( boolean local ) throws Exception
It is invoked on the queue manager that initiates access to a queue. The parameter local indicates whether this is a local access, that is the queue is on the same queue manager, local == true, or a remote access, local == false. The method should collect data to authenticate the queue manager or user and return the data in a byte array. The data is passed to the activateSlave() method. The activateMaster() method in the base class, MQeAuthenticator, simply returns null. It does not throw any exceptions. Any exceptions thrown by this method, in a subclass, are not caught by WebSphere MQ Everyplace itself, but are passed back to the user's code and terminate the attempt to access the queue.
public byte[] activateSlave( boolean local, byte data[] ) throws Exception
This is invoked on the queue manager that owns the queue. The parameter local indicates whether this is a local access, i.e. initiated on the same queue manager, local == true, or a remote access, local == false. The parameter datacontains the data returned by the activateMaster() method. The activateSlave() method should validate this data. If it is satisfied with the data it should call the setAuthenticatedID() method to set the name of the authenticated entity, this indicates that the first stage of the authentication was successful. It can then collect data to authenticate the local queue manager and return it in a byte array. The data is passed to the slaveResponse() method. If it is not satisfied with the data, it throws an exception indicating the reason. The activateSlave() method in the base class, MQeAuthenticator, checks whether the name of the authenticated entity has been set and if it has, it logs the name; it then returns null. It does not throw any exceptions. Any exceptions thrown by this method, in a subclass, are not caught by WebSphere MQ Everyplace itself, but are passed back to the initiating queue manager where they are re-thrown. WebSphere MQ Everyplace does not catch these exceptions on the initiating queue manager and they are passed back to the user's code and will terminate the attempt to access the queue.
public void slaveResponse( boolean local, byte data[] ) throws Exception
It is invoked on the queue manager that initiates access to a queue. The local parameter indicates whether this is a local access, local == true, or a remote access, local == false. The parameter data contains the data returned by the activateSlave() method. If it is satisfied with the data it should call the setAuthenticatedID() method to set the name of the authenticated entity, this indicates that the second stage of the authentication was successful. If the activateSlave() method did not return any data, and the slaveResponse() method is satisfied with this, it still calls setAuthenticatedID() to indicate success. If it is not satisfied with the data, it throws an exception indicating the reason. The slaveResponse() method in the base class, MQeAuthenticator, simply returns null. It does not throw any exceptions. Any exceptions thrown by this method, in a subclass, are not caught by WebSphere MQ Everyplace itself, but are passed back to the user's code and terminate the attempt to access the queue.
Figure 1. The slaveResponse() method in MQeAuthenticator
When a queue is accessed locally, the three methods are invoked in sequence on the local queue manager.
The example logon authenticator shows how to implment these. It has a base class, examples.attributes.LogonAuthenticator, and three subclasses, one for the NTAuthenticator, one for the UnixAuthenticator, and one for the UseridAuthenticator. The base class provides common functionality and the subclasses provide functionality that is specific to the type of authenticator, that is NT, Unix, or Userid. The activateMaster() method in the LogonAuthenticator class creates an empty MQeFields object and passes it into a method called prompt(). This is overridden in each of the subclasses, and in each case it displays a Java dialog box, collects data from it, masks the data with a simple exclusive OR operation, and adds the data to the MQeFields object. The exclusive OR is used in the example authenticators but in practice it does not provide much protection. The MQeFields object is dumped to provide a byte array which is returned by activateMaster(). The activateMaster() method is invoked on the queue manager that initiates access to the queue, so the dialog box is displayed by this queue manager.
public byte[] activateMaster(boolean local) throws Exception { MQeFields fields = new MQeFields(); /* for request fields */ this.prompt(fields); /* put up the dialog prompt */ return (fields.dump()); /* return ID */ }
The activateSlave() method receives the data returned by activateMaster(), restores it into a MQeFields object and passes the object into the validate() method. This is overridden in each of the subclasses, and in each case it validates the data in a way appropriate to the authenticator. For example, in the NTAuthenticator subclass, the validate() method unmasks the data and passes it to the logonUser() method. This method uses Java Native Interface (JNI) to access the Windows security mechanism and check whether the user name and password are valid. If they are valid, the validate() method returns the user name, otherwise it throws an exception.
public byte[] activateSlave(boolean local, byte data[]) throws Exception { MQeFields fields = new MQeFields(data); /* work object */ try { authID = this.validate(fields); /* get the auth ID value */ setAuthenticatedID(authID); /* is it allowed ? */ super.activateSlave(local, data); /* call ancestor */ trace("_:Logon " + authID); /* trace */ MQeFields result = new MQeFields(); /* reply object */ result.putAscii(Authentic_ID, authID);/* send id */ return (result.dump()); /* send back as response */ } catch (Exception e) { /* error occured */ authID = null; /* make sure authID is null */ setAuthenticatedID(null); /* invalidate */ throw e; /* re-throw the exception */ } }
If the user name is valid, the activateSlave() method calls setAuthenticatedID() to register the user name and the calls super.activateSlave() which puts out a log message. It issues a trace message, adds the user name to a MQeFields object, dumps this to a byte array and returns it. If the user name is not valid, validate() throws an exception. The activateSlave() method catches the exception, ensures the authenticated id is null and re-throws the exception. The slaveResponse method() receives the byte array returned by activateSlave() and restores it into a MQeFields object. The user name that was validated by activateSlave() is extracted from this and passed to setAuthenticatedID().
public void slaveResponse(boolean local, byte data[]) throws Exception { super.slaveResponse(local, data); /* call ancestor*/ MQeFields fields = new MQeFields(data); /* work object*/ setAuthenticatedID(fields.getAscii(Authentic_ID)); /* id to check */ }
These authenticators behave the same for both local and remote accesses, so they ignore the local parameter to these methods.
In the C codebase, you need to provide at least four functions to implement an authenticator needs. These functions are:
In terms of functionality, functions 2 to 4 behave exactly the same as their Java counterpart implementation. If your new() function allocates any private memory, you then have to provide a free() function, which frees the private memory you have allocated.
To notify the WebSphere MQ Everyplace of the existence of your implementation, call the mqeClassAlias_add() function, which has the following signature:
MQERETURN mqeClassAlias_add(MQERETURN * pExceptBlock, MQeStringHndl hWinCEAuthName, MQeStringHndl hModuleName, MQeStringHndl hInitFuncName);
In the previous example, the hWinCEAuthName is a string name for the authenticator. The hModuleName is the dynamically loadable library file name in which your authenticator has been compiled into, and the hInitFuncName is the name of your new function, which can be an arbitrary name. The new()function has the following signature:
MQERETURN new(MQeAttrPlugin_SubclassInitInput * pInput, MQeAuthenticator_SubclassInitOutput * pOutput );
The pOutput points to an MQeAuthenticator_SubclassInitOutput structure, which needs to be filled in. The MQeAuthenticator_SubclassInitOutput contains the following fields:
Any pointers or handles that are not used in the implementation must be initialised to NULL.
MQERETURN free(MQeAuthenticatorHndl hThis, MQeAttrPlugin_FreeInput * pInput, MQeAttrPlugin_FreeOutput * pOutput );
If the new() function allocates private memory, the pointer to the allocated memory can be retrieved into a pointer p using:
mqeAuthenticator_getPrivateData(hThis, pExceptBlock, (MQEVOID **) &p);
The pointer can then be used to free the memory. The MQeString assigned to the hClassName in the new() function, if any, are automatically freed by the system when mqeAttrBase_free is called.
MQERETURN activateMaster(MQeAuthenticatorHndl hAuthenticator, MQeAttrPlugin_ActivateMasterPrepInput *pInput, MQeAttrPlugin_ActivateMasterPrepOutput * pOutput );
Refer to description in the corresponding Java section for the required functionality for this function. The pOutput points to an MQeAttrPlugin_ActivateMasterPrepOutput structure which needs to be filled in. The MQeAttrPlugin_ActivateMasterPrepOutput contains the following fields:
MQERETURN activateSlave(MQeAuthenticatorHndl hAuthenticator, MQeAttrPlugin_ActivateSlavePrepInput *pInput, MQeAttrPlugin_ActivateSlavePrepOutput *pOutput );
Refer to description in the corresponding Java section for the required functionality for this function. The pInput points to an MQeAttrPlugin_ActivateSlavePrepInput structure which contains the input from the activateMaster() and the pOutput points to an MQeAttrPlugin_ActivateSlavePrepOutput structure which needs to be filled in. The MQeAttrPlugin_ActivateSlavePrepInput contains the following fields:
The MQeAttrPlugin_ActivateSlavePrepOutput contains the following fields:
MQERETURN slaveResponse(MQeAuthenticatorHndl hAuthenticator, MQeAttrPlugin_ProcessSlaveResponseInput *pInput, MQeAttrPlugin_ProcessSlaveResponseOutput *pOutput );
Refer to description in the corresponding Java section for the required functionality for this function. The pInput points to an MQeAttrPlugin_ProcessSlaveResponseInput structure which contains the input from the activateSlave(). The MQeAttrPlugin_ProcessSlaveResponseInput contains the following fields:
The example WinCEAuthenticator shows how the methods listed in the previous section can be implemented. It is functionally very similar to the example NTAuthenticator in the Java code base.
Calling winCEAuthenticator_new() function implements the new() function. This allocates a private memory block to register the type of the authenticator, private to this implementation, filling-in private variables and the function pointers mentioned above so they point to the right function implementations, and set pOutput->hClassName to "WinCEAuthenticator". Notice that no "WinCEAuthenticator" is provided in the Java package. This is because the WinCEAuthenticator is designed to be executed only on a C client. The "WinCEAuthenticator" string is created for demonstration purposes only. The pOutput->hClassName must point to an existing Java class if the authenticator is to be used in a dialogue between a C client and a Java server.
MQERETURN winCEAuthenticator_new( MQeAttrPlugin_SubclassInitInput * pInput, MQeAuthenticator_SubclassInitOutput * pOutput ) { MQeStringHndl hClassName; MQeExceptBlock * pExceptBlock = (MQeExceptBlock*) pOutput->pExceptBlock; (void)mqeString_newChar8(pExceptBlock, &hClassName, "WinCEAuthenticator"); if (MQERETURN_OK == pExceptBlock->ec) { pOutput->pSubclassPrivateData = malloc(sizeof(MQEINT32)); if (NULL != pOutput->pSubclassPrivateData) { *((MQEINT32 *)pOutput->pSubclassPrivateData) = AUTHENTICATOR; pOutput->hClassName = hClassName; pOutput->regRequired = MQE_FALSE; /* key type unknown */ pOutput->keyType = MQE_KEY_NULL; /* pointers to subclass implementations of support methods */ pOutput->fFree = winCEAuthenticator_free; pOutput->fActivateMasterPrep = winCEAuthenticator_activateMasterPrep; pOutput->fActivateSlavePrep = winCEAuthenticator_activateSlavePrep; pOutput->fProcessSlaveResponse = winCEAuthenticator_processSlaveResponse; pOutput->fClose = NULL; } else { pExceptBlock->ec = MQERETURN_ALLOCATION_FAIL; pExceptBlock->erc = MQEREASON_NA; } } return pExceptBlock->ec; }
Calling winCEAuthenticator_free() implements the free() function. It retrieves the private memory block allocated by winCEAuthenticator_new(), making sure the authenticator has got the right private signature, and then frees the memory block.
MQERETURN winCEAuthenticator_free(MQeAuthenticatorHndl hThis, MQeAttrPlugin_FreeInput * pInput, MQeAttrPlugin_FreeOutput * pOutput ) { MQeExceptBlock * pExceptBlock = (MQeExceptBlock*) pOutput->pExceptBlock; MQEINT32 * pType; pExceptBlock->ec = MQERETURN_INVALID_ARGUMENT; pExceptBlock->erc = MQEREASON_INVALID_SIGNATURE; if ((NULL != hThis) && (MQERETURN_OK == mqeAuthenticator_getPrivateData(hThis, pExceptBlock, (MQEVOID**) &pType)) ) { /* make sure it is an authenticator created here */ if (AUTHENTICATOR == *pType) { pExceptBlock->ec = MQERETURN_OK; pExceptBlock->erc = MQEREASON_NA; free(pType); } } return pExceptBlock->ec; }
Calling winCEAuthenticator_activateMasterPrep() implements the activateMaster() function. It creates an empty MQeFields structure and passes it into a function called prompt(). The prompt() function:
The exclusive OR is used in the example authenticators, but in practice it does not provide much protection. The MQeFields structure is then dumped to provide a byte array, which is returned by winCEAuthenticator_activateMasterPrep().
MQERETURN winCEAuthenticator_activateMasterPrep( MQeAuthenticatorHndl hAuthenticator, MQeAttrPlugin_ActivateMasterPrepInput * pInput, MQeAttrPlugin_ActivateMasterPrepOutput * pOutput) { static MQeFieldsHndl hActivateMasterFields = NULL; MQEINT32 * pOutputDataLen = pOutput->pOutputDataLen; MQEBYTE * pOutputData = pOutput->pOutputData; MQeExceptBlock * pExceptBlock = (MQeExceptBlock*) pOutput->pExceptBlock; /* initialize exception block */ pExceptBlock->ec = MQERETURN_OK; pExceptBlock->erc = MQEREASON_NA; if (NULL == hActivateMasterFields) { /* get data for authentication */ (void)mqeFields_new(pExceptBlock, &hActivateMasterFields); if (MQERETURN_OK == pExceptBlock->ec) { /** * Write your code here which puts the input data, * for example., userid, password into hActivateMasterFields. * The format is not important as long as it can be * understood by your corresponding code in * winCEAuthenticator_activateSlavePrep, which digests * these data. */ prompt(hActivateMasterFields, pExceptBlock);} } if (MQERETURN_OK == pExceptBlock->ec) { /* dump the fields */ (void)mqeFields_dump(hActivateMasterFields, pExceptBlock, pOutputData, pOutputDataLen); } if ((NULL != hActivateMasterFields) && ((NULL != pOutputData) || (MQERETURN_OK != pExceptBlock->ec))) { /** * Caller has supplied a buffer or operation failed. * No need to keep the Fields any more. */ (void)mqeFields_free(hActivateMasterFields, NULL); hActivateMasterFields = NULL; } return pExceptBlock->ec; }
The winCEAuthenticator_activateSlavePrep() implements the activateSlave() function. The winCEAuthenticator_activateSlavePrep() method receives the data returned by winCEAuthenticator_activateMasterPrep(), restores it into an MQeFields structure and passes it into a validate() function. The validate() function unmasks the data and passes it to the system LogonUser() function. This function checks if the user name and password are valid.
On a WinCE system, the LogonUser() never returns if the user name and password are not valid. The following winCEAuthenticator_activateSlavePrep() and winCEAuthenticator_processSlaveResponse() implementations, however, assume that LogonUser() will always return with a value indicating whether or not the input is valid, in order to demonstrate what you need to do. If the user name and password are valid, the winCEAuthenticator_activateSlavePrep() function calls mqeAuthenticator_setAuthenticatedID() to register the user name as if the code is running on a server. It may be that this code is running on a client just as the winCEAuthenticator_activateMasterPrep. It then adds the user name to an MQeFields, dumps this to a byte array, and returns it. If the user name is not valid, the winCEAuthenticator_activateSlavePrep() function returns an error.
MQERETURN winCEAuthenticator_activateSlavePrep( MQeAuthenticatorHndl hAuthenticator, MQeAttrPlugin_ActivateSlavePrepInput * pInput, MQeAttrPlugin_ActivateSlavePrepOutput * pOutput) { static MQeFieldsHndl hActivateSlaveFields = NULL; MQeFieldsHndl hTempFields = NULL; MQEINT32 inputDataLen = pInput->inputDataLen; MQEBYTE * pInputData = pInput->pInputData; MQEINT32 * pOutputDataLen = pOutput->pOutputDataLen; MQEBYTE * pOutputData = pOutput->pOutputData; MQeExceptBlock * pExceptBlock = (MQeExceptBlock*) pOutput->pExceptBlock; /* initialize exception block */ pExceptBlock->ec = MQERETURN_OK; pExceptBlock->erc = MQEREASON_NA; if (NULL == hActivateSlaveFields) { /* restore input */ (void)mqeFields_new(pExceptBlock, &hTempFields); if (MQERETURN_OK == pExceptBlock->ec) { /* restore it into a MQeFields */ (void)mqeFields_restore(hTempFields, pExceptBlock, pInputData, inputDataLen); if (MQERETURN_OK == pExceptBlock->ec) { MQeStringHndl hAuthenticID = NULL; /** * put your code, which digests(authenticates) * the input data your gathered in the * winCEAuthenticator_activateMasterPrep(). * If successful, create an AuthenicateID string * in hAuthenticID. */ (void)validate(hTempFields, pExceptBlock, &hAuthenticID); if (MQERETURN_OK == pExceptBlock->ec) { /** * If successfully authenticated, * set local id variable (recored a success) */ (void)mqeAuthenticator_setAuthenticatedID(hAuthenticator, pExceptBlock, hAuthenticID); /* preparation for sending the id to the master */ if (MQERETURN_OK == pExceptBlock->ec) { /** * Send the hAuthenticID to the Master, * indicating a a success. */ (void)mqeFields_new(pExceptBlock, &hActivateSlaceFields); if (MQERETURN_OK == pExceptBlock->ec) { MQeStringHndl hAuthenticIDField; (void)mqeString_newChar8(pExceptBlock, &hAuthenticIDField, AUTHENTIC_ID); if (MQERETURN_OK == pExceptBlock->ec) { (void)mqeFields_putAscii(hActivateSlaveFields, pExceptBlock, hAuthenticIDField, hAuthenticID); (void)mqeString_free(hAuthenticIDField, NULL); } } } } } (void)mqeFields_free(hTempFields, NULL); } } if (MQERETURN_OK == pExceptBlock->ec) { /* dump the fields */ (void)mqeFields_dump(hActivateSlaveFields, pExceptBlock, pOutputData, pOutputDataLen); } if ((NULL != hActivateSlaveFields) && ((NULL != pOutputData) || (MQERETURN_OK != pExceptBlock->ec))) { /** * Caller has supplied a buffer or operation failed. * No need to keep the Fields any more. */ (void)mqeFields_free(hActivateSlaveFields, NULL); hActivateSlaveFields = NULL; } return pExceptBlock->ec; }
Calling winCEAuthenticator_processSlaveResponse() implements the slaveResponse() function. The winCEAuthenticator_processSlaveRespons() function receives the byte array returned by winCEAuthenticator_activateSlavePrep() and restores it into an MQeFields structure. The user name, validated by activateSlave(), is extracted from this and passed to mqeAuthenticator_setAuthenticatedID().
MQERETURN winCEAuthenticator_processSlaveResponse( MQeAuthenticatorHndl hAuthenticator, MQeAttrPlugin_ProcessSlaveResponseInput * pInput, MQeAttrPlugin_ProcessSlaveResponseOutput * pOutput ) { MQEINT32 inputDataLen = pInput->inputDataLen; MQEBYTE * pInputData = pInput->pInputData; MQeFieldsHndl hFields; MQeExceptBlock * pExceptBlock = (MQeExceptBlock *)pOutput->pExceptBlock; /* initialize exception block */ pExceptBlock->ec = MQERETURN_OK; pExceptBlock->erc = MQEREASON_NA; /* restore input */ (void)mqeFields_new(pExceptBlock, &hFields); if (MQERETURN_OK == pExceptBlock->ec) { (void)mqeFields_restore(hFields, pExceptBlock, pInputData, inputDataLen); /* get ID */ if (MQERETURN_OK == pExceptBlock->ec) { MQeStringHndl hAuthenticIDField; (void)mqeString_newChar8(pExceptBlock, &hAuthenticIDField, AUTHENTIC_ID); if (MQERETURN_OK == pExceptBlock->ec) { MQeStringHndl hAuthenticID; (void)mqeFields_getAscii(hFields, pExceptBlock, &hAuthenticID, hAuthenticIDField); /** If the above call failed, * then the authentication by the slave was not successful. */ if (MQERETURN_OK == pExceptBlock->ec) { /* set local ID */ (void)mqeAuthenticator_setAuthenticatedID(hAuthenticator, pExceptBlock, hAuthenticID); } (void)mqeString_free(hAuthenticIDField, NULL); } } (void)mqeFields_free(hFields, NULL); } return pExceptBlock->ec; }