How we can create AsymmetricSecurityKey in c#. Actually we are creating signing credentials with AsymetricSecurityKey here is our code:
// Define const Key this should be private secret key stored in some safe place
string key = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
// Create Security key using private key above:
// not that latest version of JWT using Microsoft namespace instead of System
var securityKey = new AsymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
// Also note that securityKey length should be >256b
// so you have to make sure that your private key has a proper length
//
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(securityKey, SecurityAlgorithms.HmacSha256Signature);
You can generate public/private keys using:
public void GenerateRsaCryptoServiceProviderKey()
{
var rsaProvider = new RSACryptoServiceProvider(512);
SecurityKey key = new RsaSecurityKey(rsaProvider);
}
You should use RsaSha256 below:
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
(key, SecurityAlgorithms.RsaSha256);
Signing credentials with a AsymmetricSecurityKey in C# using a RSA private key:
// RSA Private Key Base64
var privateKey = #"...";
var privateKeyBuffer = new Span<byte>(new byte[privateKey.Length]);
Convert.TryFromBase64String(privateKey, privateKeyBuffer, out _);
// abstract class RSA : AsymmetricAlgorithm in namespace System.Security.Cryptography
var rsaPrivateKey = RSA.Create();
rsaPrivateKey.ImportRSAPrivateKey(privateKeyBuffer, out _);
// class RsaSecurityKey : AsymmetricSecurityKey in namespace Microsoft.IdentityModel.Tokens
var rsaSecurityKey = new RsaSecurityKey(rsaPrivateKey);
var signingCredentials = new SigningCredentials(rsaSecurityKey, SecurityAlgorithms.RsaSha256);
This is a possible solution on how to create AsymmetricSecurityKey object and a SigningCredentials object when we have a RSA private key (asymmetric key) in string format.
When you want to use asymmetric keys that are generated outside your application, you may need this additional steps to import an externally generated key.
Are you specifically looking for an AsymmetricSecurityKey?
I noticed that you are referencing the HM256 algorithm. That leads me to believe that you are looking for a SymmetricSecurityKey. Also, your approach seems very specific to using the HMAC alg.
To generate a SymmetricSecurityKey, you can try something like the following code:
// Define const Key this should be private secret key stored in some safe place
string key = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
// Create Security key using private key above:
// not that latest version of JWT using Microsoft namespace instead of System
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
// Also note that securityKey length should be >256b
// so you have to make sure that your private key has a proper length
//
var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
If you would like the a solution for using an RS256 alg (which will use a cert in pfx format), you can comment and I will do my best to give you an example of that too.
This creates security key from an RSA public key in F#.
let pem = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQAB"
let getPublicKey (pem: string) =
let publicKey = ReadOnlySpan<byte>(Convert.FromBase64String(pem))
let rsa = RSA.Create()
let mutable read = 0
rsa.ImportSubjectPublicKeyInfo(publicKey, &read)
new RsaSecurityKey(rsa)
getPublicKey pem
Related
I'm using https://www.nuget.org/packages/Paseto.Core/ and this is how I generate the PASETO token:
public async Task<TokenResponse> GenerateAsync(Client client, TokenRequest tokenRequest, string issuer, string audience)
{
var ed25519pkcs8 = await File.ReadAllTextAsync("private.pem");
var privatePemReader = new PemReader(new StringReader(ed25519pkcs8));
var ed25519pkcs8Parameters = (Ed25519PrivateKeyParameters)privatePemReader.ReadObject();
ISigner signer = new Ed25519Signer();
signer.Init(true, ed25519pkcs8Parameters);
var pasetoToken = new PasetoBuilder()
.Use(ProtocolVersion.V4, Purpose.Public)
.WithKey(signer.GenerateSignature(), Encryption.AsymmetricSecretKey)
.Issuer(issuer)
.Subject(tokenRequest.ClientId)
.Audience(audience)
.NotBefore(DateTime.UtcNow)
.IssuedAt(DateTime.UtcNow)
.Expiration(DateTime.UtcNow.AddSeconds(client.AccessTokenLifetime))
.TokenIdentifier(Guid.NewGuid().ToString())
.AddClaim("client_id", tokenRequest.ClientId)
.AddClaim("scopes", tokenRequest.Scopes)
.Encode();
return new TokenResponse
{
AccessToken = pasetoToken,
Lifetime = client.AccessTokenLifetime,
Scope = tokenRequest.Scopes
};
}
Generated PASETO token looks like that: v4.public.eyJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDMyMyIsInN1YiI6InRlc3RfY3JlZGVudGlhbHMiLCJhdWQiOiJ0ZXN0QXBpUmVzb3VyY2UiLCJuYmYiOiIyMDIyLTA1LTA3VDE4OjM4OjU2LjU0MjM2OTFaIiwiaWF0IjoiMjAyMi0wNS0wN1QxODozODo1Ni41NDI0MzUzWiIsImV4cCI6IjIwMjItMDUtMDdUMTk6Mzg6NTYuNTQyNDcwN1oiLCJqdGkiOiI5ODk3Mzc4Mi1kNWQwLTQzMjktYWY0ZS1kNTU3NGI4Y2Q2YmMiLCJjbGllbnRfaWQiOiJ0ZXN0X2NyZWRlbnRpYWxzIiwic2NvcGVzIjoidGVzdC5yZWFkIn0pQzMpSSXa-inBjgvDBNFgm7tE4w6J-TzzntJfKJErGRfm2ARuswWxJinhQMT-9v5q1ntyk4UtoIMr9ny0t4AH
So I created a test API for validating tokens, and the result always looks like this:
{
"IsValid":false,
"Paseto":null,
"Exception":{
"Expected":null,
"Received":null,
"Message":"The token signature is not valid",
"Data":{
},
"InnerException":null,
"HelpLink":null,
"Source":null,
"HResult":-2146233088,
"StackTrace":null
}
}
This is what validation looks like:
[HttpGet]
public IActionResult DecodePaseto([FromQuery] string token)
{
var ed25519x509 = System.IO.File.ReadAllText("public.pem");
var publicPemReader = new PemReader(new StringReader(ed25519x509));
var ed25519x509Parameters = (Ed25519PublicKeyParameters)publicPemReader.ReadObject();
var paseto = new PasetoBuilder()
.Use(ProtocolVersion.V4, Purpose.Public)
.WithKey(ed25519x509Parameters.GetEncoded(), Encryption.AsymmetricPublicKey)
.Decode(token);
return Ok(JsonConvert.SerializeObject(paseto));
}
Everything seems fine and yet there is sign or validation error. What could be wrong?
Paseto uses the raw public key (32 bytes) and as secret key the concatenation of raw private and raw public key (32 bytes + 32 bytes = 64 bytes), see here for an explanation of the different formats of a secret Ed25519 key.
While the public key is imported correctly in the posted code of the question, as private key the Ed25519 signature generated with the private key for an empty string is used. This is incorrect, but works (in the sense that no exception is thrown) because the signature is 64 bytes in size, which is the same length as the secret key. Of course, verification fails.
The following code shows the correct construction of the secret key for Paseto. For simplicity Linq is applied, but also e.g. Buffer.BlockCopy() can be used:
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using System;
using System.IO;
using System.Linq;
...
string ed25519pkcs8 = #"-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIAYIsKL0xkTkAXDhUN6eDheqODEOGyFZ04jsgFNCFxZf
-----END PRIVATE KEY-----";
PemReader privatePemReader = new PemReader(new StringReader(ed25519pkcs8));
Ed25519PrivateKeyParameters ed25519pkcs8Parameters = (Ed25519PrivateKeyParameters)privatePemReader.ReadObject();
string ed25519x509 = #"-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA3mcwgf2DrWLR3mQ6l2d59bGU6qUStwQrln2+rKlKxoA=
-----END PUBLIC KEY-----";
PemReader publicPemReader = new PemReader(new StringReader(ed25519x509));
Ed25519PublicKeyParameters ed25519x509Parameters = (Ed25519PublicKeyParameters)publicPemReader.ReadObject();
byte[] publicKey = ed25519x509Parameters.GetEncoded(); // raw 32 bytes public key
byte[] secretKey = ed25519pkcs8Parameters.GetEncoded().Concat(publicKey).ToArray(); // raw 32 bytes private key + raw 32 bytes public key
Test:
Using the above secret key, signing is feasible as follows (with arbitrary test data):
using Paseto;
using Paseto.Builder;
...
string pasetoToken = new PasetoBuilder()
.Use(ProtocolVersion.V4, Purpose.Public)
.WithSecretKey(secretKey) // short for .WithKey(secretKey, Encryption.AsymmetricSecretKey)
.Subject("subject")
.Issuer("whoever")
.Audience("https://www.whatever.com/someurl")
.NotBefore(DateTime.UtcNow)
.IssuedAt(DateTime.UtcNow)
.Expiration(DateTime.UtcNow.AddSeconds(3600))
.TokenIdentifier(Guid.NewGuid().ToString())
.AddClaim("client_id", "client_id")
.AddClaim("scopes", "scopes")
.Encode();
Console.WriteLine(pasetoToken);
and verifying using the above public key:
using Paseto;
using Paseto.Builder;
...
PasetoTokenValidationParameters validationParameters = new PasetoTokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = "whoever",
ValidateAudience = true,
ValidAudience = "https://www.whatever.com/someurl"
};
PasetoTokenValidationResult paseto = new PasetoBuilder()
.Use(ProtocolVersion.V4, Purpose.Public)
.WithPublicKey(publicKey) // short for .WithKey(publicKey, Encryption.AsymmetricPublicKey)
.Decode(pasetoToken, validationParameters);
Console.WriteLine(paseto.IsValid ? paseto.Paseto.RawPayload : "Decoding failed");
A possible output of the entire code is:
v4.public.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoid2hvZXZlciIsImF1ZCI6Imh0dHBzOi8vd3d3LndoYXRldmVyLmNvbS9zb21ldXJsIiwibmJmIjoiMjAyMi0wNS0wN1QyMjowNzo0NS4yNzA1NjU4WiIsImlhdCI6IjIwMjItMDUtMDdUMjI6MDc6NDUuMjcwNjQ1OVoiLCJleHAiOiIyMDIyLTA1LTA3VDIzOjA3OjQ1LjI3MDY4NzRaIiwianRpIjoiNDU0MWI2NmMtOGRlZi00Mjg3LWFmZGMtYTE3ZDNhMDY3NjYxIiwiY2xpZW50X2lkIjoiY2xpZW50X2lkIiwic2NvcGVzIjoic2NvcGVzIn1RyxW-gjy6va7IA5pL9pZMqcrBjYkYFX16AV7IqTt5Fa5YtQMbIJQkfu24uq7bR2lx0WMLHa0xr2fsJRtdpsAG
{"sub":"subject","iss":"whoever","aud":"https://www.whatever.com/someurl","nbf":"2022-05-07T22:07:45.2705658Z","iat":"2022-05-07T22:07:45.2706459Z","exp":"2022-05-07T23:07:45.2706874Z","jti":"4541b66c-8def-4287-afdc-a17d3a067661","client_id":"client_id","scopes":"scopes"}
Here eyJz...psAG is the Bas64url encoding of payload and concatenated 64 bytes Ed25519 signature.
I have taken a certificate:
X509Certificate2 x509 = store.Certificates.Find(X509FindType.FindBySubjectName, "CNGTestCert", false)[0];
and now I want to get the providertype parameter. But I cant do x509.PrivateKey.
In result of this I used var key = x509.GetRSAPrivateKey();. How can I get out of this key the ProviderType to decide the KeyNumber (looks like here: referencesource.microsoft.com). Or is there a easier way to test the private key for key function (key was created for signature or exchange)?
I found a way to check CNG certificate for exchangeable. If I read the private key of certificate by var privateKey = (cngCert.GetRSAPrivateKey() as RSACng).Key;, did I get the KeyUsage. The "KeyAgreement" flag marks the certificate for usage of secret agreement generation and key exchange.
var privateKey = (cngCert.GetRSAPrivateKey() as RSACng).Key;
if(privateKey.KeyUsage.HasFlag(CngKeyUsages.KeyAgreement))
{
//is for KeyExchange
}
I was trying to get keypair for pkcs-7 signature an X509Certificate2 object from this code.
RSACryptoServiceProvider key = (RSACryptoServiceProvider)Cert.PrivateKey;
RSAParameters rsaparam = key.ExportParameters(true);
AsymmetricCipherKeyPair keypair = DotNetUtilities.GetRsaKeyPair(rsaparam);
This works fine if X509Certificate2 object is created using .pfx file like this
X509Certificate2 cert = new X509Certificate2(".pfx file path", "password");
it works fine.
but when the certificate is listed from certificate store like this
X509Certificate2 cert;
X509Store UserCertificateStore = new X509Store("My");
UserCertificateStore.Open(OpenFlags.ReadOnly);
var certificates = UserCertificateStore.Certificates;
foreach (var certificate in certificates)
{
if (certificate.Thumbprint==thumbprint)
{
cert=certificate;
break;
}
}
it throws an exception with the message - Key not valid for use in specified state.
after #Crypt32 answer tried using RSA method sign hash
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PrivateKey;
using (SHA256Managed sHA256 = new SHA256Managed())
{
byte[] hash = sHA256.ComputeHash(data);
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
}
but the signature was not in PKCS#7 format
This is because BouncyCastle attempts to get raw key material (literally, export) from .NET object while actual key is not marked as exportable. In Windows systems, keys are stored in cryptographic service providers which control all key operations. When you need to perform a specific cryptographic operation, you are asking CSP to do a job and it does without having to expose key material to you. If the key was imported/generated in CSP as exportable, you can ask CSP to export key material. If this flag was not set, CSP won't give you the key.
I don't know how BouncyCastle works, but if it expects raw key material, then you need exportable private key in your certificate.
To answer the underlying question, "How do I make a PKCS#7 SignedData message signed with RSA+SHA-2-256?"
https://github.com/Microsoft/dotnet/blob/master/releases/net471/dotnet471-changes.md#bcl says that SHA-2-256 not only works in 4.7.1, but it's the default now:
Updated SignedXML and SignedCMS to use SHA256 as a default over SHA1. SHA1 may still be used by selected as a default by enabling a context switch. [397307, System.Security.dll, Bug]
On older frameworks it's possible via:
ContentInfo content = new ContentInfo(data);
SignedCms cms = new SignedCms(content);
CmsSigner signer = new CmsSigner(cert);
signer.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1");
cms.ComputeSignature(signer);
return cms.Encode();
Where "2.16.840.1.101.3.4.2.1" is OID-ese for SHA-2-256.
I am working with digital signature. We have to generate xml request and sign the request using private key. The private key to be taken from etoken which is non exportable. My findings showed that private key cannot be extracted when it is marked as non exportable. In this case, how can I sign the xml request. Please help.
Finally I got the solution. Took a while as the requirement was bit rare. This link https://www.codeproject.com/Articles/240655/Using-a-Smart-Card-Certificate-with-NET-Security-i helped me to get the solution. Please refer to below code. For SignXml() method, please refer this msdn link https://msdn.microsoft.com/en-us/library/ms229745(v=vs.110).aspx
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.MaxAllowed);
// find cert by thumbprint
var foundCerts = store.Certificates.Find(X509FindType.Thumbprint, "12345", true);
if (foundCerts.Count == 0)
return;
var certForSigning = foundCerts[0];
store.Close();
// prepare password
var pass = new SecureString();
var passwordstring = "password";
var chararr = passwordstring.ToCharArray();
foreach (var i in chararr)
pass.AppendChar(i);
// take private key
var privateKey = certForSigning.PrivateKey as RSACryptoServiceProvider;
// make new CSP parameters based on parameters from current private key but throw in password
CspParameters cspParameters = new CspParameters(1,
privateKey.CspKeyContainerInfo.ProviderName,
privateKey.CspKeyContainerInfo.KeyContainerName,
new System.Security.AccessControl.CryptoKeySecurity(),
pass);
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(cspParameters);
XmlDocument xmlDoc = new XmlDocument();
// Load an XML file into the XmlDocument object.
xmlDoc.PreserveWhitespace = true;
xmlDoc.Load(path);
// Sign the XML document.
SignXml(xmlDoc, rsaCryptoServiceProvider);
How can I (can I?) use X509SecurityKey for Asp.Net Core JWT validation?
My current code is roughly:
X509SecurityKey signingKey = null;
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var v = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
var v1 = v.Find(X509FindType.FindBySubjectDistinguishedName, strCertName, true);
signingKey = new X509SecurityKey(v1[0]);
}
and later on for the signing credentials...
new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
This causes an exception:
SignatureAlgorithm: 'HS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.X509SecurityKey'
is not supported.
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
I tried a few algorithms, but it doesn't seem like it works with any of them?
I tried a few algorithms, but it doesn't seem like it works with any of them?
You're trying to use an asymmetric key (embedded in a X.509 certificate) with a HMAC algorithm (that we often abusively call "symmetric signature algorithm"): this cannot work.
Assuming your certificate is a RSA certificate, you should be able to use SecurityAlgorithms.RsaSha256.
var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256)