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