Servlet Filter as Security Proxy for Web Services - web-services

Good time.
Suppose there are 8 web-services in the one application. 5 of them require authorization (a client must to provide a JSESSIONID cookie and a corresponding session must not be invalidated), other 3 can be called without the jsessionid cookie. My naive solution is to write a servlet filter which intercepts requests and retrieve their pathInfos (all the services have the same url structure: /service/serviceSuffix). There is a enum which contains the serviceSuffix of each web service that requires authorization. When the request is retrieved the pathInfo is collected; if this pathInfo is contained in the enum and there is the corresponding valid session the request is sent ahead to the filter chain. Otherwise, an error is sent back to a client. After a while I've realized that it is needed to add the possibility to retrieve the wsdl and xsds for the concrete service. So that, two more check were added.
public class SecurityFilter implements Filter {
public static final String WSDL = "wsdl";
public static final String XSD = "xsd=";
/**
* Wittingly left empty
*/
public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse)response;
String pathInfo = servletRequest.getPathInfo();
String queryString = servletRequest.getQueryString();
if (pathInfo != null && SecureWebServices.contains(pathInfo)) {
if (queryString != null && (queryString.equals(WSDL) || queryString.startsWith(XSD))) {
// wsdl or xsd is requested
chain.doFilter(request, response);
} else {
// a web service's method is called
HttpSession requestSession = servletRequest.getSession(false);
if (requestSession != null) { // the session is valid
chain.doFilter(request, response);
} else {
servletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
} else {
chain.doFilter(request, response);
}
}
/**
* Wittingly left empty
*/
public void destroy() {}
}
It seems that it is not very secure, because if the request's pathInfo is not in the enum, this request is passed on (just in case of some unexpected system calls).
Could you, please, suggest what to do, how to increase the security level. I want to build a configurable system (that is why I have the enum. It is possible just to add a path there to secure the web service and it is not required to duplicate the security code in the each web service). How to increase

Maybe I do not understand but.
jsessionid has nothink to do with security. you simply just get it.
Next I am not sure if you want authentication or authorization. The code as provided will not provide you with security features.
I suppose you are interested in authentication anyway. Security logic can be provided with standard web container features. Just send in authentication data in the header of request and you are done. web container can be configured to secure only selected resources (urls)

Related

Establish SSO/set cookies with access or id token/token exchange

I'm allowing users logged in an external application to jump into our application with their access token through Keycloak's identity brokering and external to internal token exchange.
Now I'd like to establish an SSO session in an embedded JxBrowser in our application similar to a regular browser login flow, where three cookies are set in the browser: AUTH_SESSION, KEYCLOAK_SESSION(_LEGACY) and KEYCLOAK_IDENTITY(_LEGACY).
KEYCLOAK_IDENTITY contains a token of type Serialized-ID which looks somewhat similar to an ID token.
Is it possible to create the KEYCLOAK_IDENTITY cookie using the exchanged (internal) access and/or ID token and, provided that the other two cookies are correctly created as well, would this establish a valid SSO session?
Basically all I am missing is how I could obtain or create the Serialized-ID type token.
One way to achieve this:
Implement a custom endpoint following this example
Note that the provider works fine for me without registering it in standalone.xml, I'm just adding the JAR to the Keycloak Docker image.
Add a method that validates a given access token, looks up the user, gets the user session and sets the cookies in the response (most error handling omitted for brevity):
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("sso")
public Response sso(#Context final HttpRequest request) {
final HttpHeaders headers = request.getHttpHeaders();
final String authorization = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
final String[] value = authorization.split(" ");
final String accessToken = value[1];
final AccessToken token = Tokens.getAccessToken(accessToken, keycloakSession);
if (token == null) {
throw new ErrorResponseException(Errors.INVALID_TOKEN, "Invalid access token", Status.UNAUTHORIZED);
}
final RealmModel realm = keycloakSession.getContext().getRealm();
final UriInfo uriInfo = keycloakSession.getContext().getUri();
final ClientConnection clientConnection = keycloakSession.getContext().getConnection();
final UserModel user = keycloakSession.users().getUserById(token.getSubject(), realm);
final UserSessionModel userSession = keycloakSession.sessions().getUserSession(realm, token.getSessionState());
AuthenticationManager.createLoginCookie(keycloakSession, realm, user, userSession, uriInfo, clientConnection);
return Response.noContent().build();
}
Disclaimer: I am not completely certain this implementation does not imply any security issues, but since Tokens.getAccessToken(accessToken, keycloakSession) does full validation of the access token, setting the cookies should only be possible with a valid access token.
For CORS, add:
#OPTIONS
#Produces(MediaType.APPLICATION_JSON)
#Path("sso")
public Response preflight(#Context final HttpRequest request) {
return Cors.add(request, Response.ok("", MediaType.APPLICATION_JSON))
.auth()
.preflight()
.allowedMethods("GET", "OPTIONS")
.build();
}
and in sso():
return Cors.add(request, Response.ok("", MediaType.APPLICATION_JSON))
.auth()
.allowedMethods("GET")
.allowedOrigins(token)
.build();
What I am uncertain about is why Firefox preflights the GET request, making it necessary to handle that.

