Wednesday, May 4, 2016

Java EE's mysterious message policy

Users of Java EE authentication (JASPIC) may have noticed that the initialize method of a SAM takes two parameters of type MessagePolicy. But what are these parameters used for? In this article we'll take a somewhat deeper look.

In practice, the overwhelming majority of SAMs only seem to use this MessagePolicy in one way; completely ignore it. As such, there aren't many if any examples available that demonstrate how these passed in policies should actually be enforced.

The JASPIC spec isn't quite clear about this either. It does seem to say in a somewhat cryptic way that the isMandatory method is an alias for the "javax.security.auth.message.MessagePolicy.isMandatory" entry in the MessageInfo map, and that the ProtectionPolicy of the TargetPolicy of the MessagePolicy must be ProtectionPolicy.AUTHENTICATE_SENDER and/or ProtectionPolicy.AUTHENTICATE_CONTENT.

Pretty much the only example we have in code that at least references the MessagePolicy type is within the Java EE reference implementation; GlassFish. Although GlassFish doesn't use JASPIC for the Servlet defined authentication mechanisms (FORM, BASIC, ...), it does uses JASPIC for the authentication mechanism protecting its build-in admin console. This is configured in domain.xml as follows:

<message-security-config auth-layer="HttpServlet">
  <provider-config provider-type="server" 
    provider-id="GFConsoleAuthModule"
    class-name="org.glassfish.admingui.common.security.AdminConsoleAuthModule">
    <request-policy auth-source="sender"/>
    <response-policy/>
    <property name="loginPage" value="/login.jsf"/>
    <property name="loginErrorPage" value="/loginError.jsf"/>
  </provider-config>
</message-security-config>

Unfortunately, despite a minimal request policy being configured here, the actual implementation of AdminConsoleAuthModule does the same thing that basically all other SAMs do: ignore it.

For another potential hint, let's see what the RI actually does internally with the message policy before it passes it to a SAM. For this I started at the entry point of a SAM, when the runtime calls the validateRequest method of the encapsulating context. In order to make things readable for this article, I removed all alternative branches in the code (mostly permutations of client/server modules and new/old modules (new = jaspic, old = the GlassFish proprietary predecessor of JASPIC) and Servlet/SOAP). With those branches removed and flattening the code found in many helper methods and contained objects, it looks as follows:

// Check if the resource is secured and put as string in Map
isMandatory = !webSecMgr.permitAll(req);
if (isMandatory || calledFromAuthenticate) {
   messageInfo.getMap().put(IS_MANDATORY, TRUE.toString());
}

// Get “true” / “false” string from map, convert it to Boolean and then to string again
String isMandatoryStr = messageInfo.getMap().get(IS_MANDATORY);
String authContextID = Boolean.valueOf(isMandatoryStr).toString();

// Convert once again to Boolean, and set to MANDATORY or OPTIONAL policy
messagePolicy = Boolean.valueOf(authContextID)? 
    new MessagePolicy[] { MANDATORY_POLICY, null } : // response policy always null
    new MessagePolicy[] { OPTIONAL_POLICY, null }

IDEntry idEntry = configMap.get(“HttpServlet”).idMap.get(providerID);

// Set the definite request policy, but messagePolicy[0] is never null here
// for the “HttpServlet” layer
MessagePolicy requestPolicy = (messagePolicy[0] != null || messagePolicy[1] != null)?
    messagePolicy[0] :      // will always be chosen here 
    idEntry.requestPolicy;  // the policy as parsed from domain.xml, always unused 
   
Entry entry = new Entry(idEntry.moduleClassName, requestPolicy, idEntry.options);

// Pass the message policies into the SAM instance
ServerAuthModule sam = entry.newInstance();
sam.initialize(
    entry.getRequestPolicy(),
    entry.getResponsePolicy(), handler, map);

ModuleInfo moduleInfo = ModuleInfo(sam, map);

ServerAuthModule moduleObj = moduleInfo.getModule();
Map moduleMap = moduleInfo.getMap();
    
