Verifying a JWT using the public key with C# - c#

I am building a React app backed by Azure functions written in C#. I've implemented JWT authentication via Userfront which is working fine on the front end but I'm struggling to verify the token using the public key in the functions.
I've tried numerous approaches, JWT-DotNet being the most recent but to no avail.
Can anyone please provide a working code example?
Here is what I have currently (which errors when creating the new RS256Algorithm with "Cannot find the requested object."):
var headers = req.Headers;
if (!headers.TryGetValue("Authorization", out var tokenHeader))
return new StatusCodeResult(StatusCodes.Status403Forbidden);
var token = tokenHeader[0].Replace("Bearer ", "");
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("Userfront_PublicKey"));
var urlEncoder = new JwtBase64UrlEncoder();
var publicKey = urlEncoder.Encode(plainTextBytes);
try
{
IJsonSerializer serializer = new JsonNetSerializer();
var provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IJwtAlgorithm algorithm = new RS256Algorithm(new X509Certificate2(plainTextBytes));
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder,algorithm);
var json = decoder.Decode(token[0], publicKey, verify: true);
}
catch (TokenExpiredException)
...
catch (SignatureVerificationException)
...

Assuming that the environment variable "Userfront_PublicKey" contains a PEM-encoded RSA public key, i.e.:
-----BEGIN RSA PUBLIC KEY-----
(your base64-encoded RSA public key)
-----END RSA PUBLIC KEY-----
then I would try the following (not tested, sorry):
var headers = req.Headers;
if (!headers.TryGetValue("Authorization", out var tokenHeader))
return new StatusCodeResult(StatusCodes.Status403Forbidden);
var token = tokenHeader[0].Replace("Bearer ", "");
var publicKeyPem = Environment.GetEnvironmentVariable("Userfront_PublicKey");
var publicKey = RSA.Create();
publicKey.ImportFromPem(publicKeyPem);
try
{
var json = JwtBuilder.Create()
.WithAlgorithm(new RS256Algorithm(publicKey))
.MustVerifySignature()
.Decode(token);
}
catch (TokenExpiredException)
...
catch (SignatureVerificationException)
...

Related

How do I send the data to API with encrypted data and signature with private key generated by RSA

I am C# developer and I have the below code works well in C# application. I am trying to use the same functionality in the VueJS and Node Js application. I have done with serialization and conversion to fromBase64String, but I am stuck here with generating signature to send it to API. please suggest.
byte[] privateKey = Convert.FromBase64String(File.ReadAllText(appConfig.PrivateKey));
using (CngKey signingKey = CngKey.Import(privateKey, CngKeyBlobFormat.Pkcs8PrivateBlob))
using (RSACng rsaCng = new RSACng(signingKey))
{
request.Data.Signature = Convert.ToBase64String(rsaCng.SignData(Encoding.UTF8.GetBytes(request.Data.Content), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1));
}
var response = (Response)SendRequest(request, 1);
return response;
I have tried the below in Vue and node Js code
var utf8 = (encodeURIComponent(serializedProductInfo));
var dataInfo = [];
for (var i = 0; i < utf8.length; i++) {
dataInfo.push(utf8.charCodeAt(i));
}
let base64Contenet = window.btoa((encodeURIComponent(dataInfo)));
//let base64Contenet = Buffer.toString((encodeURIComponent(dataInfo)));
console.log(base64Contenet);
var privateKey = [];
privateKey = Buffer.from("My PrivateKey", "base64");
var crypto = require('crypto');
var sha1 = crypto.createHash('sha1');
sha1.key = privateKey;
sha1.update(dataInfo);
var hash = sha1.digest();
var signature = crypto.privateEncrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_PADDING }, hash);
console.log(signature);
I am getting the below error
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in v-on handler: "TypeError: Cannot read properties of null (reading '2')"

How create a jwt signature using a private key and a rsasha256 algorithm (.net)?

