How to validate the JWT token of keycloak in C# .Net? - c#

Once we get the JWT token in the frontent, we can pass is using Authorization header or through cookies for authenticating our stateless RestAPIs in the backend server. Nicely explained in this video.
If the backend server is in C# .Net Framework (MVC), how can the received JWT be validated? The official documentation points towards OWIN, which is not maintained.
When seeing the various blogs and documentation, the theory says we need to get the Modulus & Exponent parameters from the Keycloak Realms' public certificate, and then verify it using JWT.Net
How can that be achieved?

Get the public certificate of your realm through keycloak:
You will get something like this that needs to be formatted properly:
Copy the PublicCertificate of your realm
Save it in a KeyCloakRealm.Public.crt file
Add header -----BEGIN CERTIFICATE-----
Make the single line certificate to be in 64bytes in each line
Add footer -----END CERTIFICATE-----
Then the following code snippet might be used to validate the received JWT token.
X509Certificate2 certificate = new
X509Certificate2("KeyCloakRealm.Public.crt");
RSACryptoServiceProvider key =(RSACryptoServiceProvider)certificate.PublicKey.Key;
RSAParameters rsaParameters = key.ExportParameters(false);
RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParameters);
var json = JwtBuilder.Create()
.WithAlgorithm(new RS256Algorithm(rsa)) // asymmetric
.MustVerifySignature()
.Decode(token);
// The above method will throw an appropriate error if the JWT is invalid or cannot be validated against the supplied public keycloak realm
// If there is no exception, you will get the data in your json object

Related

Web service call with a SHA-384 ECDSA certificate

For the project I am working on I have to convert the following legacy code to use a SHA-384 ECDSA certificate, .NET Framework 4.8 as target.
Is it possible natively in C# or do I need some help like BouncyCastle lib and how?
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
[...]
// Certificate is in PKCS#12 format and contains a private key, certificate is password protected with the session id
var certificateBytes = Convert.FromBase64String(base64Certificate.ToString());
var certificate = new X509Certificate2();
certificate.Import(certificateBytes, sessionId, X509KeyStorageFlags.DefaultKeySet);
var result = (HttpWebRequest)WebRequest.Create( url );
result.Headers.Add( "x-jwt-authorization", $"Bearer {JSONWebToken}" );
result.ClientCertificates.Add( Certificate );
var response = (HttpWebResponse)request.GetResponse();
I tried to find a solution/explanation online but didn't work (keep getting a "Not supported" for the PrivateKey property when trying to import the certificate, and a 403 Forbidden as server response).
Also if I want to keep backward compatibility, how do I distinguish if the Base64 string contains a X509 or a SHA one to support both?

C# Checking HMAC SHA256 from API

I'm trying to validate a hmac sha256 key that an API sends to me in a header. I don't fully understand how to validate this, can anoyone point me in the right direction?
From the API reference:
Every central webhook POST contains a header field
"X-Signature-SHA256" with the signature value of the signed body's
payload. The JSON body is signed with the HMAC SHA256 algorithm based
on RFC2104, with the "Client Secret" as the signing key.
The following awnser is pretty clear & helpfull:
https://stackoverflow.com/a/12253723/4179234
However I feel like I'm missing a string/message that I need to encrypt to get the same hmac sha256 key, as I only have the api client secret to use as a key for the hasing but no message.
Following part is taken from the above stackoverlow awnser, so I convert the api client secret to byte[] and use it for the first input var. But what should message be then in this case?
private static byte[] HashHMAC(byte[] key, byte[] message)
{
var hash = new HMACSHA256(key);
return hash.ComputeHash(message);
}

Azure Key Vault Certificates does not have the Private Key when retrieved via IKeyVaultClient.GetCertificateAsync

I have 2 approaches to do the same thing, but Azure has deprecated the one that works, and the other method doesn't work.
The approach that works, but is deprecated:
I store my PFX in Azure Key Vault Secrets. (when I create the secret I see a warning stating that this feature is deprecated)
and use the following code to retrieve it to create my certificate:
SecretBundle secret = await keyVaultClient.GetSecretAsync(keyVaultUrl, "MyCert-Secret");
X509Certificate2Collection exportedCertCollection = new X509Certificate2Collection();
exportedCertCollection.Import(Convert.FromBase64String(secret.Value));
X509Certificate2 certFromSecret = exportedCertCollection.Cast<X509Certificate2>().Single(s => s.HasPrivateKey);
credits to this answer
I'm able to use this certificate to host and access my application successfully.
The approach that doesn't work, but I should be using:
I store my certificate in the Azure Key vault Certificates
and use the following code to retrieve it and create the X509Certificate2:
CertificateBundle certificateBundle = await keyVaultClient.GetCertificateAsync(keyVaultUrl, "MyCert-Certificate");
X509Certificate2 certFromCertificate = new X509Certificate2(certificateBundle.Cer);
The problem with this approach is that the certificate does not contain the private key. i.e. certFromCertificate.HasPrivateKey is false.
My Questions
Why does certFromSecret have the PrivateKey, while certFromCertificate doesn't?
How can I retrieve a certificate from the key vault, where I can create a X509Certificate2 object to host my application in Kestrel with UseHttps.
The 2nd part of #Adrian's answer explains the concepts around the Azure KV Certificates very well, and I have changed my code as below to get the full certificate including the private keys:
SecretBundle secret = await kv.GetSecretAsync(keyVaultUrl, certName);
X509Certificate2 certificate =
new X509Certificate2(Convert.FromBase64String(secret.Value));
The trick was to use GetSecretAsync instead of GetCertificateAsync. Please refer to Adrian's SO answer to see why the secret had to be used to get the full certificate with Private keys.
Note that you should use "Certificate identifier" property (url with "/secrets/") from Azure certificate's property page.
The latest version of the SDK (Azure.Security.KeyVault.Certificates 4.2.0) now has the DownloadCertificateAsync method, which obtains the full cert (i.e. private key too) in a straightforward way.
The documentation states:
Because Cer contains only the public key, this method attempts to
download the managed secret that contains the full certificate.
X509Certificate2 cert = await certificateClient.DownloadCertificateAsync(certName);

