Create CSR with Bouncy Castle (C#) inclusive EC Curve Name - c#

I create a CSR with Bouncy Castle and sign it on HSM (EC Key). It works fine! But in the CSR content (and in the certificate later) there is no EC curve name. Instead, there are the parameters of the curve. Some clients have problems with this, e.g .: Phyton. On the screenshot you can see how OpenSSL does it. How can I force Bouncy Castle to write the curve name in the CSR?
The code for the Adding the Public Key to CSR:
IList oids = new ArrayList();
IList values = new ArrayList();
..........
SubjectKeyIdentifier subjectKeyIdentifier = new SubjectKeyIdentifierStructure(publicKey);
X509Extension ski = new X509Extension(true, new DerOctetString(subjectKeyIdentifier));
oids.Add(X509Extensions.SubjectKeyIdentifier);
values.Add(ski);
AttributePkcs attribute = new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest,
new DerSet(new X509Extensions(oids, values)));
//End Extensions
// SIGN on HSM
Pkcs10CertificationRequestDelaySigned csr = new Pkcs10CertificationRequestDelaySigned(
signatureAlgorithmStr,
subject,
publicKey,
new DerSet(attribute)
);
......
Screenshot: CSR with Bouncy Castle und OpenSSL

The issue isn't with BouncyCastle, but rather how the public key is being generated / initialized.
You can either opt for named curve or as expanded group parameters, but not both.
Technically they are equivalent, but usage wise, they don't mix well with one another. So you will have to generate both if you want to cater to both of those scenarios.
I don't know how you are initializing the public key, but you can convert to a different public key type before signing.
AsymmetricKeyParameter publicKey = null;
// ... existing public key by some means
var castedPublicKey = (ECPublicKeyParameters)publicKey;
var newPublicKey = new ECPublicKeyParameters(
castedPublicKey.AlgorithmName
,castedPublicKey.Q,
X9ObjectIdentifiers.Prime256v1);
// ... rest of code
// SIGN on HSM
Pkcs10CertificationRequestDelaySigned csr = new Pkcs10CertificationRequestDelaySigned(
signatureAlgorithmStr,
subject,
newPublicKey,
new DerSet(attribute)
);
Change Prime256v1 to whatever curve you are using

Related

Sign a CSR with Azure Key Vault

