javaaccess-controljaasaccesscontrolexception

AccessControlException when invoking method using JAAS


I have built a client/server app with JAAS. The login seems to work OK, as logins are successful. It is when trying to grant permissions to specific methods that the AccessController starts casting AccessControlException.

 java.security.AccessControlException: access denied ("myPackage.CustomPermission" "someMethod")

The permission-class:

public class CustomPermission extends BasicPermission {

    public CustomPermission(String name) {
        super(name);
    }

    public CustomPermission(String name, String method) {
        super(name, method);
    }
}

I have a custom Principal-class as well:

public class CustomPrincipal implements Principal {
    private String name;

    public CustomPrincipal(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

When logging in, a user provides a username and a password. In the DB the usernames and passwords have a "user level" associated to them. These user levels are used as principal-names, added to the Subject created when logging in. I add it when handling the commit-method in the LoginModule:

public boolean commit() throws LoginException {
    if(status == ConfigValues.NOT || subject == null)
        return false;

    principal = new CustomPrincipal(userlvl);

    if(subject.getPrincipals().add(principal)) {

        username = null;
        password = null;

        status = ConfigValues.COMMIT;

        return true;
    }

    return false;
}

For the sake of it, this is how I instantiate the login-procedure:

LoginContext lc = new LoginContext("MyLoginModule", new RemoteCallbackHandler(username, password));

lc.login();

new ServerImpl(lc.getSubject());

The Subject is then used in a "proxy" server implementation to check for permissions, like this (user = lc.getSubject):

public String someMethod() throws RemoteException, PrivilegedActionException {
    return Subject.doAs(user, new PrivilegedExceptionAction<String>() {
        @Override
        public String run() throws PrivilegedActionException, RemoteException, ServerNotActiveException {
            AccessController.checkPermission(new CustomPermission("someMethod"));
            return realServerImplObj.someMethod();
        }
    });
}

The .policy-file:

grant codeBase "file:./bin/-" Principal myPackage.CustomPrincipal "user" {
    permission myPackage.CustomPermission "someMethod";
};

The "user" is of course one of the user levels you could login with.

I've tried to add some extra grants, like:

grant codeBase "file:./bin/-" {
    permission javax.security.auth.AuthPermission "createLoginContext.MyLoginModule";
    permission javax.security.auth.AuthPermission "doAs";
};

I have a config for the LoginModule as well:

MyLoginModule {
    myPackage.MyLoginModule required debug=true;
};

I set the properties before all this, of course: edit: the files are located in the root of the project

System.setProperty("java.security.auth.login.config", "file:./MyConfig.config");
System.setProperty("java.security.policy", "file:./MyPolicy.policy");

The server is run with the -Djava.security.manager argument. The client does not use any arguments, nor any config- or policy-files.

I've tried to remove the codeBase to see if my path was wrong. If I add permissions java.util.AllPermissions, then all is fine (but... of course it's not fine, as this is definitely not as intended). What am doing wrong here? I guess it's a combination of the Principal-, Permission, .policiy- and LoginModule-implementation.

EDIT It is when the AccessController.checkPermissions(...) is called in the "proxy" server implementation that the exception is thrown.

EDIT 2 I have tried with the following edit of the code:

return AccessController.doPrivileged(new PrivilegedExceptionAction<String>() {
    @Override
    public String run() throws PrivilegedActionException, RemoteException, ServerNotActiveException {
        //AccessController.checkPermission(new CustomPermission("someMethod"));
        return realServerImplObj.someMethod();
    }
});

Note that I've changed Subject.doAs(user, new Privile.... to AccessController.doPrivileged(new Privilege. The user is no longer parsed in the parameter, and we use the static method AccessController.doPrivileged, rather than the Subject.doAs. Another note: //AccessController.checkPermission(new CustomPer... has been commented.

But now everyone can invoke any method, as we actually never check for permissions. It is as if the AccessController never is aware of the permissions granted in the .policy-file.

EDIT 3 It seemed to be an issue with the implementation of the Principal that was the issue.

Just for clarification, these are the edits that have been made:

@Override
public String someMethod() throws PrivilegedActionException {
    return Subject.doAsPrivileged(user, new PrivilegedExceptionAction<String>() {
        @Override
        public String run() throws PrivilegedActionException, RemoteException, ServerNotActiveException {
            AccessController.checkPermission(new CustomPermission("someMethod"));
            return realServerImplObj.someMethod();
        }
    }, null);
}

Difference is the return Subject.doAsPrivileged(user, ... , null);. Note the null at the end as well.

Implemented two methods in the CustomPrincipal-class, #equals(Object) and #hashCode(). See here for a general-purpose example of both methods, and a sample Principal implementation in general.

Also added (even though it seems to be running without, actually) the following the the .policy-file

grant codeBase "file:./bin/-" {
    permission javax.security.auth.AuthPermission "createLoginContext.MyLoginModule";
    permission javax.security.auth.AuthPermission "doAs";
    permission javax.security.auth.AuthPermission "doAsPrivileged";
};

permission javax.security.auth.AuthPermission "doAsPrivileged"; is the newly added entry.


Solution

