QRS API call returns "The client certificate credentials were not recognized" - asp.net

Exported Qlik Sense certificate using QMC (client.pfx, root.cer, server.pfx).
Imported certificates into IIS web server using MMC. Server and client certificates to store Personal->Certificates, root to store Trusted Root Certification Authorities.
Requested QRS API from ASP.NET controller using QlikClient certificate from store (code below). Tried various user IDs and directories, including INTERNAL/sa_repository, but in all cases got an error "An error occurred while sending the request. The client certificate credentials were not recognized".
Endpoint for test : https://server:4242/qrs/about
I've searched the web but I haven't managed to find what I'm doing wrong, what credentials I should provide.
On the other hand, as I converted exported certificates to separate .key/.crt files (using https://www.markbrilman.nl/2011/08/howto-convert-a-pfx-to-a-seperate-key-crt-file/) and used them in the Postman from web server, it worked without any problem, actually with any UserId in header (i guess it's ignored in that case).
ASP.NET controller:
public X509Certificate2 LoadQlikCertificate()
{
X509Certificate2 certificate = null;
try
{
// Open certification store (MMC)
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
// Get certiface based on the friendly name
certificate = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(c => c.FriendlyName == "QlikClient");
// Logging for debugging purposes
if (certificate != null)
{
logger.Log(LogLevel.Warning, $"Certificate: {certificate.FriendlyName} {certificate.GetSerialNumberString()}");
}
else
{
logger.Log(LogLevel.Warning, $"Certificate: No certificate");
}
// Close certification store
store.Close();
// Return certificate
return certificate;
}
catch (Exception e)
{
...
}
}
/* Get Qlik API response
***********************/
[HttpGet("getqlikapi")]
public IActionResult GetQlikAPI()
{
// Get Qlik certificate
var certificate = this.LoadQlikCertificate();
try
{
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
// Set server name
string server = "server";
// HARDCODED USER AND DIRECTORY FOR TESTING
string userID = "sa_repository"; // tried also other user ids
string userDirectory = "INTERNAL";
// Set Xrfkey header to prevent cross-site request forgery
string xrfkey = "abcdefg123456789";
// Create URL to REST endpoint
string url = $"https://{server}:4242/qrs/about?xrfkey={xrfkey}";
// The JSON object containing the UserId and UserDirectory
string body = $"{{ 'UserId': '{userID}', 'UserDirectory': '{userDirectory}', 'Attributes': [] }}";
// Encode the json object and get the bytes
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
// Create the HTTP Request
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
// Add the method to authentication the user
request.ClientCertificates.Add(certificate);
// POST request will be used
request.Method = "POST";
// The request will accept responses in JSON format
request.Accept = "application/json";
// A header is added to validate that this request contains a valid cross-site scripting key (the same key as the one used in the url)
request.Headers.Add("X-Qlik-Xrfkey", xrfkey);
request.ContentType = "application/json";
request.ContentLength = bodyBytes.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(bodyBytes, 0, bodyBytes.Length);
requestStream.Close();
// Make the web request and get response
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream stream = response.GetResponseStream();
// Return string in response
//return new OkObjectResult(stream != null ? new StreamReader(stream).ReadToEnd() : string.Empty);
return new OkObjectResult("test");
}
catch (Exception e)
{
...
}
}

I ran into this issue on a system we are building.
The problem was that the user did not have rights to the certificate.
Open certificate manager (Start > Manage Computer Certificates)
Find the required certificate.
Right-click cert > All Tasks > Manage Private Keys > Add > [Select the appropriate user]
Note: Manage User Certificates does not have the Manage Private Keys option.

Related

Thirdparty certificate authentication in .net core API between client and server API

I am trying to implement the certificate authentication in .net core API(Server/target) and this API will be invoked in to another API(Client) .Here is the piece of code of client api which makes request to server/target api.But I'm facing an error on the server/target api .I'm running these two services from local and both certificates have already installed
Client side controller logic
[HttpGet]
public async Task<List<WeatherForecast>> Get()
{
List<WeatherForecast> weatherForecastList = new List<WeatherForecast>();
X509Certificate2 clientCert = Authentication.GetClientCertificate();
if (clientCert == null)
{
HttpActionContext actionContext = null;
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "Client Certificate Required"
};
}
HttpClientHandler requestHandler = new HttpClientHandler();
requestHandler.ClientCertificates.Add(clientCert);
requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
HttpClient client = new HttpClient(requestHandler)
{
BaseAddress = new Uri("https://localhost:11111/ServerAPI")
};
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/xml"));//ACCEPT head
using (var httpClient = new HttpClient())
{
//httpClient.DefaultRequestHeaders.Accept.Clear();
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44386/ServerAPI"),
Method = HttpMethod.Get,
};
request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT head
//using (var response = await httpClient.GetAsync("https://localhost:11111/ServerAPI"))
using (var response = await httpClient.SendAsync(request))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string apiResposne = await response.Content.ReadAsStringAsync();
weatherForecastList = JsonConvert.DeserializeObject<List<WeatherForecast>>(apiResposne);
}
}
}
return weatherForecastList;
}
authentication class
public static X509Certificate2 GetClientCertificate()
{
X509Store userCaStore = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
try
{
string str_API_Cert_Thumbprint = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindByThumbprint, str_API_Cert_Thumbprint, false);
X509Certificate2 clientCertificate = null;
if (findResult.Count == 1)
{
clientCertificate = findResult[0];
if(System.DateTime.Today >= System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()))
{
throw new Exception("Certificate has already been expired.");
}
else if (System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()).AddDays(-30) <= System.DateTime.Today)
{
throw new Exception("Certificate is about to expire in 30 days.");
}
}
else
{
throw new Exception("Unable to locate the correct client certificate.");
}
return clientCertificate;
}
catch (Exception ex)
{
throw;
}
finally
{
userCaStore.Close();
}
}
Server/target api code
[HttpGet]
public IEnumerable<WeatherForecast> Getcertdata()
{
IHeaderDictionary headers = base.Request.Headers;
X509Certificate2 clientCertificate = null;
string certHeaderString = headers["X-ARR-ClientCert"];
if (!string.IsNullOrEmpty(certHeaderString))
{
//byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
//byte[] bytes = Convert.FromBase64String(certHeaderString);
//clientCertificate = new X509Certificate2(bytes);
clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
}
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
//return new List<WeatherForecast>();
}
You have much more problems here, the code is significantly flawed and insecure in various ways. Let's explain each issue:
HttpClient in using clause in client side controller logic
Although you expect to wrap anything that implements IDisposable in using statement. However, it is not really the case with HttpClient. Connections are not closed immediately. And with every request to client controller action, a new connection is established to remote endpoint, while previous connections sit in TIME_WAIT state. Under certain constant load, your HttpClient will exhaust TCP port pool (which is limited) and any new attempt to create a new connection will throw an exception. Here are more details on this problem: You're using HttpClient wrong and it is destabilizing your software
Microsoft recommendation is to re-use existing connections. One way to do this is to Use IHttpClientFactory to implement resilient HTTP requests. Microsoft article talks a bit about this problem:
Though this class implements IDisposable, declaring and instantiating
it within a using statement is not preferred because when the
HttpClient object gets disposed of, the underlying socket is not
immediately released, which can lead to a socket exhaustion problem.
BTW, you have created a client variable, but do not use it in any way.
Ignore certificate validation problems
The line:
requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
make you vulnerable to MITM attack.
you are doing client certificate authentication wrong
The line:
request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
It is not the proper way how to do client cert authentication. What you literally doing is passing certificate's public part to server. That's all. You do not prove private key possession which is required to authenticate you. The proper way to do so is:
requestHandler.ClientCertificates.Add(clientCert);
This will force client and server to perform proper client authentication and check if you possess the private key for certificate you pass (it is done in TLS handshake automatically). If you have ASP.NET on server side, then you read it this way (in controller action):
X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
if (clientCert == null) {
return Unauthorized();
}
// perform client cert validation according server-side rules.
Non-standard cert store
In authentication class you open StoreName.TrustedPeople store, while normally it should be StoreName.My. TrustedPeople isn't designed to store certs with private key. It isn't a functional problem, but it is bad practice.
unnecessary try/catch clause in authentication class
If you purposely throw exceptions in method, do not use try/catch. In your case you simply rethrow exception, thus you are doing a double work. And this:
throw new Exception("Certificate is about to expire in 30 days.");
is behind me. Throwing exception on technically valid certificate? Really?
server side code
As said, all this:
IHeaderDictionary headers = base.Request.Headers;
X509Certificate2 clientCertificate = null;
string certHeaderString = headers["X-ARR-ClientCert"];
if (!string.IsNullOrEmpty(certHeaderString))
{
//byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
//byte[] bytes = Convert.FromBase64String(certHeaderString);
//clientCertificate = new X509Certificate2(bytes);
clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
}
must be replaced with:
X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
if (clientCert == null) {
return Unauthorized();
}
// perform client cert validation according server-side rules.
BTW:
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
This is another disaster in your code. You are loading the server certificate from PFX just to compare their thumbprints? So, you suppose that client will have a copy of server certificate? Client and server certificates must not be the same. Next thing is you are generating a lot of copies of server certificate's private key files. More private key files you generate, the slower the process is and you just generate a lot of garbage. More details on this you can find in my blog post: Handling X509KeyStorageFlags in applications

