Wednesday, December 16, 2009

5.5. WS-Security



5.5. WS-Security


WS-Security
is a family of specifications (see Figure 5-6) designed to augment wire-level security by providing a
unified, transport-neutral, end-to-end framework for higher levels of security
such as authentication and authorization.



Figure 5-6. The WS-Security specifications




The layered blocks above WS-Security in
Figure 5-6 can be clarified briefly as follows. The first
layer consists of WS-Policy, WS-Trust, and WS-Privacy. The second layer of
WS-SecureConversation, WS-Federation, and WS-Authorization builds upon this
first layer. The architecture is thus modular but also complicated. Here is a
short description of each specification, starting with the first layer:





WS-Policy



This specification describes general security capabilities, constraints,
and policies. For example, a WS-Policy assertion could stipulate that a message
requires security tokens or that a particular encryption algorithm be
used.




WS-Trust



This specification deals primarily with how
security tokens are to be issued, renewed, and validated. In general, the
specification covers broker trust relationships, which are illustrated later in a code
example.




WS-Privacy



This specification explains how services can state and
enforce privacy policies. The specification also covers how a service can
determine whether a requester intends to follow such policies.




WS-SecureConversation



This specification covers, as the name
indicates, secure web service conversations across different sites and,
therefore, across different security contexts and trust domains. The
specification focuses on how a security context is created and how security keys
are derived and exchanged.




WS-Federation



