HandshakeException: Handshake error in client - Flutter http.dart post request? [duplicate] - ssl

I am sending a post request in Dart. It is giving a response when I test it on API testing tools such as Postman. But when I run the app. It gives me the following error:-
E/flutter ( 6264): HandshakeException: Handshake error in client (OS Error: E/flutter ( 6264): CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:363))
Here is my code of the function -
Future getAccessToken(String url) async {
try {
http.post('url',
body: {
"email": "xyz#xyz.example",
"password": "1234"
}).then((response) {
print("Reponse status : ${response.statusCode}");
print("Response body : ${response.body}");
var myresponse = jsonDecode(response.body);
String token = myresponse["token"];
});
} catch (e) {
print(e.toString());
}
Here's the full error body:
E/flutter ( 6264): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception: E/flutter ( 6264): HandshakeException: Handshake error in client (OS Error: E/flutter ( 6264): CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate(handshake.cc:363)) E/flutter ( 6264): #0 IOClient.send (package:http/src/io_client.dart:33:23) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #1 BaseClient._sendUnstreamed (package:http/src/base_client.dart:169:38) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #2 BaseClient.post (package:http/src/base_client.dart:54:7) E/flutter ( 6264): #3 post.<anonymous closure> (package:http/http.dart:70:16) E/flutter ( 6264): #4 _withClient (package:http/http.dart:166:20) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #5 post (package:http/http.dart:69:5) E/flutter ( 6264): #6
_MyLoginFormState.getAccessToken (package:chart/main.dart:74:7) E/flutter ( 6264): <asynchronous suspension> E/flutter ( 6264): #7
_MyLoginFormState.build.<anonymous closure> (package:chart/main.dart:64:29)

In order to enable this option globally in your project, here is what you need to do:
In your main.dart file, add or import the following class:
class MyHttpOverrides extends HttpOverrides{
#override
HttpClient createHttpClient(SecurityContext? context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}
In your main function, add the following line after function definition:
HttpOverrides.global = MyHttpOverrides();
This comment was very helpful to pass through this matter, and please note that...
This should be used while in development mode, do NOT do this when you want to release to production, the aim of this answer is to
make the development a bit easier for you, for production, you need to fix your certificate issue and use it properly, look at the other answers for this as it might be helpful for your case.

Download cert from https://letsencrypt.org/certs/lets-encrypt-r3.pem
Add this file to assets/ca/ Flutter project root directory
Add assets/ca/ assets directory in pubspec.yaml
Add this code on your app initialization:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
ByteData data = await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List());
runApp(MyApp());
}
It works with the default chain, so no changes are needed on the server.
Android < 7.1.1 clients will still have access in a browser context.

If you are using Dio library, just do this:
Dio dio = new Dio();
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(HttpClient client) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
return client;
};

This Code work for me
class MyHttpOverrides extends HttpOverrides{
#override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=> true;
}
}
void main(){
HttpOverrides.global = new MyHttpOverrides();
runApp(MyApp());
}
I think it will the same for you...

Edit & Update Feb 2021: When this question was earlier asked there were not enough docs and developers to answer. The following answers may be more helpful than this one:
Ma'moon Al-Akash Answer, Pedro Massango's Answer & Ken's Answer
If you have not found the solution in these 3 answers, you can try the solution below.
Originally Answered Jan 2019:
The correct(but a bad) way to do it, as I found out, is to allow all certificates.
HttpClient client = new HttpClient();
client.badCertificateCallback = ((X509Certificate cert, String host, int port) => true);
String url ='xyz#xyz.example';
Map map = {
"email" : "email" ,
"password" : "password"
};
HttpClientRequest request = await client.postUrl(Uri.parse(url));
request.headers.set('content-type', 'application/json');
request.add(utf8.encode(json.encode(map)));
HttpClientResponse response = await request.close();
String reply = await response.transform(utf8.decoder).join();
print(reply);

The best approach (I think so) is to allow certificate for trusted hosts, so if your API host is "api.my_app" you can allow certificates from this host only:
HttpClient client = new HttpClient();
client.badCertificateCallback = ((X509Certificate cert, String host, int port) {
final isValidHost = host == "api.my_app";
// Allowing multiple hosts
// final isValidHost = host == "api.my_app" || host == "my_second_host";
return isValidHost;
});
If you have more hosts you can just add a new check there.