How to add Authorization Header to client handshake request in Eclipse-jetty-websocket version-8

I want to add Authorization header for basic Authorization to my jetty-client request.
I am running two instances on same browser one is server one is client ,Java-8 is being used as a technology, No DB No Spring etc. using org.eclipse.jetty,jetty-websocket,version-8.1.15.v20140411.
I had a websocketEndPointManager class which contains the initialization method for both Server(dedicated class is CSWebsocket) and client(Cpwebsocket).
How can I achieve basic auth in my scenario where server maintains username and password is to matched in current session after its instance is ready, currently my setup able to perform upgrade to websocket but not basic
Snippet of client websocket start method where handshake occur :
private void startCPWebSocket() throws Exception{
String uri ="";
try {
HandlerRegistry.register(this);
WebSocketClientFactory factory = new WebSocketClientFactory();
Configuration configuration = TestSession.getConfiguration();
uri = configuration.getSystemUnderTestEndpoint();
TestSession.getConfiguration().setToolURL("NA");
TestSession.getConfiguration().setSystemUnderTestEndpoint(uri);
factory.start();
WebSocketClient client = factory.newWebSocketClient();
client.setMaxIdleTime(24*60*60*1000);
client.open(new URI(uri.trim()),new CPWebSocket()).get(24*60*60,
TimeUnit.SECONDS);
LogUtils.logInfo("WebSocket URL : "+uri,true);
}
catch (ExecutionException e) {
LogUtils.logError(e.getMessage(), e,false);
LogUtils.logWarn("Could not establish websocket connection.",true);
//System.exit(0);
TestSession.setTerminated(true);
throw e;

Same-Site flag for session cookie in Spring Security

Is it possible to set Same-site Cookie flag in Spring Security?
And if not, is it on a roadmap to add support, please? There is already support in some browsers (i.e. Chrome).
New Tomcat version support SameSite cookies via TomcatContextCustomizer. So you should only customize tomcat CookieProcessor, e.g. for Spring Boot:
#Configuration
public class MvcConfiguration implements WebMvcConfigurer {
#Bean
public TomcatContextCustomizer sameSiteCookiesConfig() {
return context -> {
final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
context.setCookieProcessor(cookieProcessor);
};
}
}
For SameSiteCookies.NONE be aware, that cookies are also Secure (SSL used), otherwise they couldn't be applied.
By default since Chrome 80 cookies considered as SameSite=Lax!
See SameSite Cookie in Spring Boot and SameSite cookie recipes.
For nginx proxy it could be solved easily in nginx config:
if ($scheme = http) {
return 301 https://$http_host$request_uri;
}
proxy_cookie_path / "/; secure; SameSite=None";
UPDATE from #madbreaks:
proxy_cookie_flags iso proxy_cookie_path
proxy_cookie_flags ~ secure samesite=none;
Instead of a Filter, In your Authentication Success Handler, you can mention in this way.
#Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
clearAuthenticationAttributes(request);
addSameSiteCookieAttribute(response);
handle(request, response);
}
private void addSameSiteCookieAttribute(HttpServletResponse response) {
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
// there can be multiple Set-Cookie attributes
for (String header : headers) {
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE,
String.format("%s; %s", header, "SameSite=Strict"));
}
}
It was mentioned in one of the answers. Couldn't find the link after I've implemented it.
All possible solutions here failed for me. Every time I tried a filter or interceptor, the Set-Cookie header had not yet been added. The only way I was able to make this work was by adding Spring Session and adding this bean into one of my #Configuration files:
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("none");
return serializer;
}
Anyway hope this helps someone else in my same situation.
You can always set cookie values by yourself in the Java world if you can get an instance of the HttpServletResponse.
Then you can do:
response.setHeader("Set-Cookie", "key=value; HttpOnly; SameSite=strict")
In spring-security you can easily do this with a filter, here is an example:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Set-Cookie", "locale=de; HttpOnly; SameSite=strict");
chain.doFilter(request, response);
}
}
Add this filter to your SecurityConfig like this:
http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class)
Or via XML:
<http>
<custom-filter after="BASIC_AUTH_FILTER" ref="myFilter" />
</http>
<beans:bean id="myFilter" class="org.bla.CustomFilter"/>
It isn't possible. There is support for this feature in Spring Session: https://spring.io/blog/2018/10/31/spring-session-bean-ga-released
I came up with a solution similar to Ron's one. But there is one important thing to note:
Cookies for cross-site usage must specify SameSite=None; Secure
to enable inclusion in third party context.
So I've included Secure attribute in header. Also, you don't have to override all three methods when you don't use them. It is only required when you are implementing HandlerInterceptor.
import org.apache.commons.lang.StringUtils;
public class CookiesInterceptor extends HandlerInterceptorAdapter {
final String sameSiteAttribute = "; SameSite=None";
final String secureAttribute = "; Secure";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
addEtagHeader(request, response);
Collection<String> setCookieHeaders = response.getHeaders(HttpHeaders.SET_COOKIE);
if (setCookieHeaders == null || setCookieHeaders.isEmpty())
return;
setCookieHeaders
.stream()
.filter(StringUtils::isNotBlank)
.map(header -> {
if (header.toLowerCase().contains("samesite")) {
return header;
} else {
return header.concat(sameSiteAttribute);
}
})
.map(header -> {
if (header.toLowerCase().contains("secure")) {
return header;
} else {
return header.concat(secureAttribute);
}
})
.forEach(finalHeader -> response.setHeader(HttpHeaders.SET_COOKIE, finalHeader));
}
}
I used xml in my project so I had to add this to my configuration file:
<mvc:interceptors>
<bean class="com.zoetis.widgetserver.mvc.CookiesInterceptor"/>
</mvc:interceptors>
Using the interceptor in SpringBoot.
I'm looking for a resolution for adding SameSite as you, and I only want to add the attribute to the existing "Set-Cookie" instead of creating a new "Set-Cookie".
I have tried several ways to meet this requirement, including:
adding a custom filter as #unwichtich said,
and more I overrode basicAuthenticationFilter. It does add the SameSite attribute. While the timing when Spring will add the "Set-Cookie" is hard to catch. I thought in onAuthenticationSuccess() method, the response must have this header, but it doesn't. I'm not sure whether it's the fault of my custom basicAuthenticationFilter's order.
using cookieSerializer, but the spring-session version comes up to a problem. Seems only the latest version support it, but I still can't figure out the version number should be added into the dependency list.
Unfortunately, none of them above can add the samesite well as expected.
Finally, I found the interceptor in spring can help me to make it.
It took me a week to get it. Hope this can help you if anyone has the same problem.
#Component
public class CookieServiceInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
//check whether it has "set-cookie" in the response, if it has, then add "SameSite" attribute
//it should be found in the response of the first successful login
Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
boolean firstHeader = true;
for (String header : headers) { // there can be multiple Set-Cookie attributes
if (firstHeader) {
response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
firstHeader = false;
continue;
}
response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=strict"));
}
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception exception) throws Exception {
}
}
and you also need to make this interceptor work in your application, which means you should add a bean as below:
#Autowired
CookieServiceInterceptor cookieServiceInterceptor;
#Bean
public MappedInterceptor myInterceptor() {
return new MappedInterceptor(null, cookieServiceInterceptor);
}
This interceptor has a flaw, it can't add samesite when the request is redirected(ex.return 302) or failed(ex. return 401), while it makes my app fail when SSO. Eventually, I have to use the Tomcat cookie, because I don't embed tomcat in my springboot app. I add
<Context>
<CookieProcessor sameSiteCookies="none" />
</Context>
in a context.xml under /META-INF of my app. It will add SameSite attribute in set-cookie header for each response. Note that this behavior is possible since Tomcat 9.0.21 and 8.5.42. according to https://stackoverflow.com/a/57622508/4033979
For Spring Webflux (reactive environment) this worked for me:
#Configuration
#EnableSpringWebSession
public class SessionModule {
#Bean
public ReactiveSessionRepository<MapSession> reactiveSessionRepository() {
return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
}
#Bean
public WebSessionIdResolver webSessionIdResolver() {
CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
resolver.setCookieName("SESSION");
resolver.addCookieInitializer((builder) -> {
builder.path("/")
.httpOnly(true)
.secure(true)
.sameSite("None; Secure");
});
return resolver;
}
}
You can add cookie by yourself by using ResponseCookie and adding it to your HttpServletResponse.
ResponseCookie cookie = ResponseCookie.from("cookiename", "cookieValue")
.maxAge(3600) // one hour
.domain("test.com")
.sameSite("None")
.secure(true)
.path("/")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
I have tested this solution for spring-webmvc without spring-security, but I think it should also work for spring-boot.
Using the SessionRepositoryFilter bean from spring-session-core
You can extend default java HttpSession with a spring Session and replace JSESSIONID cookie with a custom one, like this:
Set-Cookie: JSESSIONID=NWU4NzY4NWUtMDY3MC00Y2M1LTg1YmMtNmE1ZWJmODcxNzRj; Path=/; Secure; HttpOnly; SameSite=None
Additional spring Session cookie flags can be set using DefaultCookieSerializer:
#Configuration
#EnableSpringHttpSession
public class WebAppConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) {
servletContext
.addFilter("sessionRepositoryFilter", DelegatingFilterProxy.class)
.addMappingForUrlPatterns(null, false, "/*");
}
#Bean
public MapSessionRepository sessionRepository() {
final Map<String, Session> sessions = new ConcurrentHashMap<>();
MapSessionRepository sessionRepository =
new MapSessionRepository(sessions) {
#Override
public void save(MapSession session) {
sessions.entrySet().stream()
.filter(entry -> entry.getValue().isExpired())
.forEach(entry -> sessions.remove(entry.getKey()));
super.save(session);
}
};
sessionRepository.setDefaultMaxInactiveInterval(60*5);
return sessionRepository;
}
#Bean
public SessionRepositoryFilter<?> sessionRepositoryFilter(MapSessionRepository sessionRepository) {
SessionRepositoryFilter<?> sessionRepositoryFilter =
new SessionRepositoryFilter<>(sessionRepository);
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName("JSESSIONID");
cookieSerializer.setSameSite("None");
cookieSerializer.setUseSecureCookie(true);
CookieHttpSessionIdResolver cookieHttpSessionIdResolver =
new CookieHttpSessionIdResolver();
cookieHttpSessionIdResolver.setCookieSerializer(cookieSerializer);
sessionRepositoryFilter.setHttpSessionIdResolver(cookieHttpSessionIdResolver);
return sessionRepositoryFilter;
}
}
I have extended a bit MapSessionRepository implementation, since it does NOT support firing SessionDeletedEvent or SessionExpiredEvent - I have added clearing of expired sessions before adding new ones. I think this might be enough for a small application.
Apparently, with spring boot you can write this and it gets picked up.
#Configuration
public static class WebConfig implements WebMvcConfigurer {
#Bean
public CookieSameSiteSupplier cookieSameSiteSupplier(){
return CookieSameSiteSupplier.ofNone();
}
}
Or ... even simpler, spring boot since 2.6.0 supports setting it in application.properties.
Spring documentation about SameSite Cookies
server.servlet.session.cookie.same-site = none

