View Javadoc

1   /* 
2    * Copyright (c) 2007, Fraunhofer-Gesellschaft
3    * All rights reserved.
4    * 
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are
7    * met:
8    * 
9    * (1) Redistributions of source code must retain the above copyright
10   *     notice, this list of conditions and the disclaimer at the end.
11   *     Redistributions in binary form must reproduce the above copyright
12   *     notice, this list of conditions and the following disclaimer in
13   *     the documentation and/or other materials provided with the
14   *     distribution.
15   * 
16   * (2) Neither the name of Fraunhofer nor the names of its
17   *     contributors may be used to endorse or promote products derived
18   *     from this software without specific prior written permission.
19   * 
20   * DISCLAIMER
21   * 
22   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33   *  
34   */
35  package org.ogf.graap.wsag.security.core.server;
36  
37  import java.math.BigInteger;
38  import java.security.Principal;
39  import java.security.cert.X509Certificate;
40  import java.util.Arrays;
41  import java.util.List;
42  import java.util.Vector;
43  
44  import javax.security.auth.Subject;
45  import javax.security.auth.x500.X500Principal;
46  
47  import org.apache.axis2.context.MessageContext;
48  import org.apache.log4j.Logger;
49  import org.apache.ws.security.WSConstants;
50  import org.apache.ws.security.WSSecurityEngineResult;
51  import org.apache.ws.security.WSSecurityException;
52  import org.apache.ws.security.components.crypto.Crypto;
53  import org.apache.ws.security.handler.WSHandlerConstants;
54  import org.apache.ws.security.handler.WSHandlerResult;
55  import org.ogf.graap.wsag.security.core.SecurityConstants;
56  import org.ogf.graap.wsag.security.core.SecurityUtils;
57  import org.ogf.graap.wsag.server.api.WsagMessageContext;
58  import org.ogf.graap.wsag.server.engine.WsagEngine;
59  import org.w3c.dom.Element;
60  
61  /**
62   * The WS-Security handler checks the WS-Security processing results and populates the
63   * {@link WsagEngine#getWsagMessageContext()} with the client subject, the client certificate and the client
64   * certificate chain. In case security is disabled and anonymous access to the server is permitted the handler
65   * initializes an
66   * 
67   * @author Oliver Waeldrich
68   * 
69   */
70  public class WSSecurityHandler implements ServerSecurityHandler
71  {
72  
73      private static final Logger LOG = Logger.getLogger( WSSecurityHandler.class );
74  
75      /**
76       * {@inheritDoc}
77       * 
78       * @see org.ogf.graap.wsag.wsrf.handler.AxisHandler#handleRequest(org.apache.axiom.om.OMElement)
79       */
80      public void handleRequest( Element request ) throws Exception
81      {
82          MessageContext messageContext = MessageContext.getCurrentMessageContext();
83  
84          Crypto crypto = SecurityUtils.getCryptoFromLoginContext( WsagEngine.getLoginContext() );
85  
86          if ( crypto != null )
87          {
88              processSecurityHeader( crypto, messageContext );
89          }
90          else
91          {
92              LOG.error( "Could not process security headers. Reason: server crypto not found." );
93          }
94      }
95  
96      /**
97       * {@inheritDoc}
98       */
99      public void handleResponse( Element response ) throws Exception
100     {
101         // nothing to do
102     }
103 
104     @SuppressWarnings( { "unchecked" } )
105     private void processSecurityHeader( Crypto crypto, MessageContext messageContext )
106         throws WSSecurityException
107     {
108         WsagMessageContext context = WsagEngine.getWsagMessageContext();
109         Vector<WSHandlerResult> handlerResults =
110             (Vector<WSHandlerResult>) messageContext.getProperty( WSHandlerConstants.RECV_RESULTS );
111 
112         X509Certificate userCertificate = null;
113         // X509Certificate[] bstCertificates = null;
114 
115         for ( int i = 0; i < handlerResults.size(); i++ )
116         {
117             WSHandlerResult currentResult = handlerResults.get( i );
118 
119             Vector<WSSecurityEngineResult> wsSecEngineResults = currentResult.getResults();
120             for ( int j = 0; j < wsSecEngineResults.size(); j++ )
121             {
122                 WSSecurityEngineResult wser = wsSecEngineResults.get( j );
123                 if ( ( (Integer) wser.get( WSSecurityEngineResult.TAG_ACTION ) ).intValue() == WSConstants.SIGN
124                     && wser.get( WSSecurityEngineResult.TAG_X509_CERTIFICATE ) != null )
125                 {
126 
127                     userCertificate =
128                         (X509Certificate) wser.get( WSSecurityEngineResult.TAG_X509_CERTIFICATE );
129 
130                     if ( LOG.isTraceEnabled() )
131                     {
132                         String issuerName =
133                             ( (Principal) wser.get( WSSecurityEngineResult.TAG_PRINCIPAL ) ).getName();
134                         LOG.trace( "Found WS-Security engine result. Message signed by " + issuerName );
135                     }
136 
137                     context.put( SecurityConstants.X509_CLIENT_CERTIFICATE, userCertificate );
138 
139                     // Construct the client certificate chain from the request
140                     context.put( SecurityConstants.X509_CLIENT_CERTIFICATE_CHAIN,
141                                  createClientCertificateChain( crypto, userCertificate ) );
142                 }
143             }
144         }
145 
146         //
147         // add the security subject for authentication
148         //
149         // The subject is created based on the certificated that was used to sign the message.
150         // If no certificate is provided and anonymous usage is allowed an anonymous subject is
151         // created. Otherwise, an exception is raised.
152         //
153         if ( userCertificate != null )
154         {
155             Subject subject = new Subject();
156             subject.getPrincipals().add( userCertificate.getSubjectX500Principal() );
157             subject.getPrincipals().add( userCertificate.getIssuerX500Principal() );
158             subject.getPublicCredentials().add( userCertificate );
159             
160             WsagEngine.getWsagMessageContext().put( SecurityConstants.AUTHENTICATED_USER, subject );
161         }
162         else if ( WsagEngine.isAllowAnonymousAccess() )
163         {
164             Subject subject = new Subject();
165             subject.getPrincipals().add( new X500Principal( "anonymous" ) );
166             subject.getPublicCredentials().add( userCertificate );
167             WsagEngine.getWsagMessageContext().put( SecurityConstants.AUTHENTICATED_USER, subject );
168         }
169         else
170         {
171             throw new WSSecurityException( "The user was not authenticated." );
172         }
173     }
174 
175     /**
176      * Creates the certificate chain for the authenticated client. This method contains code from the
177      * <code>org.apache.ws.axis.security.WSDoAllReceiver.verifyTrust()</code> method.
178      * 
179      * @param crypto
180      *            the server crypto
181      * @param certificate
182      *            the client certificate
183      * @return the client certificate chain
184      * 
185      * @throws WSSecurityException
186      *             indicates an error while creating the certificate chain
187      */
188     private X509Certificate[] createClientCertificateChain( Crypto crypto, X509Certificate certificate )
189         throws WSSecurityException
190     {
191         X509Certificate[] result;
192 
193         // First step: Search the keystore for the transmitted certificate
194         String subjectString = certificate.getSubjectDN().getName();
195         String issuerString = certificate.getIssuerDN().getName();
196         BigInteger issuerSerial = certificate.getSerialNumber();
197 
198         String alias = crypto.getAliasForX509Cert( issuerString, issuerSerial );
199 
200         if ( alias != null )
201         {
202             // Retrieve the certificate for the alias from the keystore
203             try
204             {
205                 result = crypto.getCertificates( alias );
206 
207                 // If certificates have been found, the certificates must be compared
208                 // to ensure against phony DNs (compare encoded form including signature)
209                 if ( result != null && result.length > 0 && certificate.equals( result[0] ) )
210                 {
211                     return result;
212                 }
213                 result = null;
214             }
215             catch ( WSSecurityException ex )
216             {
217                 throw new WSSecurityException( getClass().getName()
218                     + ": Could not get certificates for alias " + alias, ex );
219             }
220         }
221 
222         // SECOND step: Search for the issuer of the transmitted certificate in the keystore
223         String[] aliases = null;
224         try
225         {
226             aliases = crypto.getAliasesForDN( issuerString );
227         }
228         catch ( WSSecurityException ex )
229         {
230             throw new WSSecurityException( getClass().getName()
231                 + ": Could not get alias for certificate with " + issuerString );
232         }
233 
234         // If the alias has not been found, the issuer is not in the keystore
235         // As a direct result, do not trust the transmitted certificate
236         if ( aliases == null || aliases.length < 1 )
237         {
238             if ( LOG.isDebugEnabled() )
239             {
240                 LOG.debug( "No aliases found in keystore for issuer " + issuerString + " of certificate for "
241                     + subjectString + "." );
242             }
243 
244             return null;
245         }
246 
247         // THIRD step
248         // Check the certificate trust path for every alias of the issuer found in the keystore
249         for ( int i = 0; i < aliases.length; i++ )
250         {
251             alias = aliases[i];
252 
253             if ( LOG.isTraceEnabled() )
254             {
255                 LOG.trace( "Preparing to validate certificate path with alias " + alias + " for issuer "
256                     + issuerString + "." );
257             }
258 
259             // Retrieve the certificate(s) for the alias from the keystore
260             X509Certificate[] certs;
261             try
262             {
263                 certs = crypto.getCertificates( alias );
264             }
265             catch ( WSSecurityException ex )
266             {
267                 throw new WSSecurityException( getClass().getName()
268                     + ": Could not get certificates for alias " + alias, ex );
269             }
270 
271             // If no certificates have been found, there has to be an error:
272             // The keystore can find an alias but no certificate(s)
273             if ( certs == null | certs.length < 1 )
274             {
275                 throw new WSSecurityException( getClass().getName()
276                     + ": Could not get certificates for alias " + alias );
277             }
278 
279             // Use the validation method from the crypto to check whether the subjects certificate was really
280             // signed by
281             // the issuer stated in the certificate
282             try
283             {
284                 if ( crypto.validateCertPath( certs ) )
285                 {
286                     if ( LOG.isTraceEnabled() )
287                     {
288                         LOG.trace( getClass().getName()
289                             + ": Certificate path has been verified for certificate with subject "
290                             + subjectString );
291                     }
292 
293                     // ordered users cert first, root ca last, see keystore
294                     List<X509Certificate> resultCerts = new Vector<X509Certificate>();
295                     resultCerts.add( certificate );
296                     resultCerts.addAll( Arrays.asList( certs ) );
297                     return resultCerts.toArray( new X509Certificate[resultCerts.size()] );
298                 }
299             }
300             catch ( WSSecurityException ex )
301             {
302                 throw new WSSecurityException( getClass().getName()
303                     + ": Certificate path verification failed for certificate with subject " + subjectString,
304                                                ex );
305             }
306         }
307 
308         if ( LOG.isInfoEnabled() )
309         {
310             LOG.info( getClass().getName() + ": Could not retrieve client certificate chain for subject "
311                 + subjectString );
312         }
313 
314         return null;
315     }
316 
317 }