c# JWT load ES256 PEM file into CngKey (jose-jwt) - c#

As a quick overview I am attempting to generate a ES256 algorithm -JWT token via C# using the https://github.com/dvsekhvalnov/jose-jwt library.
As the directions state:
ES256, ES384, ES256 ECDSA signatures requires CngKey (usually private)
elliptic curve key of corresponding length. Normally existing CngKey
loaded via CngKey.Open(..) method from Key Storage Provider. But if
you want to use raw key material (x,y) and d, jose-jwt provides
convenient helper EccKey.New(x,y,d).
The CngKey.Open() states it opens an existing key, but by the sounds of it I should be using the CngKey.Import() instead? When I attempt to call the CngKey.Import() it returns the following error:
The parameter is incorrect.
Basically what I am asking is what is the simplest way to convert an existing PEM file into the CngKey object which is required for the Jose.JWT.Encode() function? Any help would be highly appreciated. Thanks!
Below is my code(for security purposed that is not the real private key):
public string GenerateToken(int contactID, Database _db)
{
var contact = GetContact(contactID, _db);
var payload = new Dictionary<string, object>()
{
{"broker", 1},
{"contact_id", contact.id},
{"name", contact.fname + " " + contact.lname + ""},
{"iss", "www.somewhere.com"},
{"iat", (DateTime.Now - UnixEpoch).TotalSeconds},
{"nbf", (DateTime.Now - UnixEpoch).TotalSeconds},
{"exp", (DateTime.Now.AddDays(30) - UnixEpoch).TotalSeconds}
};
string privateKey =
"MHcCAQEffEIIIHHHHHHHHHHHHHHHffHHHHHHHHHHHHHHHHHHHHHHHoGgCCqGSM49" +
"AwEHhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhI+pRkAxAb13" +
"77vz2Yjjjjjjjjjjjjjjjjjjjjw==";
byte[] b = Convert.FromBase64String(privateKey);
CngKey cng = CngKey.Import(b, CngKeyBlobFormat.EccPrivateBlob);
string token = Jose.JWT.Encode(payload, cng, JwsAlgorithm.ES256);
return token;
}

I had the same problem with jose-jwt and got it working using my own implementation of GetECDsaPrivateKey(). Note that your project should target .NET 4.6.1. Please follow the steps below:
1.Generate a p12 X509Certificate2 using openssl
> openssl ecparam -name prime256v1 -genkey > private-key.pem
> openssl ec -in private-key.pem -pubout -out public-key.pem
> openssl req -new -key private-key.pem -x509 -nodes -days 365 -out public.cer
> winpty openssl pkcs12 -export -in public.cer -inkey private-key.pem -out publiccert.p12
2.Generate a JWT by reading private key from above generated certificate:
var claims = new Dictionary<string, object>()
{
{ "sub", "mr.x#contoso.com" },
{ "exp", 1300819380 }
};
var certificate = new X509Certificate2("publiccert.p12", "passcode");
string token = SignJWTWithCert(certificate, claims);
private static string SignJWTWithCert(X509Certificate2 cert, object claims)
{
var header = new { alg = "ES256", typ = "JWT" };
byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
byte[] claimsBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(claims, Formatting.None));
using (ECDsa ecdsa = cert.GetECDsaPrivateKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must have an ECDSA private key", nameof(cert));
var payload = Base64UrlEncode(headerBytes) + "." + Base64UrlEncode(claimsBytes);
var signature = ecdsa.SignData(Encoding.UTF8.GetBytes(payload), HashAlgorithmName.SHA256);
return payload + "." + Base64UrlEncode(signature);
}
}

Related

Import an OpenSSL ECDSA PEM private key and sign it