import 'package:http/io_client.dart';
import 'dart:io';
import 'package:http/http.dart';
import 'dart:async';
import 'dart:convert';
Future getAccessToken(String url) async {
try {
final ioc = new HttpClient();
ioc.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
final http = new IOClient(ioc);
http.post('url', body: {"email": "xyz#xyz.example", "password": "1234"}).then(
(response) {
print("Reponse status : ${response.statusCode}");
print("Response body : ${response.body}");
var myresponse = jsonDecode(response.body);
String token = myresponse["token"];
});
} catch (e) {
print(e.toString());
}
}

Check the device date and time in device settings. The device date and time is set to previous date.

This is for http library method. here is what you need to do in order to enable this option globally in your project.
class MyHttpoverrides extends HttpOverrides{
#override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = (X509Certificate cert, String host, int port)=>true;
}
}
//void main() => runApp(MyApp());
void main(){
HttpOverrides.global=new MyHttpoverrides();
runApp(MyApp());
}
for more details:https://fluttercorner.com/certificate-verify-failed-unable-to-get-local-issuer-certificate-in-flutter/

This issue happened to us as we are not using the fullchain.pem generated using let's encrypt on nginx. Once changed that it fixes this issue.
server {
listen 443 ssl;
ssl_certificate /var/www/letsencrypt/fullchain.pem;
For Apache, you might need to configure SSLCertificateChainFile. More discussion about the issue https://github.com/flutter/flutter/issues/50699

Using Dio package for request on my local server with self signed certificat, i prefer to allow a specific host rather than all domains.
//import 'package:get/get.dart' hide Response; //<-- if you use get package
import 'package:dio/dio.dart';
void main(){
HttpOverrides.global = new MyHttpOverrides();
runApp(MyApp());
}
class MyHttpOverrides extends HttpOverrides{
#override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = ((X509Certificate cert, String host, int port) {
final isValidHost = ["192.168.1.67"].contains(host); // <-- allow only hosts in array
return isValidHost;
});
}
}
// more example: https://github.com/flutterchina/dio/tree/master/example
void getHttp() async {
Dio dio = new Dio();
Response response;
response = await dio.get("https://192.168.1.67");
print(response.data);
}

For those who need to ignore certificate errors only for certain calls, you could use the HttpOverrides solution already mentioned by numerous answers.
However, there is no need to use it globally. You can use it only for certain calls that you know experience handshake errors by wrapping the call in HttpOverrides.runWithHttpOverrides().
class IgnoreCertificateErrorOverrides extends HttpOverrides{
#override
HttpClient createHttpClient(SecurityContext context){
return super.createHttpClient(context)
..badCertificateCallback = ((X509Certificate cert, String host, int port) {
return true;
});
}
}
Future<void> myNonSecurityCriticalApiCall() async {
await HttpOverrides.runWithHttpOverrides(() async {
String url = 'https://api.example.com/non/security/critical/service';
Response response = await get(url);
// ... do something with the response ...
}, IgnoreCertificateErrorOverrides());
}
In my case it is an external API which does have a valid SSL certificate and works in the browser but for some reason won't work in my Flutter app.

Well, I figured out that the actual root of the problem was out-of-sync time on my test device...

For me, it was because I am using HTTPS and the API uses HTTP so I just changed it to HTTP and it works.

For everyone landing here with a need to solve the problem and not just bypass it allowing everything.
For me the problem solved on the server side (as it should be) with no change in the code. Everything is valid now. On all the other solutions the problem still exists (eg The Postman runs but it displays a configuration error on the globe next to response status)
The configuration is Centos/Apache/LetsEncrypt/Python3.8/Django3.1.5/Mod_wsgi/ but I guess that the solution is valid for most installations of Apache/LetsEncrypt
The steps to resolve are
Locate the line "SSLCACertificateFile" on the Virtual Host you wish to config. For example:
SSLCACertificateFile /etc/httpd/conf/ssl.crt/my_ca.crt
Download
https://letsencrypt.org/certs/lets-encrypt-r3-cross-signed.txt
At the end of /etc/httpd/conf/ssl.crt/my_ca.crt (after the -----END CERTIFICATE-----) start a new line and paste from lets-encrypt-r3-cross-signed.txt everything bellow -----BEGIN CERTIFICATE----- (including -----BEGIN CERTIFICATE-----)
Save /etc/httpd/conf/ssl.crt/my_ca.crt
Restart Apache httpd
References:
https://access.redhat.com/solutions/43575
https://letsencrypt.org/certs
Also you can check the validity of your cert in https://www.digicert.com/help/.

