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

My client-side code uses WebClient and appends a certificate to ClientCertificates:
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
var x509 = GetMyCert(); // see note
request.ClientCertificates.Add( x509 );
return request;
}
NOTE: I've checked in the debugger and variable x509 does indeed contain a valid certificate object at this point in the code.
Is that not the right way to add the cert to the HttpWebRequest so that it is available to the web app?
Server-side, the certificate isn't showing up in the ProcessRequest method of the IHttpHandler:
context.Response.Clear();
// set some Response caching parameters here <snip>
HttpClientCertificate cert = context.Request.ClientCertificate;
if (cert.IsPresent)
{
Hooray();
}
else
{
BooHoo(); // we are here
}
The web.config is set to accept a client certificate:
<system.webServer>
<security>
<access sslFlags="Ssl, SslNegotiateCert" />
</security>
</system.webServer>
Is the web app configuration incomplete?
P.S. If I add the callback to the overridden GetWebRequest method:
System.Net.ServicePointManager.ServerCertificateValidationCallback =
delegate (Object obj, X509Certificate X509certificate, X509Chain chain,
System.Net.Security.SslPolicyErrors errors)
{
return (errors == SslPolicyErrors.None);
};
errors is SslPolicyErrors.None and the callback returns true. But I have not created any policy on the server, to my knowledge, unless there's a default of some kind.

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.
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();
}
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.

Related

How to obtain the ADFS Public Key and validate the signature on a JWT Token?

I need to validate the signature of a JWT token which was signed on an ADFS server. I can validate a self-signed JWT token, but not a token received from ADFS. How should I be obtaining the public key?
To get the public key from the ADFS server I asked my colleague to export
the certificate from the ADFS server. On the ADFS console He looked in
"Services" > "Edit Federation Service Properties",
"General" tab where he found three entires. They were under the headings
"Token Signing", "Token Decrypting" and "Service Communication".
For each he viewed the certificate and then exported the DER certificate
(without private key). They were then transformed to PEM (using
openssl x509 -inform der -in cert.cer -out cert.pem) but none of these keys
allow my code to validate the sniffed JWT tokens.
My Java code takes a JWT Token and a Public Key, and validates that the token was signed with the Public Key. If I use a self-signed key pair and a self generated JWT Token then the code appears to work, and reports the signature is OK. When the token is copied from an HTTP Header in a message from ADFS the same code reports the signature is invalid.
Here is the code I use to load the PEM public key:
FileInputStream fis = new FileInputStream(publicKeyFile)
Reader keyReader = new InputStreamReader(publicKeyStream);
PemReader pemReader = new PemReader(keyReader);
PEMParser pemParser = new PEMParser(pemReader);
Object pemObject = pemParser.readObject();
if (pemObject instanceof X509CertificateHolder) {
X509CertificateHolder x509CertificateHolder = (X509CertificateHolder) pemObject;
X509Certificate x509Certificate = new JcaX509CertificateConverter()
.setProvider("BC").getCertificate(x509CertificateHolder);
return x509Certificate.getPublicKey();
}
Here is the code I use to validate the signature:
String signatureAlgorithm = publicKey.getAlgorithm();
Signature signatureInstance = Signature.getInstance(signatureAlgorithm);
signatureInstance.initVerify(publicKey);
byte[] messageBytes = Base64.encodeBase64(message.getBytes(UTF_8));
signatureInstance.update(messageBytes);
byte[] receivedSignature = Base64.decodeBase64URLSafe(signature);
return signatureInstance.verify(receivedSignature);
(I've removed the exception handling and resource closing for brevity above.
I'm using tomcat's Base64 class.)
The code runs without error but indicates that the signature is not valid. It seems to me that any of the below could be wrong:
None of the public keys extracted from the ADFS server are the relevant public key.
The conversion of the public keys from .cer to .pem may be incorrect.
The code to validate the signature, or load the public key, may be wrong.
Refer this and this.
The keys you refer to:
"Token Signing" - used for a SAML token derived from a CP or RP trust e.g. federation via SAML or WS-Fed
"Token Decrypting" - likewise
"Service Communication" - used for server SSL communication
For a JWT, I assume you are using OpenId Connect?
You use:
https://[Your ADFS hostname]/adfs/.well-known/openid-configuration
This has a pointer to the keys.

