The previous tutorial, Use of JAAS Login Utility and Java GSS-API for Secure Message Exchanges, demonstrated how two applications, in particular a client and a server, could use the Java GSS-API to establish a secure context between them and then securely exchange messages.
There are additional operations the context acceptor (the server in our client/server example) can perform once the context has been established with the context initiator (the client). Basically, the server can "impersonate" the client. The level of impersonation depends upon whether or not the client has delegated credentials to the server.
One possible type of client impersonation the server can do is causing code to be executed on behalf of the same entity (user) the client code is executed on behalf of. Normally, a method executed by a thread uses the access control settings for that thread itself. However, when impersonating a client in this tutorial, the server uses the client's access control settings so that the server has access to exactly those resources that the client itself has when it runs.
One major benefit of the approach used in this tutorial is that the JAAS authorization component can be used for access control. Without the JAAS authorization component, the server principal would need access to any resources accessed by the code executed on behalf of the client user, and the server code would need to include access control logic to determine whether the user was authorized to access such resources. By utilizing JAAS authorization, providing principal-based access control, the access control is handled automatically. Permissions for the security-sensitive operations in such code only need to be granted to that user and not also to the server code. See the JAAS Authorization tutorial for more information on JAAS authorization.
How does the server "impersonate" the client to execute code on behalf of the user running the client code? Essentially the same way the client code is set up to be run on behalf of that user. All the server code needs to know is the user's principal name, which it can obtain from the context established with the client.
Recall that JAAS authentication of the user executing the client
code results in creation of a Subject containing a Principal with
the user (principal) name. The Subject is subsequently associated
with a new access control context (via a
Subject.doAsPrivileged
call from the Login utility)
and the client code is considered to be executed on behalf of the
user; subsequent access control decisions are based on whether or
not that particular user, executing the client code, is granted the
required permissions.
The server code is similarly handled, except in that case the
Principal specified for authentication is typically a "service
principal", not a user principal. Again, a Subject containing a
Principal with the specified principal name is created,
Subject.doAsPrivileged
is called, and the server code
is considered to be executed on behalf of the specified principal;
subsequent access control decisions are based on whether or not
that particular principal, executing the server code, is granted
the required permissions.
Once the client and server have established a mutual context, the context initiator's name (the client's principal name) can be determined by the following:
GSSName clientGSSName = context.getSrcName();
The context acceptor (the server) can use this name to construct a Subject containing a Principal that represents the same entity. For example, if you are using a JRE from Sun Microsystems, you can construct such a Subject via the following:
Subject client = com.sun.security.jgss.GSSUtil.createSubject(clientGSSName, null);
The
createSubject method creates a new Subject from the GSSName and
GSSCredential specified as arguments. If the server code is just
going to execute code on behalf of the user in the local JVM, the
user's credentials are not required - and in fact cannot even be
obtained unless the client has delegated credentials to the server,
as discussed in Using Credentials Delegated From
the Client. Since the credentials are not needed here, we pass
a null
for the GSSCredential argument.
KerberosPrincipal principal = new KerberosPrincipal(clientGSSName.toString());
Then use this principal to construct a new Subject or populate this principal in the principal set of an existing Subject.
The code that the server would like to execute on behalf of the
user should be initiated from the run
method of a
class that implements java.security.PrivilegedAction
(or java.security.PrivilegedExceptionAction
). That is,
the code can either be in such a run
method or invoked
from such a run
method.
The server code can pass the Subject, along with an instance of
the PrivilegedAction (or PrivilegedExceptionAction), to
Subject.doAsPrivileged
to execute the subsequent code,
starting with the run
method in the PrivilegedAction,
on behalf of the principal (user) in the specified Subject.
For example, suppose the PrivilegedAction class is called ReadFileAction and it takes as an argument a String with the principal name. You can create an instance of this class by
String clientName = clientGSSName.toString(); PrivilegedAction readFile = new ReadFileAction(clientName);
The call to doAsPrivileged
is then
Subject.doAsPrivileged(client, readFile, null);
The following sample code and policy file illustrate the server impersonating the client in order to execute code whose security-sensitive operations are only permitted to be done by the specific user executing the client.
The SampleServerImp.java file
is exactly the same as the SampleServer.java file from the previous
(Use of JAAS Login Utility and Java
GSS-API for Secure Message Exchanges) tutorial, except that
after exchanging messages with the client, it has the following
code to perform a ReadFileAction
as the client
user:
System.out.println("Impersonating client."); /* * Extract the KerberosPrincipal from the client GSSName and * populate it in the principal set of a new Subject. Pass in a * null for credentials since credentials will not be needed. */ GSSName clientGSSName = context.getSrcName(); System.out.println("clientGSSName: " + clientGSSName); Subject client = com.sun.security.jgss.GSSUtil.createSubject(clientGSSName, null); /* * Construct an action that will read a file meant only for the * client */ String clientName = clientGSSName.toString(); PrivilegedAction readFile = new ReadFileAction(clientName); /* * Invoke the action via a doAsPrivileged. This allows the * action to be executed as the client subject, and it also * runs that code as privileged. This means that any permission * checking that happens beyond this point applies only to * the code being run as the client. */ Subject.doAsPrivileged(client, readFile, null);
The ReadFileAction.java file contains the ReadFileAction class. Its constructor takes as an argument a String for the name of the client user. The client username is used to construct a file name for a file from which ReadFileAction will attempt to read. The file name will be:
./data/<name>_info.txtwhere <name> is the client username without its corresponding realm. For example, if the full username is
mjones@KRBNT-OPERATIONS.EXAMPLE.COM
, then the file
name is
./data/mjones_info.txtNote: On Microsoft Windows systems the forward slashes will be backward slashes.
The ReadFileAction run
method reads the specified
file and prints its contents.
ReadFileAction attempts to read a file, which is a security-checked operation. Since ReadFileAction is considered to be executed as the client user (Principal), the appropriate permission must be granted not only to the ReadFileAction code itself, but to the client Principal as well.
Assuming the ReadFileAction
class is placed in a
JAR file named ReadFileAction.jar
, and the user
principal name is mjones@KRBNT-OPERATIONS.EXAMPLE.COM
,
this permission can be granted via the following in a policy
file:
grant CodeBase "file:./ReadFileAction.jar" Principal javax.security.auth.kerberos.KerberosPrincipal "mjones@KRBNT-OPERATIONS.EXAMPLE.COM" { permission java.io.FilePermission "data/mjones_info.txt", "read"; };The serverimp.policy file is exactly the same as the
server.policy
file from the previous
(Use of JAAS Login Utility and Java
GSS-API for Secure Message Exchanges) tutorial, except that it
grants the SampleServer code the
javax.security.auth.AuthPermission "doAsPrivileged"
permission it needs in order to call the
doAsPrivileged
method, and it has the following
placeholder for granting the FilePermission shown above:
grant CodeBase "file:./ReadFileAction.jar" Principal javax.security.auth.kerberos.KerberosPrincipal "your_user_name@your_realm" { permission java.io.FilePermission "data/your_user_name_info.txt", "read"; };
You must substitute your Kerberos realm for
your_realm
, and your user name for
your_user_name
in both
your_user_name@your_realm
and
data/your_user_name_info.txt
. If you are working on a
Microsoft Windows sytem, you also replace the "/" in
data/your_user_name_info.txt
with a "\".
To run the sample code illustrating the server impersonating the client, do everything listed in Running the SampleClient and SampleServer Programs in the previous tutorial, except for the following:
SampleServer.java
. Compile it and create a
JAR file named SampleServerImp.jar
containing
SampleServerImp.class
via the following:
javac SampleServerImp.java jar -cvf SampleServerImp.jar SampleServerImp.class
server.policy
.cs.conf
.javac ReadFileAction.java jar -cvf ReadFileAction.jar ReadFileAction.class
csImpLogin.conf
, replace
"service_principal@your_realm" with the Kerberos name of the service
principal that represents SampleServer.serverimp.policy
, replace
"service_principal@your_realm" in both places it appears with the
Kerberos name of the service principal that represents
SampleServer. (The same name as that used in the login
configuration file.) In addition, substitute your Kerberos realm
for your_realm
, and your user name for
your_user_name
in both
your_user_name@your_realm
and
data/your_user_name_info.txt
. If you are running on a
Microsoft Windows system, then replace the "/" in
data/your_user_name_info.txt
with a "\".data
subdirectory of your current
directory and create a short text file of the specified name in
that directory. For example, if your user name is
mjones
, the file to be placed in the data subdirectory
should be named mjones_info.txt
.SampleServerImp
is executed,
serverimp.policy
and csImpLogin.conf
are
used, and ReadFileAction.jar
is included.
Important: In these commands, you must replace
<port_number>
with an appropriate port number (a
high port number such as 4444), <your_realm>
with your Kerberos realm, and <your_kdc>
with
your Kerberos KDC.
Here is the command for Microsoft Windows systems:
java -classpath Login.jar;SampleServerImp.jar;ReadFileAction.jar -Djava.security.manager -Djava.security.krb5.realm=<your_realm> -Djava.security.krb5.kdc=<your_kdc> -Djava.security.policy=serverimp.policy -Djava.security.auth.login.config=csImpLogin.conf Login SampleServerImp <port_number>
Here is the command for Solaris, Linux, and Mac OS X systems:
java -classpath Login.jar:SampleServerImp.jar:ReadFileAction.jar -Djava.security.manager -Djava.security.krb5.realm=<your_realm> -Djava.security.krb5.kdc=<your_kdc> -Djava.security.policy=serverimp.policy -Djava.security.auth.login.config=csImpLogin.conf Login SampleServerImp <port_number>
As usual, type the full command on one line. Multiple lines are used here for legibility. If the command is too long for your system, you may need to place it in a .bat file (for Microsoft Windows) or a .sh file (for Solaris, Linux, and Mac OS X) and then run that file to execute the command.
As when running SampleServer, you will be prompted for the
Kerberos password for the service principal under which
SampleServerImp is expected to be run. The Kerberos login module
specified in the login configuration file will log the service
principal into Kerberos. Once authentication is successfully
completed, the SampleServerImp
code will be executed
on behalf of the service principal. It will listen for socket
connections on the specified port.
After you follow the "Prepare SampleClient for Execution" and "Execute SampleClient" instructions as usual and perform the user login, the client code will request a socket connection with SampleServerImp. Once SampleServerImp accepts the connection, SampleClient and SampleServerImp establish a shared context and then exchange messages as described in the previous tutorial.
After the message exchange, SampleServerImp determines the
principal name of the user executing the client code, creates a new
Subject containing a Principal with that name, and calls
Subject.doAsPrivileged
to execute the code in
ReadFileAction on behalf of the specified user. ReadFileAction
reads the file named your_user_name_info.txt
(where
your_user_name
represents the actual user name) in the
data
subdirectory of the current directory, and prints
out its contents.
For login troubleshooting suggestions, see Troubleshooting.
The most complete type of client impersonation is possible if the client delegates its credentials to the server.
Recall that prior to context establishment with the context
acceptor (the server in our previous tutorial), the context
initiator (the client) sets various context options. If the
initiator calls the requestCredDeleg
method on the
context
object with a true
argument, as
in
context.requestCredDeleg(true);then this requests that the initiator's credentials be delegated to the acceptor during context establishment.
Delegation of credentials from the initiator to the acceptor enables the acceptor to authenticate itself as an agent or delegate of the initiator.
First, after context establishment, the acceptor must determine
whether or not credential delegation actually took place. It does
so by calling the getCredDelegState
method:
boolean delegated = context.getCredDelegState();
If credentials were delegated, the acceptor can obtain those
credentials by calling the getDelegCr
method:
GSSCredential clientCr = context.getDelegCr();
The resulting GSSCredential object can then be used to initiate subsequent GSS-API contexts as a "delegate" of the initiator. For example, the server could authenticate as the client to a backend server that cares more about who the original client was than who the intermediate server is.
Acting as the client, the server can establish a connection with the backend server, establish a joint security context, and exchange messages in basically the same manner that the client and server did.
One way it could be done is that when the server calls the
createContext
method of a GSSManager, it could pass
createContext
the delegated credentials instead of
passing a null
.
Alternatively, the server code could first call the
com.sun.security.jgss.GSSUtil createSubject
method and
pass it the delegated credentials. That method returns a Subject
containing those credentials as the default credentials. The server
could then associate this Subject with the current
AccessControlContext, as described in How Do You Associate a Subject with
an Access Control Context? in the JAAS Authorization tutorial.
Then, when the server code calls the GSSManager
createContext
method, it can pass a null (indicating
the credentials for the "current" Subject should be used). In other
words, the server would effectively become the client. Subsequent
connections to backend servers using GSS could be made exactly as
described in the previous tutorials. This approach is useful if you
want the code that will use the delegated credentials to be
identical to the code that uses the default local credentials.
In order to delegate credentials, the context initiator
(SampleClient in our previous tutorial) must have a
javax.security.auth.kerberos.DelegationPermission
. An
example using placeholders in italics for actual values is the
following:
permission javax.security.auth.kerberos.DelegationPermission "\"service_principal@your_realm\" \"krbtgt/your_realm@your_realm\"";
Note that DelegationPermission has a single target in quotes that contains two items, both of which are quoted. Each inner quote is escaped by a "\". Thus the first item is
"service_principal@your_realm"and the second is
"krbtgt/your_realm@your_realm"
This basically gives the code executing on behalf of the client
the permission to forward a Kerberos ticket to the specified peer
(service_principal), where the Kerberos ticket is meant to avail
service from
krbtgt/your_realm@your_realm
.
Substitute your realm for all places
your_realm
appears. Also substitute the service
principal name for the service principal representing the server
for service_principal@your_realm
. (See Kerberos User and Service Principal
Names in the previous tutorial.) Suppose your realm is
KRBNT-OPERATIONS.EXAMPLE.COM and the service principal is
"sample/raven.example.com@KRBNT-OPERATIONS.EXAMPLE.COM". Then the
permission could appear in a policy file as
permission javax.security.auth.kerberos.DelegationPermission "\"sample/raven.example.com@KRBNT-OPERATIONS.EXAMPLE.COM\" \"krbtgt/KRBNT-OPERATIONS.EXAMPLE.COM@KRBNT-OPERATIONS.EXAMPLE.COM\"";