To generate a private and public key with OpenSSL I have run
openssl ecparam -genkey -name secp256k1 -out private-key.pem
openssl ec -in private-key.pem -out public-key.pem -pubout
I upload the public key to the remote server. Then import the private-key.pem to a .Net Framework (it has to be .Net Framework) C# service to use it to sign an API payload:
public string LoadFromCng(byte[] request, string privateKeyFile)
{
CngKey cng = CngKey.Open(privateKeyFile);
// Sign the request body with the private key.
ECDsaCng dsa = new ECDsaCng(cng);
byte[] signedRequest = dsa.SignData(request, HashAlgorithmName.SHA256);
return Convert.ToBase64String(signedRequest);
}
With privateKeyFile = private-key.pem the above code gives Keyset does not exist
If I use mkcert and run mkcert -ecdsa -pkcs12 private-key.pem it generates a PKCS#12 key called private-key.pem.p12 and then:
public string LoadFromX509(byte[] request, string privateKeyFile)
{
var cert = new X509Certificate2(privateKeyFile, "changeit");
var key = cert.GetECDsaPrivateKey();
byte[] signedRequest = key.SignData(request, HashAlgorithmName.SHA256);
return Convert.ToBase64String(signedRequest);
}
With privateKeyFile = private-key.pem.p12 the above code appears to sign the request, but the API response is The remote server returned an error: (400) Bad Request which means the API provider can't decode the payload from the public key.
I get the same 400 error when going through the cheat sheet here and creating an X509 pfx certificate.
openssl req -new -x509 -key private-key.pem -out cert.pem -days 360
openssl pkcs12 -export -inkey private-key.pem -in cert.pem -out cert.pfx
The method above appears to sign the payload but the provider responds with a 400.
The suggestions here and here and here and here and others have not worked.
I can't use Net Core or NET 5 so this doesn't work either. The ImportPkcs8PrivateKey method is not available in Net Framework.
If I try and use Bouncy Castle per here I get Unable to cast object of type 'Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters' to type 'Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters'
The first OpenSSL statement creates an EC parameter file private-key.pem, which contains, among others, a PEM encoded private EC key in SEC1 format.
With .NET Framework the easiest way is to use BouncyCastle for key import. BouncyCastle also supports the ASN.1/DER format and the IEEE P1363 (r|s) format for EC signatures.
The following code imports a PEM encoded private EC key in SEC1 format and signs a message.
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
...
string pkcs8 = #"-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIMmN/YG6BtIqm9eAcYtKdcbJosNKbB76vGMTSltPLNiioAcGBSuBBAAK
oUQDQgAESGnGxuBPdrwv1TkJEorsaNk74+ZFh2jzww2SpLTqQqkvOf5IP6fuODS/
hzztSBpsBpX9LUZh8TYHX0HRMagkaA==
-----END EC PRIVATE KEY-----";
TextReader privateKeyTextReader = new StringReader(pkcs8);
AsymmetricCipherKeyPair privateKeyParams = (AsymmetricCipherKeyPair)new PemReader(privateKeyTextReader).ReadObject();
byte[] message = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA"); // ASN.1/DER format
//ISigner signer = SignerUtilities.GetSigner("SHA-256withPLAIN-ECDSA"); // r|s format
signer.Init(true, privateKeyParams.Private);
signer.BlockUpdate(message, 0, message.Length);
byte[] signature = signer.GenerateSignature();
Console.WriteLine(Hex.ToHexString(signature)); // 3046022100967c2e890d933c75468dceaf61152add41f143568dcb967583e1b307a0b495e30221008f6837c0d9cc01fc7bfe54ed5b6267fd3e64c2d3ff771e72762f24c20071c454