403 Forbidden error while accessing Azure web api using certificate authentication

I am getting 403 Forbidden exception while accessing simple azure webapi with certificate authentication, only while accessing from asp.net web application. I am using WebClient inherited object to overide GetWebRequest to add client certificate.
public class CertificateWebClient : WebClient
{
private readonly X509Certificate2 certificate;
public CertificateWebClient(X509Certificate2 cert)
{
certificate = cert;
}
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
request.ClientCertificates.Add(certificate);
return request;
}
}
And I am using the object CertificateWebClient DownloadString function to access the web api.
X509Certificate2 newCertificate = new X509Certificate2("bytearray");
CertificateWebClient webClient = new CertificateWebClient(newCertificate)
{
Proxy = new WebProxy("proxy address")
};
var result = webClient.DownloadString("https://webapiaddress/api/Values");
I am accessing the web api in the similar manner in windows forms and it works absolutely fine.
I could resolve this issue by storing and retrieving the certificate from Azure key vault in Base 64 string format. Using Convert.ToBase64String("raw certificate data") and Convert.FromBase64String("base64 cert string"), so I had to modify the above code as
X509Certificate2 newCertificate = new X509Certificate2(Convert.FromBase64String("base64 cert string"));
Only base 64 format keeps the certificate raw data intact while Adding/Retrieving certificate data to azure key vault.
The Certificate should have been ideally added to Azure key vault certificate store but the project requirement was to store it as key.

Unity WebClient SSL Timeout

