001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.soap.handlers.security;
018    
019    import java.io.IOException;
020    import java.security.GeneralSecurityException;
021    import java.security.Principal;
022    import java.security.cert.X509Certificate;
023    import java.util.HashMap;
024    import java.util.HashSet;
025    import java.util.Iterator;
026    import java.util.Map;
027    import java.util.Set;
028    import java.util.Vector;
029    
030    import javax.security.auth.Subject;
031    import javax.security.auth.callback.CallbackHandler;
032    import javax.security.auth.callback.UnsupportedCallbackException;
033    import javax.xml.namespace.QName;
034    
035    import org.apache.servicemix.common.security.AuthenticationService;
036    import org.apache.servicemix.common.security.KeystoreManager;
037    import org.apache.servicemix.soap.Context;
038    import org.apache.servicemix.soap.Handler;
039    import org.apache.servicemix.soap.SoapFault;
040    import org.apache.ws.security.WSConstants;
041    import org.apache.ws.security.WSDocInfo;
042    import org.apache.ws.security.WSDocInfoStore;
043    import org.apache.ws.security.WSPasswordCallback;
044    import org.apache.ws.security.WSSConfig;
045    import org.apache.ws.security.WSSecurityEngine;
046    import org.apache.ws.security.WSSecurityEngineResult;
047    import org.apache.ws.security.WSSecurityException;
048    import org.apache.ws.security.WSUsernameTokenPrincipal;
049    import org.apache.ws.security.components.crypto.Crypto;
050    import org.apache.ws.security.handler.RequestData;
051    import org.apache.ws.security.handler.WSHandler;
052    import org.apache.ws.security.handler.WSHandlerConstants;
053    import org.apache.ws.security.handler.WSHandlerResult;
054    import org.apache.ws.security.message.token.Timestamp;
055    import org.apache.ws.security.processor.Processor;
056    import org.apache.ws.security.util.WSSecurityUtil;
057    import org.w3c.dom.Document;
058    import org.w3c.dom.Element;
059    
060    /**
061     * WS-Security handler.
062     * This handler is heavily based on xfire-ws-security project.
063     * 
064     * @org.apache.xbean.XBean element="ws-security"
065     */
066    public class WSSecurityHandler extends WSHandler implements Handler {
067    
068        private Map properties = new HashMap();
069        private String domain = "servicemix-domain";
070        private AuthenticationService authenticationService;
071        private boolean required;
072        private String sendAction;
073        private String receiveAction;
074        private String actor;
075        private String username;
076        private String keystore;
077        private Crypto crypto;
078        private CallbackHandler handler = new DefaultHandler();
079        
080        private ThreadLocal currentSubject = new ThreadLocal();
081        private static ThreadLocal currentHandler = new ThreadLocal();
082    
083        public WSSecurityHandler() {
084            final WSSecurityEngine wssEng = new WSSecurityEngine();
085            wssEng.setWssConfig(new ServiceMixWssConfig());
086        }
087        
088        static WSSecurityHandler getCurrentHandler() {
089            return (WSSecurityHandler) currentHandler.get();
090        }
091        
092        /**
093         * @return the authenticationService
094         */
095        public AuthenticationService getAuthenticationService() {
096            return authenticationService;
097        }
098    
099        /**
100         * @param authenticationService the authenticationService to set
101         */
102        public void setAuthenticationService(AuthenticationService authenticationService) {
103            this.authenticationService = authenticationService;
104        }
105    
106        private static class ServiceMixWssConfig extends WSSConfig {
107            public Processor getProcessor(QName el) throws WSSecurityException {
108                if (el.equals(WSSecurityEngine.SIGNATURE)) {
109                    return new SignatureProcessor();
110                } else {
111                    return super.getProcessor(el);
112                }
113            }
114        }
115        
116        private static class SignatureProcessor extends org.apache.ws.security.processor.SignatureProcessor {
117            private String signatureId;
118            public void handleToken(Element elem, Crypto crypto, Crypto decCrypto, CallbackHandler cb, WSDocInfo wsDocInfo, Vector returnResults, WSSConfig wsc) throws WSSecurityException {
119                WSDocInfoStore.store(wsDocInfo);
120                X509Certificate[] returnCert = new X509Certificate[1];
121                Set returnElements = new HashSet();
122                byte[][] signatureValue = new byte[1][];
123                Principal lastPrincipalFound = null;
124                try {
125                    WSSecurityHandler handler = WSSecurityHandler.getCurrentHandler();
126                    lastPrincipalFound = verifyXMLSignature((Element) elem,
127                            crypto, returnCert, returnElements, null, signatureValue, null, null);
128                    if (lastPrincipalFound instanceof WSUsernameTokenPrincipal) {
129                        WSUsernameTokenPrincipal p = (WSUsernameTokenPrincipal) lastPrincipalFound;
130                        handler.checkUser(p.getName(), p.getPassword());
131                    } else {
132                        handler.checkUser(returnCert[0].getSubjectX500Principal().getName(), returnCert[0]);
133                    }
134                } catch (GeneralSecurityException e) {
135                    throw new WSSecurityException("Unable to authenticate user", e);
136                } finally {
137                    WSDocInfoStore.delete(wsDocInfo);
138                }
139                if (lastPrincipalFound instanceof WSUsernameTokenPrincipal) {
140                    returnResults.add(0, new WSSecurityEngineResult(
141                            WSConstants.UT_SIGN, lastPrincipalFound, null,
142                            returnElements, signatureValue[0]));
143    
144                } else {
145                    returnResults.add(0, new WSSecurityEngineResult(
146                            WSConstants.SIGN, lastPrincipalFound,
147                            returnCert[0], returnElements, signatureValue[0]));
148                }
149                signatureId = elem.getAttributeNS(null, "Id");
150            }
151            public String getId() {
152                return signatureId;
153            }
154        }
155        
156        /**
157         * @return the username
158         */
159        public String getUsername() {
160            return username;
161        }
162    
163        /**
164         * @param username the username to set
165         */
166        public void setUsername(String username) {
167            this.username = username;
168        }
169    
170        /**
171         * @return the crypto
172         */
173        public Crypto getCrypto() {
174            return crypto;
175        }
176    
177        /**
178         * @param crypto the crypto to set
179         */
180        public void setCrypto(Crypto crypto) {
181            this.crypto = crypto;
182        }
183    
184        /**
185         * @return the actor
186         */
187        public String getActor() {
188            return actor;
189        }
190    
191        /**
192         * @param actor the actor to set
193         */
194        public void setActor(String actor) {
195            this.actor = actor;
196        }
197    
198        /**
199         * @return the domain
200         */
201        public String getDomain() {
202            return domain;
203        }
204    
205        /**
206         * @param domain the domain to set
207         */
208        public void setDomain(String domain) {
209            this.domain = domain;
210        }
211    
212        /**
213         * @return the receiveAction
214         */
215        public String getReceiveAction() {
216            return receiveAction;
217        }
218    
219        /**
220         * @param receiveAction the receiveAction to set
221         */
222        public void setReceiveAction(String receiveAction) {
223            this.receiveAction = receiveAction;
224        }
225    
226        /**
227         * @return the action
228         */
229        public String getSendAction() {
230            return sendAction;
231        }
232    
233        /**
234         * @param action the action to set
235         */
236        public void setSendAction(String action) {
237            this.sendAction = action;
238        }
239    
240        /**
241         * @return the required
242         */
243        public boolean isRequired() {
244            return required;
245        }
246    
247        public boolean requireDOM() {
248            return true;
249        }
250    
251        /**
252         * @param required the required to set
253         */
254        public void setRequired(boolean required) {
255            this.required = required;
256        }
257    
258        public Object getOption(String key) {
259            return properties.get(key);
260        }
261    
262        public void setOption(String key, Object value) {
263            this.properties.put(key, value);
264        }
265    
266        public String getPassword(Object msgContext) {
267            return (String) ((Context) msgContext).getProperty("password");
268        }
269    
270        public Object getProperty(Object msgContext, String key) {
271            if (WSHandlerConstants.PW_CALLBACK_REF.equals(key)) {
272                return handler;
273            }
274            return ((Context) msgContext).getProperty(key); 
275        }
276    
277        public void setPassword(Object msgContext, String password) {
278            ((Context) msgContext).setProperty("password", password);
279        }
280    
281        public void setProperty(Object msgContext, String key, Object value) {
282            ((Context) msgContext).setProperty(key, value);
283        }
284    
285        protected Crypto loadDecryptionCrypto(RequestData reqData) throws WSSecurityException {
286            return crypto;
287        }
288        
289        protected Crypto loadEncryptionCrypto(RequestData reqData) throws WSSecurityException {
290            return crypto;
291        }
292        
293        public Crypto loadSignatureCrypto(RequestData reqData) throws WSSecurityException {
294            return crypto;
295        }
296        
297        public void onReceive(Context context) throws Exception {
298            RequestData reqData = new RequestData();
299            init(context);
300            try {
301                reqData.setNoSerialization(true);
302                reqData.setMsgContext(context);
303    
304                Vector actions = new Vector();
305                String action = this.receiveAction;
306                if (action == null) {
307                    throw new IllegalStateException("WSSecurityHandler: No receiveAction defined");
308                }
309                int doAction = WSSecurityUtil.decodeAction(action, actions);
310    
311                Document doc = context.getInMessage().getDocument();
312                if (doc == null) {
313                    throw new IllegalStateException("WSSecurityHandler: The soap message has not been parsed using DOM");
314                }
315    
316                /*
317                 * Get and check the Signature specific parameters first because
318                 * they may be used for encryption too.
319                 */
320                doReceiverAction(doAction, reqData);
321    
322                Vector wsResult = null;
323    
324                try {
325                    wsResult = secEngine.processSecurityHeader(
326                                    doc, actor, handler, 
327                                    reqData.getSigCrypto(), 
328                                    reqData.getDecCrypto());
329                } catch (WSSecurityException ex) {
330                    throw new SoapFault(ex);
331                }
332    
333                if (wsResult == null) { // no security header found
334                    if (doAction == WSConstants.NO_SECURITY) {
335                        return;
336                    } else {
337                        throw new SoapFault(new WSSecurityException(
338                                        "WSSecurityHandler: Request does not contain required Security header"));
339                    }
340                }
341    
342                if (reqData.getWssConfig().isEnableSignatureConfirmation()) {
343                    checkSignatureConfirmation(reqData, wsResult);
344                }
345    
346                /*
347                 * Now we can check the certificate used to sign the message. In the
348                 * following implementation the certificate is only trusted if
349                 * either it itself or the certificate of the issuer is installed in
350                 * the keystore.
351                 * 
352                 * Note: the method verifyTrust(X509Certificate) allows custom
353                 * implementations with other validation algorithms for subclasses.
354                 */
355    
356                // Extract the signature action result from the action vector
357                WSSecurityEngineResult actionResult = WSSecurityUtil.fetchActionResult(wsResult, WSConstants.SIGN);
358    
359                if (actionResult != null) {
360                    X509Certificate returnCert = actionResult.getCertificate();
361    
362                    if (returnCert != null) {
363                        if (!verifyTrust(returnCert, reqData)) {
364                            throw new SoapFault(new WSSecurityException(
365                                            "WSSecurityHandler: the certificate used for the signature is not trusted"));
366                        }
367                    }
368                }
369    
370                /*
371                 * Perform further checks on the timestamp that was transmitted in
372                 * the header. In the following implementation the timestamp is
373                 * valid if it was created after (now-ttl), where ttl is set on
374                 * server side, not by the client.
375                 * 
376                 * Note: the method verifyTimestamp(Timestamp) allows custom
377                 * implementations with other validation algorithms for subclasses.
378                 */
379    
380                // Extract the timestamp action result from the action vector
381                actionResult = WSSecurityUtil.fetchActionResult(wsResult, WSConstants.TS);
382    
383                if (actionResult != null) {
384                    Timestamp timestamp = actionResult.getTimestamp();
385    
386                    if (timestamp != null) {
387                        if (!verifyTimestamp(timestamp, decodeTimeToLive(reqData))) {
388                            throw new SoapFault(new WSSecurityException(
389                                            "WSSecurityHandler: the timestamp could not be validated"));
390                        }
391                    }
392                }
393    
394                /*
395                 * now check the security actions: do they match, in right order?
396                 */
397                if (!checkReceiverResults(wsResult, actions)) {
398                    throw new SoapFault(new WSSecurityException(
399                                    "WSSecurityHandler: security processing failed (actions mismatch)"));
400    
401                }
402                /*
403                 * All ok up to this point. Now construct and setup the security
404                 * result structure. The service may fetch this and check it.
405                 */
406                Vector results = null;
407                if ((results = (Vector) context.getProperty(WSHandlerConstants.RECV_RESULTS)) == null) {
408                    results = new Vector();
409                    context.setProperty(WSHandlerConstants.RECV_RESULTS, results);
410                }
411                WSHandlerResult rResult = new WSHandlerResult(actor, wsResult);
412                results.add(0, rResult);
413    
414                // Add principals to the message
415                for (Iterator iter = results.iterator(); iter.hasNext();) {
416                    WSHandlerResult hr = (WSHandlerResult) iter.next();
417                    for (Iterator it = hr.getResults().iterator(); it.hasNext();) {
418                        WSSecurityEngineResult er = (WSSecurityEngineResult) it.next();
419                        if (er.getPrincipal() != null) {
420                            context.getInMessage().addPrincipal(er.getPrincipal());
421                        }
422                    }
423                }
424                Subject s = (Subject) currentSubject.get();
425                if (s != null) {
426                    for (Iterator iterator = s.getPrincipals().iterator(); iterator.hasNext();) {
427                        Principal p = (Principal) iterator.next();
428                        context.getInMessage().addPrincipal(p);
429                    }
430                }
431    
432            } finally {
433                reqData.clear();
434                currentSubject.set(null);
435                currentHandler.set(null);
436            }
437        }
438    
439        public void onReply(Context context) throws Exception {
440            // TODO Auto-generated method stub
441            
442        }
443        
444        public void onFault(Context context) throws Exception {
445            // TODO Auto-generated method stub
446    
447        }
448    
449        public void onSend(Context context) throws Exception {
450            RequestData reqData = new RequestData();
451            reqData.setMsgContext(context);
452            init(context);
453            /*
454             * The overall try, just to have a finally at the end to perform some
455             * housekeeping.
456             */
457            try {
458                /*
459                 * Get the action first.
460                 */
461                Vector actions = new Vector();
462                String action = this.sendAction;
463                if (action == null) {
464                    throw new IllegalStateException("WSSecurityHandler: No sendAction defined");
465                }
466                
467                int doAction = WSSecurityUtil.decodeAction(action, actions);
468                if (doAction == WSConstants.NO_SECURITY) {
469                    return;
470                }
471    
472                /*
473                 * For every action we need a username, so get this now. The
474                 * username defined in the deployment descriptor takes precedence.
475                 */
476                reqData.setUsername((String) getOption(WSHandlerConstants.USER));
477                if (reqData.getUsername() == null || reqData.getUsername().equals("")) {
478                    String username = (String) getProperty(reqData.getMsgContext(), WSHandlerConstants.USER);
479                    if (username != null) {
480                        reqData.setUsername(username);
481                    } else {
482                        reqData.setUsername(this.username);
483                    }
484                }
485                
486                /*
487                 * Now we perform some set-up for UsernameToken and Signature
488                 * functions. No need to do it for encryption only. Check if
489                 * username is available and then get a passowrd.
490                 */
491                if ((doAction & (WSConstants.SIGN | WSConstants.UT | WSConstants.UT_SIGN)) != 0) {
492                    /*
493                     * We need a username - if none throw an XFireFault. For
494                     * encryption there is a specific parameter to get a username.
495                     */
496                    if (reqData.getUsername() == null || reqData.getUsername().equals("")) {
497                        throw new IllegalStateException("WSSecurityHandler: Empty username for specified action");
498                    }
499                }
500                /*
501                 * Now get the SOAP part from the request message and convert it
502                 * into a Document.
503                 * 
504                 * Now we can perform our security operations on this request.
505                 */
506                Document doc = context.getInMessage().getDocument();
507                if (doc == null) {
508                    throw new IllegalStateException("WSSecurityHandler: The soap message has not been parsed using DOM");
509                }
510                
511                doSenderAction(doAction, doc, reqData, actions, true);
512            }
513            catch (WSSecurityException e) {
514                throw new SoapFault(e);
515            }
516            finally {
517                reqData.clear();
518                reqData = null;
519                currentHandler.set(null);
520            }
521        }
522    
523        public void onAnswer(Context context) {
524            // TODO Auto-generated method stub
525    
526        }
527        
528        protected void checkUser(String user, Object credentials) throws GeneralSecurityException {
529            if (authenticationService == null) {
530                throw new IllegalArgumentException("authenticationService is null");
531            }
532            Subject subject = (Subject) currentSubject.get();
533            if (subject == null) {
534                subject = new Subject();
535                currentSubject.set(subject);
536            }
537            authenticationService.authenticate(subject, domain, user, credentials);
538        }
539    
540        protected class DefaultHandler extends BaseSecurityCallbackHandler {
541    
542            protected void processSignature(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException {
543                callback.setPassword("");
544            }
545            
546            protected void processUsernameTokenUnkown(WSPasswordCallback callback) throws IOException, UnsupportedCallbackException {
547                try {
548                    checkUser(callback.getIdentifer(), callback.getPassword());
549                } catch (GeneralSecurityException e) {
550                    throw new UnsupportedCallbackException(callback, "Unable to authenticate user");
551                }
552            }
553    
554        }
555    
556        /**
557         * @return the keystore
558         */
559        public String getKeystore() {
560            return keystore;
561        }
562    
563        /**
564         * @param keystore the keystore to set
565         */
566        public void setKeystore(String keystore) {
567            this.keystore = keystore;
568        }
569        
570        protected void init(Context context) {
571            currentSubject.set(null);
572            currentHandler.set(this);
573            if (context.getProperty(Context.AUTHENTICATION_SERVICE) != null) {
574                setAuthenticationService((AuthenticationService) context.getProperty(Context.AUTHENTICATION_SERVICE));
575            }
576            if (crypto == null && context.getProperty(Context.KEYSTORE_MANAGER) != null && keystore != null) {
577                KeystoreManager km = (KeystoreManager) context.getProperty(Context.KEYSTORE_MANAGER);
578                setCrypto(new KeystoreInstanceCrypto(km, keystore));
579            }
580        }
581    
582    }