How to Add a Certificate to Webclient (C#)

How can you add a Certificate to WebClient (C#)?

You must subclass and override one or more functions.

class MyWebClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
request.ClientCertificates.Add(new X509Certificate());
return request;
}
}

Adding Certificate to WebClient

The answer is here: How can you add a Certificate to WebClient (C#)?

Converted to VB (untested):

Public Class MyWebClient
Inherits WebClient

Protected Overrides Function GetWebRequest(ByVal address As System.Uri) As System.Net.WebRequest
Dim R = MyBase.GetWebRequest(address)
If TypeOf R Is HttpWebRequest Then
With DirectCast(R, HttpWebRequest)
.ClientCertificates.Add(new X509Certificate())
End With
End If
Return R
End Function
End Class

Webclient DownloadFile with ClientCertificate

I finally get a solution: overriding WebClient !

New webClient :

public class MyWebClient : WebClient
{
X509Certificate2 certificate;

public MyWebClient(X509Certificate2 certificate)
: base()
{
this.certificate = certificate;
}

protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(address);
request.ClientCertificates.Add(certificate);
request.Credentials = this.Credentials;
return request;
}
}

The way to use it :

using (var client = new MyWebClient(MyCertificate))
{
// optional login/password if website require both. If not, don't set the credentials
client.Credentials = new System.Net.NetworkCredential(MyLogin, MyPassword);
client.DownloadFile(MyUrl, MyFile);
}

Using System.Net.WebClient with HTTPS certificate

If you're not using client certificates and you can access your server using https:// then your code should look like:

private static WebClient client = new WebClient();
private static NameValueCollection nvc= new NameValueCollection();

nvc.Add(POST_ACTION, ACTION_CODE_LOGIN);
nvc.Add(POST_EMAIL, email);
nvc.Add(POST_PASSWORD, password);

sResponse = System.Text.Encoding.ASCII.GetString(client.UploadValues(BASE_URL + ACTION_PAGE, nvc));

As long as your BASE_URL uses https:// then all the data (to and from the server) will be encrypted.

IOW using SSL/TLS from a client to a server does not require the client to do anything special (with the certificate), besides using https:// as the scheme, since the operating system provides everything (e.g. trusted roots) you need to secure the data transmission.

Add client certificate to .NET Core HttpClient

Make all configuration in Main() like this:

public static void Main(string[] args)
{
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
string env="", sbj="", crtf = "";

try
{
var whb = WebHost.CreateDefaultBuilder(args).UseContentRoot(Directory.GetCurrentDirectory());

var environment = env = whb.GetSetting("environment");
var subjectName = sbj = CertificateHelper.GetCertificateSubjectNameBasedOnEnvironment(environment);
var certificate = CertificateHelper.GetServiceCertificate(subjectName);

crtf = certificate != null ? certificate.Subject : "It will after the certification";

if (certificate == null) // present apies even without server certificate but dont give permission on authorization
{
var host = whb
.ConfigureKestrel(_ => { })
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseConfiguration(configuration)
.UseSerilog((context, config) =>
{
config.ReadFrom.Configuration(context.Configuration);
})
.Build();
host.Run();
}
else
{
var host = whb
.ConfigureKestrel(options =>
{
options.Listen(new IPEndPoint(IPAddress.Loopback, 443), listenOptions =>
{
var httpsConnectionAdapterOptions = new HttpsConnectionAdapterOptions()
{
ClientCertificateMode = ClientCertificateMode.AllowCertificate,
SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
ServerCertificate = certificate
};
listenOptions.UseHttps(httpsConnectionAdapterOptions);
});
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseUrls("https://*:443")
.UseStartup<Startup>()
.UseConfiguration(configuration)
.UseSerilog((context, config) =>
{
config.ReadFrom.Configuration(context.Configuration);
})
.Build();
host.Run();
}

Log.Logger.Information("Information: Environment = " + env +
" Subject = " + sbj +
" Certificate Subject = " + crtf);
}
catch(Exception ex)
{
Log.Logger.Error("Main handled an exception: Environment = " + env +
" Subject = " + sbj +
" Certificate Subject = " + crtf +
" Exception Detail = " + ex.Message);
}
}

Configure file startup.cs like this:

#region 2way SSL settings
services.AddMvc();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CertificateAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CertificateAuthenticationDefaults.AuthenticationScheme;
})
.AddCertificateAuthentication(certOptions =>
{
var certificateAndRoles = new List<CertficateAuthenticationOptions.CertificateAndRoles>();
Configuration.GetSection("AuthorizedCertficatesAndRoles:CertificateAndRoles").Bind(certificateAndRoles);
certOptions.CertificatesAndRoles = certificateAndRoles.ToArray();
});