I'm working on a Unity project that is reliyng on fetching data from a web API I set up on a public webserver. The server is currently set to self-signed ssl and requires the client to send certification to be able to read the data, if the client fails to send the cert the website returns with "403 forbidden".
I've tested this in the browser and postman and everything works fine.
I've also tested the exact same function in a pure visual studio project and it worked like a charm.
However, when I try this function in Unity I am met with the WebException "The request timed out
".
The way I'm currently doing it is via a WebClient, with an overrided method of WebRequest:
private void Connect()
{
ServicePointManager.CheckCertificateRevocationList = false;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback += ignoreCertCallback;
ServicePointManager.Expect100Continue = true;
using (var wc = new CertificateWebClient())
{
try
{
var responseBytes = wc.DownloadString(url);
Debug.Log(responseBytes);
Debug.Log(wc.ResponseHeaders);
}
catch (WebException e)
{
Debug.Log(e.ToString());
}
}
}
Override of WebRequest:
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
X509Certificate2 certificate = new X509Certificate2(#"C:\temp\ClientCert.pfx", "password");
request.ServerCertificateValidationCallback = delegate (System.Object obj, X509Certificate X509certificate, X509Chain chain, System.Net.Security.SslPolicyErrors errors)
{
return true;
};
Debug.Log(request.RequestUri);
(request as HttpWebRequest).ClientCertificates.Add(certificate);
request.Timeout = 2000;
return request;
}
Important to note is that I've tried the exact same functions inside of Unity with the "client.badssl.com" and their cert, and that also worked like a charm, returning the correct error codes when no cert is sent and everything and If I turn off client certification on my website, everything also works like charm...
From What I understand, It might be Mono that might be the problem as the certification is self-signed and not from a verified CA... But i've not been able to find a workaround... so any help would be great
What are your BG settings? I mean: is it on AWS? Express/NodeJS? WebGL? I am facing the same issue but when testing locally it works like a charm
I would suggest: to check if it is an infrastructure problem you might want to create a certificate for your domain (eg 'goodboy.mytest.io") that is certified from some free SSL providers (I can say LetsEncrypt just for testing) and launch a local server that's using that certificate, then go in your "hosts" file (depending on OS that you are currently running) and mock your localhost as "goodboy.mytest.io", so you can check if connecting to that domain everything goes fine without additional layers (usually placed between connection "bouncing" on web)
I'm following up this too

Dart Add SSL certificate and key to HttpClient

I'm trying to secure a REST API using TLS/SSL, to do so I needed to update my client to use the public key and certificate.
The client is written in dart and here's how I implemented the SecurityContext :
SecurityContext clientContext = SecurityContext.defaultContext;
var certificate = (await rootBundle.load("assets/ssl/coastr.crt")).buffer.asInt8List();
print(certificate.toString());
clientContext.setTrustedCertificatesBytes(certificate);
/*var authorities = (await rootBundle.load('assets/ssl/coastr.ca-bundle')).buffer.asUint8List();
print(authorities.toString());
clientContext.setClientAuthoritiesBytes(authorities);*/
var key = (await rootBundle.load("assets/ssl/coastr_public.key")).buffer.asInt8List();
print(key.toString());
clientContext.usePrivateKeyBytes(key);
HttpClient client = HttpClient(context: clientContext);
HttpClientRequest request = await client.getUrl(Uri.parse(url));
HttpClientResponse response = await request.close();
The certificate (.crt file) is added without issue to the clientContext but adding the key to it returns me this error :
[ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception:
TlsException: Failure in usePrivateKeyBytes (OS Error:
BAD_PKCS12_DATA(pkcs8_x509.c:606)
passed a null parameter(ssl_privkey.cc:375), errno = 0)
The files I'm using are :
coastr.crt with this as a header : -----BEGIN CERTIFICATE-----
coastr_public.key with header : -----BEGIN PUBLIC KEY-----
I have no idea if I'm providing the wrong files to the client or if the error comes from elsewhere.
The files where generated using openssl.
Thank you for your help.
In general, you shouldn't have to add anything to the client to allow it to connect to a correctly configured HTTPS server. Hopefully, the server has a signed server side certificate. If that certificate is signed by a reputable CA, the client should automatically trust it. If it is signed by an in house CA, or self signed, you need to do some work. You need to provide the client with the signing certificate. In the former case that would be the root CA's certificate. In the latter case, supplying the server's certificate may work, though it's probably easier to disable the client check altogether.
The signing certificate is likely to be in CRT form as you've found. And you need to supply that exactly as you are doing. There's no need to supply any public keys as the are distributed in the certificates sent with the server hello.
Unless you want to use a client side certificate, there's no need to supply a private key, so you can skip the step that is failing. And supplying a public key to it is definitely not going to work, anyway.

SSL certificate related issue while calling rest servcies

From client (eg: https://localhost:8080/) we are passing the certificate related values and calling the rest services ( hosted on different port - https://localhost:446/serviceName).
The issue is like, when we try to pass the certificate , SSL handshake is happening correctly (no error on debug) , but the certificate value is not passed to the service hosted on another port. Certificate value is accessed in server code by referring to (X509Certificate)httpReq.getAttribute("javax.servlet.request.X509Certificate");
Note : We use Spring boot application which intenally runs on tomcat server.And desired CA authorised certificates, keystore and truststore are present in resource path in both the projects (client and service hosted). In rest service project config file, the client-auth is set to false.
Sample code snippet used to call rest service:
SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(restserviceTruststore)
.loadKeyMaterial(restserviceKeyStore, password).build();
HttpClient client = HttpClients.custom() .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.setSslcontext(sslContext).build();
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(client));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<String> request = new HttpEntity<>(XML, headers);
response = restTemplate.postForObject(endpointURL, request, String.class);
Question:
1) From client what keystore and trustore should we need to pass to SSLContext? Is it server's keystore /truststore or clients?
2)What are the exact steps to be followed to resolve this issue.