  • Subject#doAs vs Subject#doAsPrivileged

    The default authorization algorithm employed by the AccessController is based on Permission intersection: If all of an AccessControlContext's ProtectionDomains, potentially combined with a Subject's Principals, have, statically and/or as per the Policy in effect, the Permission being checked, the evaluation succeeds; otherwise it fails.

    Subject#doAs does not work in your case because your Permission is granted to the combination of your ProtectionDomain and your Principal, but not to the domain itself. Specifically, at the time of the AccessController#checkPermission(customPermission) invocation, the effective AccessControlContext included following relevant (as far as Permission evaluation is concerned) frames:

    Frame # | ProtectionDomain          | Permissions
    --------+---------------------------+---------------------------------------------
     2      | "file:./bin/"             | { CustomPermission("someMethod"),
            | + CustomPrincipal("user") |   permissions statically assigned by default
            |                           |   by the ClassLoader }
    --------+---------------------------+---------------------------------------------
     1      | "file:./bin/"             | { AuthPermission(
            |                           |   "createLoginContext.MyLoginModule"),
            |                           |   AuthPermission("doAs"), default as above }
    --------+---------------------------+---------------------------------------------
    

    The intersection of those frames' permissions does of course not include the desired CustomPermission.

    Subject#doAsPrivileged, when given a null AccessControlContext, on the other hand, does the trick, because it "trims" the effective context's stack to its top-most frame, i.e., the one from which doAsPrivileged gets invoked. What actually happens is that the null (blank) context gets treated by the AccessController as if it were a context whose permission evaluation yields AllPermission; in other words:

    AllPermissionpermissionsframe2 = { CustomPermission("someMethod"), default ones } ,

    which is (save for the minimal set of seemingly extraneous statically-assigned Permnissions) the desired outcome.

    Of course, in cases where such potentially arbitrary privilege escalation is undesired, a custom context, whose encapsulated domains' permissions express the maximum set of privileges you are willing to grant (to e.g. some Subject), can be passed to doAsPrivileged instead of the null one.

    Why is the Principal implementation forced to override equals and hashCode?

    The following stack trace snippet illustrates why:

    at java.lang.Thread.dumpStack(Thread.java:1329)
    at com.foo.bar.PrincipalImpl.equals(PrincipalImpl.java:53)
    at javax.security.auth.Subject$SecureSet.contains(Subject.java:1201)
    at java.util.Collections$SynchronizedCollection.contains(Collections.java:2021)
    at java.security.Principal.implies(Principal.java:92)
    at sun.security.provider.PolicyFile.addPermissions(PolicyFile.java:1374)
    at sun.security.provider.PolicyFile.getPermissions(PolicyFile.java:1228)
    at sun.security.provider.PolicyFile.getPermissions(PolicyFile.java:1191)
    at sun.security.provider.PolicyFile.getPermissions(PolicyFile.java:1132)
    at sun.security.provider.PolicyFile.implies(PolicyFile.java:1086)
    at java.security.ProtectionDomain.implies(ProtectionDomain.java:281)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:450)
    at java.security.AccessController.checkPermission(AccessController.java:884)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
    ...
    

    Further reading: