ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Java vs. .NET Security, Part 4
Pages: 1, 2, 3, 4, 5

User Authentication: Impersonation

In addition to the direct login, an application can impersonate the logged-in user while performing sensitive operations locally, or delegate his identity when communicating with remote servers. This way, the execution will happen in the context of the logged-in user, with privileges granted to the user's identity and not those of the application.



.NET's impersonation has a distinctive feature of being in a close relationship with the Windows process' identity. Turning on impersonation in an ASP.NET configuration will result in ASP.NET's Windows execution thread borrowing the security token of the calling IIS process. So, as far as Windows security system is concerned, ASP.NET Windows thread's identity will be the same as that of the IIS thread, although CLR identity may be completely different, as has been explained before.

Sample ASP.NET configurations are listed below.

  • Impersonating the IIS calling identity in ASP.NET process, and synchronizing CLR identity. This will result in all three threads (IIS and ASP.NET Windows, and CLR-managed) to share the same Windows identity, authenticated by IIS, or IUSR_<MACHINE> for anonymous users.

    <authentication mode="Windows">
          </authentication>
    <identity impersonate="true"/>
  • Impersonating the IIS calling identity in ASP.NET process, and using separate CLR identity (or synchronizing in code): Note that the code should have a proper CAS permission to modify the principal. This will result in IIS and ASP.NET Windows threads sharing the same Windows identity, and CLR -- having an empty one initially.

    <authentication mode="None">
          </authentication>
    <identity impersonate="true"/>
    
    // Providing a generic identity
    <%
    Identity clrIdentity = 
          new GenericIdentity("CLRUser");
    String[] roles = {"PowerUser"};
    GenericPrincipal clrPrincipal = 
           new GenericPrincipal(clrIdentity, roles);
    Thread.CurrentPrincipal = clrPrincipal;
    %>

When knowing account's credentials, it is possible to impersonate not only the currently logged-in user, but also an arbitrary user. WindowsIdentity has an overloaded constructor that accepts a Windows account token. That token can be retrieved by making an unmanaged call to the Windows function LogonUser (permissions permitting, of course).

// Impersonating a logged-in Windows user
<%
WindowsIdentity id = 
        new WindowsIdentity(userTokenHandle);
WindowsImpersonationContext ctxt = 
                            id.Impersonate();
...
ctxt.Undo();
%>

An important point to remember about Windows impersonation is that it was designed for use with trusted code only -- any unmanaged DLL down the call chain can call RevertToSelf and start using IIS process identity, which will most likely be System. Although managed code is a subject to CAS permission checks, which generally deny the corresponding security permissions to most applications, it does not apply to the locally installed code, which has FullTrust under the default policy.

Among .NET's authentication options, delegation is currently supported only by the Kerberos protocol, which is used only with WindowsIdentity for now. In order to use a Windows identity for delegation, the impersonated user's Windows account should be granted the right "Act as a part of OS" by the administrator; this is not given on a general basis.

Java uses JAAS to implement impersonation on the application level. JAAS' Subject class allows executing a particular sensitive operation (marked so by implementing the java.security.PrivilegedAction interface) as another user's identity.

public final class Subject { 
  ... 
  // associate the subject with the current 
  // AccessControlContext and 
  // execute the Privileged action 
  public static Object doAs(Subject s,
       java.security.PrivilegedAction action);
}

When this operation is run, the doAs method associates the impersonated subject with the current AccessControlContext, and then executes the action. At the end of the doAs call, the subject is removed from the AccessControlContext:

//class representing a protected operation
class ProtectedOperation 
                 implements PrivilegedAction {
  //do something veeeery sensitive here...
  public Object run();
}

public class ImpersonationExample {
  public static void main(String args[]) {
    ...
    //carry out the authentication process
    Subject subject = loginContext.getSubject();
    //run as the impersonated user
    Subject.doAs(subject, 
                 new ProtectedOperation());
  }
}

In the default JDK implementation, Java impersonation is limited to application level only -- specifications do not define any relationship to user accounts on the underlying OS. Specific vendor implementations can implement functionality that maps the logged-in user to the OS domain names. For instance, WebLogic, if configured, can use NT PAM to authenticate users against Windows account names.