How to get Authorization_code and access_Token and send envelope DocuSign using asp.net

I am trying to send envelope using docuSign, but having error while get authorization_code which need to get access_Token.
Please help me to get authorization_Code and access_Token.
I am using ASP.Net web forms and .NET framework 4.5.2
DocuSign.eSign.dll 5.2.0
DocuSign.Integration.Client.dll 1.7.2
Request:
https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature&client_id=4f464e25-6425-4ea6-915b-aa9bac5b8ce7&redirect_uri=https://account-d.docusign.com/ds/login?authType=JWT
Response:
The redirect redirect_uri is not registered properly with DocuSign
string RedirectURI = "https://account-d.docusign.com/ds/login?authType=JWT";
string ClientSecret = "****";
string IntegratorKey = "****";
Uri oauthLoginUrl = GetAuthorizationUri(IntegratorKey, scopes, RedirectURI, OAuth.CODE, null);
WebRequest request = WebRequest.Create(oauthLoginUrl);
WebResponse response = request.GetResponse();
public Uri GetAuthorizationUri(string clientId, List<string> scopes, string redirectUri, string responseType, string state = null)
{
string formattedScopes = (scopes == null || scopes.Count < 1) ? "" : scopes[0];
StringBuilder scopesSb = new StringBuilder(formattedScopes);
for (int i = 1; i < scopes.Count; i++)
{
scopesSb.Append("%20" + scopes[i]);
}
UriBuilder builder = new UriBuilder("https://account-d.docusign.com")
{
Scheme = "https",
Path = "/oauth/auth",
Port = 443,
Query = BuildQueryString(clientId, scopesSb.ToString(), redirectUri, responseType, state)
};
return builder.Uri;
}
Please make sure that the specified redirect URI is configured under the redirect URI section for the integration key that is being used. Please keep in mind that the same exact redirect URI has to be used when using your authentication URL.
The specified section is found under Settings > Apps and Keys > Click on your integration key > Edit
I would also recommend creating a new integration key, since you have shared it publicly on this thread.

