ShowTable of Contents
Introduction
The Lightweight Third-Party Authentication (LTPA) token was developed by IBM® as a Single Sign-on (SSO) technology for their WebSphere® and Lotus® Domino® product suite. The Java
TM Software Development Kit (SDK) that ships with Lotus Domino includes an API that allows a developer to access data in a Lotus Domino database via a Java client.
The Java API provides mechanisms to access the Lotus Domino database both within the Domino runtime and remotely, using the Domino Internet Inter-ORB Protocol (DIIOP) protocol. DIIOP therefore allows Java application developers to build applications that can use a Domino server as their data store without actually being hosted within the Domino server’s runtime.
All remote protocols must address the issue of security, and the Java DIIOP API supports the IBM-proprietary LTPA security mechanism. Most IBM application servers (for example, WebSphere and Domino) support LTPA out-of-the-box. However, if you're using a non-IBM application server, you may want an authentication mechanism other than LTPA, which then poses a problem at the data tier when accessing the Domino database.
Unless data in the Domino database can be accessed anonymously, you must use a username and password or an LTPA token when creating the Domino session for data access.
The username and password used to log into the Web application may be different than the credentials used to access the Domino database, requiring a credentials translation layer. This translation layer ideally would not store a new set of user passwords since passwords change fairly often, introducing a maintenance headache.
Instead, we only need to maintain a log-in ID mapping, to use an LTPA-token-based solution, because encrypted LTPA tokens only store a user’s log-in ID and not their password.
This article details how to generate the LTPA token by using a Java client running in an application server other than WebSphere. WebSphere already has built-in support for LTPA token generation, therefore making this approach redundant.
Getting started
This article discusses how to create a Java servlet (henceforth referred to as the LTPA servlet) to generate LTPA tokens. This is a robust solution that can be used by many different Web applications that rely on the Domino database as their data store. Using inbound firewall rules, we can protect the LTPA servlet so as to allow only the appropriate Web applications to access it.
This LTPA servlet can be packaged in a Web application archive (WAR) file and run on a variety of application servers (JBoss, Tomcat, Jetty, etc.). This article is not intended to be a tutorial on creating servlets and WAR files. It is assumed the reader is already familiar with these technologies; instead, we'll discuss creating a Dynamic Link Library (DLL) for LTPA token generation.
The
Lotus C API toolkit contains functions encapsulated in DLLs for generating an LTPA token. No comparable methods were found in the Java SDK that ships with Lotus Domino or with WebSphere Application Server (i.e. Notes.jar), so first we write a function in C that can then be called from the LTPA servlet.
The LTPA servlet uses
Java Native Access (JNA) to access a DLL containing the C function. This is a Microsoft® Windows® solution, but a similar strategy could be used on UNIX® by creating a Shared Object Library instead of a DLL. The instructions below show how to generate the DLL as a debug build.
Generating the DLL
First, we use Microsoft Visual C++ 2010 Express to generate the DLL by following these steps:
- Create a New Project by selecting File --- New --- Project.
- Select “Win32 Console Application,” and enter the name of the project; click OK (see figure 1).
Figure 1. New Project window
3. The Win32 Application Wizard Welcome window displays; click Next (see figure 2).
Figure 2. Win32 Application Wizard Welcome window
4. In the Application Settings window, select the DLL radio button and click Finish (see figure 3).
Figure 3. Application Settings window
Configuring project settings
To do this:
- Select Project --- LtpaProject Properties, expand Configuration Properties, and select VC++ Directories (see figure 4).
- Add to the Include Directories and Library Directories the directories for the include files and lib files, respectively, for the Lotus C API toolkit (use the 8.5 version). Click OK.
Figure 4. LtpaToken Property Pages window
3. Select Configuration Properties --- C/C++ --- Code Generation, and make sure the Runtime Library property is set to Multi-threaded (/MT), as shown in figure 5.
Figure 5. Runtime Library set to Multi-threaded (/MT)
4. Next, select Configuration Properties --- C/C++ --- Command Line, and add /D “W32” in the Additional Options section (see figure 6).
Figure 6. Add /D “W32” in Additional Options section
5. Finally, select Configuration Properties --- Linker --- Input, and add notes.lib (from the Lotus C API toolkit) to the Additional Dependencies property (see figure 7). Click OK.
Figure 7. Add notes.lib to Additional Dependencies property
Creating and adding the C function
Now, the C function must be created and added to the DLL project.
Listings 1 and 2 show the contents that should be in the include file and C file that generate the LTPA token.
Listing 1. LtpaToken.h
extern "C" __declspec(dllexport) int CreateLtpaToken(char *username, char *ltpaToken, char *errorMessage);
Listing 2. LtpaToken.cpp
// LtpaToken.cpp : Defines the exported functions for the DLL application.
#include "stdafx.h"
#include "stdio.h"
#include "string.h"
#include "global.h"
#include "osfile.h"
#include "bsafe.h"
#include "osmisc.h"
#include "osmem.h"
#include "LtpaToken.h"
extern "C" __declspec(dllexport) int CreateLtpaToken(char far *username, char far *ltpaToken, char *errorMessage)
{
STATUS errorStatus = NOERROR;
char notesError[255];
if (error = NotesInit()) {
OSLoadString(NULLHANDLE, ERR(error), notesError, 255);
printf("error = %s\n", notesError);
strcpy(errorMessage, notesError);
return errorStatus;
}
MEMHANDLE mhToken;
error = SECTokenGenerate(NULL, NULL, "LtpaToken", username, 0, 0, &mhToken, (DWORD)0, (void *)NULL);
if(error != NOERROR) {
OSLoadString(NULLHANDLE, ERR(error), szNotesError, 255);
printf("error = %s\n", szNotesError);
strcpy(errorMessage, szNotesError);
return error;
} else {
SSO_TOKEN *ssoToken = (SSO_TOKEN*)OSMemoryLock(mhToken);
char *pData = (char*)OSMemoryLock(ssoToken->mhData);
strcpy(ltpaToken, pData);
SECTokenFree(&mhToken);
}
NotesTerm();
return 0;
}
The C code above calls out to an external function called
SECTokenGenerate whose implementation is included in the Notes C API. Details of that function can be found in the document, “
SECTokenGenerate - Generate a Single Sign-On Token.”
At last, the project can be built and the DLL created. To do this, select Debug --- Build Solution; the resulting DLL can be found in the Debug directory of the project.
The LTPA servlet can now be created and will call out to the LtpaToken.dll that was just created (see listing 3).
Listing 3. LtpaServlet.java
import javax.servlet.*;
import lotus.domino.*;
import com.sun.jna.*;
public class LtpaServlet extends HttpServlet
{
public static final String USERNAME_PARAM = "un";
private byte[] ltpaToken = new byte[2048];
private byte[] errorMessage = new byte[255];
// The method signature defined below must match that defined in the DLL for the C function
interface LtpaTokenInterface extends StdCallLibrary {
int CreateLtpaToken(String username, byte[] ltpaToken, byte[] errorMessage);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
String username = request.getParameter(USERNAME_PARAM);
if (username != null)) {
try {
Native.setProtected(true);
LtpaTokenInterface ltpaTokenDll = (LtpaTokenInterface)
Native.loadLibrary("LtpaToken", LtpaToken.class);
int error = ltpaTokenDll.CreateLtpaToken(username, ltpaToken, errorMessage);
if(error == 0) {
out.println(Native.toString(ltpaToken));
} else {
out.println("Error creating LTPA token.");
out.println("Error message: " + Native.toString(errorMessage));
}
} catch(Exception e) {
out.println("Error creating LTPA token.");
out.println("Error message: " + e.toString());
}
} else {
out.println("No parameter ‘" + USERNAME_PARAM + "’ passed to servlet ");
}
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
{
doPost(request, response);
}
}
Listing 4 shows the web.xml configurations necessary for your Web application to expose the new servlet.
Listing 4. web.xml file for servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name>LTPA Token Servlet</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>LtpaTokenServlet</servlet-name>
<servlet-class>LtpaTokenServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LtpaTokenServlet</servlet-name>
<url-pattern>/ltpaTokenServlet</url-pattern>
</servlet-mapping>
</web-app>
Alternate approach
The LTPA Token generation technique discussed above uses a custom DLL as well as DLLs provided by the Lotus toolkit to ultimately generate the LTPA token. Behind the scenes, when the
SECTokenGenerate function is called, a series of other Lotus-provided DLLs will also be loaded and called, and hence those DLLs must be accessible (in the example above, those DLLs are simply found where the Domino server is installed, for example, C:\Lotus\Domino).
In addition to the DLLs, a special Domino document within the standard Names.nsf Domino database, called
WebSSOConfig, is opened to retrieve the server’s LTPA private key. That key is then used to encrypt the LTPA token. If, for some reason, this lookup for Names.nsf fails, you might see debug output like so:
"ERROR: when reading configuration [Domino Directory does not exist]."
Hosting the LTPA servlet on the same machine as the Domino server may not always be the best architectural decision---or may not even be possible. For example, instances were found using Domino Server 6.5 in which, when it was configured to use Directory Assistance, it could lock the local Names.nsf while the server was running, causing the DLL
SecTokenGenerate function call to fail to generate the LTPA token.
Although stopping the Domino server fixed the problem (unlocked Names.nsf), that is obviously not a viable solution. However, it is possible to make a portable LTPA-token-generating application that is derived from an existing Domino server install.
This alternate approach is described as follows:
Instead of relying on the DLLs to gain access to the single Names.nsf file in use while the Domino Server is running, we can construct a standalone application that uses a copy of Names.nsf and, with some additional DLLs, can be hosted on any Windows operating system (the software was tested on both Windows Server 2000/2003/2008, as well as Windows XP).
Note that, if you're deploying this solution to a 64-bit Windows server machine, make sure that your Java application is using a 32-bit JRE and not 64-bit.
To create the standalone application, copy from your Domino install directory the following files to a new location within your
portable LtpaToken servlet’s classpath (for a Web application, this is simply the
WEB-INF\lib directory):
- icudt18l.dat
- ltsci3.tlb
- js32.dll
- ndgts.dll
- nlsccstr.dll
- nnotes.dll
- nxmlcommon.dll
- nxmlpar.dll
- LtpaToken.dll (This is the custom DLL compiled in the previous section.)
- The ID file that the Domino server uses to access Names.nsf. (This is usually the name of the Domino Server with “.ID” appended to it.)
- Names.nsf. When copying Names.nsf, be sure to replace it in the future with a fresh copy should the original Names.nsf's Domino LTPA private key needs to be regenerated. Otherwise, any LTPA tokens the servlet generates will not be accepted by the Domino server (it will be unable to decrypt them).
In addition, you need to create a Notes.ini file that is used by the DLLs to locate the other DLLs used when
SecTokenGenerate is called. This new Notes.ini should also be copied to the lib directory and should contain the following content (no more is needed):
[Notes]
Directory=<full path to your lib directory>
NotesProgram=<full path to your lib directory>
KeyFilename=<the filename of the ID file used to access names.nsf>
Once this is done, you can host the LtpaTokenServlet on any Windows machine, even one that does not have a Domino server installed, as long as the
lib folder is part of your Java app’s classpath. Also, make sure to add the path to the
lib folder to the Windows machine’s PATH environment variable.
Conclusion
This article has demonstrated that the
Lotus C API toolkit can be used to generate an LTPA token without the need for the Domino user’s password. Instead, by just using a log-in ID we can generate an LTPA token by a Java servlet, using JNA to access a custom DLL that in turn accesses the underlying Domino server’s LTPA token generation engine. The LTPA token can then be used by any other application that needs to authenticate with a Domino server.
Resources
developerWorks® WebSphere Portal zone:
http://www.ibm.com/developerworks/websphere/zones/portal/
developerWorks Lotus Notes and Domino product page:
http://www.ibm.com/developerworks/lotus/products/notesdomino/
WebSphere Portal forum:
http://www.ibm.com/developerworks/forums/forum.jspa?forumID=168
Notes/Domino 8 forum:
http://www-10.lotus.com/ldd/nd8forum.nsf?OpenDatabase
About the authors
Jason Everhart has worked in the software industry for 18 years, the last 4 of which as an independent software consultant. He is currently working as a consultant for Techflow, Inc., in San Diego, California.
Paul Spinelli is a Senior Application Developer working for TechFlow, Inc., in San Diego. Since graduating from the University of Virginia with a computer science degree, Paul has worked as a software developer for over 13 years. His experience has brought him an in-depth knowledge of various programming languages, OOP techniques, and security technologies.