How can I achieve the very basic CSR Signing HSM functionality with Azure Key Vault?
I had found a very long and manual process to somehow achieve it:
Create a private key in Key Vault
Create a CSR, digest it with SHA256
Sign the digest with the previous private key using the Sign() method
Create a local x.509 cert and append the signature
Upload the new signed cert to Key Vault
Problem is, it is manual, long (also, quite a bit of latency) and error prone. Also I haven't found a single C# code example for this, and I'm looking for EC and not RSA.
The question is, is there a simple CertificateRequest.Sign() function in Key Vault? this seems to be so basic for an HSM-like service...
Thanks
This blog post by Vitaliy Slepakov describes a solution that he created which implements the steps you listed above using C#/.NET Core.
The code is available here:
https://github.com/vslepakov/keyvault-ca/
The heart of it is the following:
byte[] certificateRequest = /* ... */;
string issuerCertificateName = /* ... */;
KeyVaultServiceClient keyVaultServiceClient = /* ... */;
X509SignatureGenerator generator = /* see next section */;
var pkcs10CertificationRequest = new Pkcs10CertificationRequest(certificateRequest);
//TODO: Validate CSR via pkcs10CertificationRequest.Verify()
var info = pkcs10CertificationRequest.GetCertificationRequestInfo();
var notBefore = DateTime.UtcNow.AddDays(-1);
// Get the RSA public key from the CSR
var asymmetricKeyParameter = Org.BouncyCastle.Security.PublicKeyFactory.CreateKey(info.SubjectPublicKeyInfo);
var rsaKeyParameters = (Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)asymmetricKeyParameter;
var rsaKeyInfo = new RSAParameters
{
Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned(),
Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned()
};
var publicKey = RSA.Create(rsaKeyInfo);
//TODO: Validate publicKey
var certBundle = await keyVaultServiceClient.GetCertificateAsync(issuerCertificateName).ConfigureAwait(false);
var signingCert = new X509Certificate2(certBundle.Cer);
// new serial number
var serialNumber = new byte[SerialNumberLength];
RandomNumberGenerator.Fill(serialNumber);
serialNumber[0] &= 0x7F;
var subjectDN = new X500DistinguishedName(subjectName);
var request = new CertificateRequest(subjectDN, publicKey, GetRSAHashAlgorithmName(hashSizeInBits), RSASignaturePadding.Pkcs1);
// Basic constraints
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(caCert, caCert, 0, true));
// Subject Key Identifier
var ski = new X509SubjectKeyIdentifierExtension(
request.PublicKey,
X509SubjectKeyIdentifierHashAlgorithm.Sha1,
false);
request.CertificateExtensions.Add(ski);
// Authority Key Identifier
if (issuerCAKeyCert != null)
request.CertificateExtensions.Add(BuildAuthorityKeyIdentifier(issuerCAKeyCert));
else
request.CertificateExtensions.Add(BuildAuthorityKeyIdentifier(subjectDN, serialNumber.Reverse().ToArray(), ski));
if (caCert)
request.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature |
X509KeyUsageFlags.KeyCertSign |
X509KeyUsageFlags.CrlSign,
true));
else
{
// Key Usage
var defaultFlags =
X509KeyUsageFlags.DigitalSignature |
X509KeyUsageFlags.DataEncipherment |
X509KeyUsageFlags.NonRepudiation |
X509KeyUsageFlags.KeyEncipherment;
request.CertificateExtensions.Add(new X509KeyUsageExtension(defaultFlags, true));
// Enhanced key usage
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection {
new Oid("1.3.6.1.5.5.7.3.1"),
new Oid("1.3.6.1.5.5.7.3.2") }, true));
}
if (issuerCAKeyCert != null)
{
if (notAfter > issuerCAKeyCert.NotAfter)
{
notAfter = issuerCAKeyCert.NotAfter;
}
if (notBefore < issuerCAKeyCert.NotBefore)
{
notBefore = issuerCAKeyCert.NotBefore;
}
}
var issuerSubjectName = issuerCAKeyCert != null ? issuerCAKeyCert.SubjectName : subjectDN;
X509Certificate2 signedCert = request.Create(
issuerSubjectName,
generator,
notBefore,
notAfter,
serialNumber);
The custom X509SignatureGenerator implementation is here and used the following Key Vault SDK method:
HashAlgorithm hash = /* see GitHub */;
var digest = hash.ComputeHash(data);
var resultKeyVaultPkcs = await keyVaultClient.SignAsync(signingKey, algorithm, digest, RSASignaturePadding.Pkcs1);
Hopefully you can adapt this code to meet your needs. I'll be doing that as well. 😀
KV has a sign operation, but it's not specific to any data type (such as a cert request).
The KV .NET SDK offers this https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.keyvault.keyvaultclient.signwithhttpmessagesasync?view=azure-dotnet-legacy which is just a wrapper over REST APIs https://learn.microsoft.com/en-us/rest/api/keyvault/
This isn't exactly what you want, but I wrote some code to demonstrate C# KV encrypt/decrypt which should help you get started https://github.com/x509cert/AzureKeyVault

Tripledes from hardware

