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

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

Related

How to convert JWK (IdentityServ 4) to public key pem?

I have an RSA512 JWKS released by Identity Server 4 which is used by clients to validate the token signature. The problem is that I also wanted to validate it on the https://jwt.io/ site but I don't know how to transform the parameters:
in a public key such as this:
-----BEGIN PUBLIC KEY-----
MIIBCgKCAQEA+xGZ/wcz9ugFpP07Nspo6U17l0YhFiFpxxU4pTk3Lifz9R3zsIsu
ERwta7+fWIfxOo208ett/jhskiVodSEt3QBGh4XBipyWopKwZ93HHaDVZAALi/2A
+xTBtWdEo7XGUujKDvC2/aZKukfjpOiUI8AhLAfjmlcD/UZ1QPh0mHsglRNCmpCw mwSXA9VNmhz+PiB+Dml4WWnKW/VHo2ujTXxq7+efMU4H2fny3Se3KYOsFPFGZ1TN
QSYlFuShWrHPtiLmUdPoP6CV2mML1tk+l7DIIqXrQhLUKDACeM5roMx0kLhUWB8P
+0uj1CNlNN4JRZlC7xFfqiMbFRU9Z4N6YwIDAQAB
-----END PUBLIC KEY-----
that can be used on the site to validate the Token. Is there any kind of library or something free that I can use to do this? At the moment I found this library, but is it paid.
https://www.example-code.com/mono/publickey_rsa_load_jwk.asp
UPDATE
I have tried this snippet of code:
using var httpClient = new HttpClient();
var json = httpClient.GetStringAsync(
"http://..../.well-known/openid-configuration/jwks").GetAwaiter()
.GetResult();
var jsonWebKeySet = new JsonWebKeySet(json);
var jsonWebKey = jsonWebKeySet.Keys.First();
var rsaProvider = new RSACryptoServiceProvider();
rsaProvider.ImportParameters(new RSAParameters
{
Modulus = Base64Url.Decode(jsonWebKey.N),
Exponent = Base64Url.Decode(jsonWebKey.E)
});
var publicKey = Base64Url.Encode(rsaProvider.ExportRSAPublicKey());
Using the Base64Url class of the Jose library, but I get a public key in Base64Url that is not safe, that is, containing the characters _ and -. I also tried to do the substitution of the relative characters with / and +, but anyway and I always get Invalid Signature, while with the other library everything works. What am I doing wrong? Thanks
UPDATE
Thanks to #Topaco's suggestion I modified the last line of code like this:
var publicKey = Convert.ToBase64String(rsaProvider.ExportSubjectPublicKeyInfo());
and after having correctly indented the key and inserted the header
-----BEGIN PUBLIC KEY-----
and footer
-----END PUBLIC KEY-----
the validation of the JWT on the site https://jwt.io/ is done correctly. Thanks
.....
var rsaProvider = new RSACryptoServiceProvider(2048);
.....
string publicKey = $#"-----BEGIN PUBLIC KEY-----
{Base64Url.Encode(rsaProvider.ExportSubjectPublicKeyInfo()).Replace("-", "+").Replace("_", "/")}
-----END PUBLIC KEY-----
";
rsaPublicKey.ImportFromPem(publicKey);

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

Add Certificate X509 with passphrase key to Webservice request

I'm having trouble with adding a new SSL certificate to my webService request.
var client = new RestClient(tokenUrl);
string certif = String.Format("{0}/client.cer", CertifPath);
string key = String.Format("{0}/client.key", CertifPath);
if (File.Exists(certif) && File.Exists(key))
{
X509Certificate2 cert = new X509Certificate2(certif, key);
X509CertificateCollection collection1 = new X509CertificateCollection();
collection1.Add(cert);
client.ClientCertificates = collection1;
}
I'm getting as a response : 400 no required ssl certificate was sent nginx !!!!.
In Addition : When i use PostMan Or SoapUI .I a must add a third secret key(passphrase) to be able to get response. ex :Add certificate via postman
My Question is How can i add this third parameter(secret key) in my request c# ?.
There is another way to implement certificate to my request ???
Can you use that neat little piece of code that let's you do exactly that :
byte[] certBuffer = Helpers.GetBytesFromPEM(publicCert, PemStringType.Certificate);
byte[] keyBuffer = Helpers.GetBytesFromPEM(privateKey, PemStringType.RsaPrivateKey);
X509Certificate2 certificate = new X509Certificate2(certBuffer, password);
RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
certificate.PrivateKey = prov;
Solution :
I wasted a lot of time to search for how can include three informations (Certificat.cer,certif.key and the passphrase) in one Rest call.
The solution was easy:
the certificate Object is very flexible : i can encapsulate the
three information in one certificate called (.pfx) using OpenSSL.
you can install OpenSSL via : http://slproweb.com/download/Win64OpenSSL_Light-1_1_0g.exe
After this commande :
openssl pkcs12 -export -out certificate.pfx -inkey client.key -in client.cer.
A new file is generated : certificate.pfx.
So Now I can easly include my new certificate without any ERRORS :) .

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

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

Categories