HttpClient does not send cookies from CookieContainer

I'm developing a ASP WebAPI (ASP MVC 4) application with a WPF (.NET 4.0) client, using Visual Studio 2012. The client needs to login to the server. I use FormsAuthentication with an authentication cookie to login. The login already works fine in ASP MVC.
The problem is that, although the login is sucessfully executed on the server and the cookie is sent back to the client, the cookie is not sent in subsequent calls to the server, even though the CookieContainer is reused with the auth cookie set.
Here is a simplified version of the code:
CLIENT
public async Task<UserProfile> Login(string userName, string password, bool rememberMe)
{
using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
using (var httpClient = new HttpClient(handler))
{
httpClient.BaseAddress = new Uri("http://localhost:50000/");
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var result = await httpClient.PostAsJsonAsync("api/auth/login", new
{
username = userName,
password = password,
rememberMe = rememberMe
});
result.EnsureSuccessStatusCode();
var userProfile = await result.Content.ReadAsAsync<UserProfile>();
if (userProfile == null)
throw new UnauthorizedAccessException();
return userProfile;
}
}
public async Task<ExamSubmissionResponse> PostItem(Item item)
{
using (var handler = new HttpClientHandler() { CookieContainer = this.cookieContainer })
using (var httpClient = new HttpClient(handler))
{
httpClient.BaseAddress = new Uri("http://localhost:50000/");
var result = await httpClient.PostAsJsonAsync("api/Items/", item);
}
}
SERVER
[HttpPost]
public HttpResponseMessage Login(LoginModel model)
{
if (this.ValidateUser(model.UserName, model.Password))
{
// Get user data from database
string userData = JsonConvert.SerializeObject(userModel);
var authTicket = new FormsAuthenticationTicket(
1,
model.UserName,
DateTime.Now,
DateTime.Now.AddMinutes(10 * 15),
model.RememberMe,
userData
);
string ticket = FormsAuthentication.Encrypt(authTicket);
var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return response;
}
return null;
}
First I debugged the problem using Fiddler2 (I used the base address as "http://localhost.fiddler:50000/" to view local traffic). Then I suspected that fiddler might be interfering, so I just debugged with Visual Studio 2012.
What I have tried and verified:
The server is reached by the Login method
The user is sucessfully authenticated with the data sent from the client
The cookie is set on the server
The cookie is in the response (verified with fiddler)
The cookie is in the CookieContainer after the operation. There is a strange thing here: the domain of the cookie in the container is set as "localhost" (verified with VS2012 debugger). Shouldn't it be "http://localhost:50000" ? When I try to get the cookies of the container using cookieContainer.GetCookies(new Uri("http://localhost:50000")) it returns nothing. When I try it using cookieContainer.GetCookies(new Uri("localhost")) it gives me an invalid Uri error. Not sure what's going on here.
The cookie is in the container just before the PostItem request is made. The container is correctly set in the HttpClient when the statement httpClient.PostAsJsonAsync is reached.
The cookie is not sent to the server (I checked it with fiddler and in the Application_PostAuthenticateRequest method in the Global.asax.cs, verifying this.Request.Cookies)
I suspect the cookie is not being sent due to a domain mismatch in the CookieContainer, but why the domain is not set as it should in the CookieContainer in the first place?
Your problem is that you are not setting any path on the cookie that you send back from your Web Api controller.
There are two things that control where cookies are sent:
The domain of the cookie
The path of the cookie
Regarding the domain, the consensus seems to be that the port number should no longer (but still might) be a factor in evaluating the cookie domain. See this question for more info about how port number affects the domain.
About the path: Cookies are associated with a specific path in their domain. In your case, the Web Api is sending a cookie without specifying it's path. By default the cookie will then be associated with the path of the request/response where the cookie was created.
In your case the cookie will have the path api/auth/login. This means the the cookie will be sent to child paths (for lack of a better term) of this path but not to parent or sibling paths.
To test this, try:
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login")
This should give you the cookie. So should this:
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/login/foo/bar")
These on the other hand will not find the cookie:
cookieContainer.GetCookies(new Uri("http://localhost/")
cookieContainer.GetCookies(new Uri("http://localhost/api/")
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/")
cookieContainer.GetCookies(new Uri("http://localhost/api/auth/foo")
cookieContainer.GetCookies(new Uri("http://localhost/api/Items/")
To fix the issue, simply add the path "/" (or perhaps "/api") to the cookie before sending the resonse:
...
string ticket = FormsAuthentication.Encrypt(authTicket);
var cookie = new CookieHeaderValue(FormsAuthentication.FormsCookieName, ticket);
cookie.Path = "/";
var response = Request.CreateResponse(HttpStatusCode.Created, userModel);
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
...

Securing ASP.NET MVC controller action which returns JSON

I have an MVC3 application, and my controller actions are secured using the [Authorize] attribute. So far, so good, forms auth works great. Now I want to add a JSON API to my application so some actions are accessible to non-browser clients.
I'm having trouble figuring out the 'right' design.
1) Each user has secret API key.
2) User ID 5 calls http://myapp.com/foocontroller/baraction/5?param1=value1&param2=value2&secure_hash=someValue. Here, secure_hash is simply the SHA1 hash of param1 and param2's values appended with the secret API key for the user
2) /foocontroller/baraction will be decorated with [CustomAuthorize]. This will be an implementation of AuthorizeAttribute which will check if the request is coming in as JSON. If it is, it will check the hash and see if it matches. Otherwise, if the request is HTML, then I call into existing authorization.
I am not at all sure if this will work. Is it normal to pass a secure hash in the query string or should I be passing it in as an HTTP header? Is it better to use HTTP basic auth instead of a hash made using the secret API key?
Tips from anyone who has made a web API using ASP.NET MVC would be welcome!
I pass the secret API key along with username and password in the request body. Once authorized, a token is generated and the client has to pass that in the Authorization header. This gets checked in the base controller on each request.
Client calls myapp.com/authorize which return auth token.
Client stores auth token locally.
Client calls myapp.com/anycontroller, with authtoken in Authorization header.
AuthorizeController inherits from controller.
Anycontroller inherits from a custom base controller which performs the authorization code.
My example requires the following route which directs POST requests to an ActionResult named post in any controller. I am typing this in by hand to simplify it as much as possible to give you the general idea. Don't expect to cut and paste and have it work :)
routes.MapRoute(
"post-object",
"{controller}",
new { controller = "Home", action = "post" {,
new { httpMethod = new HttpMethodConstraint("POST")}
);
Your auth controller can use this
public class AuthorizationController : Controller
{
public ActionResult Post()
{
string authBody;
var request = ControllerContext.HttpContext.Request;
var response = ControllerContext.HttpContext.Response;
using(var reader = new StreamReader(request.InputStream))
authBody = reader.ReadToEnd();
// authorize based on credentials passed in request body
var authToken = {result of your auth method}
response.Write(authToken);
}
}
Your other controllers inherit from a base controller
public class BaseController : Controller
{
protected override void Execute(RequestContext requestContext)
{
var request = requestContext.HttpContext.Request;
var response = requestContext.HttpContext.Response;
var authToken = Request.Headers["Authorization"];
// use token to authorize in your own method
var authorized = AmIAuthorized();
if(authorized = false) {
response.StatusCode = 401;
response.Write("Invalid token");
return;
}
response.StatusCode = 200; // OK
base.Execute(requestContext); // allow inheriting controller to continue
}
}
Sample code to call the api
public static void ExecutePostRequest(string contentType)
{
request = (HttpWebRequest)WebRequest.Create(Uri + Querystring);
request.Method = "POST";
request.ContentType = contentType; // application/json usually
request.Headers["Authorization"] = token;
using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
writer.Write(postRequestData);
// GetResponse reaises an exception on http status code 400
// We can pull response out of the exception and continue on our way
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (WebException ex)
{
response = (HttpWebResponse)ex.Response;
}
finally
{
using (StreamReader reader =
new StreamReader(response.GetResponseStream()))
responseText = reader.ReadToEnd();
httpcontext = HttpContext.Current;
}
}

confusion about Certificates

I have WCF REST web service hosted by IIS, it works on HTTPS, I generate Certificate on IIS and assign Https to a port
I generate cer through IE browser. I create a test application and regardless Add a client certificate or not or even add a wrong certificate the connection take place and a I get correct response. I am wondering how the message was decrypted if there is no certificate sent.
Either the destination is not secured or I misunderstand the whole thing.
also
The error I have from the callback "CheckValidationResult()" is either
CertCN_NO_MATCH = 0x800B010F
or
"Unknown Certificate Problem" , the certificateProblem (parameter of CheckValidationResult) is 0 for this case
What is CertCN_NO_MATCH eror, what is CN?
See code below.
ServicePointManager.CertificatePolicy = new CertPolicy();
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(String.Format("https://{0}/uri", ip));
//request.ClientCertificates.Add(new X509Certificate("D:\\ThePubKey.cer"));
request.ContentType = "application/x-www-form-urlencoded";
request.Method = "POST";
using (StreamWriter stream = new StreamWriter(request.GetRequestStream()))
{
stream.Write("RequestType=CheckStatus&ReportType=Fulfillment&ReportID=5");
}
using (StreamReader stream = new StreamReader(request.GetResponse().GetResponseStream()))
{
Response.ContentType = "text/xml";
Response.Output.Write(stream.ReadToEnd());
Response.End();
}
class CertPolicy : ICertificatePolicy
{
public enum CertificateProblem : uint
{
CertEXPIRED = 0x800B0101,
CertVALIDITYPERIODNESTING = 0x800B0102,
CertROLE = 0x800B0103,
CertPATHLENCONST = 0x800B0104,
CertCRITICAL = 0x800B0105,
CertPURPOSE = 0x800B0106,
CertISSUERCHAINING = 0x800B0107,
CertMALFORMED = 0x800B0108,
CertUNTRUSTEDROOT = 0x800B0109,
CertCHAINING = 0x800B010A,
CertREVOKED = 0x800B010C,
CertUNTRUSTEDTESTROOT = 0x800B010D,
CertREVOCATION_FAILURE = 0x800B010E,
CertCN_NO_MATCH = 0x800B010F,
CertWRONG_USAGE = 0x800B0110,
CertUNTRUSTEDCA = 0x800B0112
}
public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem)
{
// You can do your own certificate checking.
// You can obtain the error values from WinError.h.
// Return true so that any certificate will work with this sample.
String error = "";
using (StringWriter writer = new StringWriter())
{
writer.WriteLine("Certificate Problem with accessing " + request.RequestUri);
writer.Write("Problem code 0x{0:X8},", (int)certificateProblem);
writer.WriteLine(GetProblemMessage((CertificateProblem)certificateProblem));
error = writer.ToString();
}
return true;
}
private String GetProblemMessage(CertificateProblem Problem)
{
String ProblemMessage = "";
CertificateProblem problemList = new CertificateProblem();
String ProblemCodeName = Enum.GetName(problemList.GetType(), Problem);
if (ProblemCodeName != null)
ProblemMessage = ProblemMessage + "-Certificateproblem:" +
ProblemCodeName;
else
ProblemMessage = "Unknown Certificate Problem";
return ProblemMessage;
}
}
I've just replied to this similar question (in Java).
CN is the "Common Name". It ought to be the hostname of the server to which you're connecting (unless it's in the subject alternative name). I guess from your code sample that you're using the IP address directly. In this case, the CN should be that IP address (it tends to be better to use a hostname rather than an IP address). See RFC 2818 (sec 3.1) for the specifications.
Note that the CN or subject alternative name is from the point of view of the client, so if you connect to https://some.example.com/, then the name in the cert should be some.example.com, if you connect to https://localhost/, then the name in the cert should be localhost, even if some.example.com and localhost may be the same server effectively.
(I guess that by default, IIS might generate a certificate for the external name, but you'd have to look at the certificate to know; this should be visible in the certificate properties somewhere.)

Resources