403 Forbidden error while accessing Azure web api using certificate authentication - webclient

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.

Related

Failure to find private key in X509 Certificate on eToken

I have been issued a certificate that is stored on a SafeNet eToken. The public key is saved in a database on each user account. I use the code below to find a certificate on an eToken using the thumbprint. I then use the private key to generate a digital signature. It works perfectly when I run my code on my PC using IISExpress. However, when I publish the application to a server on IIS the application fails to find the certificate and therefore is unable to get the private key to sign.
I am new to cryptography and this could be the reason I am facing this issue. I am seeking your help:
Below is my code that works on IISExpress
public static X509Certificate2 GetCertificateFromStore(string thumbprint)
{
X509Store store = new X509Store(StoreName.My);
try
{
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = (X509Certificate2Collection)store.Certificates;
X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
if (currentCerts.Count > 0)
{
X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindByThumbprint, thumbprint, true);
if (signingCert.Count > 0)
return signingCert[0];
}
return null;
}
finally
{
store.Close();
}
}
Our security team who issue me the certificate on the eToken told me to try installing the SafeNet drivers on the server which I did but has not resolved the issue. I have also tried that goes through all possible store locations and store names searching for the certificate but still not able to find the certificate

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.

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

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.

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.