SignerInfo.CheckSignature throws out exception "Parameter is incorrect" - c#

There is a method (simplified here) that verifies the digital signature. When I try to check a signed file, I get an error "Parameter is incorrect" in the method SignerInfo.CheckSignature. No matter what parameters I pass. This is happening on the production server and I cannot reproduce the same error on myself - everything works fine. The signature and certificate chain are valid. The code and certificate collection are the same for development and production servers.
public string Check(byte[] fileBytes)
{
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.MaxAllowed);
X509Certificate2Collection collection = new X509Certificate2Collection(store.Certificates);
store.Close();
byte[] encodedSignedCms = fileBytes;
SignedCms signedCms = new SignedCms();
signedCms.Decode(encodedSignedCms);
if (signedCms.SignerInfos.Count == 0)
return ("not found");
SignerInfoEnumerator enumerator = signedCms.SignerInfos.GetEnumerator();
while (enumerator.MoveNext())
{
SignerInfo current = enumerator.Current;
try
{
//any of these methods will return the same error
current.CheckSignature(true);
current.CheckSignature(collection, true);
current.CheckSignature(collection, false);
}
catch (CryptographicException e)
{
//parameter is incorrect
return ("error");
}
}
return ("success");
}
Error:
MESSAGE: parameter is incorrect
INNEREXCEPTION: null
STACKTRACE: in System.Security.Cryptography.Pkcs.SignerInfo.Verify(X509Certificate2Collection extraStore, X509Certificate2 certificate, Boolean verifySignatureOnly) in System.Security.Cryptography.Pkcs.SignerInfo.CheckSignature(X509Certificate2Collection extraStore, Boolean verifySignatureOnly) in ProjectName.Controllers.SignatureController.Check(Byte[] fileBytes).
TARGETSITE: Void Verify(System.Security.Cryptography.X509Certificates.X509Certificate2Collection, System.Security.Cryptography.X509Certificates.X509Certificate2, Boolean)
Technical info:
ASP.NET MVC
Production: Windows Server 2016. Net Framework 4.8. IIS 10.
Development: Windows 7 Pro or Windows Server 2016. Net Framework 4.8. Visual Studio 2015
What can I fix or check? Thanks in advance.

I noticed that the chain was not displayed when opening any certificate. And the error "A system-level error occurred while verifying trust" was displayed. I found a recommendation to reinstall the crypto provider. In my case, it is Crypto Pro CSP. After that, the chain of certificates lined up correctly, and the code worked fine. Most likely this happened after the Windows update.

Related

Unable to connect to IBM MQ from C# console application with credentials

I am unable to connect to IBM MQ from C# console application with credentials. Here is the code. Its throwing "2035 Not Authorized" error.
protected MQQueueManager mqQueueManager;
public override void Open() {
Hashtable properties = new Hashtable();
properties.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED);
properties.Add(MQC.HOST_NAME_PROPERTY, mq_hostName);
properties.Add(MQC.CHANNEL_PROPERTY, mq_channel);
properties.Add(MQC.PORT_PROPERTY, mq_port);
properties.Add(MQC.USER_ID_PROPERTY, mq_userName);
properties.Add(MQC.PASSWORD_PROPERTY, mq_password);
properties.Add(MQC.USE_MQCSP_AUTHENTICATION_PROPERTY, true);
try
{
mqQueueManager = new MQQueueManager(mq_queueManagerName, properties);
Console.WriteLine("Connected to MQ");
}
catch (MQException e)
{
log.Fatal("MQ Queue Manager creation Error", e);
throw e;
}
}
What I have tried:
If I comment user name and password properties. It’s able to connect. Not sure, why it’s not working with credentials. Please let me know if anyone has encountered this issue and any pointers to resolve the same.
Its an issue with user name. We are migrating Java application to .NET and in user name "#domain" was there at the end. Looks like, this format is required for Java and its working fine there. When we use same user name in .NET, it was not working and throwing error "2035 MQRC_NOT_AUTHORIZED". Once we removed #domain from it, it started working.

Unable to export RSA private parameters when running as administrator