However, GSS-API, in combination with JAAS, can handle both impersonation and delegation, as shown in Figure 6 below.

  • Impersonation is handled by creating a new Subject, using the name obtained from the call context, and associating it with the current thread. No credentials are obtained this way, so it will not be possible to delegate the user identity to some other service.

    GSSName name = context.getSrcName();
      Subject newSubject = 
        com.sun.security.jgss.GSSUtil.createSubject(
                                           name,null);
      //set the execution subject and call doAs
      ...
  • Delegation requires obtaining user credentials, in addition to the username, which requires cooperation from the client -- he should authorize credentials delegation to enable this mode. This would be usually done together with mutual authentication, to verify the server's identity as well. Once the server obtains client's Kerberos Ticket (TGT), it can represent the client in calls to remote services, in addition to performing local operations. The server's actions are controlled by two Kerberos-specific permissions: ServicePermission and DelegationPermission.

    // client allows using its credentials 
    // with mutual authentication
    GSSManager manager = GSSManager.getInstance();
    GSSContext contextClient = 
    		manager.createContext(serverName,krb5Oid,
    null,GSSContext.DEFAUL_LIFETIME);
    contextClient.requestMutualAuth(true);
    contextClient.requestCredDeleg(true);
    ...
    
    //server obtains client's credentials
    if (contextServer.getCredDelegState()) {
      GSSCredential credClient = 
                    contextServer.getDelegCr();
      //use the credentials to act as a client
      GSSContext contextDelegate = 
        manager.createContext(backendName,krb5Oid,
      credClient,GSSContext.DEFAUL_LIFETIME);
    }

Figure 6
Figure 6. GSS/JAAS Authentication

It is possible to configure the Kerberos provider to use an existing credentials cache so that the login happens completely transparently.

// client JAAS configuration for GSS-API
com.sun.security.jgss.initiate {
  com.sun.security.auth.module.Krb5LoginModule 
                                      required;
};

// server JAAS configuration for GSS-API
com.sun.security.jgss.accept {
  com.sun.security.auth.module.Krb5LoginModule 
                                      required
                  useKeyTab=true storeKey=true 
                          principal="nfs/host";
};

// default configuration for GSS-API 
// if the above is not present
other {
...
}

Note: .NET provides good support for impersonation on Windows-only networks, but delegation across the Internet is not possible. Java can do application-level impersonation and is capable of supporting delegation across the Internet.

User Access Security: Basic

Once a distinguished principal has been identified as a result of the authentication step and attached to the call context (usually associated with threads), it can be used in determining resource access rights. In role-based systems, application code may operate not only with specific principals, but also with their abstract roles, which results in a more flexible system configuration. So after establishing a principal, the server goes through an additional step of mapping it to the possible application roles.

Each executing .NET thread has an associated CallContext, which carries around the Principal and his Identity -- they are either copied from the creating thread, or created anew by the CLR when code tries to access them for the first time.

WindowsPrincipal principal = 
  (WindowsPrincipal) Thread.CurrentPrincipal;
WindowsIdentity identity = 
  WindowsIdentity.GetCurrent();

A configurable policy governs the type of principal created by default: NoPrincipal, UnauthenticatedPrincipal, or WindowsPrincipal. An application thatis granted the appropriate SecurityPermission can set this policy imperatively:

AppDomain.CurrentDomain.SetPrincipalPolicy(
  PrincipalPolicy.WindowsPrincipal);

An application that possesses a proper SecurityPermission to control the principal can replace the current thread's principal. However, this permission is not required for normal role-based checks:

GenericIdentity id = new GenericIdentity("user");
String[] roles = {"Manager","User"};
GenericPrincipal pr = new GenericPrincipal(
                                      id,roles);
Thread.CurrentPrincipal = pr;

To provide more consistent security architecture, .NET incorporates role-based security into code-access hierarchy by providing a PrincipalPermission, available for both declarative and imperative checks. Checks can be performed by name, role, or a combination of both.

[PrincipalPermissionAttribute(
    SecurityAction.Demand, Role = "PowerUser")]

Optionally, principal permission objects can be combined in code (but not declaratively!) to support checking several identity/roles at once:

PrincipalPermission perm1 = 
   new PrincipalPermission("John","Admin");
PrincipalPermission perm2 = 
   new PrincipalPermission("PowerUser");