Secure SOAP request (with WSE 3.0?) C#

I need to sign and encrypt a SOAP request with a certificate to access a WS method, but i'm getting the same response all the time:
"A security error was encountered when verifying the message".
I guess there's something wrong with my code rather than any other issue.
Here it is:
cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(sCertificatePath, sCertificatePassword);
userToken = new Microsoft.Web.Services3.Security.Tokens.UsernameToken(sUser, sPass, Microsoft.Web.Services3.Security.Tokens.PasswordOption.SendHashed);
secureToken = new Microsoft.Web.Services3.Security.Tokens.X509SecurityToken(cert);
encDataToken = new Microsoft.Web.Services3.Security.EncryptedData(secureToken);
mSignUsernameToken = new Microsoft.Web.Services3.Security.MessageSignature(userToken);
mSignSecurityToken = new Microsoft.Web.Services3.Security.MessageSignature(secureToken);
wsVehicleInfo = new wsBusiness.VehicleInfoWSImplService();
vehData = new wsBusiness.getVehicleInfoRequest();
vehData.vehicleRegistration = "XXXXYYY";
vehData.language = "es";
requestContext = wsVehicleInfo.RequestSoapContext;
requestContext.Security.Elements.Add(encDataToken);
requestContext.Security.Tokens.Add(secureToken);
requestContext.Security.Elements.Add(mSignSecurityToken);
requestContext.Security.Timestamp.TtlInSeconds = 300;
requestContext.Security.Tokens.Add(userToken);
Is it correct? Actually I got some questions:
I'm signing and encrypting with the same certificate issued by a CA. Don't I need to encrypt with the server's one? How can I get it?
Does order of XML elements generated matter in the request? Which should be the code order?
The algorithm used in the documentation to encrypt the soap body is "aes128-gcm" but I wasn't able to find it and instead using "aes128-cbc". May it cause any trouble?
Is it recommended the usage of WSE in this case? I read this:
"Instead of asymmetrically encrypting the message, WSE use an asymmetric algorithm with a public copy of the recipient's X.509 certificate to encrypt the symmetric key that was actually used to encrypt the message data.". No way to just encrypt the SOAP body from the request with the server's certificate with WSE instead of encrypting the symmetric key?

How to work with (and create) X509 Certificates for private/public key encryption of JWT Tokens

I am trying to figure out a way of authentication between two distributed services.
I don't want to have a shared secret distributed on every service host, because it would mean that once one host has been compromised, all hosts are compromised.
So my scenario is:
Host A knows the public key of Host B
Host A encodes and encryptes the jwt using Host B´s public key
Host B receives and decrypts the jwt using its private key, that it only knows itself.
The jose-jwt package:
https://github.com/dvsekhvalnov/jose-jwt
seems like a good option to me. Beside the signing of the jwt, it also supports encryption using private/public keys.
On the page there are the following examples for encoding and decoding a jwt:
Encode:
var publicKey=new X509Certificate2("my-key.p12", "password").PublicKey.Key as RSACryptoServiceProvider;
string token = Jose.JWT.Encode(payload, publicKey, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM);
Decode:
var privateKey=new X509Certificate2("my-key.p12", "password", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).PrivateKey as RSACryptoServiceProvider;
string json = Jose.JWT.Decode(token,privateKey);
Now, here is what i don´t understand:
How can I create a .p12 certificate file that only contains the public key information (for the host/service A that encodes the jwt) ?
.. and how can I create a .p12 certificate file that contains both, the public and the private key information (for the host/service B that decodes the jwt) ?
From all the research that I have done, i get the impression that you can either only make a .p12 file that contains both, or one that contains only the public key. But it seems there is no way to create two .p12 files, one with both information and one with only the public key. What am I missing?
Thanks for your answers.
Normally a PKCS12/PFX is not used for public-only, but you can do it if you like.
Assuming that cert.HasPrivateKey is true: cert.Export(X509ContentType.Pkcs12, somePassword) will produce a byte[] that you can write to "publicAndPrivate.p12" (or whatever).
Normally for a public-only certificate you'll write it down just as the X.509 data, either DER-binary or PEM-DER encoded. .NET doesn't make PEM-DER easy, so we'll stick with DER-binary. You can get that data by either cert.RawData, or cert.Export(X509ContentType.Cert) (both will produce identical results, since this export form has no random data in it). (publicOnly.cer)
If you really want a PKCS12 blob which has just the public certificate:
using (X509Certificate2 publicOnly = new X509Certificate2(publicPrivate.RawData))
{
return publicOnly.Export(X509ContentType.Pkcs12, somePassword);
}
The resulting byte[] could then be publicOnly.p12.

Categories

Resources