How to call InternetErrorDlg to deal with certificate issues on SSL connections (C#)

Hi all,

The following C# sample shows how to call WinInet APIs to make an SSL request and deal with possible certificate issues with InternetErrorDlg (which will show the same standard dialogs that Internet Explorer shows when something is wrong with server or client certs):

 using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Net;

namespace WindowsApplication1
{
    class Tester
    {
        public static string TestSSLRequest(IntPtr hWnd, string lpszServerName, short nServerPort, string lpszUrl)
        {
            // Variables
            IntPtr hInternet = IntPtr.Zero;
            string lpszAgent;
            int dwAccessType = 0;
            string lpszProxyName;
            string lpszProxyBypass;
            int dwFlags = 0;
            string lpszUserName;
            string lpszPassword;
            int dwService = 0;
            IntPtr dwContext = IntPtr.Zero;
            IntPtr hConnect = IntPtr.Zero;
            string lpszVerb;
            string lpszObjectName;
            string lpszVersion;
            string lpszReferer;
            IntPtr lplpszAcceptTypes = IntPtr.Zero;
            IntPtr hRequest = IntPtr.Zero;
            IntPtr lpOptional = IntPtr.Zero;
            int dwError = 0;
            string lpszHeaders;
            int dwHeadersLength = 0;
            int dwOptionalLength = 0;
            IntPtr lppvData = IntPtr.Zero;
            int dwNumberOfBytesAvailable = 0;
            IntPtr lpBuffer = IntPtr.Zero;
            int dwOption = 0;
            ulong ulOptionMask = 0;
            int dwBufferLength = 0;
            int dwNumberOfBytesToRead = 0;
            int dwNumberOfBytesRead = 0;

            bool bResult = false;
            int iResult = 0;

            // Let's begin!!!
            try
            {
                // Initializes the app's use of the WinINet functions
                lpszAgent = "AlejaCMa";
                dwAccessType = Win32API.INTERNET_OPEN_TYPE_PRECONFIG;
                lpszProxyName = null;
                lpszProxyBypass = null;
                dwFlags = 0;
                hInternet = Win32API.InternetOpen(lpszAgent, dwAccessType, lpszProxyName, lpszProxyBypass, dwFlags);
                if (hInternet.Equals(IntPtr.Zero))
                {
                    throw new Exception("InternetOpen: Error #" + Marshal.GetLastWin32Error().ToString());
                }

                // Opens HTTP session for the site
                lpszUserName = null;
                lpszPassword = null;
                dwService = Win32API.INTERNET_SERVICE_HTTP;
                dwFlags = 0;
                dwContext = IntPtr.Zero;
                hConnect = Win32API.InternetConnect(hInternet, lpszServerName, nServerPort, lpszUserName, lpszPassword, dwService, dwFlags, dwContext);
                if (hConnect.Equals(IntPtr.Zero))
                {
                    throw new Exception("InternetConnect: Error #" + Marshal.GetLastWin32Error().ToString());
                }

                // Create HTTP request handle
                lpszVerb = "GET";
                lpszObjectName = lpszUrl;
                lpszVersion = null;
                lpszReferer = null;
                lplpszAcceptTypes = IntPtr.Zero;
                dwFlags = Win32API.INTERNET_FLAG_SECURE;
                dwContext = IntPtr.Zero;
                hRequest = Win32API.HttpOpenRequest(hConnect, lpszVerb, lpszObjectName, lpszVersion, lpszReferer, lplpszAcceptTypes, dwFlags, dwContext);
                if (hRequest.Equals(IntPtr.Zero))
                {
                    throw new Exception("HttpOpenRequest: Error #" + Marshal.GetLastWin32Error().ToString());
                }
                
                // Configure request to get combined cert errors
                dwOption = Win32API.INTERNET_OPTION_ERROR_MASK;
                ulOptionMask = Win32API.INTERNET_ERROR_MASK_COMBINED_SEC_CERT;
                dwBufferLength = Marshal.SizeOf(lpBuffer);
                bResult = Win32API.InternetSetOption(hRequest, dwOption, ref ulOptionMask, dwBufferLength);
                if (!bResult)
                {
                    throw new Exception("InternetSetOption: Error #" + Marshal.GetLastWin32Error().ToString());
                }

                do
                {
                    // Send request to the server
                    lpszHeaders = null;
                    dwHeadersLength = 0;
                    lpOptional = IntPtr.Zero;
                    dwOptionalLength = 0;
                    bResult = Win32API.HttpSendRequest(hRequest, lpszHeaders, dwHeadersLength, lpOptional, dwOptionalLength);

                    if (!bResult)
                    {
                        // Deal with possible errors
                        switch (Marshal.GetLastWin32Error())
                        {
                            case Win32API.ERROR_INTERNET_SEC_CERT_ERRORS:
                                dwError = Win32API.ERROR_INTERNET_SEC_CERT_ERRORS;
                                break;
                            case Win32API.ERROR_INTERNET_INVALID_CA:
                                dwError = Win32API.ERROR_INTERNET_INVALID_CA;
                                break;
                            case Win32API.ERROR_INTERNET_SEC_CERT_CN_INVALID:
                                dwError = Win32API.ERROR_INTERNET_SEC_CERT_CN_INVALID;
                                break;
                            case Win32API.ERROR_INTERNET_SEC_CERT_DATE_INVALID:
                                dwError = Win32API.ERROR_INTERNET_SEC_CERT_DATE_INVALID;
                                break;
                            case Win32API.ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED:
                                dwError = Win32API.ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED;
                                break;
                            default:
                                // Unknown error
                                throw new Exception("HttpSendRequest: Error #" + Marshal.GetLastWin32Error().ToString());
                        }

                        // Display cert error dialog box
                        dwFlags = Win32API.FLAGS_ERROR_UI_FLAGS_GENERATE_DATA + Win32API.FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS;
                        lppvData = IntPtr.Zero;
                        iResult = Win32API.InternetErrorDlg(hWnd, hRequest, dwError, dwFlags, lppvData);
                        switch (iResult)
                        {
                            case Win32API.ERROR_SUCCESS:
                                break;
                            case Win32API.ERROR_CANCELLED:
                                throw new Exception("InternetErrorDlg: The function was canceled by the user");
                            case Win32API.ERROR_INTERNET_FORCE_RETRY:
                                throw new Exception("InternetErrorDlg: Function needs to redo its request. In the case of authentication this indicates that the user clicked the OK button.");
                            case Win32API.ERROR_INVALID_HANDLE:
                                throw new Exception("InternetErrorDlg: The handle to the parent window is invalid");
                            default:
                                throw new Exception("InternetErrorDlg: Error #" + iResult.ToString());
                        }
                    }
                } while (!bResult);

                // Determine the amount of data available
                dwNumberOfBytesAvailable = 0;
                dwFlags = 0;
                dwContext = IntPtr.Zero;
                bResult = Win32API.InternetQueryDataAvailable(hRequest, ref dwNumberOfBytesAvailable, dwFlags, dwContext);
                if (!bResult)
                {
                    throw new Exception("InternetQueryDataAvailable: Error #" + Marshal.GetLastWin32Error().ToString());
                }

                // Read data
                lpBuffer = Marshal.AllocHGlobal(dwNumberOfBytesAvailable);
                dwNumberOfBytesToRead = dwNumberOfBytesAvailable;
                dwNumberOfBytesRead = 0;
                bResult = Win32API.InternetReadFile(hRequest, lpBuffer, dwNumberOfBytesToRead, ref dwNumberOfBytesRead);
                if (!bResult)
                {
                    throw new Exception("InternetReadFile: Error #" + Marshal.GetLastWin32Error().ToString());
                }
               
                // Everything went well. Return data
                return Marshal.PtrToStringAnsi(lpBuffer, dwNumberOfBytesRead);
            }
            catch (Exception ex)
            {
                // Show error
                MessageBox.Show("Exception: " + ex.Message);
                return "";
            }
            finally
            {
                // Clean up
                if (!lpBuffer.Equals(IntPtr.Zero))
                {
                    Marshal.FreeHGlobal(lpBuffer);
                }
                if (!hInternet.Equals(IntPtr.Zero))
                {
                    Win32API.InternetCloseHandle(hInternet);
                }
                if (!hConnect.Equals(IntPtr.Zero))
                {
                    Win32API.InternetCloseHandle(hConnect);
                }
                if (!hRequest.Equals(IntPtr.Zero))
                {
                    Win32API.InternetCloseHandle(hRequest);
                }
            }
        }
    }
}

These are the P/Invoke declarations of the APIs that I've used before: 

 using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace WindowsApplication1
{
    class Win32API
    {
        // #define INTERNET_OPEN_TYPE_DIRECT                       1 
        public const int INTERNET_OPEN_TYPE_DIRECT = 1;

        //#define INTERNET_OPEN_TYPE_PRECONFIG                    0
        public const int INTERNET_OPEN_TYPE_PRECONFIG = 0;

        // #define INTERNET_SERVICE_HTTP   3
        public const int INTERNET_SERVICE_HTTP = 3;

        // #define INTERNET_FLAG_SECURE            0x00800000 
        public const int INTERNET_FLAG_SECURE = 0x00800000;

        // #define INTERNET_OPTION_SECURITY_CERTIFICATE    35
        public const int INTERNET_OPTION_SECURITY_CERTIFICATE = 35;

        // #define ERROR_SUCCESS                    0L
        public const int ERROR_SUCCESS = 0;

        // #define ERROR_INVALID_HANDLE             6L 
        public const int ERROR_INVALID_HANDLE = 6;

        // #define ERROR_INSUFFICIENT_BUFFER        122L  
        public const int ERROR_INSUFFICIENT_BUFFER = 122;
        
        // #define ERROR_CANCELLED                  1223L 
        public const int ERROR_CANCELLED = 1223;
        
        // #define INTERNET_OPTION_ERROR_MASK       62
        public const int INTERNET_OPTION_ERROR_MASK = 62;

        // #define INTERNET_ERROR_MASK_COMBINED_SEC_CERT       0x2
        public const ulong INTERNET_ERROR_MASK_COMBINED_SEC_CERT = 0x2;

        // #define INTERNET_ERROR_BASE                     12000
        public const int INTERNET_ERROR_BASE = 12000;

        // #define ERROR_INTERNET_FORCE_RETRY              (INTERNET_ERROR_BASE + 32)
        public const int ERROR_INTERNET_FORCE_RETRY = INTERNET_ERROR_BASE + 32;

        // #define ERROR_INTERNET_SEC_CERT_DATE_INVALID              (INTERNET_ERROR_BASE + 37)
        public const int ERROR_INTERNET_SEC_CERT_DATE_INVALID = INTERNET_ERROR_BASE + 37;

        // #define ERROR_INTERNET_SEC_CERT_CN_INVALID              (INTERNET_ERROR_BASE + 38)
        public const int ERROR_INTERNET_SEC_CERT_CN_INVALID = INTERNET_ERROR_BASE + 38;

        // #define ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED              (INTERNET_ERROR_BASE + 44)
        public const int ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED = INTERNET_ERROR_BASE + 44;

        // #define ERROR_INTERNET_SEC_CERT_ERRORS              (INTERNET_ERROR_BASE + 55)
        public const int ERROR_INTERNET_SEC_CERT_ERRORS = INTERNET_ERROR_BASE + 55;       
        

        //#define ERROR_INTERNET_INVALID_CA               (INTERNET_ERROR_BASE + 45)
        public const int ERROR_INTERNET_INVALID_CA = INTERNET_ERROR_BASE + 45;

        // #define FLAGS_ERROR_UI_FILTER_FOR_ERRORS        0x01
        public const int FLAGS_ERROR_UI_FILTER_FOR_ERRORS = 0x01;

        // #define FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS     0x02
        public const int FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS = 0x02;

        // #define FLAGS_ERROR_UI_FLAGS_GENERATE_DATA      0x04
        public const int FLAGS_ERROR_UI_FLAGS_GENERATE_DATA = 0x04;

        // DWORD InternetErrorDlg(
        //  __in     HWND hWnd,
        //  __inout  HINTERNET hRequest,
        //  __in     DWORD dwError,
        //  __in     DWORD dwFlags,
        //  __inout  LPVOID *lppvData
        // );
        [DllImport("wininet.dll", SetLastError = true)]
        public extern static int InternetErrorDlg
        (
            IntPtr hWnd,
            IntPtr hRequest,
            int dwError,
            int dwFlags,
            IntPtr lppvData
        );

        // BOOL HttpSendRequest(
        //  __in  HINTERNET hRequest,
        //  __in  LPCTSTR lpszHeaders,
        //  __in  DWORD dwHeadersLength,
        //  __in  LPVOID lpOptional,
        //  __in  DWORD dwOptionalLength
        // );
        [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool HttpSendRequest
        (
            IntPtr hRequest,
            string lpszHeaders,
            int dwHeadersLength,
            IntPtr lpOptional,
            int dwOptionalLength
        );

        // HINTERNET HttpOpenRequest(
        //  __in  HINTERNET hConnect,
        //  __in  LPCTSTR lpszVerb,
        //  __in  LPCTSTR lpszObjectName,
        //  __in  LPCTSTR lpszVersion,
        //  __in  LPCTSTR lpszReferer,
        //  __in  LPCTSTR *lplpszAcceptTypes,
        //  __in  DWORD dwFlags,
        //  __in  DWORD_PTR dwContext
        // );
        [DllImport("wininet.dll", CharSet=CharSet.Auto, SetLastError = true)]
        public extern static IntPtr HttpOpenRequest
        (
            IntPtr hConnect,
            string lpszVerb,
            string lpszObjectName,
            string lpszVersion,
            string lpszReferer,
            IntPtr lplpszAcceptTypes,
            int dwFlags,
            IntPtr dwContext
        );

        // HINTERNET InternetConnect(
        //  __in  HINTERNET hInternet,
        //  __in  LPCTSTR lpszServerName,
        //  __in  INTERNET_PORT nServerPort,
        //  __in  LPCTSTR lpszUsername,
        //  __in  LPCTSTR lpszPassword,
        //  __in  DWORD dwService,
        //  __in  DWORD dwFlags,
        //  __in  DWORD_PTR dwContext
        // );
        [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static IntPtr InternetConnect
        (
            IntPtr hInternet,
            string lpszServerName,
            short nServerPort,
            string lpszUsername,
            string lpszPassword,
            int dwService,
            int dwFlags,
            IntPtr dwContext
        );

        // BOOL WINAPI InternetCloseHandle(
        //  HINTERNET hInternet
        // );
        [DllImport("wininet.dll", SetLastError = true)]
        public extern static bool InternetCloseHandle
        (
            IntPtr hInternet
        );

        // HINTERNET InternetOpen(
        //  __in  LPCTSTR lpszAgent,
        //  __in  DWORD dwAccessType,
        //  __in  LPCTSTR lpszProxyName,
        //  __in  LPCTSTR lpszProxyBypass,
        //  __in  DWORD dwFlags
        //);
        [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static IntPtr InternetOpen
        (
            string lpszAgent,
            int dwAccessType,
            string lpszProxyName,
            string lpszProxyBypass,
            int dwFlags
        );

        // BOOL InternetReadFile(
        //  __in   HINTERNET hFile,
        //  __out  LPVOID lpBuffer,
        //  __in   DWORD dwNumberOfBytesToRead,
        //  __out  LPDWORD lpdwNumberOfBytesRead
        // );
        [DllImport("wininet.dll", SetLastError = true)]
        public extern static bool InternetReadFile
        (
            IntPtr hFile,
            IntPtr lpBuffer,
            int dwNumberOfBytesToRead,
            ref int dwNumberOfBytesRead
        );

        // BOOL InternetQueryDataAvailable(
        //  __in   HINTERNET hFile,
        //  __out  LPDWORD lpdwNumberOfBytesAvailable,
        //  __in   DWORD dwFlags,
        //  __in   DWORD_PTR dwContext
        // );
        [DllImport("wininet.dll", SetLastError = true)]
        public extern static bool InternetQueryDataAvailable
        (
            IntPtr hFile,
            ref int dwNumberOfBytesAvailable,
            int dwFlags,
            IntPtr dwContext
        );

        // BOOL InternetSetOption(
        //  __in  HINTERNET hInternet,
        //  __in  DWORD dwOption,
        //  __in  LPVOID lpBuffer,
        //  __in  DWORD dwBufferLength
        // );
        [DllImport("wininet.dll", SetLastError = true)]
        public extern static bool InternetSetOption
        (
            IntPtr hInternet,
            int dwOption,
            ref ulong lpBuffer,
            int dwBufferLength
        );
    }
}

And this is a sample Form that shows how to use my sample code:

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.textBox1.Text = Tester.TestSSLRequest(this.Handle, "127.0.0.1", 443, "TEST.ASPX");
        }
    }
}

 

I hope this helps. 

Regards,

 

Alex (Alejandro Campos Magencio)