This specification addresses the challenge of managing security
identities across different platforms and organizations. At the heart of the
challenge is how to maintain a single, authenticated identity (for example,
Alice's security identity) in a heterogeneous security environment.




WS-Authorization



This specification covers the
management of authorization data such as security tokens and underlying policies
for granting access to secured resources.


WS-Security is often associated with
federated security in the broad sense, which has the goal of cleanly separating
web service logic from the high-level security concerns, in particular
authentication/authorization, that challenge web service deployment. This
separation of concerns is meant to ease collaboration across computer systems
and trust realms.


Recall that SOAP-based web services
are meant to be transport-neutral. Accordingly, SOAP-based services cannot
depend simply on the reliable transport that HTTP and HTTPS provide, although
most SOAP messages are transported over HTTP. HTTP and HTTPS rest on TCP/IP (Transmission Control Protocol/Internet
Protocol), which supports reliable messaging. What if TCP/IP infrastructure is
not available? The WS-ReliableMessaging specification addresses precisely the
issue of delivering SOAP-based services over unreliable infrastructure.


A SOAP-based service cannot rely on the
authentication/authorization support that a web container such as Tomcat or an
application server such as BEA WebLogic, JBoss, GlassFish, or WebSphere may
provide. The WS-Security specifications therefore address issues of high-level
security as part of SOAP itself rather than as the part of the infrastructure
that happens to be in place for a particular SOAP-based service. The goals of
WS-Security are often summarized with the phrase end-to-end security, which
means that security matters are not delegated to the transport level but rather
handled directly through an appropriate security API. A framework for end-to-end
security needs to cover the situation in which a message is routed through
intermediaries, each of which may have to process the message, before reaching
the ultimate receiver. Accordingly, end-to-end security focuses on message
content rather than on the underlying transport.


5.5.1. Securing a @WebService with
WS-Security Under Endpoint


Herein is the source code for a barebones service that
will be secured with WS-Security:

package ch05.wss;

import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.xml.ws.WebServiceContext;
import javax.annotation.Resource;

@WebService
public class Echo {
@Resource
WebServiceContext ws_ctx;

@WebMethod
public String echo(String msg) {
return "Echoing: " + msg;
}
}


Nothing in the code hints at WSS
security. The publisher code provides the first hint:

package ch05.wss;

import javax.xml.ws.Endpoint;
import javax.xml.ws.Binding;
import javax.xml.ws.soap.SOAPBinding;
import java.util.List;
import java.util.LinkedList;
import javax.xml.ws.handler.Handler;

public class EchoPublisher {
public static void main(String[ ] args) {
Endpoint endpoint = Endpoint.create(new Echo());
Binding binding = endpoint.getBinding();
List<Handler> hchain = new LinkedList<Handler>();
hchain.add(new EchoSecurityHandler());
binding.setHandlerChain(hchain);

endpoint.publish("http://localhost:7777/echo");
System.out.println("http://localhost:7777/echo");
}
}


Note that there is a programmatically added
handler. Here is the code:


package ch05.wss;

import java.util.Set;
import java.util.HashSet;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.FileInputStream;
import java.io.File;
import com.sun.xml.wss.ProcessingContext;
import com.sun.xml.wss.SubjectAccessor;
import com.sun.xml.wss.XWSSProcessorFactory;
import com.sun.xml.wss.XWSSProcessor;
import com.sun.xml.wss.XWSSecurityException;

public class EchoSecurityHandler implements SOAPHandler<SOAPMessageContext> {
private XWSSProcessor xwss_processor = null;
private boolean trace_p;

public EchoSecurityHandler() {
XWSSProcessorFactory fact = null;
try {
fact = XWSSProcessorFactory.newInstance();
}
catch(XWSSecurityException e) { throw new RuntimeException(e); }

FileInputStream config = null;
try {
config = new FileInputStream(new File("META-INF/server.xml"));
xwss_processor =
fact.createProcessorForSecurityConfiguration(config, new Verifier());
config.close();
}
catch (Exception e) { throw new RuntimeException(e); }
trace_p = true; // set to true to enable message dumps
}

public Set<QName> getHeaders() {
String uri = "http://docs.oasis-open.org/wss/2004/01/" +
"oasis-200401-wss-wssecurity-secext-1.0.xsd";
QName security_hdr = new QName(uri, "Security", "wsse");
HashSet<QName> headers = new HashSet<QName>();
headers.add(security_hdr);
return headers;
}

public boolean handleMessage(SOAPMessageContext msg_ctx) {
Boolean outbound_p = (Boolean)
msg_ctx.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
SOAPMessage msg = msg_ctx.getMessage();

if (!outbound_p.booleanValue()) {
// Validate the message.
try {
ProcessingContext p_ctx = xwss_processor.createProcessingContext(msg);
p_ctx.setSOAPMessage(msg);
SOAPMessage verified_msg = xwss_processor.verifyInboundMessage(p_ctx);
msg_ctx.setMessage(verified_msg);

System.out.println(SubjectAccessor.getRequesterSubject(p_ctx));
if (trace_p) dump_msg("Incoming message:", verified_msg);
}
catch (XWSSecurityException e) { throw new RuntimeException(e); }
catch(Exception e) { throw new RuntimeException(e); }
}
return true;
}


public boolean handleFault(SOAPMessageContext msg_ctx) { return true; }
public void close(MessageContext msg_ctx) { }

private void dump_msg(String msg, SOAPMessage soap_msg) {
try {
System.out.println(msg);
soap_msg.writeTo(System.out);
System.out.println();
}
catch(Exception e) { throw new RuntimeException(e); }
}
}



Two sections of the
EchoSecurityHandler are of special
interest. The first callback is the getHeaders,
which the runtime invokes before invoking the
handleMessage callback. The getHeaders method generates a
security header block that complies with
OASIS (Organization for the Advancement of Structured Information
Standards) standards, in particular the standards for WSS (Web Services Security). The security
processor validates the security header.


The second section of interest is, of course, the
handleMessage callback that does most of the
work. The incoming SOAP message (that is, the client's request) is verified by
authenticating the client with a username/password check. The details will
follow shortly. If the verification succeeds, the verified SOAP message becomes
the new request message. If the verification fails, a
XWSSecurityException is thrown as a
SOAP fault. The code segment is:

try {
ProcessingContext p_ctx = xwss_processor.createProcessingContext(msg);
p_ctx.setSOAPMessage(msg);
SOAPMessage verified_msg = xwss_processor.verifyInboundMessage(p_ctx);
msg_ctx.setMessage(verified_msg);

System.out.println(SubjectAccessor.getRequesterSubject(p_ctx));
if (trace_p) dump_msg("Incoming message:", verified_msg);
}
catch (XWSSecurityException e) { throw new RuntimeException(e); }


On a successful verification, the
print statement outputs:

Subject:
Principal: CN=fred
Public Credential: fred


where Fred is the authenticated subject with a principal, which is a specific identity under
users/roles security. (The CN
stands for Common Name.) Fred's name acts as the public credential, but his
password remains secret.






Publishing a WS-Security
Service with Endpoint


Web services that use WS-Security require
packages that currently do not ship with core Java 6. It is easier to develop
and configure such services with an IDE such as NetBeans and to deploy the
services with an application server such as GlassFish, an approach taken in Chapter
6.


It is possible to publish a WSS-based
service with the Endpoint publisher,
as this section illustrates. Here are the setup steps:




  • The JAR file xws-security-3.0.jar should be downloaded, as the packages therein are not
    currently part of core Java 6. A convenient site for downloading is http://fisheye5.cenqua.com/browse/xwss/repo/com.sun.xml.wss.
    For convenience, this JAR file can be placed in METRO_HOME/lib.



  • For compilation and execution, two JAR files should be on the
    classpath: jaxws-tools.jar and xws-security-3.0.jar.



  • The configuration files for a WSS-based service
    usually are housed in a META-INF subdirectory of
    the working directory; that is, the directory in which the service's publisher
    and the client are invoked. In this case, the working directory is the parent
    directory of the ch05 subdirectory. There are
    two configuration files used in this example: server.xml and
    client.xml. The two files are identical to keep
    the example as simple as possible; in production, of course, they likely would
    differ.


Once the web service is deployed, wsimport can be used to generate the usual client-side artifacts.
The Metro runtime kicks in automatically to generate the wsgen artifacts; hence, wsgen need not be run
manually.


The example illustrates the clean
separation of security concerns and application logic. All of the WS-Security
code is confined to handlers on the client side and the service
side.



The EchoSecurityHandler has
a no-argument constructor that creates a security processor from information in
the configuration file, in this case server.xml. Here is the
constructor:


public EchoSecurityHandler() {
XWSSProcessorFactory fact = null;
try {
fact = XWSSProcessorFactory.newInstance();
}
catch(XWSSecurityException e) { throw new RuntimeException(e); }

FileInputStream config = null;
try {
config = new FileInputStream(new File("META-INF/server.xml"));
xwss_processor =
fact.createProcessorForSecurityConfiguration(config, new Verifier());

config.close();
}
catch (Exception e) { throw new RuntimeException(e); }
trace_p = true; // set to true to enable message dumps
}



The Verifier object in the
highlighted line does the actual validation. The configuration file is very
simple:


<!-- Copyright 2004 Sun Microsystems, Inc. All rights reserved.
SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. -->
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config"
dumpMessages="true" >
<xwss:RequireUsernameToken passwordDigestRequired="false"/>
</xwss:SecurityConfiguration>



and usually is stored in a META-INF directory. In this example, the configuration file
stipulates that messages to and from the web service should be dumped for
inspection and that the password rather than a password digest is acceptable in
the request message. The Verifier
object handles the low-level details of authenticating the request by validating
the incoming username and password. Here is the code:


package ch05.wss;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import com.sun.xml.wss.impl.callback.PasswordCallback;
import com.sun.xml.wss.impl.callback.PasswordValidationCallback;
import com.sun.xml.wss.impl.callback.UsernameCallback;

// Verifier handles service-side callbacks for password validation.
public class Verifier implements CallbackHandler {
// Username/password hard-coded for simplicity and clarity.
private static final String _username = "fred";
private static final String _password = "rockbed";

// For password validation, set the validator to the inner class below.
public void handle(Callback[ ] callbacks) throws UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof PasswordValidationCallback) {
PasswordValidationCallback cb=(PasswordValidationCallback)callbacks[i];
if (cb.getRequest() instanceof
PasswordValidationCallback.PlainTextPasswordRequest)
cb.setValidator(new PlainTextPasswordVerifier());
}
else
throw new UnsupportedCallbackException(null, "Not needed");
}
}

