My question relates to the following posts:
HttpClient does not send client certificate on Windows using .NET Core
WebApi HttpClient not sending client certificate
My env:
Mac osx ( 10.15.6 )
Visual studio code for Mac ( 8.3.2 )
Goal:
I want to use self-signed certificates ( as an experiment ), to test TLS with c# and a nginx server. I can use this curl command to confirm that the crt and key allow me to access a resource:
curl --cert client.crt --key client.key -k https://localhost:443
I read the p12 keystore using this code
public X509Certificate2 readCertificates(String path, String password)
{
return new X509Certificate2(File.ReadAllBytes(path), password);
}
I use this code to make a Https request. The loaded certificate has a subject, identifier, and private key that I expect. This confirms the above step.
public async System.Threading.Tasks.Task validateRequest(X509Certificate2 certificate)
{
try
{
WebRequestHandler handler = new WebRequestHandler();
handler.ClientCertificates.Add(certificate);
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
HttpClient client = new HttpClient(handler);
HttpRequestMessage httpContent = new HttpRequestMessage(HttpMethod.Get, new Uri("https://localhost:443"));
HttpResponseMessage response = await client.SendAsync(httpContent);
string resultContent = response.Content.ReadAsStringAsync().Result;
if (response.IsSuccessStatusCode)
{
Console.WriteLine("## we authenticated...");
}
else {
Console.WriteLine("### we have an issue");
}
response.Dispose();
client.Dispose();
httpContent.Dispose();
}
catch (Exception e) {
Console.WriteLine("##### error:: " + e);
}
}
After executing this code, my ngix server states:
client sent no required SSL certificate while reading client request header
The c# side evalutes $resultContent$ to:
400 No required SSL certificate was sent
If I replace the below lines, I get a different error:
// take this line
ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
// replace it with the below
handler.ServerCertificateValidationCallback = (a, b, c, d) => { return true; };
I can observe an error on the ngix server with:
peer closed connection in SSL handshake while SSL handshaking
I can observe this error on the c# side:
Mono.Security.Interface.TlsException: CertificateUnknown
In reading the aforementioned stack posts, I was under the impression that I could read and ignore self-signed certificates by modifying ServerCertificateValidationCallback. This experiment is leading me to believe otherwise. Am I missing a key step in using self-signed certificates in making a https request?
Related
I am trying to use REST services from corporative (inner) site. That system is outsourcing (we can't change anything in it) and uses TLS 1.3. Issuer is
RapidSSL Global TLS RSA4096 SHA256 2022 CA1
and algorithm is SHA256withRSA
I have tried all the possible libraries including RestSharp, Flurl and HttpClient. Passing Tls13 protocol as parameter. The same problem exists with .NET Core 7 preview (which should have better TLS support).
My code looks like this:
var options = new RestClientOptions("https://xx.xxx.com/auth/login")
{
ThrowOnAnyError = true,
Timeout = 1000,
Expect100Continue = true,
};
var client = new RestClient(options);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13;
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
var response = client.Post(new RestRequest().AddJsonBody(new { user = "xxx#xxx.com", password = "123456" }));
or this code :
var tokenrequest = new TokenRequest() { User = "xxx", Password = "123" };
HttpClient _httpClient;
var httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
_httpClient = new HttpClient(httpClientHandler);
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls13;
var response = await _httpClient.PostAsync("https://yyy.com", CreateHttpContent<TokenRequest>(tokenrequest));
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsStringAsync();
Console.WriteLine(data);
In all cases I get the same error:
Authentication failed because the remote party sent a TLS alert:
'HandshakeFailure'.
The message received was unexpected or badly formatted.
Fiddler detailed error message says:
HTTPS handshake to yyy.com (for #7) failed.
System.Security.Authentication.AuthenticationException A call to SSPI
failed, see inner exception. < The message received was unexpected or
badly formatted
Win32 (SChannel) Native Error Code: 0x80090326
What OS are you using to run your app?
Because TLS 1.3 Clients are supported only on Windows 11/Server 2022, that is it's OS level error you have.
Most probably your machine is sending TLS handshake specifing it's max supported TLS version is 1.2 and it fails here because server is requiring 1.3 so you get 'HandshakeFailure'.
I'm actually trying to expose some methods of an ASP.NET MVC specific controller, in order to secure sensitive calls.
The entire website doesn't have to be protected by a specific SSL certificate, but some requests are.
Here is my code (as simple as it is) to get "Data", as you can see, I first check the SSL certificate, then the process continues if the SSL Certificate is correct :
public string GetData()
{
try
{
var certificate = Request.ClientCertificate;
if (certificate == null || string.IsNullOrEmpty(certificate.Subject))
{
// certificate may not be here
throw new Exception("ERR_00_NO_SSL_CERTIFICATE");
}
if (!certificate.IsValid || !IsMyCertificateOK(certificate))
{
// certificate is not valid
throw new Exception("ERR_01_WRONG_SSL_CERTIFICATE");
}
// Actions here ...
}
catch (Exception)
{
Response.StatusCode = 400;
Response.StatusDescription = "Bad Request";
}
}
Here is my IIS configuration :
SSL Certificate is set to "Accept", thus, I hope I could get the client certificate in the Request.ClientCertificate property, but it's never the case, I never get the certificate set in my client.
Here is my client code (copied from generated Postman C# code) :
string PFX_PATH = #"C:\Test\test.pfx"; // set here path of the PFX file
string PFX_PASSWORD = "password"; // set here password of the PFX file
var client = new RestClient("https://mywebsite.com/GetData?input=test");
client.Timeout = -1;
client.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection()
{
new System.Security.Cryptography.X509Certificates.X509Certificate(PFX_PATH,
PFX_PASSWORD,
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable)
};
var request = new RestRequest(Method.GET);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
The PFX file has a private key, and is accessible from client side.
Am I missing something regarding the IIS configuration, or should I update my web.config somehow ?
The location of the certificates
Inherit auth from parent
The test in postman
I want to use Restsharp to send this GET request -
This is what I want to send -
Request - GET,
URL - https://18.0.1.230:8080/api/report/test,
Header - Key = Content-Type , Value = application/x-www-form-urlencoded
On Postman , I am not sending anything on Authorisation type (its set on "Inherit auth from parent") check 'inherit auth from parent' image
I have set Client certificates in Postman, so I have set the location of them, which postman uses when sending this request. Check 'Location of certificate' image
I want to know how do I add these certificates to a GET request using restSharp c# code, so that I pass the authentication and can get a 200 response?
What you need to do is first import the certificates:
public static async Task<X509Certificate2> LoadPemCertificate(string certificatePath, string privateKeyPath)
{
using var publicKey = new X509Certificate2(certificatePath);
var privateKeyText = await File.ReadAllTextAsync(privateKeyPath);
var privateKeyBlocks = privateKeyText.Split("-", StringSplitOptions.RemoveEmptyEntries);
var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
using var rsa = RSA.Create();
if (privateKeyBlocks[0] == "BEGIN PRIVATE KEY")
{
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
}
else if (privateKeyBlocks[0] == "BEGIN RSA PRIVATE KEY")
{
rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
}
var keyPair = publicKey.CopyWithPrivateKey(rsa);
return new X509Certificate2(keyPair.Export(X509ContentType.Pfx));
}
Once you're you have loaded the certificate in memory, all you have to do is attach certificate to restsharp client:
var client = new RestClient("https://18.0.1.230:8080/api/report/test");
client.ClientCertificates.Add(new X509Certificate(certificate));
...
I have a c# .net Client with this Code:
using(WebClient client = new WebClient())
{
string serialisedData = "";
serialisedData = JsonConvert.SerializeObject(myData);
client.Credentials = new NetworkCredential(config.UserData.Username, config.UserData.Password);
byte[] responsebyte = client.UploadData(config.ServerAddress, System.Text.Encoding.UTF8.GetBytes(serialisedData));
}
That Client sends data to my nodejs Server.
Nodejs Code:
var http = require('http');
var _server = http.createServer(_listener);
_server.listen(1234);
console.log( 'started' );
function _listener(req, res) {
let data = []
req.on('data', chunk => {
data.push(chunk)
})
req.on('end', () => {
data = Buffer.concat(data);
var dataString = new Buffer.from(data).toString("utf-8");
const data = JSON.parse(dataString);
// data has all the data from the c# object "myData"
res.write('response')
res.end()
})
}
But how can I access the credentials of this connection?
This is how I can Access the credentials in c#:
HttpListener listener = new HttpListener();
listener.Prefixes.Add($"https://+:{Config.Port}/");
listener.AuthenticationSchemes = AuthenticationSchemes.Basic;
listener.Start();
for (; ; )
{
Console.WriteLine("Listening...");
IAsyncResult result = listener.BeginGetContext(new AsyncCallback(DoWork), listener);
result.AsyncWaitHandle.WaitOne();
result = null;
}
private void DoWork(IAsyncResult asyncResult)
{
HttpListener listener = (HttpListener)asyncResult.AsyncState;
HttpListenerContext context = listener.EndGetContext(asyncResult);
HttpListenerBasicIdentity identity = (HttpListenerBasicIdentity)context.User.Identity;
// identity has the credentials
}
Edit: I cant change the c# Code anymore. So only nodejs solutions are needed
Edit2: The headers also have no Auth or Authentification property…
Edit3: I cant even find if other location exists except the header for credentials/authentification. But this must be possible right? I mean c# can somehow read this stuff from somewhere…
Any Idea what I can try to find the credentials?
To make your C# client to send its networkCredentials as HTTP Basic Authentication to your Nodejs server; the server should return a response whose header contains a HTTP 401 Unauthorized status and a WWW-Authenticate field if the request does not contain the Authorization header. This will cause your C# client retry the POST with Authorization header.
This process it is called Authentication challenge in case you want to search for more info.
There are serveral packages that does that for you; like http-auth or you can code it by hand (it is not very hard as it is just a matter of checking the existence of the Authorization header in the request and, if there is none or incorrect credentials, make a 401 response with a WWW-Authenticate field)
i.e. from the top of my head:
var http = require('http');
var _server = http.createServer(listener);
_server.listen(1234);
console.log('started');
function listener(req, res) {
if (!req.headers.authorization) {
res.statusCode = 401;
res.statusMessage = 'Unauthorized';
res.setHeader('WWW-Authenticate', 'Basic');
res.end();
}
}
I talk to my server using SSL, but have self-signed cert.
In Android I use this code to pass my SSL cert to system to be able make requests to my server:
KeyStore ks = KeyStore.getInstance("BKS");
InputStream in = new ByteArrayInputStream(<byte [] my_ssl_data>);
ks.load(in, <string mypassword>.toCharArray());
in.close();
TrustManagerFactory Main_TMF = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
Main_TMF.init(ks);
X509TrustManager Cur_Trust_Manager = new X509TrustManager()
{
public void checkClientTrusted(X509Certificate [] chain, String authType) throws CertificateException { }
public void checkServerTrusted(X509Certificate [] chain, String authType) throws CertificateException { }
public X509Certificate [] getAcceptedIssuers() { return null; }
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { Cur_Trust_Manager }, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier()
{
#Override
public boolean verify(String hostname, SSLSession session)
{
try
{
Cur_Trust_Manager.checkServerTrusted((X509Certificate []) session.getPeerCertificates(), session.getCipherSuite());
return true;
}
catch (Exception e) {}
return false;
}
});
Now I need something like this in Windows Universal App 8.1+ (Windows+WindowsPhone) and iOS 7.0+.
For network requests I use System.Net.Http.HttpClient which works both with UWP and Xamarin.iOS. I have cert from my server in DER format but still can't add a handler to HttpClient.
Xamarin.iOS version says "Not implemented"
HttpClientHandler myHandler = new HttpClientHandler();
X509Certificate2 certificate = new X509Certificate2();
certificate.Import(my_cert_der_bytes);
myHandler.ClientCertificates.Add(certificate);
myHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
myHandler.AllowAutoRedirect = false;
HttpClient c = new HttpClient(myHandler);
....
UWP version unfortunately (and why the hell?) doesn't know X509Certificate2 and all that stuff. I tried to use WinRtHttpClientHandler but didn't understand where should I pass my cert. I tried to skip errors (started to work), yet that's not a solution, because I don't want my requests to be redirected to another untrasted server.
var filter = new HttpBaseProtocolFilter();
Certificate cer = new Certificate(my_cert_bytes.AsBuffer());
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
WinRtHttpClientHandler myHandler = new WinRtHttpClientHandler(filter);
HttpClient c = new HttpClient(myHandler);
....
I suspect this is common task for most indies, certs are seldom for tests and small apps. But it seems task is made very difficult by platform devs. Is there any reliable solution?
UWP apps cannot work with invalid (and self-signed) certificates. You may use Fiddler as proxy with fiddler's certificate for tests https.
Telerik Fiddler options - HTTPS - Capture HTTPS connects.
This work only on desktop not a mobile emulator.