NemID Integration
This section shows how to integrate with DanID / NemID’s danish single sign-on as a service provider.
First, add an X.509 certificate authentication plugin to the session controllers, e.g. “dk.itp.security.authentication.x509useradm.X509CertificatePlugin” – usually your organisation will have a customized version of this plugin.
Add the appropriate CA provider (see further default in the configuration reference section in this document, this is an example from portalprotect-configuration.xml which contains the properties required for NemID applet parameter signing, as well as authentication. Note that in the example, CRL checking is disabled – this should not be the case in real-life scenarios.
Also note that you will need to switch to keystores with your own private keys for your own certificates instead of the sample ones on the list.
<property name="ca.certificates" value="${portalprotect.home}/ppcfg/x509/nemid/danidexttest.cer" description="pkcs#7 files containing CA certificate"/> <property name="ca.provider.nemidtest.allow.obsolete.crl" value="true" description="Allow logons to complete if CRL is obsolete"/> <property name="ca.provider.nemidtest.check.ocsp" value="false" description="If true, certificate validity will be checked online using the OCSP protocol"/> <property name="ca.provider.nemidtest.class" value="dk.itp.security.authentication.x509.CA_NEMID_TEST" description="implementation class"/> <property name="ca.provider.nemidtest.clientcert.keystore.type" value="PKCS12" description="keystore type"/> <property name="ca.provider.nemidtest.crl" value="" description="url where to get certificate revocation list for specificed issuer"/> <property name="ca.provider.nemidtest.ssl.providername" value="SunJSSE" description="Name of SSL provider"/> <property name="ca.provider.nemidtest.ssl.protocol" value="TLS" description="SSL Protocol"/> <property name="ca.provider.nemidtest.nemid.appletparam.10.jceprovider" value="BC" description="JCE provider used for signing"/> <property name="ca.provider.nemidtest.nemid.appletparam.10.keystore.certalias" value="alias" description="Keystore alias for certificate to use for signing"/> <property name="ca.provider.nemidtest.nemid.appletparam.10.keystore.file" value="${portalprotect.home}/ppcfg/x509/nemid/applet-parameter-signing-keystore-cvr30808460-uid1263281782319.jks" description="Providers signing certificate"/> <property name="ca.provider.nemidtest.nemid.appletparam.10.keystore.password" value="Test1234" description="Password for the file"/> <property name="ca.provider.nemidtest.nemid.appletparam.10.keystore.privkeyalias" value="alias" description="Keystore alias for private key to use when signing"/> <property name="ca.provider.nemidtest.nemid.appletparam.10.keystore.provider" value="SUN" description="Keystore provider is SUN"/> <property name="ca.provider.nemidtest.nemid.appletparam.10.keystore.type" value="JKS" description="Keystore type is java keystore"/> <property name="ca.provider.nemidtest.nemid.providerid" value="10" description="List of NemID provider IDs"/> <property name="ca.providers" value="nemidtest" description="list of certificate issuers (providers)"/>
The listed properties will make sure that you can do applet parameter signing as well as Javascript parameter generation.
NemID Implementations
There are a number of different ways of using NemID – either the newest variation; pure Javascript, or the older versions which relies on Java Applets.
The Javascript variant supports authenticating personal certificates that are stored centrally, as well as employee certificates in the central variant.
For NemID hardware (where the certificate is stored on a smartcard) you will still need the java applet, likewise for employees that do not have their private keys stored on DanIDs servers but instead have them on the local machine, you will also need the java applets.
The javascript version of NemID can be used on mobile devices, e.g. phone or tables and does not require the end users to have java installed.
No matter the implementation the NemID integration error codes from Nets are described here: https://www.nets.eu/dk-da/kundeservice/nemid-tjenesteudbyder/NemID-tjenesteudbyderpakken/Documents/NemID%20Error%20Codes.pdf
NemID Javascript
DanID/Nets has information available about Javascript here:
http://www.nets.eu/dk-da/Produkter/Sikkerhed/NemID-tjenesteudbyder/NemID-JavaScript/
Compared to applets, there is only a difference in the GUI, so within PortalProtect you still need the exact same configuration on the server for both Javascript and Java applets.
If you are already using PortalProtect with NemID Applets, you will only need to change the GUI part.
NemID JS works in the way that it is an iframe you embed in your page, and you communicate with that using http messaging.
PortalProtect’s distribution contains examples on how to authenticate using javascript, e.g. in demologinnemidjs.jsp in the sample web application included.
From your application, you need to use “dk.itp.portalprotect.nemid.NemIDJSGenerator” which will do the work of generating the signed JSON parameters needed to kickstart NemID JS login/signing.
If you are a bank, you will be able to use the same class for doing 1-factor authentication/signing if needed, but this is not possible for normal service providers, only banks.
This is an example of a .jsp page which uses the API.
<%@page import="dk.itp.portalprotect.nemid.NemIDJSGenerator"%> <%@page import="dk.itp.portalprotect.demoapp.JSPHelper"%> <%@page isThreadSafe="true" import="dk.itp.security.passticket.*" %> <%@page pageEncoding="UTF-8" %> <% int NEMID_PROVIDERID = 10; String sessionId = JSPHelper.getSessionID(request); NemIDJSGenerator generator = new NemIDJSGenerator(sessionId, AuthTypes.AUTHTYPE_X509_CA, NEMID_PROVIDERID ); generator.setClientFlow(NemIDJSGenerator.CLIENTFLOW_OCESLOGIN2); generator.setChallenge(JSPHelper.generateChallenge(request)); String nemidparams = generator.generateJSON(); %> <html> <head><title>NemID Javascript test</title></head> <body> <form name="PPForm" method="post" action="/NemIDJSLoginServlet"> <input name="message" value="" type="hidden"> <input name="action" value="" type="hidden"> </form> <script type="text/x-nemid" id="nemid_parameters"><%=nemidparams%></script> <iframe id="nemid_iframe" name="nemid_iframe" scrolling="no" frameborder="0" style="width:500px;height:450px;border:0" src="https://appletk.danid.dk/launcher/<%=System.currentTimeMillis()%>"></iframe> <script> function onNemIDMessage(e) { var event = e || event; var win = document.getElementById("nemid_iframe").contentWindow, postMessage = {}, message; message = JSON.parse(event.data); if (message.command === "SendParameters") { var htmlParameters = document.getElementById("nemid_parameters").innerHTML; postMessage.command = "parameters"; postMessage.content = htmlParameters; win.postMessage(JSON.stringify(postMessage), "*"); } if (message.command === "changeResponseAndSubmit") { document.PPForm.action.value="logonok"; document.PPForm.message.value = message.content; document.PPForm.submit(); } if (message.command === "changeResponseAndSubmitSign") { document.PPForm.action.value="signok"; document.PPForm.message.value = message.content; document.PPForm.submit(); } } if (window.addEventListener) { window.addEventListener("message", onNemIDMessage); } else if (window.attachEvent) { window.attachEvent("onmessage", onNemIDMessage); } </script> </body> </html>
The example creates a new NemIDJSElementGenerator object, using the type of authentication plugin, and the provider ID (note that plugins for multiple providers can be installed and running simultaneously).
It then adds a challenge to the JSON parameters. You can also add additional parameters, please refer to DanID/Nets documentation on which values are available.
Next, the code calls generator.generateJSON() which generates the signed parameters – the generation and signing take place on the PortalProtect server, which has access to the private keys so the applications themselves using PP only needs the Agent API and does not itself need access to any private keys.
Like with the applet, we have a form used to transmit the result of the NemID action to a server, in this case called PPForm.
The followingsigning takes
<script type="text/x-nemid" id="nemid_parameters"><%=nemidparams%></script>
makes the signed JSON parameters available to the javascript which transmits it to the iframe.
Next, is an iframe element pointing to the appropriate NemID environment – refer to DanIDs documentation for a description of the various test environments available. For normal Service Providers, use https://appletk.danid.dk/launcher/XXXX for test, and https://applet.danid.dk/launcher/XXXX for production. Note that XXXX should be a unique value to prevent caching by proxy servers.
Finally, we have the javascript code which communicates with the iframe, initially sets the nemid params, and obtains the result of the authentication, puts it in the form and submits it.
Note that this bit of javascript works for both login and signing so it can safely be reused for both cases.
When authentication is completed at NemID, an XMLDSIG document will be sent to the target specified in the form, in this case /NemIDJSLoginServlet.
The code within the servlet should look similar to this:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String message = request.getParameter("message"); String action = request.getParameter("action"); String sessionID = JSPHelper.getSessionID(request); if (message != null) { String decodedMsg = Base64.isBase64(message) ? Base64.decode(message) : message; if (!decodedMsg.startsWith("<")) { if (decodedMsg.startsWith("CAN")) { // User cancelled... response.sendRedirect("index.jsp"); return; } request.setAttribute("error", JSPHelper.getErrorText(decodedMsg)); } else { try { Agent.getInstance().logon(sessionID, AuthTypes.AUTHTYPE_X509_CA, message); try { String challenge = Agent.getInstance().getStateVariable(sessionID, "challenge"); JSPHelper.verifyChallenge(request, challenge); request.getRequestDispatcher("/demologin_done.jsp").forward(request, response); return; } catch(InvalidParameterException e) { Agent.getInstance().logoff(sessionID); request.setAttribute("error", e.getMessage()); } catch(PTException e) { request.setAttribute("error", e); } request.getRequestDispatcher("/demologinnemidjs.jsp").forward(request, response); } } }
Note that the logon call transfers control to the authentication plugin within the session controller which handles all the details of validating the XMLDSIG document, checking signatures, certificates, etc. and logging the user in. It can also check CRL lists, do OCSP (online certificate checks), do PID lookups or PID/CPR verification, and it can retrieve attributes from DanIDs attribute service.
If you wish to sign data instead of just authenticating users, the procedure is very similar, you just need to add these two calls to the javascript generator:
generator.setClientFlow(NemIDJSGenerator.CLIENTFLOW_OCESSIGN2); generator.setSignText(signtext, NemIDJSGenerator.SIGNFORMAT_TEXT);
Here, you first specify that the flow is not login (which is the default) but instead signing, and you specify the signtext and the format of the sign text.
On the servlet receiving the data, you will need to call confirm instead of calling authenticate, like this:
Agent.getInstance().confirm(sessionID, AuthTypes.AUTHTYPE_X509_CA, message);
This will verify the signature in the exact same way as when using a Java applet.
NemID Applets
From your application, you will need to use “dk.itp.portalprotect.nemid.NemIDAppletElementGenerator” in order to get the applet tags created and signed – the output from must be sent to the browser as in this example of a .jsp page which uses the API.
<%@ page isThreadSafe="true" import="dk.itp.security.passticket.*,dk.itp.portalprotect.nemid.NemIDAppletElementGenerator" %><% int NEMID_PROVIDERID = 10; String sessionId = request.getRemoteUser(); NemIDAppletElementGenerator generator = new NemIDAppletElementGenerator(sessionId, AuthTypes.AUTHTYPE_X509_CA, NEMID_PROVIDERID ); // You might use another authtype here depending on your plugin, such as AuthTypes_AUTHTYPE_NEMID generator.setChallenge("random_string_containing_challenge_value"); generator.setLogLevel("debug"); // INFO/DEBUG/ERROR generator.setUrlPrefix("https://applet.danid.dk"); String applettag = generator.generateLogonAppletElement(); %> <html> <head><title>NemID Applet test</title></head> <body> <script language="JavaScript"> function appletStarted() { } function onLogonOk(signature) { document.PPForm.message.value=signature; document.PPForm.action.value="logonok"; document.PPForm.submit(); } function onLogonCancel() { document.location="demologin.jsp"; } function onLogonError(reason) { document.PPForm.message.value=reason; document.PPForm.action.value="logonerror"; document.PPForm.submit(); } </script> <%=applettag%> <form name="PPForm" method="post" action="/NemIDLoginServlet"> <input name="message" value="" type="hidden"> <input name="action" value="" type="hidden"> </form> </body> </html>
The example creates a new NemIDAppletElementGenerator object, using the type of authentication plugin, and the provider ID (note that plugins for multiple providers can be installed and running simultaneously).
It then adds a challenge to the applet parameters, sets the loglevel and the hostname of the DanID environment to use. “applet.danid.dk” is the production environment, refer to DanID’s documentation for the appropriate names for the various test environments.
Note that setChallenge() should be called with a random or at least non-repeating string to prevent replay attacks. The value in the signature will be made available by the authentication plugin, so the application can verify that the challenge is correct.
You can also add additional parameters to the applet – refer to DanIDs documentation on which values are available.
Finally, the code calls generateLogonAppletElement(). The output is the applet tag with all parameters signed using the specified keys and certificates.
When authentication is completed at NemID, an XMLDSIG document will be sent to the target specified in the form, in this case /NemIDLoginServlet.
The code within the servlet could look like this:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String message = request.getParameter("message"); String action = request.getParameter("action"); String sessionID = request.getRemoteUser(); if (action != null && action.equalsIgnoreCase("logoncancel")) { response.sendRedirect("demo.html"); return; } if (message != null) { if (action.equalsIgnoreCase("logonerror")) { request.setAttribute("error", message); } else { try { Agent.getInstance().logon(sessionID, AuthTypes.AUTHTYPE_X509_CA, message); // Might be another authentication type depending on how your authentication // plugins are structured. request.getRequestDispatcher("/demologin_done.jsp").forward(request, response); return; } catch(PTException e) { request.setAttribute("error", e); } } // Go back to the login page, it will check if an error is on the request, and display // an error mesage if it is request.getRequestDispatcher("/demologinnemid.jsp").forward(request, response); }
Note that the logon call transfers control to the authentication plugin within the session controller which handles all the details of validating the XMLDSIG document, checking signatures, certificates, etc. and logging the user in. It can also check CRL lists, do OCSP (online certificate checks), do PID lookups or PID/CPR verification, and it can retrieve attributes from DanIDs attribute service.
If you wish to sign data instead of just authenticating users, the procedure is very similar. Instead of this code in the JSP:
String applettag = generator.generateLogonAppletElement();
You need the following code:
generator.setSignText(signtext, signtextformat); String applettag = generator.generateSignAppletElement();
You might also want to change the target in the form to e.g. /NemIDSignServlet and within your servlet, you need this code: (node that for clarity, error handling is removed)
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String message = request.getParameter("message"); String sessionID = request.getRemoteUser(); if (message != null) { try { Agent.getInstance().confirm(sessionID, AuthTypes.AUTHTYPE_X509_CA, message); // Might be another authentication type depending on how your authentication // plugins are structured. // Proceed with handling the request here - the signature was verified. } catch(PTException e) { // Something went wrong } } }
Example X.509 Authentication Plugin
The following shows en example of an X.509 Authentication plugin you can use if you wish.
package dk.itp.security.auth; import java.util.Hashtable; import java.util.Properties; import dk.itp.security.authentication.x509.AbstractX509CertificatePlugin; import dk.itp.security.certificate.Subject; import dk.itp.security.passticket.PTException; import dk.itp.security.passticket.User; import dk.itp.security.passticket.server.AuthErrorCodes; /** * Authenticates users using OCES I and OCES II (DanID / NemID). * * @author Kim Rasmussen * */ public class SampleOCESAuthenticationPlugin extends AbstractX509CertificatePlugin { /** * Returns the authentication level, the higher the level, the more secure this authentication is considered to be. */ public int getAuthenticationLevel() { return 20; } public String getName() { return "Sample OCES II Certificate plugin"; } /** * Returns the type of authentication, all plugins have assigned a particular id identifying that specify type * of authentication. */ public int getAuthenticationType() { return dk.itp.security.passticket.AuthTypes.AUTHTYPE_X509_CA; } /** * Try to login the specified user session, using the supplied userid and password. * @param User this method will update the provided user object * @param userid reserved * @param credentials OCES login sign text * @param newPassword not used */ public void login(User user, String userid, Object credentials) throws PTException { try { Subject subject = verifyCertificate(user, credentials.toString(), false, false, null, null); userid = subject.getSerialNumber(); if (user.stateVariables == null) user.stateVariables = new Hashtable(); user.stateVariables.put("company", subject.getO()); user.stateVariables.put("country", subject.getC()); user.stateVariables.put("full_subject", subject.getOrderedSubjectDN()); user.stateVariables.put("certificate_serialnumber", subject.getCertificate().getSerialNumber().toString()); if (subject.getEmailAddress() != null) user.stateVariables.put("email", subject.getEmailAddress()); user.username = subject.getCn(); String serial = subject.getSerialNumber(); if (serial.startsWith("PID")) { user.stateVariables.put("pid", serial.substring(4)); } else if (serial.startsWith("CVR")) { String cvr = serial.substring(4); if (cvr.indexOf('-') > 0) cvr = cvr.substring(0, cvr.indexOf('-')); user.stateVariables.put("cvr", cvr); int idx = serial.indexOf("-UID:"); if (idx < 0) { idx = serial.indexOf("-RID:"); if (idx >= 0) { // Medarbejder cert user.stateVariables.put("rid", serial.substring(idx + 5)); } } else { // Virksomheds cert user.stateVariables.put("uid", serial.substring(idx + 5)); } } // Add additional code here to lookup user groups from database etc. user.isLoggedOn = true; } catch(PTException e) { cat.warn("Exception trying to log user in", e); throw e; } } public void confirm(User user, String signtext, Object credentials) throws PTException { // Verify Digital signatures here Subject subject = verifyCertificate(user, credentials.toString(), false, false, null, signtext); // Check that same certificate was used at login (you might not care, so remove this code if you do not). if (!user.stateVariables.get("certificate_serialnumber").equals(subject.getCertificate().getSerialNumber().toString())) throw new PTException("Wrong certificate used - should be the same certificate as the one used at login"); } }
To install this plugin, add it to the list of active plugins for the session controller in portalprotect-configuration.xml. Refer to the description of “server.authenticationplugins” property for more information.
Example:
<property name="server.authenticationplugins" value=" dk.itp.security.passticket.server.FileLogin,dk.itp.security.auth.SampleOCESAuthenticationPlugin,dk.itp.security.authentication.ntlm.NTLMAuthenticationPlugin" description="The list of authentication plugins (classes) to load"/>
Using multiple Applet Signer Certificates
An applet signing certificate is a VOCES certificate containing your company name. The name from within the certificate is displayed to the user by the NemID applet.
If you wish to provide different names to the user depending on where on your site your user authenticates/signs then it is possible to list multiple provider IDs in the ca.provider.xxxx.nemid.providerid property.
<property name="ca.provider.nemidtest.nemid.providerid" value="10;11" description="List of NemID provider IDs"/>
And then list multiple keystores for each provider ID (ca.provider.xxxx.nemid.appletparam.ZZ.*) where ZZ is the provider ID. Your application which displays the applet can then choose between different providers and thus what is displayed in the applet by changing the provider ID used as input to NemIDAppletElementGenerator.
© Ceptor ApS. All Rights Reserved.