Forwarding requests to external endpoint from an CFX service

Currently I am fighting with the following problem:
I need to forward SOAP requests to an external service in special cases (decision based on tenantId provided in the SOAP message). I created an interceptor for this task to extract tenantId from the message request, get assignment (each tenantId is assigned to its own service instance running on a different server) and if no assignment is made, I need to process the request just as normal.
Currently I implemented on this way: I create HttpUrlConnection in the interceptor and forward the request to an external endpoint (in case there is an assignment) and take the outputStream of the response and send the response over HttpServletResponse.getOutputStream etc...
I also need to consider that the interceptor be used with various service (tenantId must be provided in the SOAP request).
I also read about Provider and Dispatch objects not sure how this should work.
Is there any way to get target service and port (QNames) from the incoming message?
I cannot use Camel at the moment (only CXF is allowed).
Maybe you can try something like this :
/** Your interceptor */
public void handleMessage(SoapMessage msg) throws Fault {
Exchange exchange = msg.getExchange();
Endpoint ep = exchange.get(Endpoint.class);
// Get the service name
ServiceInfo si = ep.getEndpointInfo().getService();
String serviceName = si.getName().getLocalPart();
XMLStreamReader xr = msg.getContent(XMLStreamReader.class);
if (xr != null) { // If we are not even able to parse the message in the SAAJInInterceptor (CXF internal interceptor) this can be null
// You have the QName
QName name = xr.getName();
SOAPMessage msgSOAP = msg.getContent(SOAPMessage.class);
// Read soap msg
if (msgSOAP != null) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
msgSOAP.writeTo(byteArrayOutputStream);
String encoding = (String) msg.get(Message.ENCODING);
String xmlRequest = new String(byteArrayOutputStream.toByteArray(), encoding);
}
// Forward to external service with JAX-RS implementation
Client client = ClientBuilder.newClient()
.target("http://your-target")
.path("/custom-path")
.request()
.post(Entity.entity(xmlRequest, MediaType.APPLICATION_XML));
}
}
Hope this help.