// Encapsulated validate method verifies the username/password.
private class PlainTextPasswordVerifier
implements PasswordValidationCallback.PasswordValidator {
public boolean validate(PasswordValidationCallback.Request req)
throws PasswordValidationCallback.PasswordValidationException {

PasswordValidationCallback.PlainTextPasswordRequest plain_pwd =
(PasswordValidationCallback.PlainTextPasswordRequest) req;
if (_username.equals(plain_pwd.getUsername()) &&
_password.equals(plain_pwd.getPassword())) {
return true;
}
else
return false;
}
}
}



The authentication succeeds with a username of fred
and a password of rockbed but fails otherwise. The security processor
created in the EchoSecurityHandler constructor is responsible for
invoking the handle callback in the Verifier.
To keep the example simple, the username and password are hardcoded rather than
retrieved from a database. Further, the password is plain text.


A client-side SOAPHandler generates and inserts the WSS artifacts that the
Echo service expects. Here is the source
code:


package ch05.wss;

import java.util.Set;
import java.util.HashSet;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.FileInputStream;
import java.io.File;
import com.sun.xml.wss.ProcessingContext;
import com.sun.xml.wss.SubjectAccessor;
import com.sun.xml.wss.XWSSProcessorFactory;
import com.sun.xml.wss.XWSSProcessor;
import com.sun.xml.wss.XWSSecurityException;