Generate base-64 encoded X509 certificate pair (public/private

I'm trying to generate RSA X509 public/private key to dynamically,
below is how I do it with openssh command line:
openssl genrsa -out privatekey.pem 1024
openssl req -new -x509 -key privatekey.pem -out publickey.cer -days 1825
openssl pkcs12 -export -out public_privatekey.pfx -inkey privatekey.pem -in publickey.cer
Also how do I add passphrase to encrypt the private key?
and I only got here by far
//Generate a public/private key pair.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
//Save the public key information to an RSAParameters structure.
RSAParameters rsaKeyInfo = rsa.ExportParameters(true);
I have used this class however it does not result into a valid SSL, when I submit it to developer portal it did not get accepted as valid public key: developer.xero.com/myapps
Regards
Note that I have replaced the RSACryptoServiceProvider class with the recommended RSA base class which is cross-platform and also the better RSA implementation.
This SO question put me in the right direction.
using (var rsa = RSA.Create(1024))
{
var distinguishedName = new X500DistinguishedName($"CN=SelfSignedCertificate");
var request = new CertificateRequest(distinguishedName, rsa, HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1);
var certificate = request.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(1825));
// Create PFX (PKCS #12) with private key
File.WriteAllBytes("privatekey.pfx", certificate.Export(X509ContentType.Pfx, "RGliXtaLkENste"));
// Create Base 64 encoded CER (public key only)
File.WriteAllText("publickey.cer",
"-----BEGIN CERTIFICATE-----\r\n"
+ Convert.ToBase64String(certificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks)
+ "\r\n-----END CERTIFICATE-----");
}
I have tested the resulting .cer file on xero so it should work

Verify signature with public key only (C#)

I have two applications. One that signs a file and the other that verifies.
The signing application does the following:
X509Certificate2 certificate = new X509Certificate2(PROJECT_DIR_PATH + "cert.pfx", "password");
using (RSA rsa = certificate.GetRSAPrivateKey())
{
signature = rsa.SignData(exeContent, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
}
cert.pfx is a self-signed certificate, generated with Openssl.
The verifying application:
X509Certificate2 certificate = new X509Certificate2(PROJECT_DIR_PATH + "cert.pfx", "password");
using (RSA rsa = certificate.GetRSAPublicKey())
{
return rsa.VerifyData(exeContentWithoutSignature, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
}
As I understand the .pfx file contains public and private key information, thus I should not make it available to anyone. As I know, only the public key is needed for the verification step.
How can I use rsa.VerifyData or other functions to verify the signature without exposing my pfx file?
I already had .pfx file that can be generated this way:
openssl req -x509 -days 365 -newkey rsa:2048 -keyout test-key.pem -out test-cert.pem
openssl pkcs12 -export -in test-cert.pem -inkey test-key.pem -out test-cert.pfx
In order to extract a certificate containing only public key following command can be used:
openssl pkcs12 -in test-cert.pfx -clcerts -nokeys -out cert.pem
-clcerts - Only output client certificates.
-nokeys - Don't output private keys.
Cert.pem can be used to create an instance of X509Certificate2:
X509Certificate2 certificate = new X509Certificate2(PROJECT_DIR_PATH + "test-cert.pem");
using (RSA rsa = certificate.GetRSAPublicKey())
{
return rsa.VerifyData(exeContentWithoutSignature, signature, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
}

C# Store Public-Key X.509 encoded key in ANS.1

i am trying to implement an API. (https://www.loxone.com/dede/wp-content/uploads/sites/2/2016/08/0903_Communicating-with-the-Miniserver.pdf)
When you go to Page 5 you will see under 2.b) that I need to store a public key in Format: X.509 encoded key in ANS.1.
The problem is I have no idea how to do this in C#...
Can anyone please help me with that problem :)
Thanks in advance!
Here is an example for the publicKey that I get:
-----BEGIN CERTIFICATE-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC393RoCVFixg3cZicByw05GYqlIkmqfx1lylJqBaF7BMI/FffD5BBoFpFurj4N1pDdMF/QhBEj1Pb3XGOkrHFCinYc0lCMbSjreE+QPjmnWnZeaPd3mtBfdcTSF6hYH0regFU8nvFVgLsoBUCMjuhfsvuaW4sLylU6KhZ9hCqVQwIDAQAB-----END CERTIFICATE-----
I think you need to export this public key as X.509 encoded as ANS.1 which can be a .pem file.
From https://serverfault.com/questions/9708/what-is-a-pem-file-and-how-does-it-differ-from-other-openssl-generated-key-file/9717#9717 :
PEM on it's own isn't a certificate, it's just a way of encoding data. X.509 certificates are one type of data that is commonly encoded using PEM.
PEM is a X.509 certificate (whose structure is defined using ASN.1), encoded using the ASN.1 DER (distinguished encoding rules), then run through Base64 encoding and stuck between plain-text anchor lines (BEGIN CERTIFICATE and END CERTIFICATE).
You can use openssl to make a .pem file which is ANS.1 compliant.
openssl req -new -x509 -days 365 -nodes -out ./cert.pem -keyout ./cert.pem
Presumably, by "ANS.1" they meant "ASN.1" (Abstract Syntax Notation 1), and by "ASN.1" they meant "DER" (Distinguished Encoding Rules for Abstract Syntax Notation 1).
To convert your current PEM certificate into a "DER" certificate you either a) openssl x509 -in cert.pem -out cert.der -outform der or b) read the base64 string between "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" and convert it to bytes (e.g. Convert.FromBase64String).
If you still need it after 3 years, you can use the following C# (.NET 6) method:
public static async Task<RSA> GetPublicKey(string certStr)
{
string base64 = certStr;
if (base64.StartsWith("-----BEGIN CERTIFICATE-----"))
{
base64 = base64.Substring(27);
}
if (base64.EndsWith("-----END CERTIFICATE-----"))
{
base64 = base64.Substring(0, base64.Length - 25);
}
var asn = new AsnReader(Convert.FromBase64String(base64), AsnEncodingRules.DER);
asn = asn.ReadSequence();
asn.ReadSequence();
//string objectIdentifier = asn.ReadObjectIdentifier(Asn1Tag.ObjectIdentifier);
//asn.ReadNull();
var data2 = asn.ReadBitString(out int bitCount);
asn = new AsnReader(data2, AsnEncodingRules.DER);
asn = asn.ReadSequence();
var modulus = asn.ReadInteger();
var exponent = asn.ReadInteger();
var rsa = new RSACng();
rsa.ImportParameters(new RSAParameters
{
Modulus = modulus.ToByteArray(true, true),
Exponent = exponent.ToByteArray(true, true),
});
return rsa;
}
Not the best and no error handling, but works.
Then you don't need an X.509 certificate, just this RSA object:
var aes = Aes.Create();
string s = ByteArrayToHex(aes.Key) + ":" + ByteArrayToHex(aes.IV);
var encData = rsa.Encrypt(Encoding.ASCII.GetBytes(s), RSAEncryptionPadding.Pkcs1);
s = "jdev/sys/keyexchange/" + Convert.ToBase64String(encData);
Probably you can implement the ByteArrayToHex yourself :)