I am trying to sign a JWT with a private key and the rsasha256 algorithm. Everytime a try to create the tokenHandler, it goes into the Catch part.
The exception that I get is:
System.Exception: 'Erro ao obter ID do processo: IDX10634: Unable to create the SignatureProvider.
Algorithm: 'System.String', SecurityKey: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey'
is not supported. The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms'
var privateKey = File.ReadAllText(#"C:\Cencosud\Git\APIs\Api_Unico\Unico.Infra.Data\API.Services\Implementations\.key.pem");
privateKey = privateKey.Replace("-----BEGIN PRIVATE KEY-----", string.Empty).Replace("-----END PRIVATE KEY-----", string.Empty);
privateKey = privateKey.Replace(Environment.NewLine, string.Empty);
var privateKeyBytes = Encoding.UTF8.GetBytes(privateKey);
var tokenHandler = new JwtSecurityTokenHandler();
var key = privateKeyBytes;
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
{
Issuer = "service_account_name#tenant_id.iam.acesso.io",
Audience = "https://identityhomolog.acesso.io",
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.RsaSha256)
});
var jwt = tokenHandler.WriteToken(token);
A couple problems here:
The code is converting the private key as if it's a UTF8 string using Encoding.UTF8.GetBytes. Use Convert.FromBase64String instead.
The code is attempting to initialize SigningCredentials as a symmetric key but a private RSA key isn't symmetrical and needs to be created differently.
See my changes below:
var privateKey = File.ReadAllText(#"C:\Cencosud\Git\APIs\Api_Unico\Unico.Infra.Data\API.Services\Implementations\.key.pem");
privateKey = privateKey.Replace("-----BEGIN PRIVATE KEY-----", string.Empty).Replace("-----END PRIVATE KEY-----", string.Empty);
privateKey = privateKey.Replace(Environment.NewLine, string.Empty);
// CHANGE CONVERSION TYPE
var privateKeyBytes = Convert.FromBase64String(privateKey);
// INITIALIZE RSA
using var rsa = RSA.Create();
// Since the private key starts with "BEGIN PRIVATE KEY" it's PKCS8 encoded
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
{
Issuer = "service_account_name#tenant_id.iam.acesso.io",
Audience = "https://identityhomolog.acesso.io",
// CREATE SIGNING CREDENTIALS WITH THE RSA INITIALIZED ABOVE
SigningCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256)
});
var jwt = tokenHandler.WriteToken(token);

Create a valid JWT Token for DocuSign API