I'm using NET Core 3.1, and need to export the private RSA parameters (D, P and Q), as they are being used as keying material for an HKDF function (HMAC-based Extract-and-Expand Key Derivation Function) in order to provide deterministic shared secrets.
The code I have is working great - but, bizarrely it throws if it's ran from an elevated admin prompt:
var flags = X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable;
var certs = new X509Certificate2Collection();
certs.Import(#"C:\MyCert.pfx", String.Empty, flags);
var cert = certs.OfType<X509Certificate2>().Where(x => x.HasPrivateKey);
using (var rsa = cert.GetRSAPrivateKey())
{
// This works - *unless* executed from an elevated admin prompt!?
var rsaParms = rsa.ExportParameters(true);
// use the params here...
}
Stack trace:
Unhandled exception. Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The requested operation is not supported.
at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
at System.Security.Cryptography.RSACng.ExportKeyBlob(Boolean includePrivateParameters)
at System.Security.Cryptography.RSACng.ExportParameters(Boolean includePrivateParameters)
If it makes any difference, the certificate in question is self-signed, generated using C#'s System.Security.Cryptography.X509Certificates.CertificateRequest.CreateSelfSigned.
Any idea why this would throw only when executed elevated, or/and how to get it to not throw?
UPDATE
I've done some more digging, and it works as expected if I use a self-signed cert generated using OpenSSL instead of .NET - all the X509 extensions/settings are the same.
I did some debugging, and see that there is a difference when I inspect rsa.Key.
Normal execution, .NET generated certificate:
rsa.Key.ExportPolicy is AllowExport | AllowPlaintextExport
rsa.Key.Provider is Microsoft Enhanced Cryptographic Provider v1.0
Elevated execution, .NET generated certificate:
rsa.Key.ExportPolicy is AllowExport
rsa.Key.Provider is Microsoft Software Key Storage Provider
So, it's using the deprecated CAPI provider when not running elevated, and the "modern" CNG provider when elevated. And we can see that AllowPlaintextExport is missing with the CNG provider, which I assume is the problem.
When using an OpenSSL-generated certificate the provider is always the deprecated CAPI provider, whether elevated or not.
Yet more digging led to this answer, which involves using interop to get the AllowPlaintextExport flag added when using the CNG. Now, this works from an admin prompt when using a .NET-generated certificate... but the call to CryptAcquireCertificatePrivateKey returns false when using an OpenSSL-generated certificate (means "didn't get the private key"), regardless of privileges!
I found another, much simpler answer here, which involves importing the key into an RSACng, and then toggling on the AllowPlaintextExport. However, as expected the call to rsa.ExportParameters(true) still fails with The requested operation is not supported, so I'm unable to import into the RSACng
It really shouldn't be this difficult :(
I can't explain the reason the original code didn't work when running elevated, but I have come up with 2 workarounds, which presumably are ensuring the AllowPlaintextExport policy flag is present.
Workaround 1 - Load PEM Key
In the original code, I load a certificate from a PKCS#12 (.p12/.pfx) file, which contains both the public and private parts. If instead I load a PEM key, it works as expected:
// We need to strip the labels from the beginning and end of the key - working
// with PEM files is much easier in .NET 5, as it handles all this cruft for us
var regex = new Regex(#"^[-]+BEGIN.+[-]+\s(?<base64>[^-]+)[-]+", RegexOptions.Compiled | RegexOptions.ECMAScript | RegexOptions.Multiline);
var keyText = File.ReadAllText(#"C:\app\cert.key");
var keyBase64 = regex.Match(keyText).Groups["base64"].Value;
var keyBytes = keyBase64.FromBase64String();
using var rsaKey = RSA.Create();
rsaKey.ImportRSAPrivateKey(keyBytes, out _);
var rsaParams = rsaKey.ExportParameters(true);
Workaround 2 - Export/Import Key
In this workaround, we export the key to a blob, then import it back again. When I first tried this, it didn't work - for reasons unknown, you have to export it in encrypted form; attempting to export without encryption throws!
Code is based on this internal .NET utility.
public static RSA GetExportableRSAPrivateKey(this X509Certificate2 cert)
{
const CngExportPolicies exportability = CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport;
var rsa = cert.GetRSAPrivateKey();
// Thankfully we don't have to deal with all this shit on Linux
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return rsa;
// We always expect an RSACng on Windows these days, but that could change
if (!(rsa is RSACng rsaCng))
return rsa;
// Is the AllowPlaintextExport policy flag already set?
if ((rsaCng.Key.ExportPolicy & exportability) != CngExportPolicies.AllowExport)
return rsa;
try
{
// Export the original RSA private key to an encrypted blob - note you will get "The requested operation
// is not supported" if trying to export without encryption, so we export with encryption!
var exported = rsa.ExportEncryptedPkcs8PrivateKey(nameof(GetExportableRSAPrivateKey),
new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 2048));
// Load the exported blob into a fresh RSA object, which will have the AllowPlaintextExport policy without
// having to do anything else
RSA copy = RSA.Create();
copy.ImportEncryptedPkcs8PrivateKey(nameof(GetExportableRSAPrivateKey), exported, out _);
return copy;
}
finally
{
rsa.Dispose();
}
}

Could not Connect to IBMMQ 7.5 using SSL

Below Code is just working fine with IBMMQ 8.0 DLL and server when I switch to 7.5 (both DLL and server) it is giving me this error using same certificate
The SSL key repository cannot be used because MQ cannot obtain a
password to access it. Reasons giving rise to this error include: &B
(a) the key database file and password stash file are not present in
the location configured for the key repository, &B (b) the key
database file exists in the correct place but that no password stash
file has been created for it, &B (c) the files are present in the
correct place but the userid under
public void test() {
Environment.SetEnvironmentVariable("MQCCSID", "437");
MQQueueManager mQQueueManager = null;
MQQueue mQQueue = null;
Hashtable hashTable = null;
try {
hashTable = new Hashtable();
// Setup properties for connection
hashTable.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED);
hashTable.Add(MQC.HOST_NAME_PROPERTY, "IP");
hashTable.Add(MQC.PORT_PROPERTY, 1414);
hashTable.Add(MQC.CHANNEL_PROPERTY, "Channel");
hashTable.Add(MQC.PASSWORD_PROPERTY, "123");
hashTable.Add(MQC.USER_ID_PROPERTY, "user");
mQQueueManager = new MQQueueManager("QueueName", hashTable);
// Open queue for browsing
mQQueue = mQQueueManager.AccessQueue("que", MQC.MQOO_BROWSE | MQC.MQOO_FAIL_IF_QUIESCING);
ListOfMessages = new List < MQMessageDto > ();
// In a loop browse all messages till we reach end of queue
while (true) {
try {
// Need to create objects everytime
var mQMessage = new MQMessage();
var mQGetMessageOptions = new MQGetMessageOptions {
// Use browse next option to start browsing
Options = MQC.MQGMO_BROWSE_NEXT
};
mQQueue.Get(mQMessage, mQGetMessageOptions);
ListOfMessages.Add(new MQMessageDto() {
Id = ListOfMessages.Count + 1,
Message = Encoding.UTF8.GetString(mQMessage.ReadBytes(mQMessage.MessageLength))
});
} catch (MQException mqex) {
if (ListOfMessages.Count == 0) {
MessageBox.Show("There is no messages in MQ");
}
mQQueue.Close();
break;
}
}
mQQueueManager.Disconnect();
grdMessages.DataSource = ListOfMessages;
grdMessages.Columns["Id"].Width = (int)(grdMessages.Width * 0.1);
grdMessages.Columns["Message"].Width = (int)(grdMessages.Width * 0.8);
} catch (Exception ex) {
MessageBox.Show(ex.Message);
}
}
What are you describing means you have wrong configuration at IBM side, and Since you are using IBM MQ 7.5. I think you got the path for the SSL key repository wrong, it should point to the key name not the folder.
Also make sure that you have selected Optional from SSL tab inside your Channel.
For more details.. More details about this issue can be found here about error this error code:
2538 error on MQ for SSL channel connection
You didn't mention which specific level of 7.5 you are using. If it is 7.5.0.7 or earlier, the stash file will likely be the problem:
https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.mig.doc/q128820_.htm
Older versions of the cryptographic provider used by MQ (GSKit) use a different stash file format for the keystore password.
While newer GSKit versions can handle the old stash file format, the new format is not readable by older GSKit versions. If you are using a level which uses the new format, you can create a backwards-compatible stash file with the -v1stash option:
runmqakm -keydb -stashpw -db <filename> -pw <password> -v1stash
A better alternative, as MQ 7.5 is out of support, would be to use a newer client level, which can still communicate with a 7.5 queue manager if required.
For reference, the first GSKit level which uses the new stash file format is 8.0.50.69. Levels of GSKit bundled with MQ are listed here: https://www.ibm.com/support/pages/levels-jre-and-gskit-bundled-ibm-mq
Regarding:
When I upgraded my client to V9 I'm getting "MQRC_Q_MGR_NOT_AVAILABLE" on client and "4/23/2020 21:03:22 - Process(11764.64) User() Program(amqrmppa.exe) Host(HOST) Installation(Installation1) VRMF(7.5.0.2) QMgr() Remote channel '' did not specify a CipherSpec. Remote channel '' did not specify a CipherSpec when the local channel expected one to be specified. &P The remote host is '...* (...)'. &P The channel did not start. Change the remote channel '' on host ()' to specify a CipherSpec so that both ends of the channel have matching CipherSpecs." in server
display the cipher spec being used dis chl(xxx) SSLCIPH
You may have specified something which is no longer supported by the underlying TLS support.
dis chl(xxx)

.NET Core X509Certificate2.PrivateKey throws nte_bad_keyset error

When trying to get a X509Certificate2 object from the X509Store using the following code:
private X509Certificate2 GetKey()
{
try
{
X509Store store = new X509Store("WebHosting", StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var collection = store.Certificates.Find(X509FindType.FindBySubjectName, "xxxxxxx", true);
if (collection.Count == 0)
{
throw new Exception("No keys matched");
}
if (collection.Count > 1)
{
StringBuilder sb = new StringBuilder();
sb.Append("More than 1 key matched:\r\n");
foreach (var cert in collection)
{
sb.Append($"{cert.SubjectName} - {cert.Thumbprint}\r\n");
}
throw new Exception(sb.ToString());
}
return collection[0];
}
catch (Exception ex)
{
// something really bad happened, log it
Logger.LogException(ex);
throw;
}
}
I successfully get the key; however, when trying to get the private key within the object using key.PrivateKey, I get the following error: OpenCSP failed with error code 2148073494. Looking up the Windows error 2148073494, I get nte_bad_keyset. It looks like there have been errors around other situations that throw this same error here, but they closed that bug as fixed. When I run this bit of code in a console app, it works fine and it also works fine in my test environment running under IISExpress. When run in a production environment under IIS, I get this error every time. I've tried running under the context of an admin user just to make sure it wasn't an odd permission error, same thing. From what I understand about this Windows error, is that Windows gave me where the key lives, then told me there is nothing at that address. I'm using "System.Security.Cryptography.Algorithms": "4.3.0" for this.
EDIT: I should note that as part of my testing, I actually grabbed the exact certificate I'm looking for from the production environment onto my test environment and it loaded fine. I also ran the console app on the production environment pulling the same key and it worked fine.
Ultimately, the answer is "something deleted the private key after importing it to the certificate store" (or, maybe it's possible to confuse Windows into remembering where a key lives despite it not actually living there).
If you know that it, for example, works for a little while then stops:
> certutil -store my
...
================ Certificate 6 ================
Serial Number: 3451b93c10f9279348a949f729d1ff10
Issuer: CN=localhost
NotBefore: 1/26/2015 2:19 PM
NotAfter: 1/25/2020 4:00 PM
Subject: CN=localhost
Signature matches Public Key
Root Certificate: Subject matches Issuer
Template:
Cert Hash(sha1): 15 e3 4c d3 2d a7 54 99 a9 17 8f 17 26 25 63 25 8f 3a 94 28
Key Container = IIS Express Development Certificate Container
Unique container name: fad662b360941f26a1193357aab3c12d_1fcb2e07-cec4-4ba1-9c78-58a431e1aefd
Provider = Microsoft RSA SChannel Cryptographic Provider
Encryption test passed
CertUtil: -store command completed successfully.
Seeing that it's in "Microsoft RSA SChannel Cryptographic Provider", turn to https://msdn.microsoft.com/en-us/library/windows/desktop/bb204778(v=vs.85).aspx and see that the keyfile will be located at %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys. The Unique container name happens to be the name of the file it'll have.
So, open that directory, right click on the file.
Properties
Security Tab
Advanced button
Auditing tab
Edit button
Add button
Type "Everyone", push "Check Names", pick "Everyone", hit OK, hit OK.
Check Delete: Success.
Hit OK to dismiss all the dialogs.
Later, after you start getting keyset errors, search the security log for an audit on the file delete (event 4663 from Security-Auditing):
An attempt was made to access an object.
Subject:
Security ID: SOMEDOMAIN\theaccount
Account Name: theaccount
Account Domain: SOMEDOMAIN
Logon ID: 0xabcdef
Object:
Object Server: Security
Object Type: File
Object Name: C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\fad662b360941f26a1193357aab3c12d_1fcb2e07-cec4-4ba1-9c78-58a431e1aefd
Handle ID: 0xef8
Process Information:
Process ID: 0xf54
Process Name: C:\Windows\explorer.exe
Access Request Information:
Accesses: DELETE
Access Mask: 0x10000
That will tell you what process/user did the delete operation... and maybe that'll be enough to identify what went wrong.
You could presumably do the private key file identification and audit registration more programmatically; but this was the quickest way I knew to explain it.

.NET/C# reading certificate from store cause AccessViolationException

I've getting very strange code exception, which is should work but not work.
I have following code:
X509Certificate certificate;
X509CertificateStore store = X509CertificateStore.LocalMachineStore(X509CertificateStore.MyStore);
store.OpenRead();
var certs = new X509CertificateCollection();
Microsoft.Web.Services2.Security.X509.X509CertificateCollection findCertificateBySubjectName = store.FindCertificateBySubjectName("localhost");
foreach (X509Certificate tempCertificate in store.Certificates)
{
if (String.CompareOrdinal(tempCertificate.Subject, "") == 0)
{
certs.Add(tempCertificate);
}
}
Current code runs inside w3wp process under admin rights. But I get exception which you can see on screenshot. I tried many things, give all access to all certificates, change users, reimport certificate.
I will be appreciate for any help or assistance.

Categories