I am trying to authenticate against a SOAP webservice using NTLM authentication as mentioned at Apache CXF with stack as following -
jcifs-1.3.17.jar
cxf-2.7.11
NTLM + SSL
JDK 5 (I possibly cannot change this)
Every time I try and connect it refuses with 401 unauthorized access because it uses my underlying NT credentials which are not authorised instead of valid ones that I configured in code. (I had to modify jCIFS as it doesnt support SSL + NTLM to return HTTPs version of NtlmHttpURLConnection). Similar result when used HTTP Async mechanism.
String domainController = "xxx.xxx.xxx";
UniAddress dc = UniAddress.getByName(domainController, true);
jcifs.Config.setProperty("http.auth.ntlm.domain", "xxx.xxx.xxx");
jcifs.Config.setProperty("jcifs.smb.client.domain", "domain");
jcifs.Config.setProperty("jcifs.netbios.wins", dc.getHostAddress());
jcifs.Config.setProperty("jcifs.smb.client.soTimeout", "300000"); // 5 minutes
jcifs.Config.setProperty("jcifs.netbios.cachePolicy", "1200"); // 20 minutes
jcifs.Config.setProperty("jcifs.smb.client.username", USER);
jcifs.Config.setProperty("jcifs.smb.client.password", PWD);
//Register the jcifs URL handler to enable NTLM
jcifs.Config.registerSmbURLHandler();
HelloWorld src = new HelloWorld();
ClientProxyFactoryBean factory = new ClientProxyFactoryBean(new JaxWsClientFactoryBean());
factory.setServiceClass( IHelloWorld.class );
factory.setAddress(SERVICE_URL);
factory.setUsername(USER);
factory.setPassword(PWD);
IHelloWorld service = (IHelloWorld ) factory.create();
Client client = ClientProxy.getClient(service);
HTTPConduit http = (HTTPConduit) client.getConduit();
System.out.println(http.getClass().getName());
//org.apache.cxf.transport.http.URLConnectionHTTPConduit
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(36000);
httpClientPolicy.setAllowChunking(false);
http.setClient(httpClientPolicy);
http.getAuthorization().setAuthorizationType("NTLM");
http.getAuthorization().setUserName(USER);
http.getAuthorization().setPassword(PWD);
http.getClient().setAllowChunking( false );
http.getClient().setAutoRedirect( true );
TLSClientParameters tcp = new TLSClientParameters();
tcp.setTrustManagers( new TrustManager[]{ new TrustAllX509TrustManager() } );
http.setTlsClientParameters( tcp );
System.out.println("Invoking service...");
String msg= "echo";
try {
String res = service.readMessage(msg);
System.out.println("readMessage.result=" + res);
} catch (Exception e) {
e.printStackTrace();
}
Upong running this code I get following exception trace
: domain\ is unauthorized user at
sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown
Source) at
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown
Source) at java.lang.reflect.Constructor.newInstance(Unknown Source)
at
org.apache.cxf.interceptor.ClientFaultConverter.processFaultDetail(ClientFaultConverter.java:175)
at
org.apache.cxf.interceptor.ClientFaultConverter.handleMessage(ClientFaultConverter.java:78)
at
org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:272)
at
org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:113)
at
org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor.handleMessage(CheckFaultInterceptor.java:69)
at
org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor.handleMessage(CheckFaultInterceptor.java:34)
at
org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:272)
at org.apache.cxf.endpoint.ClientImpl.onMessage(ClientImpl.java:845)
at
org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1624)
at
org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1513)
at
org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1318)
at
org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
at
org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:632)
at
org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)
at
org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:272)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:570)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:479) at
org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:382) at
org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:335) at
org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)
at org.apache.cxf.frontend.ClientProxy.invoke(ClientProxy.java:81)
at com.sun.proxy.$Proxy44.readMessage(Unknown Source)
CXF 2.7.x does not support JDK 5. From the CXF FAQ:
Can CXF run with JDK 1.5?
Yes for CXF 2.6.x and older. Keep in mind though that Java 2 SE 5.0
with JDK 1.5 has reached end of life (EOL). CXF 2.7.x no longer
supports Java 5. In order to upgrade to 2.7.x, you must be using Java
6 (or newer).
After hours of struggle trying to juggle between JDK 5 stack and other SOAP framework like Axis2 and CXF, I finally manged to do with a raw SOAP client that can pretty much do the job I neeed. Following is code that helped get it done even with custom NT logins than underlying ones.
public final class JCIFSEngine implements NTLMEngine {
private static final int TYPE_1_FLAGS = NtlmFlags.NTLMSSP_NEGOTIATE_56
| NtlmFlags.NTLMSSP_NEGOTIATE_128
| NtlmFlags.NTLMSSP_NEGOTIATE_NTLM2
| NtlmFlags.NTLMSSP_NEGOTIATE_ALWAYS_SIGN
| NtlmFlags.NTLMSSP_REQUEST_TARGET;
public String generateType1Msg(final String domain, final String workstation)
throws NTLMEngineException {
final Type1Message type1Message = new Type1Message(TYPE_1_FLAGS,
domain, workstation);
return Base64.encode(type1Message.toByteArray());
}
public String generateType3Msg(final String username,
final String password, final String domain,
final String workstation, final String challenge)
throws NTLMEngineException {
Type2Message type2Message;
try {
type2Message = new Type2Message(Base64.decode(challenge));
} catch (final IOException exception) {
throw new NTLMEngineException("Invalid NTLM type 2 message",
exception);
}
final int type2Flags = type2Message.getFlags();
final int type3Flags = type2Flags
& (0xffffffff ^ (NtlmFlags.NTLMSSP_TARGET_TYPE_DOMAIN | NtlmFlags.NTLMSSP_TARGET_TYPE_SERVER));
final Type3Message type3Message = new Type3Message(type2Message,
password, domain, username, workstation, type3Flags);
return Base64.encode(type3Message.toByteArray());
}
}
public class JCIFSNTLMSchemeFactory implements AuthSchemeProvider {
public AuthScheme create(final HttpContext context) {
return new NTLMScheme(new JCIFSEngine());
}
}
The use HttpClient object to register custom NTLM Engine and Auth scheme registry -
protected Registry<AuthSchemeProvider> getAuthRegistry() {
Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder
.<AuthSchemeProvider> create()
.register(AuthSchemes.NTLM, new JCIFSNTLMSchemeFactory())
.build();
return authSchemeRegistry;
}
protected CredentialsProvider getCredentialsProvider(String user,
String pass, String domain) {
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST,
AuthScope.ANY_PORT, AuthScope.ANY_REALM, AuthSchemes.NTLM),
new NTCredentials(user, pass, null, domain));
return credsProvider;
}
HttpClientBuilder httpClientBuilder = HttpClients.custom();
httpClientBuilder.setDefaultAuthSchemeRegistry(getAuthRegistry());
httpClientBuilder
.setDefaultCredentialsProvider(getCredentialsProvider(
config.getUserName(), config.getPassword(),
config.getDomain()));
if (config.isProxy()) {
HttpHost proxy = new HttpHost(config.getProxyHost(),
config.getPort());
httpClientBuilder.setProxy(proxy);
}
httpClientBuilder.build();
Hope this helps someone with similar issue. Cheers
Related
I want to create a servlet using 2 way ssl connector.
I created test2wayssl.jks and initiated SslSelectChannelConnector
When i send request from postman with client certificate, the response in postman is
There was an error connecting to 127.0.0.1:29226/2wayssl.
Here is my code below. But it does not work.
Server server = new Server(29226);
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath("2-way-ssl-authentication/test2wayssl.jks");
sslContextFactory.setKeyStorePassword("123456");
sslContextFactory.setKeyManagerPassword("123456");
sslContextFactory.setTrustAll(true);
SslSelectChannelConnector sslConnector = new SslSelectChannelConnector(sslContextFactory);
sslConnector.setAllowRenegotiate(true);
sslConnector.setHost("localhost");
sslConnector.setServer(server);
server.addConnector(sslConnector);
ServletHandler handler = new ServletHandler();
handler.addServletWithMapping(HelloServlet.class, "/2wayssl");
server.setHandler(handler);
try {
server.start();
} catch (Exception e) {
e.printStackTrace(); // TODO impl
}
Below is my servlet class
#SuppressWarnings("serial")
public static class HelloServlet extends HttpServlet
{
#Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws IOException
{
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
response.getWriter().println("<h1>2 Way SSL Authentication</h1>");
}
}
Any help is appreciated.
SslSelectChannelConnector is from Jetty 8 and older which are now EOL/End of Life, and does not support client certificates, upgrade to supported and stable version of Jetty first.
How this is done with Jetty 9.4.27.v20200227 is by using the SslContextFactory.Server and one (or both) of the options
setWantClientAuth(true) which turns on JVM features on the SSL connection related to javax.net.ssl.SSLParameters.getWantClientAuth()
setNeedClientAuth(true) which turns on JVM features on the SSL connection related to javax.net.ssl.SSLParameters.getNeedClientAuth()
Example:
Server server = new Server();
int httpsPort = 8443;
// Setup HTTP Connector
HttpConfiguration httpConf = new HttpConfiguration();
httpConf.setSecurePort(httpsPort);
httpConf.setSecureScheme("https");
// Setup SSL
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStoreResource(findKeyStorePath());
sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
sslContextFactory.setWantClientAuth(true); // Option 1
sslContextFactory.setNeedClientAuth(true); // Option 2
// Setup HTTPS Configuration
HttpConfiguration httpsConf = new HttpConfiguration();
httpsConf.setSecureScheme("https");
httpsConf.setSecurePort(httpsPort);
httpsConf.addCustomizer(new SecureRequestCustomizer()); // adds ssl info to request object
// Establish the HTTPS ServerConnector
ServerConnector httpsConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory,"http/1.1"),
new HttpConnectionFactory(httpsConf));
httpsConnector.setPort(httpsPort);
server.addConnector(httpsConnector);
// Add a Handlers for requests
HandlerList handlers = new HandlerList();
handlers.addHandler(new SecuredRedirectHandler());
handlers.addHandler(new HelloHandler("Hello Secure World"));
server.setHandler(handlers);
server.start();
server.join();
I am trying to add proxy settings to a Java Swing client app, which connects and gets data over https from an external server. However the ClientResource (restlet:2.4.0) ignores all efforts with parameters and connects directly to the url? If the syntax is correct, what are the correct parameters?
Further, how can I use system proxy settings?
private static ClientResource getClientResource(String url) {
ClientResource clientResource = null;
try {
// test
Client client = new Client(new Context(), Protocol.HTTPS);
client.getContext().getParameters().add("https.proxyHost", "PROXY_IP");
client.getContext().getParameters().add("https.proxyPort", "PROXY_PORT");
clientResource = new ClientResource(url);
// test
clientResource.setNext(client);
} catch (Exception e) {
e.printStackTrace();
}
return clientResource;
}
private static Response sendGetRequest(String url) {
ClientResource resource = getClientResource(BASE_URL + url);
try {
resource.get();
} catch (ResourceException e){
e.printStackStrace();
return null;
}
return getResponse();
}
EDIT added compiles:
compile 'org.restlet.jse:org.restlet:2.3.12'
compile 'org.restlet.jse:org.restlet.ext.jackson:2.3.12'
// switch to Apache Http Client, enable proxy'
compile 'org.restlet.jse:org.restlet.ext.httpclient:2.3.12'
// httpClient for Class Definitions
compile 'org.apache.httpcomponents:httpclient:4.3'
CURRENT EXCEPTION:
Starting the Apache HTTP client
An error occurred during the communication with the remote HTTP server.
org.apache.http.client.ClientProtocolException
at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:867)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:57)
at org.restlet.ext.httpclient.internal.HttpMethodCall.sendRequest(HttpMethodCall.java:339)
at org.restlet.engine.adapter.ClientAdapter.commit(ClientAdapter.java:105)
at org.restlet.engine.adapter.HttpClientHelper.handle(HttpClientHelper.java:119)
at org.restlet.Client.handle(Client.java:153)
I think this is only supported with the httpClient extension, that relies on the Apache HTTP client library (maven artifact id: org.restlet.ext.httpclient).
You can then either use the system environment properties: http.proxyHost and http.proxyPort, or set these parameters on the client instance (as you did, but names are distinct and documented here ).
Client client = new Client(new Context(), Protocol.HTTPS);
client.getContext().getParameters().add("proxyHost", "PROXY_IP");
client.getContext().getParameters().add("proxyPort", "PROXY_PORT");
I am writing an application that needs to read from a REST api that is only available over https. I am running into the issue where the request fails in Mono.Security, with the message: "The authentication or decryption has failed."
I did my research and found that Mono by default doesn't have any trusted certificates. All the sources I found said that I could use
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
within the Main() and OnCreate() methods in the iOS and Droid projects respectively to override that check and allow any ssl cert. Even with that workaround, I'm still getting the same error. I have stepped through the code and confirmed that the above line is executed when running on iOS and Android.
My code works perfectly when accessing non-https APIs. This is a PCL, not shared, project.
I referred to these questions/resources before asking:
Ignore SSL certificate errors in Xamarin.Forms (PCL)
stackoverflow.com/questions/2675133/c-sharp-ignore-certificate-errors/2675183#2675183
bugzilla.xamarin.com/show_bug.cgi?id=6501
stackoverflow.com/questions/12287528/webclient-ssl-exception-with-android-4-and-mono-for-android
www.mono-project.com/docs/faq/security/
Here is the code so far:
public class PawPrintsDataConnection
{
private string response = "";
private Task<string> StartWebRequest(string url)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.ContentType = "application/json";
request.Method = "GET";
Task<WebResponse> task = Task.Factory.FromAsync (request.BeginGetResponse, asyncResult => request.EndGetResponse (asyncResult), (object)null);
return task.ContinueWith (t => ReadStreamFromResponse (t.Result));
}
private string ReadStreamFromResponse(WebResponse response)
{
using (Stream responseStream = response.GetResponseStream ())
using (StreamReader sr = new StreamReader (responseStream)) {
string strContent = sr.ReadToEnd ();
return strContent;
}
}
public string getRawResponse(){
var task = StartWebRequest(string.Format (#"https://pawprints.rit.edu/v1/petitions?key={0}&limit={1}", "apikey", 50));
this.response = task.Result;
return response;
}
}
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
protected override void OnCreate (Bundle bundle)
{
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
base.OnCreate (bundle);
global::Xamarin.Forms.Forms.Init (this, bundle);
LoadApplication (new App ());
}
}
static void Main (string[] args)
{
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main (args, null, "AppDelegate");
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
}
In my research, I discovered a bug on the Xamarin bugzilla that may be relevant, but I'm not sure that it applies to the version I'm using. I'm very new to Xamarin dev, so I'm not familiar with things like which version of Mono.security is included. https://bugzilla.xamarin.com/show_bug.cgi?id=26658
If it's helpful, here is the relevant portion of the exception:
System.AggregateException: One or more errors occurred ---> System.Exception: One or more errors occurred ---> System.Exception: Error: SendFailure (Error writing headers) ---> System.Exception: Error writing headers ---> System.Exception: The authentication or decryption has failed. ---> System.Exception: The authentication or decryption has failed.
at Mono.Security.Protocol.Tls.RecordProtocol.ProcessAlert (AlertLevel alertLevel, AlertDescription alertDesc) [0x00013] in ///Library/Frameworks/Xamarin.iOS.framework/Versions/8.6.1.26/src/mono/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/RecordProtocol.cs:654
at Mono.Security.Protocol.Tls.RecordProtocol.InternalReceiveRecordCallback (IAsyncResult asyncResult) [0x000dc] in ///Library/Frameworks/Xamarin.iOS.framework/Versions/8.6.1.26/src/mono/mcs/class/Mono.Security/Mono.Security.Protocol.Tls/RecordProtocol.cs:377
You're accessing pawprints.rit.edu right ?
Then the certificate for the site (and it's root CA) are fine, i.e. iOS would accept it (and Xamarin.iOS delegate the trust decision to iOS). IOW setting the delegate does not help you (it's for the certificate only and that's fine).
The issue here is that the server is configured to allow only a small subset of TLS 1.0 cipher suites. None of them compatible with Mono's current SSL/TLS implementation used by HttpWebRequest.
Your best alternative is to use a HttpClient and the CFNetworkHandler (for iOS) or a 3rd party handle (e.g. ModernHttpClient would work for both iOS and Android). That will use the native (from the OS) SSL/TLS implementation which has support for those cipher suites (and much better performance).
I am using apache cxf java client to connect my WS. I am also using NTLM for authentication.
Now problem I am facing due to credential caching. First time i tried user which does not have privileges to access WS method. when I changed the user , it is still using same user to access WS method.
I am running in tomcat, so cannot kill my JVM .. tried all possible combination on httpClientPolicy.
Any help will be appreciated.
This is NTLM specific problem. sun.net.www.protocol.https.HttpsURLConnectionImpl is getting serverAuthorization via java.net.Authenticator. requestPasswordAuthentication(). This authorization info is maintained in sun.net.www.protocol.http.AuthCacheValue.cache.
So if we override sun.net.www.protocol.http.AuthCacheValue means we can fix this issue.
AuthCacheValue.setAuthCache(new AuthCache()
{
#Override
public void remove(String arg0, AuthCacheValue arg1) { }
#Override
public void put(String arg0, AuthCacheValue arg1) { }
#Override
public AuthCacheValue get(String arg0, String arg1)
{
return null;
}
});
Reference :
http://web.archiveorange.com/archive/v/ACbGtycfTs2dqbRNpy6d
http://tigrou.nl/2011/06/11/cached-credentials-in-http-basic-authentication/
I googled and tried a lot of solutions to this problem.. apparently the simplest code that worked is as below using the JCIFS library
//Set the jcifs properties
jcifs.Config.setProperty("jcifs.smb.client.domain", "domainname");
jcifs.Config.setProperty("jcifs.netbios.wins", "xxx.xxx.xxx.xxx");
jcifs.Config.setProperty("jcifs.smb.client.soTimeout", "300000"); // 5 minutes
jcifs.Config.setProperty("jcifs.netbios.cachePolicy", "1200"); // 20 minutes
jcifs.Config.setProperty("jcifs.smb.client.username", "username");
jcifs.Config.setProperty("jcifs.smb.client.password", "password");
//Register the jcifs URL handler to enable NTLM
jcifs.Config.registerSmbURLHandler();
Apparently CXF 3.0 doesnt have a valid way of configuring the HTTP Client (4.3.x) with NTCredentials instance. Please refer to bug https://issues.apache.org/jira/browse/CXF-5671
By the way, if you have a simple message which needs to be transmitted, just use HTTP Client (I worked using 4.3.4.. not sure of the earlier versions) with NTCredentials Instance. That too did the magic for me.. The sample is as below:
final NTCredentials ntCredentials = new NTCredentials("username", "Passworrd","destination", "domain");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY, ntCredentials);
CloseableHttpClient httpclient = HttpClientBuilder.create()
.setDefaultCredentialsProvider(credsProvider)
.build();
I want to read certificate details (e.g. expiration date or CN) for security reasons.
Usually there are some properties in network classes available, that allow to check the certificate. This is missing in WP8 implementations.
Also I tried to create an SslStream but also there is no way to get any certificate detail like the RemoteCertificate on .net 4.5.
var sslStream = new SslStream(new NetworkStream(e.ConnectSocket));
The SslStream is missing everything relating security. So it looks like also BountyCastle and other libraries cannot be able to get the certificate, because the underlying framework doesn't support it.
So my questions are:
Can I read the CN or other Certificate details on WP8 using other approaches.?
If not, how can you create then seriously secure apps (line banking) on WP8 using techniques like SSL Pinning or client side certificate validation and is there any reason why this is not supported in WP8?
Regards
Holger
I issued a user voice request to Microsoft .NET team asking them to provide a solution for reading server SSL certificate details from portable class libraries (targeting also WP8). You can vote it here: http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4784983-support-server-ssl-certificate-chain-inspection-in
On Windows Phone 8.1 this can be done with HttpClient, as well as with StreamSocket (as Mike suggested).
Example for certificate validation with StreamSocket can be found here (Scenario5_Certificate in source code).
Certificate validation with HttpClient can be done by handling the ERROR_INTERNET_INVALID_CA exception, validating the server certificate using the HttpTransportInformation class, creating new instance of HttpBaseProtocolFilter class and specifying the errors to ignore.
Note that not all the errors are ignorable. You will receive an exception if you'll try to add Success, Revoked,
InvalidSignature, InvalidCertificateAuthorityPolicy, BasicConstraintsError, UnknownCriticalExtension or OtherErrors enum values.
I'm adding a sample code that bypasses certificate errors using HttpClient:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.Security.Cryptography.Certificates;
using Windows.Web.Http;
using Windows.Web.Http.Filters;
namespace Example.App
{
public class HttpsHandler
{
private const int ERROR_INTERNET_INVALID_CA = -2147012851; // 0x80072f0d
public static async void HttpsWithCertificateValidation()
{
Uri resourceUri;
if (!Uri.TryCreate("https://www.pcwebshop.co.uk/", UriKind.Absolute, out resourceUri))
return;
IReadOnlyList<ChainValidationResult> serverErrors = await DoGet(null, resourceUri);
if (serverErrors != null)
{
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
foreach (ChainValidationResult value in serverErrors)
{
try {
filter.IgnorableServerCertificateErrors.Add(value);
} catch (Exception ex) {
// Note: the following values can't be ignorable:
// Success Revoked InvalidSignature InvalidCertificateAuthorityPolicy
// BasicConstraintsError UnknownCriticalExtension OtherErrors
Debug.WriteLine(value + " can't be ignorable");
}
}
await DoGet(filter, resourceUri);
}
}
private static async Task<IReadOnlyList<ChainValidationResult>> DoGet(HttpBaseProtocolFilter filter, Uri resourceUri)
{
HttpClient httpClient;
if (filter != null)
httpClient = new HttpClient(filter);
else
httpClient = new HttpClient();
HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, resourceUri);
bool hadCertificateException = false;
HttpResponseMessage response;
String responseBody;
try {
response = await httpClient.SendRequestAsync(requestMessage);
response.EnsureSuccessStatusCode();
responseBody = await response.Content.ReadAsStringAsync();
} catch (Exception ex) {
hadCertificateException = ex.HResult == ERROR_INTERNET_INVALID_CA;
}
return hadCertificateException ? requestMessage.TransportInformation.ServerCertificateErrors : null;
}
}
}
After trying open source libs like bouncyCastle, supersocket or webSocket4net I tested an evaluation of a commercial lib named ELDOS SecureBlackbox. This test was successfull. Here is a code snipped, that gets the X509Certificates with all details:
public void OpenSSL()
{
var c = new TElSimpleSSLClient();
c.OnCertificateValidate += new TSBCertificateValidateEvent(OnCertificateValidate);
c.Address = "myhostname.com";
c.Port = 443;
c.Open();
c.Close(false);
}
private void OnCertificateValidate(object sender, TElX509Certificate x509certificate, ref TSBBoolean validate)
{
validate = true;
}
The validation is getting all certificates... if validate is set to true, the next certificate will be shown. That means the callback is called forreach certificate there.
Regards
Holger
For WP8, you can use the StreamSocket class, which has an UpgradeToSslAsync() method that will do the TLS handshake for you as an async operation. Once that completes, you can use the .Information.ServerCertificate property to check that you got the server certificate you were expecting.