Search code examples
security.net-corejwtpublic-keyx509certificate2

Creating JWT token with x5c header parameter using System.IdentityModel.Tokens.Jwt


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);

Solution

  • 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)