How x509chain works in C#? What is purpose of using it? - asp.net

I have implement client certificate based security in C#. Everything is clear and working good for me. I was finding perfect way to verify client certificate on server side, through which i came to x509Chain. But i am not clear about How it exactly works? How can we configure it? Is it secure way to verify certificate?
Any help will be Appreciated! Thanks!
Note: By theoretical point of view, i have read my documents on that but i am not sure for it's functionality. Please provide practical example and guide for that.

A bit of theory I wrote some time ago: http://social.technet.microsoft.com/wiki/contents/articles/3147.certificate-chaining-engine-cce.aspx
This article describes what is certificate chaining engine (CCE) and how it works in Windows in general. It is based on RFC5280 and Microsoft-specific implementation of certificate chaining engine. .NET uses native CryptoAPI functions, so X509Chain behaves in the same way as in native CryptoAPI.

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, the 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 extend 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.

Related

Can EnvelopedCms be used to identify the encryption certificate?

I'm working on a .NET system that receives encrypted files, and I'm decrypting them using an EnvelopedCms object.
The decryption process is working just fine but I would like to be able to determine the thumbprint of the certificate that the sender actually used when they encrypted.
The reason for this is that when a certificate is approaching its expiry date, and the sender has been asked to used a new certificate with a valid-from date that overlaps the valid-to of the expiring certificate, I would like to be able to identify which senders have made the switch and which ones haven't.
Until the old certificate actually expires, both private keys will be available so the file will successfully decrypt regardless of which one they use.
The code I have is currently:
Dim ecms As New EnvelopedCms()
Try
ecms.Decode(data)
Catch ex As Exception
DecryptError = True
DecryptErrorMessage = ex.Message
End Try
Try
ecms.Decrypt()
Catch ex As Exception
DecryptError = True
DecryptErrorMessage = ex.Message
End Try
If DecryptError = False Then
ActualEncryptionCertificate = MyCertificate
If UCase(MyCertificate) <> UCase(ActualEncryptionCertificate) Then
DecryptError = True
DecryptErrorMessage = "Unexpected encryption certificate used."
End If
MessageData = ecms.Encode()
End If
"data" contains the cyphertext, "MyCertificate" contains the thumbprint of the cert we want them to use and the intention is for "ActualEncrpytionCertificate" to contain the thumbprint of the cert they actually used but as you can see, this is just a placeholder at the moment.
I've looked through the properties of the EnvelopedCms object hoping to find a certificates collection, but this does not exist. There is a RecipientInfos collection, but this doesn't seem to contain any information about the encryption certificate.
Am I on a hiding to nothing or is there some other way to identify the certificate from the EnvelopedCms object?
The API won't emit the matched certificate, and the Decrypt overloads which accept certificates will still always search in the Windows Certificate stores, so the only real viable option is to do a match based on the RecipientInfo values.
Note that there are two different implementations for the IssuerAndSerialNumber match. The first one is more efficient because it doesn't throw extra data at the garbage collector. The second one is so lifetime management is symmetric across the the SubjectKeyIdentifier and IssuerAndSerialNumber variants. If you don't need to return the certificates to anyone after calling FindRecipientCerts then you probably want the first one (since you won't need to be concerned with who does/doesn't call Dispose).
private static X509Certificate2Collection FindRecipientCerts(
X509Certificate2Collection haystack,
RecipientInfo recipient)
{
SubjectIdentifier identifier = recipient.RecipientIdentifier;
if (identifier.Type == SubjectIdentifierType.IssuerAndSerialNumber)
{
X509Certificate2Collection coll = new X509Certificate2Collection();
X509IssuerSerial issuerSerial = (X509IssuerSerial)identifier.Value;
#if !CLONE_REQUIRED
foreach (X509Certificate2 cert in haystack)
{
if (cert.SerialNumber == issuerSerial.SerialNumber &&
cert.Issuer == issuerSerial.IssuerName)
{
coll.Add(cert);
}
}
return coll;
#else
// To reduce the number of clones generated, do a local filter on IssuerName, then
// use the cloning Find method for SerialNumber.
foreach (X509Certificate2 cert in haystack)
{
if (cert.Issuer == issuerSerial.IssuerName)
{
coll.Add(cert);
}
}
return coll.Find(
X509FindType.FindBySerialNumber,
issuerSerial.SerialNumber,
false);
#endif
}
else if (identifier.Type == SubjectIdentifierType.SubjectKeyIdentifier)
{
return haystack.Find(
X509FindType.FindBySubjectKeyIdentifier,
identifier.Value,
false);
}
else
{
throw new InvalidOperationException();
}
}
Apologies for answering your VB question with C#.

Azure KeyVault how to load X509Certificate? [duplicate]

This question already has answers here:
How to serialize and deserialize a PFX certificate in Azure Key Vault?
(5 answers)
Closed 6 years ago.
I uploaded a Certificate to Azure KeyVault and obtained "all" access to it using an application registered into the Active Directory. That all works fine. Now I need to load the obtained key into an X509Certificate to be able to use it as a client certificate for calling a 3rdparty legacy SOAP webservice. As far as I know I can only use a X509Certificate to call that webservice.
It does not matter if I upload it as a Key or Secret? I have tried both.
var clientId = "...."
var clientSecret = "...."
..
var token = authenticationContext.GetAccessToken(resource, adCredential);
var keyClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(token));
KeyBundle key = keyClient.GetKeyAsync("https://mycertifcates.vault.azure.net/keys/MyCertificate/123456789");
So far so good, I got a KeyBundle as a result and it seems like I can convert it to a Base64String, but whatever I try I end up with exceptions :(
1st attempt I tried:
var publicKey = Convert.ToBase64String(key.Key.N);
var cert = X509Certificate.FromBase64String(publicKey);
System.Security.Cryptography.CryptographicException {"Cannot find the requested object.\r\n"}
Ahhhh
2nd attempt, loading it into an RSACryptoServiceProvider, but what to do with that? Results into the same exception and I'm not even able to get the private key if I want to
var rsaCryptoProvider = new RSACryptoServiceProvider();
var rsaParameters = new RSAParameters()
{
Modulus = key.Key.N,
Exponent = key.Key.E
};
rsaCryptoProvider.ImportParameters(rsaParameters);
var cspBlob = rsaCryptoProvider.ExportCspBlob(false);
// what to do with the cspBlob ?
var publicKey = Convert.ToBase64String(cspBlob);
No private key as well, the public key is different. Ofcourse this does not work either.
3rd attempt
I uploaded it as a Secret Certificate ContentType using the management portal.
var secret = helper.GetSecret("https://myCertificate.vault.azure.net/secrets/MyCertificate/1234567890");
var value = secret.Value;
// Now I got the secret.. works flawless
// But it crash with the same exception at .Import
var exportedCertCollection = new X509Certificate2Collection();
exportedCertCollection.Import(Convert.FromBase64String(value));
var cert2 = exportedCertCollection.Cast<X509Certificate2>().Single(s => s.HasPrivateKey);
System.Security.Cryptography.CryptographicException {"Cannot find the
requested object.\r\n"}
Any suggestion is welcome.
I need the pfx including private key, looking at the tracelog
ystem.Net Information: 0 : [37880] SecureChannel#23107755 - Left with 1 client certificates to choose from.
System.Net Information: 0 : [37880] SecureChannel#23107755 - Trying to find a matching certificate in the certificate store.
System.Net Information: 0 : [37880] SecureChannel#23107755 - Locating the private key for the certificate: [Subject]
To answer my own question (I asked it too quickly I guess)
It turns out that this question was in fact a duplicate, but I did not understand it at first. The fact is that the Azure Management Portal is limited. For now uploading a certificate can only be done by using a powershell (or other code for that matter).
https://stackoverflow.com/a/34186811/578552
The 3rd attempt code works fine for the X509Certificate2
var secret = helper.GetSecret("https://myCertificate.vault.azure.net/secrets/MyCertificate/1234567890");
var exportedCertCollection = new X509Certificate2Collection();
exportedCertCollection.Import(Convert.FromBase64String(secret.Value));
var cert2 = exportedCertCollection.Cast<X509Certificate2>().Single(s => s.HasPrivateKey);

Sign and decrypt data using private keys located on a HSM

I have a task to sign and decrypt data, but the private keys are located at a HSM ( Luna SA, / Safenet ). I installed all the client software and connected the Luna SA CSP to the test servers.
Using the PKCS#11 functions provided, I am able to list and export the public keys on the HSM as PCCERT_CONTEXT (CertCreateCertificateContext). When I try to acquire the private key (using CryptoAPI function CryptAcquireCertificatePrivateKey), I receive an error code CRYPT_E_NO_KEY_PROPERTY.
I am probably missing the link between the certificate data and the CSP/HSM. Has anybody done something similar and can give any hints?
EDIT
I sucessfully created CER files from all the keys located on the HSM.
When i know use signtool.exe (the one that ships with Microsoft Plattform SDK) i am able to sign a dll with a key on the HSM (the tool wizard lets me choose key container, key spec, ...). I tried to use the information the tool shows me and set the private key
bool LinkPrivateKey(PCCERT_CONTEXT cert)
{
CRYPT_KEY_PROV_INFO cryptKeyProvInfo;
memset(&cryptKeyProvInfo, 0, sizeof(cryptKeyProvInfo));
cryptKeyProvInfo.pwszContainerName = L"MSS";
cryptKeyProvInfo.pwszProvName = L"Luna Cryptographic Services for Microsoft Windows";
cryptKeyProvInfo.dwProvType = PROV_RSA_FULL;
cryptKeyProvInfo.dwFlags = CRYPT_MACHINE_KEYSET; // CERT_SET_KEY_CONTEXT_PROP_ID | CERT_SET_KEY_PROV_HANDLE_PROP_ID;
cryptKeyProvInfo.cProvParam = 0;
cryptKeyProvInfo.dwKeySpec = AT_SIGNATURE;
return CertSetCertificateContextProperty(cert, CERT_KEY_PROV_INFO_PROP_ID, 0, &cryptKeyProvInfo) != FALSE;
}
but CryptAcquirePrivateKey still gives me the same error. I believe I am missing only a small bit here, since the signtool is able to access the private key
Edit2
The screnshot shows KEYEXCHANGE but I chose SIGNATURE
Edit3
I changed the LinkPrivateKeyfunction a little bit, now it works
bool LinkPrivateKey(PCCERT_CONTEXT cert)
{
CRYPT_KEY_PROV_INFO cryptKeyProvInfo;
memset(&cryptKeyProvInfo, 0, sizeof(cryptKeyProvInfo));
cryptKeyProvInfo.pwszContainerName = L"MSS";
cryptKeyProvInfo.pwszProvName = L"Luna Cryptographic Services for Microsoft Windows";
cryptKeyProvInfo.dwProvType = PROV_RSA_FULL;
cryptKeyProvInfo.dwFlags = 1; // CERT_SET_KEY_CONTEXT_PROP_ID | CERT_SET_KEY_PROV_HANDLE_PROP_ID;
cryptKeyProvInfo.dwKeySpec = AT_SIGNATURE;
return CertSetCertificateContextProperty(cert, CERT_KEY_PROV_INFO_PROP_ID, 0, &cryptKeyProvInfo) != FALSE;
}
I don't have any experience with Luna but this worked well for nCiper HSM:
certutil -repairstore -csp "nCipher Enhanced Cryptographic Provider" My <serial number of certificate>
where "nCipher Enhanced Cryptographic Provider" is the name of CSP that comes with HSM.
Serial number of certificate can be obtained using this command:
certutil -store My
It will print all certificates in Local_Machine\My store. The serial number will be between bunch of ====== like ================ Certificate 5 ================. It will also parse information about the certificates like serial number, subject etc. and it will run encrypt/decrypt test to verify usability of the certificate.
After you repair the binding you can use this (second) command to verify that it went well. Don't be fooled by output of the first command, I have never seen it put out anything other than success.
You can find more information about usage of certutil here.
As stated in my post, I can link the private key using
I changed the LinkPrivateKeyfunction a little bit, now it works
bool LinkPrivateKey(PCCERT_CONTEXT cert)
{
CRYPT_KEY_PROV_INFO cryptKeyProvInfo;
memset(&cryptKeyProvInfo, 0, sizeof(cryptKeyProvInfo));
cryptKeyProvInfo.pwszContainerName = L"MSS";
cryptKeyProvInfo.pwszProvName = L"Luna Cryptographic Services for Microsoft Windows";
cryptKeyProvInfo.dwProvType = PROV_RSA_FULL;
cryptKeyProvInfo.dwFlags = 1; // CERT_SET_KEY_CONTEXT_PROP_ID | CERT_SET_KEY_PROV_HANDLE_PROP_ID;
cryptKeyProvInfo.dwKeySpec = AT_SIGNATURE;
return CertSetCertificateContextProperty(cert, CERT_KEY_PROV_INFO_PROP_ID, 0, &cryptKeyProvInfo) != FALSE;
}
YOu have to replace L"MSS"with the key container defined on you server.
LunSA provides the tool keymap.exe that is insatlled along with the LunaCSP to get the container names.

.Net Support of SHA256withRSA

I need to support the following signature:
Sign the UTF-8 representation of the input using SHA256withRSA (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function) with the private key obtained from the Google Developers Console. The output will be a byte array.
The following code fails, with "Invalid algorithem specified". Is this a limitation of .NET? Here is a snippet of my code:
var rsa2 = new RSAPKCS1SignatureFormatter(rsa);
rsa2.SetHashAlgorithm("SHA256");
bytes = rsa2.CreateSignature(bytes);
The above requirement is from computing the signature for a Server to Server Applications for Google API.
https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature
Thanks for any help.
Karl..
Here is the code that signs the JWT. I've removed the use of RSAPKC1SingatureFormatter class and using another HASHCompute method in an effort to get something to work (still not working)
I'm not sure this is correct and unfortunately my response from the Rest service is always the same "Invalid Grant" so hard to tell.
public string Generate(string HeadJWT, string ContentJWT, X509Certificate2 certificate)
{
var bytes = Utility.getBytes(HeadJWT);
var base64Head = Utility.Base64UrlEncode(bytes);
// base64 Url Encode Payload (Json Content)
bytes = Utility.getBytes(ContentJWT);
var base64Payload = Utility.Base64UrlEncode(bytes);
var secureInputValue = String.Format("{0}.{1}", base64Head, base64Payload);
bytes = Stub.Jwt.Utility.getBytes(secureInputValue);
bytes = Stub.Jwt.Utility.ComputeHMACSha265(bytes, certificate.PublicKey.EncodedKeyValue.RawData);
_signature = Stub.Jwt.Utility.Base64UrlEncode(bytes);
return String.Format("{0}.{1}.{2}", base64Head, base64Payload, _signature);
}
This cannot be a limitation of .NET in general, as the example Microsoft code seems to use "SHA256" itself. But it could be a limitation of your particular runtime.

Node.js Password Encryption Same As In Asp.Net

I have created a wesite in asp.net and use ms-sql database to save the records. Now want to convert it in node.js application. And want to use same sql database. In asp.net application I have encrypt the password for registered user. Below is code.
public static string CreateHash(string unHashed)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] data = System.Text.Encoding.ASCII.GetBytes(unHashed);
data = x.ComputeHash(data);
return System.Text.Encoding.ASCII.GetString(data);
}
public static bool MatchHash(string HashData, string HashUser)
{
HashUser = CreateHash(HashUser);
if (HashUser == HashData)
return true;
else
return false;
}
Now problem is that how I use same encryption in node.js. So when node application is ready old user can also make login. It only possible if node app also use same encryption that I have use in asp.net.
For node I have created all environment and use mssql module for database communication. Please help me fix that. Thanks!!
First of all MD5 shall no longer be used if you are serious about security.
Based on your comment and code, I fear there is a 'data loss' in the initial ASP.net code.
Let us have a look at CreateHash function again, I've added comments:
public static string CreateHash(string unHashed)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
// Convert unHashed string to bytes using ASCII coding
byte[] data = System.Text.Encoding.ASCII.GetBytes(unHashed);
// Compute MD5 hash from bytes
data = x.ComputeHash(data);
// Decode MD5 resulting bytes as ASCII
return System.Text.Encoding.ASCII.GetString(data);
}
The last line confuses me, it is decoding bytes received from MD5 function as if they were ASCII, but that is incorrect assumption. And the resulting encoded string as you gave in comment contains lots of "?'s".
Next node.js code will do similar except encode the string using hex rather than ascii:
var crypto = require('crypto')
function createHash(data) {
return crypto.createHash('md5').update(data, 'ascii').digest('hex')
}
To emulate "bytes to ascii" you could try .digest('binary') instead of hex. If it does not give what you expect, then you have to make a separate 'conversion' step from hex to ascii. (I am not experienced enough to give you elegant solution to the later one)

Resources