Thank you, that is exactly what I needed.
Original Message:
Sent: Thu November 23, 2023 06:12 PM
From: Shane Weeden
Subject: User Mapping for LMI Certificate authentication
I think you can, using the native Java JNDI interface to lookup ldap. This is a very rough example (not much error handling, no LDAP context cleanup, etc) but should give you the idea of how to do it. In my case I just searched for "testuser" in the embedded LDAP on my appliance, but you can change this around as needed. Also my example always just maps to "admin@local" because I was only trying to prove you could do an LDAP search.
function mapUser(props) { // dump all available properties for debugging java.lang.System.out.println("props: " + props.toString()); // this is java.security.cert.X509Certificate var cert = props.get("cert"); // build the PEM version var b64encoder = java.util.Base64.getMimeEncoder(64, [ 0x0A ]); var pemcert = "-----BEGIN CERTIFICATE-----" + java.lang.System.lineSeparator(); + b64encoder.encodeToString(cert.getEncoded()); + java.lang.System.lineSeparator(); + "-----END CERTIFICATE-----" + java.lang.System.lineSeparator(); // and log it java.lang.System.out.println(pemcert); // LDAP search example var ldapEnv = new java.util.Hashtable(); ldapEnv.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); ldapEnv.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple"); ldapEnv.put(javax.naming.Context.SECURITY_PRINCIPAL, "cn=root,secauthority=default"); ldapEnv.put(javax.naming.Context.SECURITY_CREDENTIALS, "passw0rd"); ldapEnv.put(javax.naming.Context.PROVIDER_URL, "ldaps://localhost:636"); var ldapCtx = new javax.naming.directory.InitialDirContext(ldapEnv); var baseDN = "dc=iswga"; var searchForUser = "testuser"; var searchFilter = "(uid="+searchForUser+")"; var searchControls = new javax.naming.directory.SearchControls(); searchControls.setSearchScope(javax.naming.directory.SearchControls.SUBTREE_SCOPE); // NamingEnumeration<SearchResult> java.lang.System.out.println("About to perform search for: " + searchForUser); var searchResults = ldapCtx.search("dc=iswga", searchFilter, searchControls); if (searchResults != null && searchResults.hasMoreElements()) { var searchResult = searchResults.nextElement(); // make sure there is not another item available, there should be only 1 match if(searchResults.hasMoreElements()) { java.lang.System.out.println("Matched multiple users for: " + searchForUser); } else { // print contents of searchResult java.lang.System.out.println("Found user searchResult: " + searchResult.toString()); } } else { java.lang.System.out.println("No search results for user: " + searchForUser); } return "admin@local";}
------------------------------
Shane Weeden
IBM
Original Message:
Sent: Wed November 22, 2023 09:52 AM
From: Peter Lindqvist
Subject: User Mapping for LMI Certificate authentication
Hi Shane and Rakesh,
We have a similar use case so I would like ask a follow up question here.
We also need to map the certificate to the user in Active directory but our organization does certificate pinning. We would need to do a ldap callout from to get the user object with that particular certificate. This is supported in the reverse proxy using xsl certificate mapping and in mapping rules, but I have not found a way to do this for authenticating to the LMI.
Is there a way to make an ldap callout from this User Mapping Script for LMI authentication?
/Peter
------------------------------
Peter Lindqvist
Original Message:
Sent: Tue March 14, 2023 10:46 PM
From: Shane Weeden
Subject: User Mapping for LMI Certificate authentication
Sorry about that - I missed "LMI" :)
Anyway, this is not an area of ISVA I am intimately familiar with, but I have experiemented a bit and here's something to get started.
First, I believe you will need to have System -> Administrator Settings -> Validate Client Certificate Identity turned ON before the user mapping function can work.
You can access the X509Certificate as its *Java class* (java.security.cert.X509Certificate) via:
var cert = props.get("cert");
You can also use java.lang.System.out.println("some string") to print to the LMI message and trace logs (which you can monitor from the command line using isam logs monitor
).
Finally you can also turn on LMI trace (System -> Administrator Settings -> LMI Tracing) for the component com.ibm.mesa.security.authentication.modules=all
to get trace of the authentication process (then monitor the LMI trace.log).
Here's a more elaborate example that will print out the PEM version of the client certificate, then just maps everything to "admin". You should be able to use other standard Java APIs available from X509Certificate to parse other parts of the data.
function mapUser(props) { // dump all available properties for debugging java.lang.System.out.println("props: " + props.toString()); // this is java.security.cert.X509Certificate var cert = props.get("cert"); // build the PEM version var b64encoder = java.util.Base64.getMimeEncoder(64, [ 0x0A ]); var pemcert = "-----BEGIN CERTIFICATE-----" + java.lang.System.lineSeparator(); + b64encoder.encodeToString(cert.getEncoded()); + java.lang.System.lineSeparator(); + "-----END CERTIFICATE-----" + java.lang.System.lineSeparator(); // and log it java.lang.System.out.println(pemcert); return "admin";}
------------------------------
Shane Weeden
IBM
Original Message:
Sent: Tue March 14, 2023 07:22 AM
From: Rakesh Vohra
Subject: User Mapping for LMI Certificate authentication
Hello, Shane,
Thanks for your reply and the information.
In my case, it is the certificate authentication for the ISVA LMI. I am not sure if I could use a similar code there. The management authentication configuration portion of the LMI setup provides an option to use javascript to do user mapping. I am not sure what all functions and libraries are available when writing code for that user mapping.
Thanks,
Rakesh
Original Message:
Sent: 3/14/2023 2:19:00 AM
From: Shane Weeden
Subject: RE: User Mapping for LMI Certificate authentication
I have done this several times using an InfoMap AAC mechanism as the "certificate EAI" implementation, including for some very bespoke ASN.1 encoded extensions, using Javascript libraries to parse the x509 certificate data. The KJUR (actually jsrsasign - https://github.com/kjur/jsrsasign) library is your friend here - it's very easy to import that into your infomap and parse standard extensions with that.
Here's a sample InfoMap that acts as a certificate EAI where the username to login as can be found in a very specific OID encoded as an otherName part of the SAN extension. You can most likely dumb this down a lot for your own use case....
importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils);// must use jsrsasign 10.5.3 or later for otherName supoprt (see https://kjur.github.io/jsrsasign/api/symbols/KJUR.asn1.x509.GeneralName.html)importMappingRule("jsrsasign")function getRequestHeader(h) { return context.get(Scope.REQUEST, "urn:ibm:security:asf:request:header", h);}function addOptionalResponseAttribute(attrName, attrValue) { if (attrValue != null) { context.set(Scope.SESSION, "urn:ibm:security:asf:response:token:attributes", attrName, attrValue); }}function getPrincipalNameFromSAN(san) { let result = null; if (san != null && san["array"] != null && san["array"].length > 0) { for (let i = 0; i < san["array"].length && result == null; i++) { let aObj = san["array"][i]; if (aObj["other"] != null && aObj["other"]["oid"] == "1.3.6.1.4.1.311.20.2.3" && aObj["other"]["value"] != null && aObj["other"]["value"]["utf8str"] != null && aObj["other"]["value"]["utf8str"]["str"] != null) { result = aObj["other"]["value"]["utf8str"]["str"]; } } } return result;}let headerToAttribute = { "cert": "cert", "subjectcn": "SubjectCN", "fingerprint": "fingerprint", "subjectdn": "subjectDN", "issuerdn": "issuerDN", "subjectorganizationalunit": "subjectOU", "alternativednsname": "alternativeDNSName", "alternativeipaddress": "alternativeIPAddress", "alternativeuri": "alternativeURI", "alternativeemail": "alternativeEmail"};let headerMap = {};Object.keys(headerToAttribute).forEach((x) => { let val = getRequestHeader(x); if (val != null) { headerMap[x] = ''+val; }});IDMappingExtUtils.traceString("Entering CertEAI Infomap");// useful traceIDMappingExtUtils.traceString("CertEAI headers: " + JSON.stringify(headerMap));let eaiResult = false;if (headerMap["cert"] != null) { // use the jsrsasign library to parse the x509 cert and extract SAN details let mycert = new X509(); mycert.readCertHex(b64tohex(headerMap["cert"])); let san = mycert.getExtSubjectAltName(); if (san != null) { IDMappingExtUtils.traceString("SAN: " + JSON.stringify(san)); let sanUPN = getPrincipalNameFromSAN(san); if (sanUPN != null) { // login as the username from UPN IDMappingExtUtils.traceString("logging in as: " + sanUPN); addOptionalResponseAttribute("username", sanUPN); addOptionalResponseAttribute("AUTHENTICATION_LEVEL", "1"); addOptionalResponseAttribute("san", JSON.stringify(san)); eaiResult = true; } else { IDMappingExtUtils.traceString("Certificate SAN did not contain valid UPN"); } }} else { IDMappingExtUtils.traceString("Certificate information unavailable");}success.setValue(eaiResult);
The configuration of it in the web reverse proxy is something like this:
[certificate]accept-client-certs = required eai-uri = /mga/sps/authsvc/policy/certeaieai-data = Base64Certificate:cert
That should be plenty to get you started.
Regards,
Shane.
------------------------------
Shane Weeden
IBM
Original Message:
Sent: Fri March 10, 2023 07:46 AM
From: Rakesh Vohra
Subject: User Mapping for LMI Certificate authentication
Hello,
To address one of the STIG findings, we need to enable PIV authentication for Security Verify Access LMI. We need to map one of the attributes from the certificate to the user id in LDAP. Has anyone written a user map function for this? The documentation gives a simple example of extracting 'cn' attribute and combining it with the baseDN. I want to list all available attributes and then determine which one can be used to generate the resultant LDAP dn. Also, I tried to place a System.out.println in the code, but that did not work-any thoughts on what functions I can use to log some messages?
Thanks,
Rakesh
------------------------------
Rakesh Vohra
Great Falls VA
2405683495
------------------------------