Hi all,
Imagine we are running a 32bit .NET app in a x64 machine. This app is failing so we have taken a memory dump of the app when a specific CLR exception gets raised.
We open the dump with our 64bit version of Windbg (Windbg x64), and we verify that we actualy got the dump when the exception happened:
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(27e8.2904): CLR exception - code e0434f4d (first/second chance not available)
wow64!Wow64NotifyDebugger+0x9:
00000000`78be6369 b001 mov al,1
We check the unmanaged call stack and it looks like this:
0:000> k
Child-SP RetAddr Call Site
00000000`0012e170 00000000`78be64f2 wow64!Wow64NotifyDebugger+0x9
00000000`0012e1a0 00000000`78be6866 wow64!Wow64KiRaiseException+0x172
00000000`0012e510 00000000`78b83c7d wow64!Wow64SystemServiceEx+0xd6
00000000`0012edd0 00000000`78be6a5a wow64cpu!ServiceNoTurbo+0x28
00000000`0012ee60 00000000`78be5e0d wow64!RunCpuSimulation+0xa
00000000`0012ee90 00000000`78ed8501 wow64!Wow64LdrpInitialize+0x2ed
00000000`0012f6c0 00000000`78ed6416 ntdll!LdrpInitializeProcess+0x17d9
00000000`0012f9d0 00000000`78ef3925 ntdll!_LdrpInitialize+0x18f
00000000`0012fab0 00000000`77d59640 ntdll!KiUserApcDispatch+0x15
00000000`0012ffa8 00000000`00000000 0x77d59640
00000000`0012ffb0 00000000`00000000 0x0
00000000`0012ffb8 00000000`00000000 0x0
00000000`0012ffc0 00000000`00000000 0x0
00000000`0012ffc8 00000000`00000000 0x0
00000000`0012ffd0 00000000`00000000 0x0
00000000`0012ffd8 00000000`00000000 0x0
00000000`0012ffe0 00000000`00000000 0x0
00000000`0012ffe8 00000000`00000000 0x0
00000000`0012fff0 00000000`00000000 0x0
00000000`0012fff8 00000000`00000000 0x0
Well, all this wow64 stuff doesn't tell us much... This is the 64bit look of our 32bit app. Let's try to take a look to the managed call stack. We need to load SOS extension for that:
0:000> .loadby sos mscorwks
The call to LoadLibrary(C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos) failed, Win32 error 0n193
"%1 is not a valid Win32 application."
Please check your debugger configuration and/or network access.
Well, this error makes sense. We are trying to load 32bit version of SOS into 64bit version of Windbg. 64bit version of SOS should be here: C:\Windows\Microsoft.NET\Framework64\v2.0.50727.
Let's try our 32bit version of Windbg (Windbg x86) then. We can also see the exception and the unmanaged call stack with all that wow64 stuff. Same as before. Let's try to load SOS then:
0:000> .loadby sos mscorwks
Ok, it seems to be loaded. Let's check it:
0:000> .chain
Extension DLL search Path:
...
Extension DLL chain:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos: image 2.0.50727.1434, API 1.0.0, built Thu Dec 06 05:42:38 2007
[path: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll]
...
Great. We are doing business. I don't want to see all that wow64 stuff, I want to see the managed (.NET) call stack with SOS:
0:000> !clrstack
Failed to load data access DLL, 0x80004005
Verify that 1) you have a recent build of the debugger (6.2.14 or newer)
2) the file mscordacwks.dll that matches your version of mscorwks.dll is
in the version directory
3) or, if you are debugging a dump file, verify that the file
mscordacwks___.dll is on your symbol path.
4) you are debugging on the same architecture as the dump file.
For example, an IA64 dump file must be debugged on an IA64
machine.
You can also run the debugger command .cordll to control the debugger's
load of mscordacwks.dll. .cordll -ve -u -l will do a verbose reload.
If that succeeds, the SOS command should work on retry.
If you are debugging a minidump, you need to make sure that your executable
path is pointing to mscorwks.dll as well.
Weird. We got an unexpected error. Our debugger is the latest version, but it seems our version of mscorwks.dll is different than the one in the machine where we got the dump. Mscordacwks.dll is different too, of course. But Windbg should find mscordacwks in my symbol path... Let's verify that the path is correct:
0:000> .sympath
Symbol search path is: srv*c:\symbolspub*http://msdl.microsoft.com/downloads/symbols
The symbol path is correctly pointing to our Microsoft public symbol server, that should be enough... I'm also supposed to be debugging in the same architecture because I took the dump in AMD64 and my machine is AMD64, too. Let's try that command they mention in the error:
0:000> .cordll -ve -u -l
CLRDLL: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.1434 f:0
doesn't match desired version 2.0.50727.832 f:0
CLRDLL: ERROR: DLL c:\symbolspub\mscordacwks_x86_x86_2.0.50727.832.dll\461F2E2A566000\mscordacwks_x86_x86_2.0.50727.832.dll init failure, Win32 error 0n87
CLR DLL status: ERROR: DLL c:\symbolspub\mscordacwks_x86_x86_2.0.50727.832.dll\461F2E2A566000\mscordacwks_x86_x86_2.0.50727.832.dll init failure, Win32 error 0n87
Interesting. We just confirmed the version we need of mscordacwks is different than the one we have, it seems we are getting the right mscordacwks version from the symbol server, but we can't initialize the dll.
Wait a sec! All that wow64 stuff we saw in the call stack implies that we are in the 64bit part of our 32bit application. Let's try to see 32bit stuff only:
0:000> .load wow64exts
0:000> !sw
Switched to 32bit mode
Let's check the unmanaged call stack:
0:000:x86> kL
ChildEBP RetAddr
002df57c 79f55b05 kernel32!RaiseException+0x53
002df5dc 7a0904d5 mscorwks!RaiseTheExceptionInternalOnly+0x226
002df6a0 79646e0c mscorwks!JIT_Throw+0xfc
002df700 79465607 mscorlib_ni!System.IO.__Error.WinIOError(Int32, System.String)+0x1e17f8
002df71c 79649b75 mscorlib_ni!System.IO.__Error.WinIOError()+0x1f
002df71c 06e1c8f7 mscorlib_ni!System.IO.Path.GetTempFileName()+0x1d9d5d
...
No wow64 stuff in there. This is a typical 32bit call stack. We are good. Can we init mscordacwks.dll now?
0:000:x86> .cordll -ve -u -l
CLRDLL: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.1434 f:0
doesn't match desired version 2.0.50727.832 f:0
CLRDLL: Loaded DLL c:\symbolspub\mscordacwks_x86_x86_2.0.50727.832.dll\461F2E2A566000\mscordacwks_x86_x86_2.0.50727.832.dll
CLR DLL status: Loaded DLL c:\symbolspub\mscordacwks_x86_x86_2.0.50727.832.dll\461F2E2A566000\mscordacwks_x86_x86_2.0.50727.832.dll
It seems so! Let's check the managed call stack:
0:000:x86> !clrstack
OS Thread Id: 0x2904 (0)
ESP EIP
002df604 78be254a [HelperMethodFrame: 00000000002df604]
002df6a8 79646e0c System.IO.__Error.WinIOError(Int32, System.String)
002df708 79465607 System.IO.__Error.WinIOError()
002df710 79649b75 System.IO.Path.GetTempFileName()
...
002df8cc 7b074fef System.Windows.Forms.Control.WndProc(System.Windows.Forms.Message ByRef)
002df8d0 7b082365 [InlinedCallFrame: 00000000002df8d0]
002df93c 7b147ff6 [InlinedCallFrame: 00000000002df93c]
Ok, now we are really doing business. Which exception did we get?
0:000:x86> !pe
Exception object: 000000000293a1cc
Exception type: System.IO.IOException
Message: The directory name is invalid.
InnerException:
StackTrace (generated):
StackTraceString:
HResult: 8007010b
We can now continue from here.
Do you want to know more on debugging wow64? Check these links:
Debugging WOW64
A look at the WoW64 layer in the debugger
I hope this helps.
Regards,
Alex (Alejandro Campos Magencio)
Hi all,
When using RSACryptoServiceProvider in i.e. ASP.NET you may get the following exception under a heavy load scenario:
"System.Security.Cryptography.CryptographicException: CryptoAPI cryptographic service provider (CSP) for this implementation could not be acquired".
We've seen in past posts (sample) how we can use my CryptoAPI Tracer script to take a look to the CryptoAPI calls that RSACryptoServiceProvider makes behind the scenes.
In this case we can see that RSACryptoServiceProvider constructor calls CryptAcquireContext API. The first call to CryptAcquireContext API fails with error NTE_BAD_KEYSET ("Keyset does not exist"). Then a second call is attempted to create the key set, but it fails with error NTE_EXISTS ("Object already exists"). So it seems the keyset is there, but it can't be accessed. Concurrency issues? Maybe. Check this article first to verify that we don't get these errors for another reason: CryptAcquireContext fails with NTE_BAD_KEYSET.
So we are having concurrency issues. Consider the application performs the following sequence in two steps:
1) Open a given named machine key container.
2) If open fails, create that named machine key container.
This sequence is not an automatic operation. If multiple threads are attempting to do the same sequence and assume 2 threads attempt to create the same named machine key container, the second call will fail with "key container already exists".
As we've seen this is what RSACryptoServiceProvider constructor does similar to any application calling CryptAcquireContext API twice.
If the application is always working with the same RSA key in a named key container, key containers must be acquired only once in the process using CryptAcquireContext. When multiple threads are using the same RSA instance for encryption/decryption, the instance methods are not thread safe.
At the CryptoAPI level we've already seen threading issues when dealing with key handles in applications. When calling CryptoAPI directly we have a lot more control when the key handle is obtained, the type of handle, how it is used and whether it is thread safe. e.g. After a RSA key handle has been obtained by using the CryptGetUserKey function, RSA operations are thread safe.
There is not much control when using RSACryptoServiceProvider as it doesn’t expose key handles.
These are some suggestions to minimize threading issues in ASP.NET:
1) Set CspParameters.KeyContainer name when creating an RSACryptoServiceProvider object. Never use the default one, NEVER! This is prone to store corruption. We talked about this already.
2) Explicitely call Dispose on the RSACrytpoServiceProvider object when possible. This way, we don't rely on the Garbage Collector to "eventually" release the CSP.
3) Set UseMachineKeyStore in CspParameters.Flags when creating an RSACryptoServiceProvider. This flag tells RSACryptoServiceProvider to use "machine location" for storing RSA public/private key pair instead of the "current user" location. This is equivalent to calling CryptAcquireContext API with CRYPT_MACHINE_KEYSET. Make sure all the threads using the RSA instance are running under the same account.
4) Don't set RSACryptoServiceProvider.PersistKeyInCSP to False. This will cause the key container to be deleted when RSA instance is released or garbage collected. If multiple threads are trying to access the same named key container, the state of that named key container could be in a temporary cleaned up state and it cannot be acquired.
5) Another option would be for us to instantiate RSACryptoServiceProvider once as part of Web Application initialization and then use that same instance for encryption or decryption under a critical section like lock (i.e. Mutex). This would also avoid reacquiring key container multiple times from the same process.
I hope this helps.
Regards,
Alex (Alejandro Campos Magencio)
Hi all,
Let's talk a bit about concurrency in CryptoAPI. When calling CryptoAPI from different threads, we have to take into consideration that key handles are not thread safe.
The following document describes the threading issues when dealing with key handles in applications: Threading Issues with Cryptographic Service Providers.
Imagine that you have developed an application which creates a session key on startup. The handles to the CSP and session key are stored globally.
According to previous article, the status of the key may be important:
Most algorithms and modes require that data be decrypted in the same order that it was encrypted. This is a difficult task in a multithreaded environment because use of a critical section will not address the ordering issue. If you are using a block cipher (that is, RC2, DES, or 3DES) in ECB cipher mode, then this issue is not a factor because the internal key state does not change. However, ECB is not the default cipher mode. CBC is the default cipher mode. With CBC cipher mode, the internal key state does change.
For instance, when CryptEncrypt/CryptDecrypt is used with the value TRUE for the Final flag, the HCRYPTKEY is actually finalized and shouldn’t be used again.
So when you encrypt/decrypt data you can use a duplicated local HCRYPTKEY handle to perform the operation. You can duplicate the global HCRYPTKEY handle by calling CryptDuplicateKey API. Global HCRYPTKEY will remain in its initial state. Make sure that you call CryptDestroyKey on the local handle. This is Microsoft’s recommended approach.
I hope this helps.
Regards,
Alex (Alejandro Campos Magencio)
Hi all,
You may be trying to install a hotfix or update silently, but you keep getting a security warning dialog because the file was downloaded from the Internet. The following article explains this situation: 889815 The Open File - Security Warning dialog box is displayed when you try to silently install a hotfix or an update by using a Visual Basic script in Windows XP Service Pack 2.
Attachment Execution Service (AES) is responsible for this dialog. KB 889815 suggests to use a script to set SEE_MASK_NOZONECHECKS environment variable before installing the hotfix or update. Well, this will work on VBScript, but may not work in other scenarios.
Alternatively you may set that environment variable in a batch job, but it may not help in your scenario either.
Are we out of luck then? Well, not necessarily. Keep reading.
Run the following command on the file you downloaded from the Internet which contains the patch you want to install:
notepad patch.exe:Zone.Identifier
This will show us the Zone.Identifier Alternate Data Stream (ADS) of our file. We will see the following contents:
[ZoneTransfer]
ZoneId=3
Value 3 means that the file was downloaded from the Internet and it's potentially unsafe. This makes the warning dialog to appear. We may edit ZoneId value to make the warning disappear. A value of i.e. 2 may work in your environemnt. Change the value, save the ADS file and try installing the patch again.
This is a very good post to understand how Attachment Execution Service (AES) works and the effects of changing the ZoneId value: Demo of "Attachment Execution Service internals" in Windows XP SP2 and Windows Server 2003 SP1.
This is a list of some possible values we can use as ZoneId:
How Windows Knows that a File Is from the Internet: Manipulating Alternate Data Streams
NoZone = -1
MyComputer = 0
Intranet = 1
Trusted = 2
Internet = 3
Untrusted = 4
A value of 6 may work for us, too. This value should be the same as resetting the checkbox "don't ask again" in the warning dialog.
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Hi all,
Today I'm posting a sample which shows how to sign a text with a certificate in my Personal store (this cert will have public and private key associated to it) and how to verify that signature with a .cer file (for i.e. WinForms) applications or a client certificate (for i.e. ASP.NET) (both will only have public key associated to them).
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace ConsoleApplication1
{
class Program
{
static byte[] Sign(string text, string certSubject)
{
// Access Personal (MY) certificate store of current user
X509Store my = new X509Store(StoreName.My, StoreLocation.CurrentUser);
my.Open(OpenFlags.ReadOnly);
// Find the certificate we'll use to sign
RSACryptoServiceProvider csp = null;
foreach (X509Certificate2 cert in my.Certificates)
{
if (cert.Subject.Contains(certSubject))
{
// We found it.
// Get its associated CSP and private key
csp = (RSACryptoServiceProvider)cert.PrivateKey;
}
}
if (csp == null)
{
throw new Exception("No valid cert was found");
}
// Hash the data
SHA1Managed sha1 = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
// Sign the hash
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
}
static bool Verify(string text, byte[] signature, string certPath)
{
// Load the certificate we'll use to verify the signature from a file
X509Certificate2 cert = new X509Certificate2(certPath);
// Note:
// If we want to use the client cert in an ASP.NET app, we may use something like this instead:
// X509Certificate2 cert = new X509Certificate2(Request.ClientCertificate.Certificate);
// Get its associated CSP and public key
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
// Hash the data
SHA1Managed sha1 = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
// Verify the signature with the hash
return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), signature);
}
static void Main(string[] args)
{
// Usage sample
try
{
// Sign text
byte[] signature = Sign("Test", "cn=my cert subject");
// Verify signature. Testcert.cer corresponds to "cn=my cert subject"
if (Verify("Test", signature, @"C:\testcert.cer"))
{
Console.WriteLine("Signature verified");
}
else
{
Console.WriteLine("ERROR: Signature not valid!");
}
}
catch (Exception ex)
{
Console.WriteLine("EXCEPTION: " + ex.Message);
}
Console.ReadKey();
}
}
}
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Hi all,
You may get an Access Denied error (COMException 0x80070005) when using System.DirectoryServices.DirectorySynchronization in your .NET application with a non-admin user, but everything works fine with a domain administrator.
This issue will happen if we use DirectorySynchronization this way:
DirectorySearcher directorySearcher = new DirectorySearcher(rootPath);
directorySearcher.DirectorySynchronization = new DirectorySynchronization();
If we want to run this code as it is, we need to pass administrative credentials. If we are using standard user credentials we need to pass the right Flag saying that this is a normal user who do not have all the rights over Active Directory.
To understand this in detail please see this article which talks about the flags we can pass to DirectorySynchronization constructor:
DirectorySynchronizationOptions Enumeration
"
- ObjectSecurity: If this flag is not present, the caller must have the right to replicate changes. If this flag is present, the caller requires no rights, but is allowed to see only objects and attributes that are accessible to the caller.
"
So modify your code to use DirectorySynchronization in this way:
directorySearcher.DirectorySynchronization = new DirectorySynchronization(DirectorySynchronization.ObjectSecurity);
Code should not fail with Access Denied error anymore. Now a standard user will have access to all the objects that she usually has access to.
I hope this helps.
Regards,
Alex (Alejandro Campos Magencio)
Hi all,
If you ever use WMI Diag script (The WMI Diagnosis Utility -- Version 2.0) on a non-English version of Windows (i.e. Spanish, French, Italian, German...), you will get tons of errors when the script checks out default permissions on WMI namespaces or DCOM components related to WMI, for instance.
The cause is simple: the script checks permissions for groups of users which name is language-dependant.
If we use WMI Diag in i.e. a Spanish Windows, we will have to edit the script and replace:
- "BUILTIN\Administrators" with "BUILTIN\Administradores",
- "Everyone" with "Todos",
- "NT AUTHORITY\NETWORK SERVICE" with "NT AUTHORITY\Servicio de red",
- "NT AUTHORITY\LOCAL SERVICE" with "NT AUTHORITY\SERVICIO LOCAL",
- "NT AUTHORITY\Authenticated Users" with "NT AUTHORITY\Usuarios autentificados",
Now you can run the script and find out if your WMI is really broken.
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Hi all,
Some customers asked me in the past if there was any API to verify if a password meets Windows complexity requirements. Unfortunately there is no such API. We could implement our own if we know the requirements of the password filter used in our machines.
The default password filter (passfilt.dll) in Windows checks for the following:
1) Not contain significant portions of the user's account name or full name.
2) Be at least six characters in length.
3) Contain characters from three of the following four categories:
a) English uppercase characters (A through Z).
b) English lowercase characters (a through z).
c) Base 10 digits (0 through 9).
d) Non-alphabetic characters (for example, !, $, #, %).
See the following articles for details:
Passwords must meet complexity requirements of the installed password filter (Windows 2000)
Passwords must meet complexity requirements (Windows Server 2003)
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Hi all,
If you read CryptAcquireContext documentation, you'll see that setting pszContainer to NULL allow us to use a default key container.
Microsoft recommends that every application creates its own key container instead of the default one, because key containers can only contain one key or key pair for each key type. That is, it can have one session key, or one public-private key pair. If two or more apps use the default key container, and each stores a key pair there, the last app to write the keys overwrites the previous keys. If the other app has already encrypted data with the previous keys, the data is lost because the overwritten keys are not recoverable. A reasonable key container naming scheme would include your company name, product name, and possibly a version or incremented counter as a serial number. An alternative to this scheme would be to generate a GUID and use it as key container name. Just don't lose the GUID so that you don't lose the location of your keys!
To obtain a GUID for your app you may i.e. use UUIDGEN.EXE tool (included in Platform SDK or Visual Studio Tools) or use the API (CoCreateGuid, UuidCreate).
More info here: CryptAcquireContext() use and troubleshooting.
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Hi all,
When we try to access a key container, CryptAcquireContext may return NTE_BAD_KEYSET (or error # 0x80090016 or -2146893802 or "Keyset does not exist") for the following two reasons:
1) key container doesn't exist. You may repeat the call to CryptAcquireContext, but this time using CRYPT_NEWKEYSET flag to create a new key container.
2) user doesn't have permission to open the key container. If you need to find out where the key container is in order to set additional permissions, this post may help: Key Containers: Basics.
Let's imagine for a sec that we are already calling CryptAcquireContext with CRYPT_NEWKEYSET flag after the first call to CryptAcquireContext failes with error NTE_BAD_KEYSET, and this second call fails with error NTE_EXISTS (or error # 0x8009000F or -2146893809 or "Object already exists"). First we try to open the container and we fail, then we try to create it and it already exists. NTE_BAD_KEYSET means in this case that the user doesn't have permissions to open the key container, and not that it doesn't exist.
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Hi all, welcome back,
The following .NET 2.0 sample shows how to get security info from a folder to find out the permissions for users/groups on it:
using System;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Variables
//
string strFolderPath = "";
DirectoryInfo dirInfo = null;
DirectorySecurity dirSec = null;
int i = 0;
try
{
// Read the path to the directory
//
do
{
Console.Write("Enter existing directory > ");
strFolderPath = Console.ReadLine();
} while (!Directory.Exists(strFolderPath));
// Get the ACL of the directory
//
dirInfo = new DirectoryInfo(strFolderPath);
dirSec = dirInfo.GetAccessControl();
// Show the ACEs of the ACL
//
i = 0;
foreach (FileSystemAccessRule rule in dirSec.GetAccessRules(true, true, typeof(NTAccount)))
{
Console.WriteLine("[{0}] - Rule {1} {2} access to {3}",
i++,
rule.AccessControlType == AccessControlType.Allow ? "grants" : "denies",
rule.FileSystemRights,
rule.IdentityReference.ToString());
}
}
catch (Exception ex)
{
Console.Write("EXCEPTION: ");
Console.WriteLine(ex.Message);
}
Console.WriteLine("\n<
>");
Console.Read();
}
}
}
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Hi all, welcome back,
CryptAcquireContext API will fail with error #2 or ERROR_FILE_NOT_FOUND if:
1) the user's profile is not loaded, as we saw in my post RSACryptoServiceProvider fails when used with ASP.NET.
2) AppData registry value in the following registry key is not present or is misconfigured:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
As we saw in the other post I mentioned above, private keys are protected by DPAPI (Data Protection API). DPAPI needs to find the Application Data directory where the Master Key is stored. DPAPI will read the directory from AppData value and will use the Master Key to protect CryptoAPI private keys or EFS private keys, for instance.
Note that similar errors may occur for the same reasons:
1. CryptProtectData API fails with ERROR_FILE_NOT_FOUND.
2. Encrypting a file/folder with EFS via Windows Explorer fails with "The system cannot find the file file specified".
Note that Security Bulletin MS04-011 changed the registry key where the path could be found. Previous one was in here:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
I hope this helps.
Regards,
Alex (Alejandro Campos Magencio)
Hi all, welcome back,
I recently had a customer who needed to retrieve extensions from certificates the easy way in .NET 1.1, and they wanted to use CAPICOM for that. In .NET 2.0 and later we may use X509Certificate2 to achieve the same results (I strongly recommend this approach), but in .NET 1.1 we only have X509Certificate class which is much more limited.
Note that the same ideas shown in the same below may be taken into account when using CAPICOM from i.e. VBScript.
The following sample shows how to use CAPICOM from a C# app to read a couple of extensions from a test cert (remember to add CAPICOM as a reference to the project first):
using CAPICOM;
...
// Load the cert
Certificate CAPICOMCertClass = new CertificateClass();
CAPICOMCertClass.Load("C:\\test.cer", null, CAPICOM_KEY_STORAGE_FLAG.CAPICOM_KEY_STORAGE_DEFAULT, CAPICOM_KEY_LOCATION.CAPICOM_CURRENT_USER_KEY);
// Find the extensions we are interested in
foreach (Extension CertExtension in CAPICOMCertClass.Extensions())
{
// Does the extension have a Friendly Name? Yes? Use it!
if (CertExtension.OID.FriendlyName == "Subject Alternative Name")
{
String stringSubjectAltName = CertExtension.EncodedData.Format(true);
MessageBox.Show(stringSubjectAltName);
}
// The extension has no Friendly Name, but we can use its OID instead
if (CertExtension.OID.Value.ToString() == "1.3.6.1.4.1.5734.1.33")
{
// For demostration purposes of Utilities class, let's assume the value of the OID is an hex string which represents the chars of a string but we need the string itself
// This property is in Hexadecimal
String stringOIDHex = CertExtension.EncodedData.Format(true).Replace(" ", "");
// We convert it to binary
Utilities utils = new UtilitiesClass();
String stringOIDBinary = utils.HexToBinary(stringOIDHex);
byte[] OIDBinary = (byte[])utils.BinaryStringToByteArray(stringOIDBinary);
// We convert it to string
String stringOID = System.Text.Encoding.ASCII.GetString(OIDBinary);
MessageBox.Show(stringOID);
}
}
References to the classes I've used: Certificate, Extensions, Extension, OID, Utilities.
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Hi all, welcome back,
As we read in Windows Security Center – Managing the State of Security, the vast majority of antivirus Independent Software Vendors (ISVs) support WMI integration. Windows Security Center uses it to detect antivirus and firewall solutions.
The following script shows how to get some information from those solutions:
strComputer = "."
Set oWMI = GetObject( _
"winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\SecurityCenter")
Set colItems = oWMI.ExecQuery("Select * from AntiVirusProduct")
For Each objItem in colItems
With objItem
WScript.Echo .companyName
WScript.Echo .displayName
WScript.Echo .instanceGuid
WScript.Echo .onAccessScanningEnabled
WScript.Echo .pathToSignedProductExe
WScript.Echo .productHasNotifiedUser
WScript.Echo .productState
WScript.Echo .productUptoDate
WScript.Echo .productWantsWscNotifications
WScript.Echo .versionNumber
End With
Next
Cheers,
Alex (Alejandro Campos Magencio)
Hi all, welcome back,
When working with System.DirectoryServices.DirectoryEntry in .NET, we may change the password of the user with a code like the following (C#):
user.Invoke("ChangePassword", new object[] { oldPassword, newPassword }
But invoking ChangePassword may fail with the following System.Reflection.TargetInvocationException:
"Exception has been thrown by the target of an invocation"
This error is not very descriptive, I know. I've seen several causes for this error in the past:
1) Any of the passwords is incorrect.
2) The new password doesn't meet the domain complexity requirements.
3) Minimum Password Age is > 0.
4) WinNT provider is used instead of LDAP.
By using InnerException.ToString() from the TargetInvocationException we may get a more descriptive error message. We could even see the HResult value associated to the exception. But this property is protected, so we could try to parse it from the error message string, with a code like this:
string errorMessage;
Int32 errorCode = 0;
try
{
...
}
catch (TargetInvocationException e)
{
errorMessage = e.InnerException.ToString();
try
{
string HResult = errorMessage.Substring(errorMessage.IndexOf("0x") + 2, 8);
errorCode = Int32.Parse(HResult, System.Globalization.NumberStyles.HexNumber);
}
catch
{
errorCode = -1;
}
}
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)