Ι try to create a valid jwt token
From settings i create an RSA keypairs and i get the private key without the "-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----
"
var rsaPrivateKey = #"MIIEogIBAAKCAQEAoGujdXbYVy68a4CSWz963SpYxVs20/..............HQ/jW8pFom6gJreCDkca5axYo/gXp3W3rQHFTkooTNbOk2MyFMZUqRD3aCG1wuUW3w8TgGX4slrLDV0pP4=";
var jwt = Sign(rsaPrivateKey);
I follow the instructions here https://developers.docusign.com/docs/admin-api/admin101/application-auth/ and after a lot of hours i create this method
public string Sign(string privateKey)
{
List<string> segments = new List<string>();
var header = new { alg = "RS256", typ = "JWT" };
//For production environments, use account.docusign.com
var payload = new
{
iss = "4f489d61-dc8b------a828-3992e670dcbc",
iat = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds,
aud = "account-d.docusign.com",
scope = "signature impersonation"
};
byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
segments.Add(Base64UrlEncode(headerBytes));
segments.Add(Base64UrlEncode(payloadBytes));
string stringToSign = string.Join(".", segments.ToArray());
byte[] bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
byte[] keyBytes = Convert.FromBase64String(privateKey);
var privKeyObj = Asn1Object.FromByteArray(keyBytes);
var privStruct = RsaPrivateKeyStructure.GetInstance((Asn1Sequence)privKeyObj);
ISigner sig = SignerUtilities.GetSigner("SHA256withRSA");
sig.Init(true, new RsaKeyParameters(true, privStruct.Modulus, privStruct.PrivateExponent));
sig.BlockUpdate(bytesToSign, 0, bytesToSign.Length);
byte[] signature = sig.GenerateSignature();
segments.Add(Base64UrlEncode(signature));
return string.Join(".", segments.ToArray());
}
private static string Base64UrlEncode(byte[] input)
{
var output = Convert.ToBase64String(input);
output = output.Split('=')[0]; // Remove any trailing '='s
output = output.Replace('+', '-'); // 62nd char of encoding
output = output.Replace('/', '_'); // 63rd char of encoding
return output;
}
When i check the JWT validation in this tool https://jwt.io/#debugger-io, i get invalid signature error.
How can i fix the token ?? I cant proceed with Step 2 Obtain the access token...
I'm sorry you having problems with JWT. I would recommend you use the DocuSign C# SDK instead of trying to write your own code.
Then you can find the example of how to use JWT here - https://github.com/docusign/code-examples-csharp.
The specific code relevant to JWT is here - https://github.com/docusign/code-examples-csharp/blob/38c2eb46948a3cbf55edcce758f88d775f80cae9/launcher-csharp/Common/RequestItemService.cs under the UpdateUserFromJWT() method.
Common problems with JWT:
Not obtaining consent.
Using public token instead of private.
Using malform token. Token must be exactly, including new-lines, as provided.
Not using correct UserId (GUID) in the request.
Not requesting "impersonation" scope in consent (#1 above).

Using Key by Azure Key Vault to Encrypt and Decrypt text

I'm trying use a key from Azure Key Vault to Encrypt and Decrypt the cookies of a web API.
To encryption proccess I'm using the RSA, in that class:
public class SimpleRSA
{
private RSA _rsa;
public SimpleRSA(RSA rsa)
{
_rsa = rsa;
}
public string EncryptAsync(string value)
{
var byteData = Encoding.Unicode.GetBytes(value);
var encryptedText = _rsa.Encrypt(byteData, RSAEncryptionPadding.OaepSHA1);
var encodedText = Convert.ToBase64String(encryptedText);
return encodedText;
}
public string DecryptAsync(string encryptedText)
{
var encryptedBytes = Convert.FromBase64String(encryptedText);
var decryptionResult = _rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA1);
var decryptedText = Encoding.Unicode.GetString(decryptionResult);
return decryptedText;
}
}
And I'm getting my RSA from the Key, using that code:
public RSA GetRSA(string appId, string appSecret)
{
AuthenticationCallback callback = async (authority, resource, scope) =>
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(appId, appSecret);
var authResult = await authContext.AcquireTokenAsync(resource, credential);
return authResult.AccessToken;
};
var client = new KeyVaultClient(callback);
var result = client.GetKeyAsync(_vaultBaseUrl, _keyId).Result;
var key = result.Key;
return key.ToRSA();
}
I got the RSA from my Azure Key Vault and I managed encrypt my string. The problem is when I'm trying Decrypt the value. In that process I got that error:
System.Security.Cryptography.CryptographicException: 'Error decoding OAEP padding.'
I think that can be happening because I'm without the private keys in RSA, but I've tried use this method to get the RSA with private key::
key.ToRSA(true);
But a got that error:
So, I don't know how I can complete this process. Are there other way to do that? Or what's wrong?
If you want to use Azure Key Vault to Encrypt and Decrypt text, you can use SDK Azure.Security.KeyVault.Keys to implement it.
For example
Install SDK
Install-Package Azure.Security.KeyVault.Keys -Version 4.0.3
Install-Package Azure.Identity -Version 1.1.1
Code
ClientSecretCredential clientSecretCredential = new ClientSecretCredential(tenantId, // your tenant id
clientId, // your AD application appId
clientSecret // your AD application app secret
);
//get key
var KeyVaultName = "<your kay vault name>";
KeyClient keyClient = new KeyClient(new Uri($"https://{KeyVaultName}.vault.azure.net/"), clientSecretCredential);;
var keyName="<your key name>"
var key = await keyClient.GetKeyAsync(keyName);
// create CryptographyClient
CryptographyClient cryptoClient = new CryptographyClient(key.Value.Id, clientSecretCredential);
var str ="test"
Console.WriteLine("The String used to be encrypted is : " +str );
Console.WriteLine("-------------encrypt---------------");
var byteData = Encoding.Unicode.GetBytes(str);
var encryptResult = await cryptoClient.EncryptAsync(EncryptionAlgorithm.RsaOaep, byteData);
var encodedText = Convert.ToBase64String(encryptResult.Ciphertext);
Console.WriteLine(encodedText);
Console.WriteLine("-------------dencrypt---------------");
var encryptedBytes = Convert.FromBase64String(encodedText);
var dencryptResult = await cryptoClient.DecryptAsync(EncryptionAlgorithm.RsaOaep, encryptedBytes);
var decryptedText = Encoding.Unicode.GetString(dencryptResult.Plaintext);
Console.WriteLine(decryptedText);
Key Vault can store three item types: Keys, Secrets and Certificates. Keys are always asymmetric - RSA or Elliptic Curve, and the private keys don't leave KV. What you need is to use a symmetric key, but you need to store that as a Secret, not a key.
So store a 256-bit random secret in KV, call it MyCoolCryptoKey, pull that symmetric key into your C# code and use that as a key for AES.