Unable to Instantiate X509Certificate2 from Byte Array

I am trying to use a self-signed certificate to configure IdentityServer3,
I used openssl to create a x509 certificate as follows:
openssl req -x509 -sha256 -days 365 -key key.pem -in csr.csr -out certificate.pem
then merged the key and cert using :
pkcs12 -export -in my-cert.pem inkey my-key.pem -out xyz-cert.pfx
i then converted the content of xyz-cert.pfx to Base64String which is stored in a key in web.config, then tried to use the certificate to instantiate X509Certificate2 as follows:
var certificate = Convert.FromBase64String(ConfigurationManager.AppSettings["SigningCertificate"]);
var options = new IdentityServerOptions
{
SigningCertificate = new X509Certificate2(certificate, ConfigurationManager.AppSettings["SigningCertificatePassword"]),
RequireSsl = false, // DO NOT DO THIS IN
Factory = factory
};
the following exception is then thrown:
I can't figure out where i got it wrong.
Thanks for your help
var options = new IdentityServerOptions
{
string CertText = ConfigurationManager.AppSettings["SigningCertificatePassword"];
byte[] certBytes = Convert.FromBase64String(certText);
SigningCertificate = new X509Certificate2(certificate, certBytes),
RequireSsl = false, // DO NOT DO THIS IN
Factory = factory
};

Categories