For me, it was the problem with the android emulator.
I just created a new android emulator that fixed my problem.

Actually in my case I fixed it after updating the date and time on my pc. Might help someone I guess

I specifically needed to use lib/client.dart Client interface for http calls (i.e. http.Client instead of HttpClient) . This was required by ChopperClient (link).
So I could not pass HttpClient from lib/_http/http.dart directly to Chopper.
ChopperClient can receive HttpClient in the constructor wrapped in ioclient.IOClient.
HttpClient webHttpClient = new HttpClient();
webHttpClient.badCertificateCallback = ((X509Certificate cert, String host, int port) => true);
dynamic ioClient = new ioclient.IOClient(webHttpClient);
final chopper = ChopperClient(
baseUrl: "https://example.com",
client: ioClient,
services: [
MfService.create()
],
converter: JsonConverter(),
);
final mfService = MfService.create(chopper);
This way you can temporarily ignore CERTIFICATE_VERIFY_FAILED error in your calls. Remember - that's only for development purposes. Don't use this in production environment!

Update on January 30, 2021:
I know the reason, because nginx is configured with some encryption algorithms that flutter does not support! , The specific need to try.
Use tls 1.3 request URL, no problem.
Example
import 'dart:io';
main() async {
HttpClient client = new HttpClient();
// tls 1.2 error
// var request = await client.getUrl(Uri.parse('https://shop.io.mi-img.com/app/shop/img?id=shop_88f929c5731967cbc8339cfae1f5f0ec.jpeg'));
// tls 1.3 normal
var request = await client.getUrl(Uri.parse('https://ae01.alicdn.com/kf/Ud7cd28ffdf6e475c8dc382380d5d1976o.jpg'));
var response = await request.close();
print(response.headers);
client.close(force: true);
}

This Solution is finally worked. Thanks to Milad
final ioc = new HttpClient();
ioc.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
final http = new IOClient(ioc);
http.post(); //Your Get or Post Request

I fixed the issue by generating the full_chain.crt file.
You might have received the your_domain.crt file and your_domain.ca-bundle file. Now what you have to do is combine the crt file and ca-bundle file to generate the crt file.
cat domain.crt domain.ca-bundle >> your_domain_full_chain.crt
Then you just need to put the your_domain_full_chain.crt file in the nginx and it will start working properly.

In my case I needed to remake my backend's ssl certs. I had generated the certs using mkcert and just had to make new ones.

Note: If this error occurs other than trying to connect to a local SSL please fix it correctly and don't just use badCertificateCallback = (cert, host, port) => true as it is a security risk!
But.. if you run into this issue because you want to connect to a local back-end running a self signed certificate you could do the following.
You can use this client to connect to sources other then you local back-end.
class AppHttpClient {
AppHttpClient({
Dio? client,
}) : client = client ?? Dio() {
if (kDebugMode) {
// this.client.interceptors.add(PrettyDioLogger());
}
}
final Dio client;
}
You can use this client to connect to your local back-end. Make sure you set the --dart-define FLAVOR=development flag when running your app.
class InternalApiHttpClient extends AppHttpClient {
ApiHttpClient({
super.client,
required this.baseUrl,
}) {
_allowBadDevelopmentCertificate();
}
void _allowBadDevelopmentCertificate() {
const flavor = String.fromEnvironment('FLAVOR');
if (flavor == 'development') {
final httpClientAdapter =
super.client.httpClientAdapter as DefaultHttpClientAdapter;
httpClientAdapter.onHttpClientCreate = (client) {
return client..badCertificateCallback = (cert, host, port) => true;
};
}
}
final Uri baseUrl;
}
Doing it this way only suppresses the certificate message when you are on your development environment only when connecting to your local API. Any other requests stay untouched and if failing should be solved in a different way.

If you're using the emulator. So ensure that your date and time are right. Because in my case I found that issue.

If you use Android Emulator I've found out that this occurs when there is no internet connection. Check that your emulator has a network connection!

Related

Add Proxy to restlet ClientRessource

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");

WebRequest not working with ssl on Xamarin.Forms, despite servicePointManager hack

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).