Nodejs verify jwt token from .net failed

I create token from .net by this C# code (with System.IdentityModel.Tokens.Jwt):
var keybytes = Convert.FromBase64String("MYCUSTOMCODELONGMOD4NEEDBEZE");
var signingCredentials = new SigningCredentials(
new InMemorySymmetricSecurityKey(keybytes),
SecurityAlgorithms.HmacSha256Signature,
SecurityAlgorithms.Sha256Digest);
var nbf = DateTime.UtcNow.AddDays(-100);
var exp = DateTime.UtcNow.AddDays(100);
var payload = new JwtPayload(null, "", new List<Claim>(), nbf, exp);
var user = new Dictionary<string, object>();
user.Add("userId", "1");
payload.Add("user", user);
payload.Add("success", true);
var jwtToken = new JwtSecurityToken(new JwtHeader(signingCredentials), payload);
var jwtTokenHandler = new JwtSecurityTokenHandler();
var resultToken = jwtTokenHandler.WriteToken(jwtToken);
I send the resultToken to nodejs and verify it (with jsonwebtoken library) with below code:
var jwt = require('jsonwebtoken');
var result = jwt.verify(
resultToken,
new Buffer('MYCUSTOMCODELONGMOD4NEEDBEZE').toString('base64'),
{ algorithms: ['HS256'] },
function(err, decoded) {
if (err) {
console.log('decode token failed with error: '+ JSON.stringify(err));
}
}
);
I got the error: invalid signature. The resultToken content:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0OTQ4MTMxMTUsIm5iZiI6MTQ3NzUzMzExNSwidXNlciI6eyJ1c2VySWQiOiIxIn0sInN1Y2Nlc3MiOnRydWV9.4bjYyIUFMouz-ctFyxXkJ_QcJJQofCEFffUuazWFjGw
I have debug it on jwt.io with above signature (MYCUSTOMCODELONGMOD4NEEDBEZE) and secret base64 encoded checked, it's ok.
I have tried a signature without base64 encoded by chaging keybytes in C# code:
var keybytes = Encoding.UTF8.GetBytes("MYCUSTOMCODELONGMOD4NEEDBEZE");
And it verified successfully in nodejs. So i think the issue comes from my nodejs code when verify a base64 encoded signature. Did i miss some options when verify token or somethings?
I have no idea what you did but this snippet is working for me with the token you provided above.
var jwt = require('jwt-simple')
var secret = new Buffer('MYCUSTOMCODELONGMOD4NEEDBEZE').toString('base64')
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0OTQ4MTMxMTUsIm5iZiI6MTQ3NzUzMzExNSwidXNlciI6eyJ1c2VySWQiOiIxIn0sInN1Y2Nlc3MiOnRydWV9.4bjYyIUFMouz-ctFyxXkJ_QcJJQofCEFffUuazWFjGw'
var decoded = jwt.decode(token, secret)
console.log(decoded)
Output:
❯ node jwt.js
{ exp: 1494813115,
nbf: 1477533115,
user: { userId: '1' },
success: true }
Using jsonwebtoken library
// var jwt = require('jwt-simple')
var jwt = require('jsonwebtoken');
var secret = Buffer.from('MYCUSTOMCODELONGMOD4NEEDBEZE', 'base64')
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0OTQ4MTMxMTUsIm5iZiI6MTQ3NzUzMzExNSwidXNlciI6eyJ1c2VySWQiOiIxIn0sInN1Y2Nlc3MiOnRydWV9.4bjYyIUFMouz-ctFyxXkJ_QcJJQofCEFffUuazWFjGw'
jwt.verify(token, secret, { algorithms: ['HS256'] }, function(err, decoded) {
if (err) {
console.log(err)
} else {
console.log(decoded)
}
})
Again still working fine.
The only difference i can see is the secret.

Categories

Resources