FAPI - MTLS and Certificate Bound Tokens
FAPI specs require that Verify Access supports [OAUTB] or [MTLS] as a hold of key mechanism.
This specification requires clients to authenticate to token endpoint or resource endpoint with a client certificate. The authenticated client certificate is then bound to tokens (access_token, refresh_token, and code) generated for the client.
FAPI- Certificate Authentication and MTLS
To handle Client Cert Authentication according to FAPI Specs, Cert-EAI can be used. The following are steps to configure this.
When there is an issue with certificate bound token and an error is thrown. WebSEAL returns a stepuplogin error. In FAPI, this error is a 401 Unauthorized or Invalid_request. To achieve this error on IBM Security Verify Access, create an error file (Select Reverse Proxy > Manage > Management Root) . Under management/C/, create stepuplogin.401.json. This returns a 401 error. The following is a sample content of the file:
- Create Authentication Mechanism (available by default from IBM Security Verify Access version 10.0.0)
/authsvc/authenticator/Infomap/cert_mismatch.json
- Create an InfoMap Authentication Mechanism with FAPI_CertEAI as Mapping rule and cert_mismatch.json as template page.
- Create an Authentication Policy with the authentication mechanism that is created from the previous step.
{ "Error": "Certificate Mismatch" <% templateContext.response.setHeader("am-eai-flags", "stream"); templateContext.response.setHeader("Content-Type", "application/json"); templateContext.response.setStatus(401); %> }
- Configure CertEAI in WebSEAL
- Configure CertEAI to call authentication policy that is created in Create Authentication Mechanism (available by default from IBM Security Verify Access version 10.0.0) and pass the certificate data. See the following example:
[certificate] accept-client-certs = optional eai-uri = junction+"/sps/authsvc?PolicyId=urn:ibm:security:authentication:asf:fapi_CertAuth" eai-data = Base64Certificate:cert eai-data = SubjectCN:SubjectCN eai-data = Fingerprint:FingerprintClient Certificate must be added to WebSEAL’s SSL DB (pdsrv) and ensure that junction has an appropriate value- FAPI Certificate Authentication EAI
- FAPI_CertEAI, the infomap used in FAPI Authentication Mechanism, checks if certificate is bound to token matches the certificate in incoming request.
- FAPI_CertEAI
importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.IDMappingExtUtils); importClass(Packages.com.ibm.security.access.user.UserLookupHelper); importPackage(Packages.com.ibm.security.access.httpclient); importClass(Packages.com.tivoli.am.fim.trustserver.sts.utilities.OAuthMappingExtUtils); IDMappingExtUtils.traceString("Entering FAPI Infomap"); var cert = context.get(Scope.REQUEST, "urn:ibm:security:asf:request:header", "cert"); var subjectCN = context.get(Scope.REQUEST, "urn:ibm:security:asf:request:header", "SubjectCN"); var fingerprint = context.get(Scope.REQUEST, "urn:ibm:security:asf:request:header", "Fingerprint"); if (cert != null && subjectCN != null && fingerprint!= null){ // useful trace IDMappingExtUtils.traceString(" cert: " + cert); IDMappingExtUtils.traceString(" SubjectCN: " + subjectCN); IDMappingExtUtils.traceString(" fingerprint: " + fingerprint); var auth_header = context.get(Scope.REQUEST, "urn:ibm:security:asf:request:header", "Authorization"); //Check if there is a authorization header if(auth_header != null){ var array = auth_header.split(" "); if (array[0].equals("Bearer")){ var token = array[1]; //Introspect the access_token var tkn = OAuthMappingExtUtils.getToken(token) var cnf = OAuthMappingExtUtils.getAssociation(tkn.getStateId(), "cnf") IDMappingExtUtils.traceString("cnf:: [" + cnf + "]"); if (cnf != fingerprint){ IDMappingExtUtils.traceString("MISMATCHED CERTS"); success.setValue(false); } else{ var iv_user_l = context.get(Scope.REQUEST, "urn:ibm:security:asf:request:header", "iv-user-l"); context.set(Scope.SESSION, "urn:ibm:security:asf:response:token:attributes", "username", subjectCN); context.set(Scope.SESSION, "urn:ibm:security:asf:response:token:attributes", "cert", cert); context.set(Scope.SESSION, "urn:ibm:security:asf:response:token:attributes", "fingerprint", fingerprint); success.setValue(true); } } } else{ var iv_user_l = context.get(Scope.REQUEST, "urn:ibm:security:asf:request:header", "iv-user-l"); context.set(Scope.SESSION, "urn:ibm:security:asf:response:token:attributes", "username", subjectCN); context.set(Scope.SESSION, "urn:ibm:security:asf:response:token:attributes", "cert", cert); context.set(Scope.SESSION, "urn:ibm:security:asf:response:token:attributes", "fingerprint", fingerprint); success.setValue(true); }}else{ IDMappingExtUtils.traceString("Certificate information unavailable"); success.setValue(false); }
Certificate bound token can be achieved by associating token’s state ID to the certificate thumbprint upon token creation in oauth post_token mapping rule. When these tokens are used again at resource endpoint, a check is done in the pre_token mapping rule to retrieve the associated thumbprint and check if it matches the incoming certificate thumbprint of the client. If it matches, access is allowed to the resource. If it does not match, an error is thrown. See the following example:
{ "error_code" : " Unauthorized " "error_message" : " Client Certificate Mismatch. This resource can only be access by an authorized user. " }FAPI specs requires resource endpoint to be protected by mtls. This can be done by enforcing a ‘ext-auth-interface’ authentication level using a Protected Object Policy (POP).[session] require-mpa = yes
Set require-mpa to 'yes' means that HTTP headers are not valid session keys or authentication tokens unless received through an MPA. In FAPI, this functionality is used to ensure each token and the cert information are build as one unique session without any form of session caching.
FAPI- Certificate Bound Token
At the resource endpoint, a check is done in the pre_token mapping rule to retrieve the associated thumbprint and checks if the thumbprint matches the incoming certificate thumbprint of the client. If the thumbprint matches, access is allowed to the resource. If the thumbprint does not match, an error is thrown. See the following example:
- oauth20 pre_token mapping rule
/*Cert Bound Tokens*/ if (request_type == "resource"){ var incoming_thumbprint = stsuu.getContextAttributes().getAttributeValueByName("tagvalue_x509fingerprint"); var incoming_access_token = stsuu.getContextAttributes().getAttributeValueByName("access_token"); var incoming_refresh_token = stsuu.getContextAttributes().getAttributeValueByName("refresh_token"); var incoming_code = stsuu.getContextAttributes().getAttributeValueByName("code"); if (incoming_access_token != null && incoming_thumbprint != null){ var state_id = OAuthMappingExtUtils.getToken(incoming_access_token).getStateId(); var original_thumbprint = OAuthMappingExtUtils.getAssociation(state_id, "cnf"); if (original_thumbprint != incoming_thumbprint){ OAuthMappingExtUtils.throwSTSCustomUserPageException("INCOMING MTLS client cert does not match access_token client's cert",400,"invalid_request"); } }else if (incoming_refresh_token != null && incoming_thumbprint != null){ var state_id = OAuthMappingExtUtils.getToken(incoming_refresh_token).getStateId(); var original_thumbprint = OAuthMappingExtUtils.getAssociation(state_id, "cnf"); if (original_thumbprint != incoming_thumbprint){ OAuthMappingExtUtils.throwSTSCustomUserPageException("INCOMING MTLS client cert does not match access_token client's cert",400,"invalid_request"); } }else if (incoming_code != null && incoming_thumbprint != null){ var state_id = OAuthMappingExtUtils.getToken(incoming_code).getStateId(); var original_thumbprint = OAuthMappingExtUtils.getAssociation(state_id, "cnf"); if (original_thumbprint != incoming_thumbprint){ OAuthMappingExtUtils.throwSTSCustomUserPageException("INCOMING MTLS client cert does not match access_token client's cert",400,"invalid_request"); } } } /*Cert Bound Tokens*/
- oauth20 post_token mapping rule
/* FAPI - Cert Bound Tokens */ var client_thumbprintAttribute = stsuu.getAttributeValueByName("tagvalue_x509fingerprint"); if (state_id != null && client_thumbprintAttribute != null){ var result = OAuthMappingExtUtils.associate(state_id, "cnf", client_thumbprintAttribute); } /* FAPI - Cert Bound Tokens */
Parent topic: Achieving Financial-grade API (FAPI) conformance with IBM Security Verify Access