MonoTouch Web Service Request over SSL Gets 'authentication or decryption has failed'

A web service request over SSL raises a WebException on Monotouch v4.0.4.1:
'Error getting response stream (Write: The authentication or decryption has failed)'
Since the server's SSL certificate is self-signed (and btw I think it is not X.509), I am bypassing the certificate validation using ServicePointManager.ServerCertificateValidationCallback. The exact same code works fine on Windows .NET, where the web service call returns the correct result. On Monotouch adding a Writeline shows that the ServerCertificateValidationCallback delegate code is never reached.
Note: Although probably not relevant, the content of the request is SOAP with embedded WS-Security UsernameToken.
Has anyone got something like this to work on MonoTouch? Have seen reports of similar symptom but no resolution. The code and stacktrace are below, any comment appreciated. Can email a self-contained test case if wanted.
I gather there is an alternative approach using certmgr.exe to store the self-signed server certificate in the local trust store, but can't seem to find that app in the MonoTouch distribution. Could anyone point me to it?
..
public class Application
{
static void Main (string[] args)
{
UIApplication.Main (args);
}
}
// The name AppDelegate is referenced in the MainWindow.xib file.
public partial class AppDelegate : UIApplicationDelegate
{
// This method is invoked when the application has loaded its UI and its ready to run
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
// If you have defined a view, add it here:
// window.AddSubview (navigationController.View);
string soapResponse;
string soapRequest = #" SOAP envelope is here but omitted for brevity ";
soapResponse = WebService.Invoke("myOperation", soapRequest);
window.MakeKeyAndVisible ();
return true;
}
// This method is required in iPhoneOS 3.0
public override void OnActivated (UIApplication application)
{
}
}
public class WebService
{
public static string Invoke(string operation, string soapRequest)
// Input parameters:
// operation = WS operation name
// soapRequest = SOAP XML request
// Output parameter:
// SOAP XML response
{
HttpWebResponse response;
try
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, ssl) => true;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://myserver.com:7570/MyEndpoint");
request.Method = "POST";
request.Headers.Add("SOAPAction", "/MyEndpoint/" + operation);
request.ContentType = "text/xml;charset=UTF-8";
request.UserAgent = "Smartphone";
request.ContentLength = soapRequest.Length;
request.GetRequestStream().Write(System.Text.Encoding.UTF8.GetBytes(soapRequest), 0, soapRequest.Length);
request.GetRequestStream().Close();
response = (HttpWebResponse)request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream(), System.Text.Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
catch (WebException e)
{
throw new WebException(e.Message);
}
}
}
Stack trace (some names changed to protect the innocent, original available on request):
WS.WebService.Invoke (operation="myOperation", soapRequest="<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" \n\txmlns:ns1=\"http://mycompany/Common/Primitives/v1\" \n\txmlns:ns2=\"http://mycompany/Common/actions/externals/Order/v1\" \n\txmlns:ns3=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\">\n\t<SOAP-ENV:Header> <wsse:Security SOAP-ENV:mustUnderstand=\"1\" \n\txmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"> \n\t<wsse:UsernameToken wsu:Id=\"UsernameToken-1\" \n\txmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"> \n\t<wsse:Username>myusername</wsse:Username> <wsse:Password \n\tType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">mypw</wsse:Password> \n\t<wsse:Nonce>{0}</wsse:Nonce> \n\t<wsu:Created xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">{1}</wsu:Created> \n\t</wsse:UsernameToken> </wsse:Security> \n\t</SOAP-ENV:Header><SOAP-ENV:Body><ns2:tp_getOrderDetailRequest><ns2:header><ns1:source>TEAM</ns1:source>\n\t<ns1:userAccessKey>12345678901234567</ns1:userAccessKey></ns2:header>\n\t<ns2:OrderId>myid1</ns2:OrderId>\n\t<ns2:OrderId>myid2</ns2:OrderId>\n\t</ns2:tp_getOrderDetailRequest>\n\t</SOAP-ENV:Body>\n\t</SOAP-ENV:Envelope>") in /Users/billf/Projects/WS/WS/Main.cs:103
WS.AppDelegate.FinishedLaunching (app={MonoTouch.UIKit.UIApplication}, options=(null)) in /Users/billf/Projects/WS/WS/Main.cs:52
MonoTouch.UIKit.UIApplication.Main (args={string[0]}, principalClassName=(null), delegateClassName=(null)) in /Developer/MonoTouch/Source/monotouch/monotouch/UIKit/UIApplication.cs:26
MonoTouch.UIKit.UIApplication.Main (args={string[0]}) in /Developer/MonoTouch/Source/monotouch/monotouch/UIKit/UIApplication.cs:31
WS.Application.Main (args={string[0]}) in /Users/billf/Projects/WS/WS/Main.cs:18
MonoTouch (just like Mono) does not support TLS_DH* cipher suites (like TLS_DHE_DSS_WITH_AES_128_CBC_SHA).
When a server is configured to accept only them then the negotiation stage fails very early (an Alert is received from the server after the Client Hello message is sent) which explains why the callback was never called.
Ensure your server allows the more traditional cipher suites, e.g. the very secure (but slow) TLS_RSA_WITH_AES_256_CBC_SHA or the faster (and very common) Cipher Suite: TLS_RSA_WITH_RC4_128_[MD5|SHA], and Mono[Touch] should work well using them.
Note that this is unrelated to SOAP or web-services (and even X.509 certificates) - it's just plain SSL.
1) An untrusted root certificate is not the only problem that could result in this exception.
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, ssl) => true;
Add a Console.WriteLine in there so you'll see if it gets called (or not).
throw new WebException(e.Message);
and another here, with full stack trace (not just the Message property).
2) Each application is isolated. This means that:
applications cannot updates the global iOS certificate stores (that would create security issues);
if a certmgr tool existed (for MT) it could only use a local (mono) store that would be usable only for itself (which would not be of any help for your own apps)