services.AddAuthorization(options =>
{
options.AddPolicy("CanAccessAdminMethods", policy => policy.RequireRole("Admin"));
options.AddPolicy("CanAccessUserMethods", policy => policy.RequireRole("User"));
});
#endregion

The certificate helper

public class CertificateHelper
{
protected internal static X509Certificate2 GetServiceCertificate(string subjectName)
{
using (var certStore = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
certStore.Open(OpenFlags.ReadOnly);
var certCollection = certStore.Certificates.Find(
X509FindType.FindBySubjectDistinguishedName, subjectName, true);
X509Certificate2 certificate = null;
if (certCollection.Count > 0)
{
certificate = certCollection[0];
}
return certificate;
}
}

protected internal static string GetCertificateSubjectNameBasedOnEnvironment(string environment)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile($"appsettings.{environment}.json", optional: false);

var configuration = builder.Build();
return configuration["ServerCertificateSubject"];
}
}

Accessing a cross-signed SSL service with a .net WebClient

If your computer trusts the Root CA, then you trust certificates issued by that CA. You need to add the Digicert Root CA certificate to the computer's Trusted Root CA group. This can be done using the Certificates MMC snap-in for the computer account.

To Export the Root CA Certificate

  1. View the site's certificate from the browser.
  2. On the Certification Path tab, select the Root CA cert (at the top of the chain).
  3. Click the View Certificate button
  4. On the Details tab of the Root CA cert click Copy to File. (DER encoded .CER is fine).

To trust the Root CA

  1. Open the Certificates MMC snap-in for the Computer (start > mmc > Add snap-in > certificates)
  2. Navigate to the "Trusted Root Certification Authorities" folder
  3. Right-click on the Certificates folder, choose All Tasks > Import.
  4. Browse to the .cer file and follow the wizard.

If WebClient adds a cert to request.ClientCertificates will the cert be found at context.Request.ClientCertificate in the web app?

The reason why the certificate is not showing up in the HttpContext is the certificate authentication hasn’t been established yet between the client-side and the server-side.
Simply speaking, when the web application is hosted in IIS, we disable other authentication modes in IIS and enable the IIS client certificate mapping authentication. the server requires a client certificate when the client tries to access the website/service.

Sample Image

Sample Image

Sample Image

Subsequently, the below function method will have a returned value.

public ActionResult About()
{
var result = System.Web.HttpContext.Current.Request.ClientCertificate;
ViewBag.Message = result.Subject+result.ServerSubject;
return View();
}

Sample Image

Please refer to the documentation for what is IIS Client Certificate Mapping Authentication and how to implement it in IIS.

https://learn.microsoft.com/en-us/iis/configuration/system.webserver/security/authentication/iisclientcertificatemappingauthentication/

https://learn.microsoft.com/en-us/troubleshoot/iis/configure-many-to-one-client-mappings

https://learn.microsoft.com/en-us/iis/manage/configuring-security/configuring-one-to-one-client-certificate-mappings

Feel free to let me know if there is anything I can help with.

Force HttpWebRequest to send client certificate

I resolved the problem, The point is that a P12 file (as a PFX) contains more then 1 certificate, so it must be loaded in this way:

X509Certificate2Collection certificates = new X509Certificate2Collection();
certificates.Import(certName, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

and added to a HttpWebRequest in this way: request.ClientCertificates = certificates;

Thanks everybody for support.

COMPLETE SAMPLE CODE

string host = @"https://localhost/";
string certName = @"C:\temp\cert.pfx";
string password = @"password";

try
{
X509Certificate2Collection certificates = new X509Certificate2Collection();
certificates.Import(certName, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(host);
req.AllowAutoRedirect = true;
req.ClientCertificates = certificates;
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
string postData = "login-form-type=cert";
byte[] postBytes = Encoding.UTF8.GetBytes(postData);
req.ContentLength = postBytes.Length;

Stream postStream = req.GetRequestStream();
postStream.Write(postBytes, 0, postBytes.Length);
postStream.Flush();
postStream.Close();
WebResponse resp = req.GetResponse();

Stream stream = resp.GetResponseStream();
using (StreamReader reader = new StreamReader(stream))
{
string line = reader.ReadLine();
while (line != null)
{
Console.WriteLine(line);
line = reader.ReadLine();
}
}

stream.Close();
}
catch(Exception e)
{
Console.WriteLine(e);
}


Related Topics



Leave a reply



Submit