(perm2.Union(perm1)).Demand();

Finally, ordinary checks for user names and role can be performed in code by directly accessing the IPrincipal object:

Principal principal = Thread.CurrentPrincipal;
if (principal.IsInRole("Admin"))
{
  //do something for Admin
} else if (principal.Identity.Name == "John")
{
  //do something for John
}

An application demonstrating the user access checks in .NET can be downloaded from here.

It is important to realize that .NET policy cannot extend the final permission set granted to the assembly, based on user's identity. In other words, if an assembly A is granted (as a result of policy evaluation) permission set PA, the same will be granted happen for any user executing this assembly. This set can be further restricted based on the results of role and user checks. This is in contrast to the way most modern operating systems, including Windows, work -- a user is granted certain additional privileges based on his identity or group membership.

In Java, JAAS grants permissions based on user identity, as defined by name, as opposed to the pure policy-based model, which grants the permissions based on the code's origin. Declarative security is set through the java.policy file -- JAAS adds Principal entries to the Java policy. As an important difference from the .NET model, JAAS' principal-based model can extend the permission set granted to a module. In the example below, the code, signed by "MyPublisher", is granted write permissions to "C:\" only if it is executed by "user":

grant Signedby "MyPublisher" {
	permission java.io.FilePermission "c:\","read";
}

//an example of grant by username
grant Principal com.comp.PrincipalClass "user" {
	permission java.io.FilePermission "c:\","write";
}

Principal-based access policy enforcement is performed using PrivilegedAction and impersonation. As explained in Part 3, running this class effectively asserts all privileges granted to the code, including those based on the current Principal. Technically, after doAs has been called with an impersonated Subject, java.lang.SecurityManager updates the current AccessControlContext from the policy file, adding permissions for the impersonated user. An internal JAAS implementation of java.security.DomainCombiner is responsible for instructing the installed SecurityManager to query JAAS policy and update the AccessControlContext. In the server environment, which concurrently handles multiple calls, it is important to use doAsPrivileged and pass it null AccessControlContex to force policy re-evaluation by the Combiner and to create a new context customized for the user, instead of borrowing the server's existing one. At the security checkpoints during the execution, the total granted permission set now includes code-based and Principal-based application permissions.

This application demonstrates the effects of dynamic policy evaluation in JAAS.

There is no notion of roles in the JAAS hierarchy; everything is determined by usernames. Although it is not very convenient, roles and groups may be treated as named principals, and access control may be imposed on them in the same way. Moreover, since a Subject may contain any number of Principals, objects representing role(s) can be added to its Principal collection. Later the Subject's roles may be retrieved by requesting principals of only a particular class, which denotes a particular role. To build application name-based role hierarchies, JAAS defines the com.sun.security.auth.PrincipalComparator interface, which may be implemented by the Principal classes specified in the policy's grant entries. The PrincipalComparator.implies method should return true when the specified Subject is in a particular role:

// an example of role-based entry
grant Principal com.MySite.AppRole "PowerUser" {
  permission java.io.FilePermission "c:\","read";
}

// this class is used for building role hierarchy
public class AppRole implements 
                      PrincipalComparator {
  // the role this object represents
  public AppRole(String role) {...}

  //this method checks the Subject 
  //for being in the role
  public boolean implies(Subject currSubject) {
    ...
  }
}

Subjects are assigned by JAAS to the current thread's execution context, and are available for examining directly from the code -- therefore, programmatic security checks can also be based on principal names, as obtained from the current execution context.

AccessControlContext ctxt = 
                     AccessController.getContext();
Subject subj = Subject.getContext(ctxt);
if (subj == null) {
  //no authenticated user
} else {
  Set principalsSet = subj.getPrincipals();
  Iterator iter = principalsSet.iterator();
  while(iter.hasNext()) {
    MyPrincipalClass princ = 
      (MyPrincipalClass)iter.next();
    if (princ.getName().equals("MyUser")) {
      // have an authenticated user
    }
  }
}

Note: .NET has a very convenient, permission-based user access system. However, it can only restrict the total permission set for an assembly, never extend it. JAAS makes use of dynamic policies in Java to extend a granted permission set with user-specific permissions.

Pages: 1, 2, 3, 4, 5

Next Pagearrow