public class ClientHandler implements SOAPHandler<SOAPMessageContext> {
private XWSSProcessor xwss_processor;
private boolean trace_p;

public ClientHandler() {
XWSSProcessorFactory fact = null;
try {
fact = XWSSProcessorFactory.newInstance();
}
catch(XWSSecurityException e) { throw new RuntimeException(e); }

// Read client configuration file and configure security.
try {
FileInputStream config =
new FileInputStream(new File("META-INF/client.xml"));
xwss_processor =
fact.createProcessorForSecurityConfiguration(config, new Prompter());
config.close();
}
catch (Exception e) { throw new RuntimeException(e); }
trace_p = true; // set to true to enable message dumps
}

// Add a security header block
public Set<QName> getHeaders() {
String uri = "http://docs.oasis-open.org/wss/2004/01/" +
"oasis-200401-wss-wssecurity-secext-1.0.xsd";
QName security_hdr = new QName(uri, "Security", "wsse");
HashSet<QName> headers = new HashSet<QName>();
headers.add(security_hdr);
return headers;
}

public boolean handleMessage(SOAPMessageContext msg_ctx) {
Boolean outbound_p = (Boolean)
msg_ctx.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
SOAPMessage msg = msg_ctx.getMessage();

if (outbound_p.booleanValue()) {
// Create a message that can be validated.
ProcessingContext p_ctx = null;
try {
p_ctx = xwss_processor.createProcessingContext(msg);
p_ctx.setSOAPMessage(msg);
SOAPMessage secure_msg = xwss_processor.secureOutboundMessage(p_ctx);
msg_ctx.setMessage(secure_msg);

if (trace_p) dump_msg("Outgoing message:", secure_msg);
}
catch (XWSSecurityException e) { throw new RuntimeException(e); }
}
return true;
}

public boolean handleFault(SOAPMessageContext msg_ctx) { return true; }
public void close(MessageContext msg_ctx) { }

private void dump_msg(String msg, SOAPMessage soap_msg) {
try {
System.out.println(msg);
soap_msg.writeTo(System.out);
System.out.println();
}
catch(Exception e) { throw new RuntimeException(e); }
}
}



5.5.2. The Prompter and the
Verifier


On the service side, the security processor uses a Verifier object to authenticate the request. On the
client side, the security processor uses a Prompter object to get the username and password, which then are
inserted into the outgoing SOAP message. Just as the service-side security
processor generates a validated SOAP message if the authentication succeeds, so
the client-side security processor generates a secured SOAP message if the
Prompter works correctly. Here is the Prompter code:


package ch05.wss;

import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import com.sun.xml.wss.impl.callback.PasswordCallback;
import com.sun.xml.wss.impl.callback.PasswordValidationCallback;
import com.sun.xml.wss.impl.callback.UsernameCallback;
import java.io.BufferedReader;
import java.io.InputStreamReader;

// Prompter handles client-side callbacks, in this case
// to prompt for and read username/password.
public class Prompter implements CallbackHandler {
// Read username or password from standard input.
private String readLine() {
String line = null;
try {
line = new BufferedReader(new InputStreamReader(System.in)).readLine();
}
catch(IOException e) { }
return line;
}

// Prompt for and read the username and the password.
public void handle(Callback[ ] callbacks) throws UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof UsernameCallback) {
UsernameCallback cb = (UsernameCallback) callbacks[i];
System.out.print("Username: ");
String username = readLine();
if (username != null) cb.setUsername(username);
}
else if (callbacks[i] instanceof PasswordCallback) {
PasswordCallback cb = (PasswordCallback) callbacks[i];
System.out.print("Password: ");
String password = readLine();
if (password != null) cb.setPassword(password);
}
}
}
}



The security processor interacts with the Prompter
through a UsernameCallback and a PasswordCallback, which
prompt for, read, and store the client's username and password.


5.5.3. The Secured SOAP
Envelope


The client-side security processor generates a SOAP
message with all of the WSS information in the SOAP header. The SOAP body is
indistinguishable from one in an unsecured SOAP message. Here is the SOAP
message that the client sends:


<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-secext-1.0.xsd"
S:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="XWSSGID-1216851209528949783553">
<wsse:Username>fred</wsse:Username>
<wsse:Password
Type="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-username-token-profile-1.0#PasswordText">
rockbed
</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-soap-message-security-1.0#Base64Binary">
Vyg90XUn/rl2F4m6lSFIZCoU
</wsse:Nonce>
<wsu:Created>2008-07-23T22:13:33.001Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</S:Header>
<S:Body>
<ns2:echo xmlns:ns2="http://wss.ch05/">
<arg0>Hello, world!</arg0>
</ns2:echo>
</S:Body>
</S:Envelope>



In addition to the username and the
password, the SOAP header includes a nonce,
which is a randomly generated, statistically unique cryptographic token inserted
into the message. A nonce is added to safeguard against message theft and replay attacks in which an unsecured credential such as a
password is retransmitted to perform an unauthorized operation. For example, if
Eve were to intercept Alice's password from a SOAP message that transfers funds
from one of Alice's bank accounts to another of Alice's accounts, then Eve might
replay the scenario at a later time using the pirated password to transfer
Alice's funds into Eve's account. If the message receiver requires not only the
usual credentials such as a password but also a nonce with certain secret
attributes, then Eve would need more than just the pirated password鈥擡ve also
would need to replicate the nonce, which should be computationally
intractable.


The SOAP message from the
Echo service to the client has no
WSS artifacts at all:

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header/>
<S:Body>
<ns2:echoResponse xmlns:ns2="http://wss.ch05/">
<return>Echoing: Hello, world!</return>
</ns2:echoResponse>
</S:Body>
</S:Envelope>


5.5.4. Summary of the WS-Security
Example


This first example of WS-Security
introduces the API but has the obvious drawback of sending the client's username
and password, together with the nonce, over an unsecure channel. The reason, of
course, is that the Endpoint publisher does not
support HTTPS connections. A quick fix would be to use the HttpsServer
illustrated earlier.


Yet the example does meet the
goal of illustrating how WS-Security itself, without any support from a
transport protocol such as HTTP or a container such as Tomcat, supports
authentication and authorization. The example likewise shows that WS-Security
encourages a clean separation of security concerns from web service logic. Chapter
6 drills deeper into the details of WS-Security and
provides a production-grade example.


 


No comments: