Can EnvelopedCms be used to identify the encryption certificate? - encryption

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#.

Related

How x509chain works in C#? What is purpose of using it?

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.

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.

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)

How to Get the ConnectionString that the Membership Provider is using?

... and not by reading it from the config file! Nor inferring it from anyplace other than be reading exactly what the Membership Provider is itself using. Call me paranoid.
The first data access in my application is an access to the membership provider. The vast majority of connectivity issues have been where the application is deployed to staging or production with a connection string from development, so I'd like to change this:
MembershipUser me = Membership.GetUser();
to this:
MembershipUser me;
try
{
me = Membership.GetUser();
}
catch ( SqlException E )
{
Response.Write( "SQL Error " + E.Message + ".<br />" );
Response.Write( "Connection String: " + Membership.Provider.WHAT? + "<br />" );
}
Seems so obvious, but every reference I find instructs me to use the ConfigurationManager, which is what I specifically don't want to do. Although I concede that such may be my only option, and a satisfactory one at that.
I'm perfectly willing to accept the possibility that my question is on par with this:
int i;
try
{
i = 42;
}
catch ( Exception e )
{
Response.Write( "Error assigning literal to integer." );
}
If this is the case, please comment accordingly.
I don't believe there is a direct property that you can use that will give you the connection information. One thing you could do though is subclass your chosen membership provider and implement your own properties to give you the info.
It's generally considered a bad idea to surface connection strings in the UI (i.e. poor security) which is why you won't find readily available properties to pass on the value from classes that have read it from the config file.
You may want to consider addressing the root cause of the problem which is related to deployment. This problem is easily solved by using different configuration files for development, staging and production. Visual Studio has built-in support for automatically managing the deployment of the appropriate config file. Full details here:
http://blogs.msdn.com/b/webdevtools/archive/2009/05/04/web-deployment-web-config-transformation.aspx
Hi Here is a way to get connection string from or by the specified Provider Name (ex MySQL provider).
using MySql.Data.MySqlClient;
using MySql.Data;
using MySql.Web.Security;
using System.Collections.Specialized;
using System.Reflection;
void SomeFunction()
{
Type t = Membership.Provider.GetType();
FieldInfo fi = null;
while (fi == null && t != null)
{
fi = t.GetField("connectionString", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
t = t.BaseType;
}
MySql.Web.Security.MySQLMembershipProvider a =(MySql.Web.Security.MySQLMembershipProvider)Membership.Provider;
string Connection_String_Value= fi.GetValue(a).ToString();
}
By replace 'connectionString' with other Non-Public field name You
can Access its Value too.
By replacing Provider(default) with
Membership.Providers["Name_Of_Your_Provider"] you can get its
connection string too.

How to protect from tampering of query string?

Hii,
I have a query string like "http://project/page1.aspx?userID=5". The operation won't be performed, if the 'userID' parameter changed manually. How it is possible?
Hii all, thank you for your assistance... and i got some difference sort of solution from some other sites. i don't know that the best solution. that is to encode the value using an encryption and decryption algorithm... The sample code has been written like this...
<a href='Page1.aspx?UserID=<%= HttpUtility.UrlEncode(TamperProofStringEncode("5","F44fggjj")) %>'>
Click Here</a> <!--Created one anchor tag and call the function for TamperProofStringEncode-->
private string TamperProofStringEncode(string value, string key)
{
System.Security.Cryptography.MACTripleDES mac3des = new System.Security.Cryptography.MACTripleDES();
System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
mac3des.Key = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(key));
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(value)) + "-" + Convert.ToBase64String(mac3des.ComputeHash(System.Text.Encoding.UTF8.GetBytes(value)));
}
In the page load of 'Page1' call the decode algorithm to decode the query string
try
{
string DataString = TamperProofStringDecode(Request.QueryString["UserID"], "F44fggjj");
Response.Write(DataString);
}
catch (Exception ex)
{
Response.Write(ex.Message);
}
private string TamperProofStringDecode(string value, string key)
{
string dataValue = "";
string calcHash = "";
string storedHash = "";
System.Security.Cryptography.MACTripleDES mac3des = new System.Security.Cryptography.MACTripleDES();
System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
mac3des.Key = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(key));
try
{
dataValue = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(value.Split('-')[0]));
storedHash = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(value.Split('-')[1]));
calcHash = System.Text.Encoding.UTF8.GetString(mac3des.ComputeHash(System.Text.Encoding.UTF8.GetBytes(dataValue)));
if (storedHash != calcHash)
{
//'Data was corrupted
throw new ArgumentException("Hash value does not match");
// 'This error is immediately caught below
}
}
catch (Exception ex)
{
throw new ArgumentException("Invalid TamperProofString");
}
return dataValue;
}
It sounds like a strange requirement. Are you trying to implement some sort of home-grown security? If it's so, you really shouldn't.
Anyway, one way you could do it would be to take the entire url http://project/page1.aspx?userID=5 and calculate its md5 sum. Then you append the md5 sum to the final url, such as http://project/page1.aspx?userID=5&checksum=YOURCALCULATEDMD5SUM. Then in page1.aspx you will have to validate that the checksum parameter is correct.
However, this approach is quite naïve and it would not necesarily take very long for anyone to figure out the algorithm you have used. If they did they could "easily" change the userid and calculate an md5 sum themselves. A more robust approach would be one where the checksum was encrypted by a key that only you had access to. But again I have to question your motive for wanting to do this, because other security solutions exist that are much better.
Here is another option that I found incredibly useful for my requirements:
4 Guys From Rolla - Passing Tamper-Proof QueryString Parameters
You can't.
Anything in the HTTP request (including URL, query string, cookies, ...) is under the control of the client and is easy to fake.
This is why it is important to whitelist valid content, because the client can arbitrarily add anything it likes in addition to what you you prompt to receive.
My favourite is the following. It uses a HTTPmodule to transparently encode and decode the Querystring with the explicit purpose of preventing tamperring of the querystring.
http://www.mvps.org/emorcillo/en/code/aspnet/qse.shtml
It is perfect when Session is not an option!
You can't tell whether it has been changed manually. If you use query strings then you hyave to make sure that it doesn't matter if it is changed. e.g. if you are using it to show a user their account details, you need to check wether the selected user, is the current user and show an error message instead of user data if it is not.
If the user is allowed to change record 5, but not record 7 for example, this has to be enforced server-side. To do this you need to be able to identify the user, by requiring a login, and giving them a unique session key that is stored in their browser cookie, or as another parameter in the url query string.
There are abundant packages/modules/libraries in man languages for dealing with authentication and sessions in a sensible way - roll you own at your own peril :)
Well - it depends :)
One possibility is to put the userID into a session variable. So the user cannot see or edit the value.
If you have other means to detect if the value is invalid (i.e. does not exist or cannot be for that user (who you can identify through some other way) or the like) you might get away with validating the input yourself in code behind.
But as you probably know you cannot prevent the user changing the query string.

Resources