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 }