LibGit2Sharp: Fetching fails with "Too many redirects or authentication replays"

Here's the code I'm using to fetch:
public static void GitFetch()
{
var creds = new UsernamePasswordCredentials()
{Username = "user",
Password = "pass"};
var fetchOpts = new FetchOptions {Credentials = creds};
using (repo = new Repository(#"C:\project");)
{
repo.Network.Fetch(repo.Network.Remotes["origin"], fetchOpts);
}
}
but it fails during fetch with the following exception:
LibGit2Sharp.LibGit2SharpException: Too many redirects or authentication replays
Result StackTrace:
at LibGit2Sharp.Core.Ensure.HandleError(Int32 result)
at LibGit2Sharp.Core.Proxy.git_remote_fetch(RemoteSafeHandle remote, Signature signature, String logMessage)
at LibGit2Sharp.Network.DoFetch(RemoteSafeHandle remoteHandle, FetchOptions options, Signature signature, String logMessage)
at LibGit2Sharp.Network.Fetch(Remote remote, FetchOptions options, Signature signature, String logMessage)
I have verified that the config file has the required remote name and that git fetch works from the command line. I found that the exception originates from libgit2\src\transport\winhttp.c but I couldn't come up with a workaround/solution.
I tried #Carlos' suggestion in the following way:
public static void GitFetch()
{
var creds = new UsernamePasswordCredentials()
{Username = "user",
Password = "pass"};
CredentialsHandler credHandler = (_url, _user, _cred) => creds;
var fetchOpts = new FetchOptions { CredentialsProvider = credHandler };
using (repo = new Repository(#"C:\project");)
{
repo.Network.Fetch(repo.Network.Remotes["origin"], fetchOpts);
}
}
I could fetch from public repos on github as well as from password protected private repos on bitbucket; however, I couldn't do the same for the repositories hosted over LAN at work. Turns out they were configured in a way which does not accept UsernamePasswordCredentials provided by libgit2sharp. The following modification allowed me to fetch from repositories over LAN:
CredentialsHandler credHandler = (_url, _user, _cred) => new DefaultCredentials();
(I'm trying to find out what is the exact difference between the two; if I get further insight into it, I'll update the answer.)
The shim that should make the Credentials option work is currently buggy (and is deprecated anyway), pass a CredentialsProvider instead as a callback.
This seems to be a very common error message.
We were getting it on pushes to GitHub, because credentials were disabled for security:
https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/
We've solved it by enabling SAML SSO and doing the push outside the C# code, but perhaps using SSH keys somehow with the library or personal access tokens fixes the problem too.

Read SSL Certificate Details on WP8

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.

accesing a https site using firefox addon

I am using a self signed ssl certificate to set up a https site and using the request package to access the contents on this site. However the program seems to get stuck and it is not printing the contents of the site. Is there any way to overcome this issue.
Warning: This should only be used for debugging. Automatically adding an override for a wrong SSL certificate compromises the entire connection - if you do that then you can just skip using SSL in the first place. When you release this extension for other people you should use a valid certificate.
You probably want to add a certificate override manually. That's something you would use nsICertOverrideService.rememberValidityOverride() for (chrome authority required). The only problem is getting the certificate that you want to add an override for. But trying to contact the server and calling nsIRecentBadCertsService.getRecentBadCert() then should do. Something like this:
var Request = require("request").Request;
var host = "example.com";
var port = "443";
Request({
url: "https://" + host + ":" + port + "/foo",
onComplete: function(response)
{
var status = null;
try
{
status = response.status;
} catch(e) {}
if (!status)
{
// There was a connection error, probably a bad certificate
var {Cc, Ci} = require("chrome");
var badCerts = Cc["#mozilla.org/security/recentbadcerts;1"]
.getService(Ci.nsIRecentBadCertsService);
var status = badCerts.getRecentBadCert(host + ":" + port);
if (status)
{
var overrideService = Cc["#mozilla.org/security/certoverride;1"]
.getService(Ci.nsICertOverrideService);
overrideService.rememberValidityOverride(host, port, status.serverCert,
Ci.nsICertOverrideService.ERROR_UNTRUSTED, false);
// Override added, now you should re-do the request
...
}
}
}
});
Note: This code hasn't been tested, in particular I'm not sure whether detecting connection errors will really work by checking response.status (my guess is that it should throw if there was a connection error but the documentation doesn't say anything).