ServerAuthContext serverAuthContext = new GFServerAuthContext(moduleObj, moduleMap);

// Invoke the context, which on its turn will invoke the SAM we just initialized 
AuthStatus authStatus = serverAuthContext.validateRequest(messageInfo, subject, null);

As can be seen, despite there being a path for setting the parsed request policy from domain.xml (the idEntry.requestPolicy), the code always chooses between one of two fixed policies; MANDATORY_POLICY or OPTIONAL_POLICY, depending on the same IS_MANDATORY value ("javax.security.auth.message.MessagePolicy.isMandatory") that is put in the message info map.

Those two fixed policies are set as static final variables as follows:

private static final MessagePolicy MANDATORY_POLICY =
    getMessagePolicy(true);
    
private static final MessagePolicy OPTIONAL_POLICY =
    getMessagePolicy(false);
Where the getMessagePolicy() method has the following relevant content (code branches that were never taken have been cut out again):
public static MessagePolicy getMessagePolicy (boolean mandatory) {
        
    List<TargetPolicy> targetPolicies = new ArrayList<TargetPolicy>();
       
    targetPolicies.add(new TargetPolicy(
        null, // No Target
        new ProtectionPolicy() {
            public String getID() {
                return ProtectionPolicy.AUTHENTICATE_SENDER;
            } 
        })
    );
         
    return new MessagePolicy(
        targetPolicies.toArray(new TargetPolicy[targetPolicies.size()]),
        mandatory);
}

Conclusion

The conclusion seems to be, assuming that the RI code is as spec compliant as we can get, that MessagePolicy#isMandatory() is just an alternative for checking the message info map, while the TargetPolicy, Target and ProtectionPolicy are essentially useless for a Servlet Container Profile SAM, since at least for the RI they always have the same constant value. It has to be noted that if a SAM is registered programmatically, the entire initialization of the SAM is under the application's control and the behavior of the server doesn't apply.

Speculating, it might be the case that AUTHENTICATE_SENDER simply means "do what a SAM is supposed to do in validateRequest()" (e.g. authenticating callers), while the potential alternative or additional AUTHENTICATE_CONTENT means "do what a SAM is supposed to do in secureResponse()" (e.g. encrypting the response). If that interpretation would indeed be correct, one may see these message policies as standard switches (properties) for these two methods. E.g. a message policy with AUTHENTICATE_CONTENT and isMandatory() returning true would then mean that the SAM *must* encrypt the response. This latter thing however seems to be just as rare in practice as actually looking at the message policy is.

A quick glimpse at the SOAP Profile, and some of the implementation code in the RI, revealed that the entire message policy concept may have some more use over there. As JASPIC was originally designed with many potential other profiles in mind (e.g. for EJB, JMS, etc), it could also well be that the concept was designed for a future that just never came to be.

Arjan Tijms

1 comment:

  1. MessagePolicy et al. indeed appear to make more sense in the SOAP / WS-* universe; generally in use cases where actual message-level security is required. Speculating myself, XMLDSig / XMLEnc was what came to mind when I first took notice of those JASPIC interfaces; the policies passed to the SAM during its initialization could e.g. prescribe different levels of confidentiality / integrity (expressed as MessagePolicy.ProtectionPolicy's) for different portions (e.g. XML subtrees) of the message's payload (represented by MessagePolicy.Target's), based on which, respectively, a SAM would know which data to decrypt or verify the signature of (during validateRequest) and/or encrypt and sign (during secureResponse). Stateful MessageInfo's or callbacks for maintaining container-level authentication state--as mentioned in B.6 of the spec--would be another neat feature to have when dealing with cases where message correlation is necessary. Anyways, lots of possibilities to daydream of... (un)fortunately the actual set of network protocols supported by Java EE is limited, so that's sort of the point where one is forced to return to reality. :) But who knows--another framework might one day leverage JASPIC's capabilities at a much higher degree than Java EE has to date been able to. Thanks for another interesting article!

    ReplyDelete