using System;
using System.Security.Cryptography;
namespace SmartCardSign
{
class SCSign
{
static void Main(string[] args)
{
// To idendify the Smart Card CryptoGraphic Providers on your
// computer, use the Microsoft Registry Editor (Regedit.exe).
// The available Smart Card CryptoGraphic Providers are listed
// in HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
// Create a new CspParameters object that identifies a
// Smart Card CryptoGraphic Provider.
// The 1st parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider Types.
// The 2nd parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
CspParameters csp = new CspParameters(1, "Schlumberger Cryptographic Service Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
// Initialize an RSACryptoServiceProvider object using
// the CspParameters object.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
// Create some data to sign.
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
Console.WriteLine("Data : " + BitConverter.ToString(data));
// Sign the data using the Smart Card CryptoGraphic Provider.
byte[] sig = rsa.SignData(data, "SHA1");
Console.WriteLine("Signature : " + BitConverter.ToString(sig));
// Verify the data using the Smart Card CryptoGraphic Provider.
bool verified = rsa.VerifyData(data, "SHA1", sig);
Console.WriteLine("Verified : " + verified);
}
}
}
Is it possible to implement same with tripledes? I tried but cant find a solution.
Link: https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-access-hardware-encryption-devices
Generally you cannot. And the reason is relatively simple: symmetric ciphers, such as triple-DES are not very useful for person to person cryptography such as signature generation (or MAC for symmetric algorithms) or encryption.
The advantage of asymmetric ciphers is that you can distribute the encryption key to other persons. To do this the other person needs to be certain that they receive the correct key: the key needs to be trusted. For this a Public Key Infrastructure or PKI needs to be setup. Known PKI's are PGP and PKIX which is based on X.509 certificates - the same that are used for your HTTPS connections.
So triple DES on a smart card only makes sense if other entities can use the same key. This would be just to gain a tiny performance enhancement because you can encrypt with a public key as well, even in software. Then you can decrypt using the private key on the card. Generally a hybrid cryptosystem is used where a symmetric key such as a triple-DES key is established using RSA, after which the message is encrypted with that key.
That's not to say it is impossible - I've actually designed a PKCS#11 compliant smart card, but most PKCS#11 and CSP's implementations won't support 3DES, and then you would need to have a compatible smart card with a 3DES key as well. Also see the answer from BartonJS.
using (Pkcs11 pkcs11 = new Pkcs11("cryptoki.dll", true))
{
// Get list of available slots with token present
List<Slot> slots = pkcs11.GetSlotList(true);
// Find first slot with token present
Slot slot = slots[0];
// Open RO session
using (Session session = slot.OpenSession(true))
{
session.Login(CKU.CKU_USER, "userPin");
// Prepare attribute template that defines search criteria
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, "TestKey"));
// Initialize searching
session.FindObjectsInit(objectAttributes);
// Get search results
List<ObjectHandle> foundObjects = session.FindObjects(2);
// Terminate searching
session.FindObjectsFinal();
ObjectHandle objectHandle = foundObjects[0];
byte[] iv = Encoding.UTF8.GetBytes("00000000");
byte[] inputData = Encoding.UTF8.GetBytes("data to encrypt.");
Mechanism mechanism = new Mechanism(CKM.CKM_DES3_CBC, iv);
byte[] result = session.Encrypt(mechanism, objectHandle, inputData);
Console.WriteLine(Convert.ToBase64String(result));
}
}
Ä°s that possbile solution?
If have a TripleDES key on a hardware device and know the CNG KSP and key name, you should be able to use TripleDESCng:
using (TripleDES des3 = new TripleDESCng("YourKeyIdentifier", new CngProvider("Your KSP Name"))
{
// So long as you use CreateEncryptor() or CreateDecryptor() (the 0-argument ones)
// the operation is done on the hardware.
}
This, of course, requires that you have a hardware device that supports DES3-EDE.
Getting such a key onto the device is an exercise left to the reader.

Convert from RSACryptoServiceProvider to RSACng

I am currently using RSACryptoServiceProvider and I want to change to RSACng. I am using it to sign data. The reason for the change is that I am using Pkcs1 padding and I understand that Pss padding is preferred. We are undergoing security audits.
My question is how do I instantiate RSACng so that it uses the same private / public key each time?
With RSACryptoServiceProvider I am doing:
CspParameters cp = new CspParameters();
cp.KeyContainerName = "ContainerName";
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cp);
passing in the container name means it uses the key that persists in the in the container store on the machine.
With RSACng, I tried this, but I get an exception: "The requested operation is not supported"
RSACng RSA = new RSACng(CngKey.Create(CngAlgorithm.Sha256, ContainerName));
I just need to be able to pass the store key name so it uses the same key each time instead of generating a new key.
If you want to create a named/persisted RSA key with CNG:
private static RSA CreatePersistedRSAKey(string name, int keySizeInBits)
{
CngKeyCreationParameters creationParameters = new CngKeyCreationParameters
{
// This is what an ephemeral key would have had
// (allows ExportParameters(true) to succeed). Adjust as desired.
//
// The default is not exportable (only applies to the private key)
ExportPolicy =
CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport,
};
creationParameters.Parameters.Add(
new CngProperty(
"Length",
BitConverter.GetBytes(keySizeInBits),
CngPropertyOptions.Persist));
// RSACng will extract the data it needs from this key object,
// but doesn't take ownership
using (CngKey key = CngKey.Create(CngAlgorithm.Rsa, name, creationParameters))
{
return new RSACng(key);
}
}
This skips the parts where you would do a try/catch around a call to CngKey.Open, or might want to delete the key (open it with CngKey.Open, and call Delete on the CngKey instance).
(CngAlgorithm.Rsa was added in net46. If you're on an older version then an equivalent would be new CngAlgorithm("RSA"))

How to validate a certificate chain from a specific root CA in C#

I have a certificate chain that looks like this: root CA -> intermediate CA -> client certificate. How can I validate that a received certificate is explicitly created by "root CA"?
To validate the whole chain is not a problem. This can be done like this:
X509Certificate2 rootCert = new X509Certificate2(rootCertFile);
X509Certificate2 intermediateCert = new X509Certificate2(intermediateCertFile);
X509Certificate2 clientCert = new X509Certificate2(clientCertFile);
chain.ChainPolicy.ExtraStore.Add(rootCert);
chain.ChainPolicy.ExtraStore.Add(intermediateCert);
if(chain.Build(clientCert))
{
// ... chain is valid
}
The issue here is that the certificate gets validated against the (Windows) certificate store but I just want to validate it against a specific root CA.
I also thought that it would be possible to check if the chain.ChainElements contains my expected root CA. But what if someone sends me a valid chain from a different root CA and just adds the my expected root CA?
The certificate chain API checks that each element signed the preceding element, so there's no way that someone can just tack your root CA on the end (provided you're not using something like a 384-bit RSA key with MD5 signatures, in which case they can just forge your signature).
You can encode any extra checks that you like, such as that you know none of your chains will exceed length 3 (though you could just have encoded that in your root CA's X509 Basic Constraints extension).
if (!chain.Build(cert))
{
return false;
}
if (chain.ChainElements.Length > 3)
{
return false;
}
X509Certificate2 chainRoot = chain.ChainElements[chain.ChainElements.Length - 1].Certificate;
return chainRoot.Equals(root);
If you prefer the last line could be return root.RawData.SequenceEquals(chainRoot.RawData); (ensure they have the same bytes).
Some things of note:
When you call X509Chain.Build() every X509Certificate2 object it returns via an X509ChainElement is a new object. You may wish to Dispose any of the objects you aren't returning (which may be all of them).
Even when chain.Build returns false it will populate the ChainElements array so you can inspect why.
The X509Chain object itself is Disposable, you likely want to Dispose it (which you might already be doing outside of your snippet).
Disposing the chain won't Dispose any of the created certificates, since you could have been holding an object reference already.
The X509Chain does not work reliably for scenarios where you do not have the root certificate in the trusted CA store on the machine.
Others will advocate using bouncy castle. I wanted to avoid bringing in another library just for this task, so I wrote my own.
As see in RFC3280 Section 4.1 the certificate is a ASN1 encoded structure, and at it's base level is comprised of only 3 elements.
The "TBS" (to be signed) certificate
The signature algorithm
and the signature value
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
C# actually has a handy tool for parsing ASN1, the System.Formats.Asn1.AsnDecoder.
Using this, we can extract these 3 elements from the certificate to verify the chain.
The first step was extracting the certificate signature, since the X509Certificate2 class does not expose this information and it is necessary for the purpose of certificate validation.
Example code to extract the signature value part:
public static byte[] Signature(
this X509Certificate2 certificate,
AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
var signedData = certificate.RawDataMemory;
AsnDecoder.ReadSequence(
signedData.Span,
encodingRules,
out var offset,
out var length,
out _
);
var certificateSpan = signedData.Span[offset..(offset + length)];
AsnDecoder.ReadSequence(
certificateSpan,
encodingRules,
out var tbsOffset,
out var tbsLength,
out _
);
var offsetSpan = certificateSpan[(tbsOffset + tbsLength)..];
AsnDecoder.ReadSequence(
offsetSpan,
encodingRules,
out var algOffset,
out var algLength,
out _
);
return AsnDecoder.ReadBitString(
offsetSpan[(algOffset + algLength)..],
encodingRules,
out _,
out _
);
}
The next step is to extract the TBS certificate. This is the original data which was signed.
example code to extract the TBS certificate data:
public static ReadOnlySpan<byte> TbsCertificate(
this X509Certificate2 certificate,
AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
var signedData = certificate.RawDataMemory;
AsnDecoder.ReadSequence(
signedData.Span,
encodingRules,
out var offset,
out var length,
out _
);
var certificateSpan = signedData.Span[offset..(offset + length)];
AsnDecoder.ReadSequence(
certificateSpan,
encodingRules,
out var tbsOffset,
out var tbsLength,
out _
);
// include ASN1 4 byte header to get WHOLE TBS Cert
return certificateSpan.Slice(tbsOffset - 4, tbsLength + 4);
}
You may notice that when extracting the TBS certiifcate I needed to include the ASN1 header in the data, this is because the signature of the TBS Certificate INCLUDES this data (this annoyed me for a while).
For the first time in history, Microsoft does not impede us with their API design, and we are able to obtain the Signature Algorithm directly from the X509Certificate2 object. Then we just need to decide to what extent we are going to implement different hash algorithms.
var signature = signed.Signature();
var tbs = signed.TbsCertificate();
var alg = signed.SignatureAlgorithm;
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad
switch (alg)
{
case { Value: var value } when value?.StartsWith("1.2.840.113549.1.1.") ?? false:
return signedBy.GetRSAPublicKey()?.VerifyData(
tbs,
signature,
value switch {
"1.2.840.113549.1.1.11" => HashAlgorithmName.SHA256,
"1.2.840.113549.1.1.12" => HashAlgorithmName.SHA384,
"1.2.840.113549.1.1.13" => HashAlgorithmName.SHA512,
_ => throw new UnsupportedSignatureAlgorithm(alg)
},
RSASignaturePadding.Pkcs1
) ?? false;
case { Value: var value } when value?.StartsWith("1.2.840.10045.4.3.") ?? false:
return signedBy.GetECDsaPublicKey()?.VerifyData(
tbs,
signature,
value switch
{
"1.2.840.10045.4.3.2" => HashAlgorithmName.SHA256,
"1.2.840.10045.4.3.3" => HashAlgorithmName.SHA384,
"1.2.840.10045.4.3.4" => HashAlgorithmName.SHA512,
_ => throw new UnsupportedSignatureAlgorithm(alg)
},
DSASignatureFormat.Rfc3279DerSequence
) ?? false;
default: throw new UnsupportedSignatureAlgorithm(alg);
}
As shown in the code above, https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad is a good resource to see the mapping of algorithms and OIDs.
Another thing you should be aware of is that there are some articles out there that claim that for elliptical curve algorithms, microsoft expects a R,S formatted key instead of a DER formatted key. I tried to convert the key to this format but it ultimately didn't work. What I discovered was that it was necessary to use the DSASignatureFormat.Rfc3279DerSequence parameter.
Additional certificate checks, like "not before" and "not after", or CRL and OCSP checks can be done in addition to the chain verification.

x509 certificate example in OpenSSL .NET Wrapper [duplicate]

Hi Iam using the OpenSSL .NET wrapper in my c# project. i want to generate an X509 certification but i don't really know the procedure. what should it contain (what parameters)...etc
this is my code, I did it after looking some tests:
OpenSSL.X509.X509Certificate x509 = new OpenSSL.X509.X509Certificate();
OpenSSL.Crypto.RSA rsa = new OpenSSL.Crypto.RSA();
rsa.GenerateKeys(1024, 0x10001, null, null);
OpenSSL.Crypto.CryptoKey key = new OpenSSL.Crypto.CryptoKey(rsa);
OpenSSL.Crypto.MessageDigestContext digest = new OpenSSL.Crypto.MessageDigestContext(
OpenSSL.Crypto.MessageDigest.SHA1);
I suppose that the certificate should take RSA private key and the digest as parameters and i have to configure it (date...and others parameters).
Can any one help me about that ? to finish my code ? thank you.
I use the following routine:
// Initialize the following with your information
var serial = 1234;
var issuer = new X509Name("issuer");
var subject = new X509Name("subject");
// Creates the key pair
var rsa = new RSA();
rsa.GenerateKeys(1024, 0x10001, null, null);
// Creates the certificate
var key = new CryptoKey(rsa);
var cert = new X509Certificate(serial, subject, issuer, key, DateTime.Now, DateTime.Now.AddYears(20));
// Dumps the certificate into a .cer file
var bio = BIO.File("C:/temp/cert.cer", "w");
cert.Write(bio);

Categories