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, 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) {
576 KeystoreManager km = (KeystoreManager) context.getProperty(Context.KEYSTORE_MANAGER);
577 setCrypto(new KeystoreInstanceCrypto(km, keystore));
578 }
579 }
580
581 }