4
votes

I have an AspNetCore application which generates a JWT token for me based on a PFX certificate.

public string GenerateToken()
{
    using (var certificate = new X509Certificate2("certificate.pfx"))
    {
        var credentials = new X509SigningCredentials(certificate);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new Claim[]
            {
                new Claim(ClaimTypes.Name, "Name"),
                new Claim(ClaimTypes.Role, "Tester"),
            }),
            IssuedAt = DateTime.UtcNow,
            Expires = DateTime.UtcNow.AddDays(1),
            SigningCredentials = credentials
        };

        var handler = new JwtSecurityTokenHandler();
        var token = handler.CreateToken(tokenDescriptor);
        return handler.WriteToken(token);
    }
}

And with the same PFX you can verify if the signature is valid.

public ClaimsPrincipal ValidateToken(string token)
{
    using (var certificate = new X509Certificate2("certificate.pfx"))
    {
        var key = new X509SecurityKey(certificate);
        var validationParameters = new TokenValidationParameters()
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = key
        };

        var handler = new JwtSecurityTokenHandler();
        try
        {
            var claims = handler.ValidateToken(token, validationParameters, out var validatedToken);
            if (claims != null && validatedToken != null)
            {
                return claims;
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
        }
    }

    return null;
}

But this requires for other applications which only need to validate the JWT token to also have access to this PFX certificate.

If I take a generated token with this PFX

eyJhbGciOiJSUzI1NiIsImtpZCI6IjZEQzE4QTU4MEI4QjIxNzE4MjY5MTdDNkRGRDdGNjM5NEFBMTAwNDgiLCJ4NXQiOiJiY0dLV0F1TElYR0NhUmZHMzlmMk9VcWhBRWciLCJ0eXAiOiJKV1QifQ.eyJ1bmlxdWVfbmFtZSI6Ik5hbWUiLCJyb2xlIjoiVGVzdGVyIiwibmJmIjoxNTY5NDI4MDIwLCJleHAiOjE1Njk1MTQ0MTksImlhdCI6MTU2OTQyODAxOX0.kmszDk3cYEfK7dOzynotOuzTkc0GFIMbRau5ELOUROz5De-6M4YA_tWpjH1Ss6wPM078RKZeMIpolMOGvCLaza1XUp2w7fiJ_zWNJ1r4XiYzfdGiHnrUM7IwjYfwVbV7Ez2sk0FMmka-yO0UeHOKICEoxUAEBA_KzJiWHQfJJssOxcNdu4rwX1CeJ0xnJCKi77GDZ54GhdrQ_NuHzWOQZrsLa7_QfJtNTufOAzAg_kSI7XRCt6LOIkNTiENY_iJSnurlbg0O3VqkmlBvBEOS-ihd0h2Gt_AGEJ22OB4oh7Xip-xf7gm4dPR2u3ISEVmEq5H6J444CDuuKKulB7OLeKEnh9-2fGw8o-q-RIZeOo4r6UqxEW3n6TrEu7hIoAP72x5xc8ptpAPG9GtCsUjMgZcQV4QCLA9zxNxApC6J9cmgZgfI7kTGeardjsy32I23BjUl0AH0c4lh5I3SVeZB7W0mvqLj_UG_O8o57c-0dhU5kcYXJjI5gtQc1gba0ZCokA5NomnHmtpWuNvtRp4O2PwbyjM2O2RxxN07fAkPC1o4Ukm-b_n_OudyznLfmFZwv9wxqn5n-_vIy4WPuM1tm-LIzuXb0dRHDJrI8OPtj43VKNMEkQO604Fb13tVvj3iMB_n3ZWSMuLtMBLNeIED-3eynZfnr0vdMYktHNsvf6o

and place it in https://jwt.io/ I only need the public key to validate the signature.

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxd05ZB9CBfleKjaKzBTv
HylxmrEbm/yPLQKgB3oFwK4qCtcvr9dwsdK7iqFKqrmWIU/6CQrMdrx/k1s0NDWn
jdQCsnTpJ/i/rbGyasL80ilsXjOnA6G07OpvVO88Jn6J9Jz8oDsKSRpk1YV8Z6UM
pGnvlwhkhvTa43sICAPlbdc+FVzT5/3UGHiZ7D0M7I4cMLchrjQM+XP3M7AJl2vC
x/oo7twIzl/J4R5AvKlVz57NSggvyLkAIUIGEM8NpINt1f4aWEthWSLcMf8KUBJX
33MGDWb0HXaA/m1ovxWzmh001qa/miw8xl/ViurEeX9lZZjtNvW3PbV4TB4CxPUj
u61occD/Mek664PBMq+qztM22jg3tyAiIhyntDeLZtRpI3e1TwYMgF21PAc3Vskd
iuqhwFM9V/lVaRJJFJIqd94dOT20FZ78ECFCSnSEjNLaAd2Cm6WzoCf2qYPdb9ZD
2aRUm9+x0bbKxr7dFAM8mC753zuWpAPd0HGo6peAdVBQ4ZYQJ+9Px6lKcSsz4dMq
2OkoBNBCLgDaBZjfbNSxWt57OIDZpteFGgPETYf4JHs85/91JnztS/bAU1h0zrdh
L8s0l8IF89abOt5ZQ6PlwtljhMHBOjO41o/5pFo3hAEmTfv2aUjOH+amhbX9jeOJ
wFtHPr7inytlyltrrNwByeMCAwEAAQ==
-----END PUBLIC KEY-----

I would like that other application only require my public key so i do not have to distribute the PFX over multiple applications (which allows them to generate JWT tokens by themself).

How can I validate JWT tokens signed with a X509 certificate with only the public key in AspNetCore?

1
you can distribute only public part of certificate to validate the token. No need to distribute PFX to validate the token.Crypt32
I think you can create instances of X509SecurityKey that only contain a public key. Export the certificate without the private key, load up that there and see what happens? :)juunas
@juunas. The only way a X509SecurityKey is constructed is with a X509Certificate2. See docs.microsoft.com/en-us/dotnet/api/…. Isn't there another way?Andries
@Crypt32. Do you have an example for this? It would really help.Andries
Use RawData property on X509Certificate2 object and save byte array to a.CER file. Then in 2nd code snippet replace.PFX file with. CER file.Crypt32

1 Answers

0
votes

Base on the suggestion of @Crypt32 i was able to generate a seperate certificate.

using (var certificate = new X509Certificate2("certificate.pfx"))
{
    using (var file = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
    {
        await file.WriteAsync(certificate.RawData);
    }
}

With this new certificate I was able to validate tokens successfully, but when trying the create a new token I received an InvalidOperationException.

System.InvalidOperationException
  HResult=0x80131509
  Message=IDX10638: Cannot create the SignatureProvider, 'key.HasPrivateKey' is false, cannot create signatures. Key: [PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.].
  Source=Microsoft.IdentityModel.Tokens
  StackTrace:
   at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures)
   at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
   at Microsoft.IdentityModel.JsonWebTokens.JwtTokenUtilities.CreateEncodedSignature(String input, SigningCredentials signingCredentials)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateJwtSecurityTokenPrivate(String issuer, String audience, ClaimsIdentity subject, Nullable`1 notBefore, Nullable`1 expires, Nullable`1 issuedAt, SigningCredentials signingCredentials, EncryptingCredentials encryptingCredentials)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateToken(SecurityTokenDescriptor tokenDescriptor)

So this seems to be the way of distributing a JWT token validator.