4

My project is building an authentication service based on .NET Core and the System.IdentityModel.Tokens.Jwt nuget package. We want to create JWT tokens that include the public key certificate (or certificate chain) that can be used to verify the JWT digital signatures. This is possible with commercial identity providers (SaaS), and is supported in the JWT specification by means of a header parameter called "x5c". But I have so far been unable to get this to work using System.IdentityModel.Tokens.Jwt.

I am able to create a JWT token signed using a certificate. The certificate is self-signed and created using openssl (commands included underneath). My test code in C# looks like this:

using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
// more usings..

public static string GenerateJwtToken(int exampleAccountId, string x509CertFilePath, string x509CertFilePassword)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var signingCert = new X509Certificate2(x509CertFilePath, x509CertFilePassword);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, exampleAccountId.ToString()) }),
        Expires = DateTime.UtcNow.AddDays(30),
        Audience = "myapp:1",
        Issuer = "self",
        SigningCredentials = new X509SigningCredentials(signingCert, SecurityAlgorithms.RsaSha512Signature),
        Claims = new Dictionary<string, object>()
        {
            ["test1"] = "hello world",
            ["test2"] = new List<int> { 1, 2, 4, 9 }
        }
    };

    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}

The generated token header deserializes to this in jwt.io:

{
  "alg": "RS512",
  "kid": "193A49ED67F22850F4A95258FF07571A985BFCBE",
  "x5t": "GTpJ7WfyKFD0qVJY_wdXGphb_L4",
  "typ": "JWT"
}

Thing is, I would like to get the "x5c" header parameter output as well. The reason for this is that my project is trying to include the certificate with the public key to validate the token signature inside the token itself, and "x5c" is a good way to do this. But I just cannot get this to work.

I have tried adding x5c manually with AdditionalHeaderClaims on SecurityTokenDescriptor, but it just isn't being output in the token.

Does anybody know how to do this, or can you point me to some solid resources on the subject?

By the way, this is how I generated the certificate used (on Windows):

openssl genrsa -out private2048b.key 2048 

openssl req -new -key private2048b.key -out myrequest2048.csr -config <path to openssl.cfg>

openssl x509 -req -days 3650 -in myrequest2048.csr -signkey private2048b.key -out public2048b.crt

openssl pkcs12 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in public2048b.crt -inkey private2048b.key -out mypkcs2048.pfx -name "Testtest"

The PFX is the file being read and used in the code.

Update for posterity

Using Abdulrahman Falyoun's answer, the final part of the code was updated to use token.Header.Add to manually add in the "x5c" header parameter, before serializing the JWT token. Token had to be cast as JwtSecurityToken. This worked, and created a token that was valid (and had a signature that could immediatly be verified) in https://jwt.io :

// create JwtSecurityTokenHandler and SecurityTokenDescriptor instance before here..

var exportedCertificate = Convert.ToBase64String(signingCert.Export(X509ContentType.Cert, x509CertFilePassword));
 
// Add x5c header parameter containing the signing certificate:
var token = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken;
token.Header.Add(JwtHeaderParameterNames.X5c, new List<string> { exportedCertificate });
 
return tokenHandler.WriteToken(token);
Grubl3r
  • 417
  • 1
  • 7
  • 19

1 Answers1

6

What is x5c?

The "x5c" (X.509 certificate chain) Header Parameter contains the X.509 public key certificate or certificate chain [RFC5280] corresponding to the key used to digitally sign the JWS. The certificate or certificate chain is represented as a JSON array of certificate value strings. Each string in the array is a base64-encoded (not base64url-encoded) DER [ITU.X690.2008] PKIX certificate value. The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. This MAY be followed by additional certificates, with each subsequent certificate being the one used to certify the previous one. The recipient MUST validate the certificate chain according to RFC 5280 [RFC5280] and consider the certificate or certificate chain to be invalid if any validation failure occurs. The use of this Header Parameter is OPTIONAL.

Note

From the security point of view - do not use the x5c certificate to validate the signature directly. In that case, anybody could just provide their own certificate and spoof any identity. The purpose of the x5t / x5t#S256 header is to identify the signer - check you trust the certificate provided by x5c or x5t#S256 (or its issuer) under the specified iss, only then you should validate the signature.

so to build the X509 chain

X509Chain chain = new X509Chain()
bool success = chain.Build(cert);

if (!success) throw Error

Then for each chain.ChainElements value, take the Certificate property RawValue property (and base64 encode it).

finally, you got the string for x5c and should only provide it to the headers of jwt.

See the following links

Hope it's useful.

#Edit

If the issue was to supply the x5c to the header, you have to add it using

token.Header.Add(name, value)
Saikat
  • 14,222
  • 20
  • 104
  • 125
Abdulrahman Falyoun
  • 3,676
  • 3
  • 16
  • 43
  • I appreciate the effort you took, but the problem is not really about creating the certificate chain. It is about having the System.IdentityModel.Tokens.Jwt nuget package (specifically the `JwtSecurityTokenHandler` class) output JWT tokens containing the x5c header parameter, instead of only the x5t header paramaeter. I will update the question to be clearer about this. – Grubl3r Sep 28 '20 at 09:08
  • The `SecurityTokenDescriptor.Claims` and `SecurityTokenDescriptor.AdditionalHeaderClaims` are currently only supported by the `Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.` – Abdulrahman Falyoun Sep 28 '20 at 10:58
  • When you install the `System.IdentityModel.Tokens.Jwt` nuget package, you also get `Microsoft.IdentityModel.Tokens`, so that you can use the `SecurityTokenDescriptor` class together with the `JwtSecurityTokenHandler` class. Adding x5c manually in `SecurityTokenDescriptor.AdditionalHeaderClaims` seems like the right thing to do, but unfortunately "x5c" is not being output. It seems like `JwtSecurityTokenHandler` just ignores it. – Grubl3r Sep 28 '20 at 11:05
  • Sorry for not providing any help – Abdulrahman Falyoun Sep 28 '20 at 11:15
  • Your suggestion to use `token.Header.Add(..)` (now removed) was brilliant! This solved the whole issue! If you can update your answer with this code snippet then I will grant you the bounty: https://pastebin.com/cBnkRFgu – Grubl3r Sep 28 '20 at 11:33
  • @Grubl3r Glad it solved your problem, question is updated – Abdulrahman Falyoun Sep 28 '20 at 13:33
  • I downgraded this because the x5c is the certificate part in the JWK and does not go into the headers of the JWT that is signed. The X5t is the value that is OPTIONALLY used in the Headers of a JWT. – Quadrivium Jun 07 '22 at 18:59