Retired Microsoft Blog disclaimer

This directory is a mirror of retired "Decrypt My World" MSDN blog and is provided as is. All posting authorship and copyrights belong to respective authors.

Posts on this page:

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/09/05/how-to-get-assembly-version-without-loading-it/
Post name: How to get assembly version without loading it
Original author: Alejandro Campos Magencio
Posting date: 2008-09-05T09:00:00+00:00


Hi all,


The other day I was trying to add a simple autoupdate functionality to a little tool I developed, and I needed to check the version of current assembly against the udpated one. If current assembly was older than the updated one, I needed to substitute the older one with the newer. Plain and simple.


This was my first attempt to achieve this (code has been simplified):

using System.Reflection;
using System.IO;

...

// Get current and updated assemblies
Assembly currentAssembly = Assembly.LoadFile(currentAssemblyPath);
Assembly updatedAssembly = Assembly.LoadFile(updatedAssemblyPath);

AssemblyName currentAssemblyName = currentAssembly.GetName();
AssemblyName updatedAssemblyName = updatedAssembly.GetName();

// Compare both versions
if (updatedAssemblyName.Version.CompareTo(currentAssemblyName.Version) <= 0)
{
// There's nothing to update
return;
}

// Update older version
File.Copy(updatedAssemblyPath, currentAssemblyPath, true);


But File.Copyfailes because current assembly is in use. Why? Because of Assembly.LoadFile. When we load an assembly no other process (including ours) can change or delete the file because we are using it. The issue is that we can't unload an assembly that we loaded in an AppDomain unless the AppDomain itself gets unloaded. Here I'm using the default AppDomain which will only get unloaded when the application exits. So then I tried creating a new AppDomain, load the assemblies in there and unload the AppDomain afterwards before changing the file. It didn't help either. So...


How can we get the assembly version without loading the assembly? The solution is easy:

using System.Reflection;
using System.IO;

...

// Get current and updated assemblies
AssemblyName currentAssemblyName = AssemblyName.GetAssemblyName(currentAssemblyPath);
AssemblyName updatedAssemblyName = AssemblyName.GetAssemblyName(updatedAssemblyPath);

// Compare both versions
if (updatedAssemblyName.Version.CompareTo(currentAssemblyName.Version) <= 0)
{
// There's nothing to update
return;
}

// Update older version
File.Copy(updatedAssemblyPath, currentAssemblyPath, true);


AssemblyName.GetAssemblyNamewon't load the assembly, so we can change the file afterwards.


I hope this helps.


Kind regards,



Alex (Alejandro Campos Magencio)



PS: In case you are interested, I will post my simple autoupdate code one of these days, once I have time to change a couple of things on it.

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/09/05/how-to-create-a-certificate-request-with-certenroll-and-net-c/
Post name: How to create a certificate request with CertEnroll and .NET (C#)
Original author: Alejandro Campos Magencio
Posting date: 2008-09-05T08:18:00+00:00


Hi all,


The following C# sample shows how to use CertEnroll COM component to create a certificate request, send the request to the CA, get the response from the CA, and install the new certificate in the machine:


(Note that this sample is a WinForms app with 3 buttons -createRequestButton, sendRequestButton, acceptPKCS7Button-and 2 textboxes -requestText & responseText-)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

// Add the CertEnroll namespace
using CERTENROLLLib;
using CERTCLIENTLib;

namespace CATest
{
public partial class Form1 : Form
{
private const int CC_DEFAULTCONFIG = 0;
private const int CC_UIPICKCONFIG = 0x1;
private const int CR_IN_BASE64 = 0x1;
private const int CR_IN_FORMATANY = 0;
private const int CR_IN_PKCS10 = 0x100;
private const int CR_DISP_ISSUED = 0x3;
private const int CR_DISP_UNDER_SUBMISSION = 0x5;
private const int CR_OUT_BASE64 = 0x1;
private const int CR_OUT_CHAIN = 0x100;

public Form1()
{
InitializeComponent();
}

// Create request
private void createRequestButton_Click(object sender, EventArgs e)
{
// Create all the objects that will be required
CX509CertificateRequestPkcs10 objPkcs10 = new CX509CertificateRequestPkcs10Class();
CX509PrivateKey objPrivateKey = new CX509PrivateKeyClass();
CCspInformation objCSP = new CCspInformationClass();
CCspInformations objCSPs = new CCspInformationsClass();
CX500DistinguishedName objDN = new CX500DistinguishedNameClass();
CX509Enrollment objEnroll = new CX509EnrollmentClass();
CObjectIds objObjectIds = new CObjectIdsClass();
CObjectId objObjectId = new CObjectIdClass();
CX509ExtensionKeyUsage objExtensionKeyUsage = new CX509ExtensionKeyUsageClass();
CX509ExtensionEnhancedKeyUsage objX509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsageClass();
string strRequest;

try
{
requestText.Text = "";

// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
objCSP.InitializeFromName(
"Microsoft Enhanced Cryptographic Provider v1.0"
);

// Add this CSP object to the CSP collection object
objCSPs.Add(
objCSP
);

// Provide key container name, key length and key spec to the private key object
//objPrivateKey.ContainerName = "AlejaCMa";
objPrivateKey.Length = 1024;
objPrivateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE;
objPrivateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
objPrivateKey.MachineContext = false;

// Provide the CSP collection object (in this case containing only 1 CSP object)
// to the private key object
objPrivateKey.CspInformations = objCSPs;

// Create the actual key pair
objPrivateKey.Create();

// Initialize the PKCS#10 certificate request object based on the private key.
// Using the context, indicate that this is a user certificate request and don't
// provide a template name
objPkcs10.InitializeFromPrivateKey(
X509CertificateEnrollmentContext.ContextUser,
objPrivateKey,
""
);

// Key Usage Extension
objExtensionKeyUsage.InitializeEncode(
X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE
);
objPkcs10.X509Extensions.Add((CX509Extension)objExtensionKeyUsage);

// Enhanced Key Usage Extension
objObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.2"); // OID for Client Authentication usage
objObjectIds.Add(objObjectId);
objX509ExtensionEnhancedKeyUsage.InitializeEncode(objObjectIds);
objPkcs10.X509Extensions.Add((CX509Extension)objX509ExtensionEnhancedKeyUsage);

// Encode the name in using the Distinguished Name object
objDN.Encode(
"CN=AlejaCMa",
X500NameFlags.XCN_CERT_NAME_STR_NONE
);

// Assing the subject name by using the Distinguished Name object initialized above
objPkcs10.Subject = objDN;

// Create enrollment request
objEnroll.InitializeFromRequest(objPkcs10);
strRequest = objEnroll.CreateRequest(
EncodingType.XCN_CRYPT_STRING_BASE64
);

requestText.Text = strRequest;

} catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

// Submit request to CA and get response
private void sendRequestButton_Click(object sender, EventArgs e)
{
// Create all the objects that will be required
CCertConfig objCertConfig = new CCertConfigClass();
CCertRequest objCertRequest = new CCertRequestClass();
string strCAConfig;
string strRequest;
int iDisposition;
string strDisposition;
string strCert;

try
{
strRequest = requestText.Text;

// Get CA config from UI
//strCAConfig = objCertConfig.GetConfig(CC_DEFAULTCONFIG);
strCAConfig = objCertConfig.GetConfig(CC_UIPICKCONFIG);

// Submit the request
iDisposition = objCertRequest.Submit(
CR_IN_BASE64 | CR_IN_FORMATANY,
strRequest,
null,
strCAConfig
);

// Check the submission status
if (CR_DISP_ISSUED != iDisposition) // Not enrolled
{
strDisposition = objCertRequest.GetDispositionMessage();

if (CR_DISP_UNDER_SUBMISSION == iDisposition) // Pending
{
MessageBox.Show("The submission is pending: " + strDisposition);
return;
}
else // Failed
{
MessageBox.Show("The submission failed: " + strDisposition);
MessageBox.Show("Last status: " + objCertRequest.GetLastStatus().ToString());
return;
}
}

// Get the certificate
strCert = objCertRequest.GetCertificate(
CR_OUT_BASE64 | CR_OUT_CHAIN
);

responseText.Text = strCert;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

// Install response from CA
private void acceptPKCS7Button_Click(object sender, EventArgs e)
{
// Create all the objects that will be required
CX509Enrollment objEnroll = new CX509EnrollmentClass();
string strCert;

try
{
strCert = responseText.Text;

// Install the certificate
objEnroll.Initialize(X509CertificateEnrollmentContext.ContextUser);
objEnroll.InstallResponse(
InstallResponseRestrictionFlags.AllowUntrustedRoot,
strCert,
EncodingType.XCN_CRYPT_STRING_BASE64,
null
);

MessageBox.Show("Certificate installed!");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}


I hope this helps.


Cheers,



Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/09/02/filenotfoundexception-when-using-xenroll-in-asp-net/
Post name: FileNotFoundException when using XEnroll in ASP.NET
Original author: Alejandro Campos Magencio
Posting date: 2008-09-02T06:09:00+00:00


Hi all,


If you ever try to use XEnroll.dll in your ASP.NET application (through Interop.XENROLLLib.dll, of course), you may face an issue like the following:


Your ASP.NET app impersonates the clientuser and tries to make a certificate request on her behalf:

using XENROLLLib;

CEnroll objEnroll = new CEnrollClass();
string strCertRequest = objEnroll.createRequest(XECR_CMC , "CN=Alex", "1.3.6.1.4.1.311.2.1.21");


But createRequest fails with the following exception:

System.IO.FileNotFoundException was unhandled by user code
Message="The system cannot find the file specified. (Exception from HRESULT: 0x80070002)"
Source="Interop.XENROLLLib"
StackTrace:
at XENROLLLib.CEnrollClass.createRequest(Int32 Flags, String strDNName, String Usage)
...

If we run the very same code from a VBScript, it works! Mmmmmm,this error looksfamiliar to me... Check this out: RSACryptoServiceProvider fails when used with ASP.NET.


The exception is a bit different, but the error message is the same and the cause, too. In order to create a request on user's behalf, that user's profile needs to be loaded. By default, ASP.NET won't load user profiles for us as we already know. And we need the user profile to i.e. create keys associated to the cert request.


That also explains why the code works when running in a script. We logged on the server to run the script and the user profile got loaded.


Summing up (see the other post I mentioned for details), we have several options here:


1) Load user profile via LoadUserProfile API. This API requires many permissions to call it andI wouldn't recommend promoting a standard user to admin to be able to call this API, for instance.


2) Load user profile via dummyWindows service. This is not feasible if we are impersonating many users.


3) Use machine profile instead of user profile. This way we don't need to load user profile.We can use ICEnroll4::MyStoreFlags Property to select CERT_SYSTEM_STORE_LOCAL_MACHINE instead of the default CERT_SYSTEM_STORE_CURRENT_USER before calling createRequest.



I hope this helps.


Kind regards,



Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/08/19/how-to-debug-windows-services-with-windbg/
Post name: How to debug Windows services with Windbg
Original author: Alejandro Campos Magencio
Posting date: 2008-08-19T02:46:00+00:00


Hi all,

If you want to know how to debug a Windowsservice, the following article will be of great assistance: How to debug Windows services. I suggest you read this article before you continue reading this post. Also, you should have Debugging Tools for Windows installed in your machine; the tools I'll mention (windbg.exe, cdb.exe, gflags.exe & remote.exe) are part of them.

Basically, we can attach a debugger to a service:

a) Just when process starts.

1) Go to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control registry key and add or modify ServicesPipeTimeout REG_DWORD value to i.e. 3600000 (1h in milliseconds). Restart machine to apply changes. Default is 30 seconds.

2) Use Gflags to associate a debugger to the service. The debugger will attach to the service when starting.

2.1) If service interacts with desktop, we canassociate 'windgb' to the service directly.

2.2) If service won't interact with desktop, we can associate 'cdb -server tcp:port=9999' to the service instead. Cdb.exe is the command line version of Windbg. It will attach to the service and expose itself as a server. Then we can launch and connect a windbg instance to that cdb with 'windbg.exe -remote tcp:Port=9999,Server=machinename' and control it remotely.

Note: I use cdb to attach to the service because windbg may fail to start if service won't interact with desktop.

3) Don'tforget to undo registry and gflags changes once you are done debugging.

b) At any time. Just find out the service's process ID (PID) or name and attach a debugger to it with i.e. 'windbg -p PID' or 'windbg -pn servicename.exe'.

Here you may have an issue on Windows 2000, for instance. We can only do live debugging on Windows 2000 if we are in the same Window Station as the process we are debugging. When we connect through Terminal Services, we are in a different Window Station than the services, so we can’t attach to their processes.

On Windows Server 2003, for instance, we can cross the barrier between WinStations. More info on WinStations here: INFO: Services, Desktops, and Window Stations.

So in order to debug services with Windbg on Windows 2000 we can do the following:

1) Use AT command to launch remote.exe and expose a cmd.exe as a server with 'at 14:48 remote /S cmd "any string"'. AT command launches a process under the System acccount at a given time. Remote.exe is a very cool tool which exposes any command line tool as a server. Cmd.exe won't be visible and it will be running under System account in the same WinStation as the services.

Note: We may also use Task Scheduler or any other app which allows us to launch processes under the System account.

2) Use remote.exe to launch another cmd.exe as a client of server cmd instance with 'remote /C machinename "any string"'. Now we can control the invisible cmd.exe which is running under System account.

3) Thanks to client cmd we can tell server cmd to launch cdb.exe and attach to the service with i.e. 'cdb -p PID'. We can nowuse cdb to debug the service directly, as cdb is running as System in same WinStation as services.

4) If we prefer to debug with Windbg.exe instead ofcdb (so we can do live debugging with source code, for instance) we can expose cdb as a server with its'.server tcp:port=9999' command and connect to it with 'windbg.exe -remote tcp:Port=9999,Server=machinename'.

I hope this helps.

Regards,

Alex (Alejandro Campos Magencio)

Original URL: https://blogs.msdn.microsoft.com/alejacma/2008/08/05/how-to-get-more-than-1000-group-members-including-foreign-sams-vbscript/
Post name: How to get more than 1000 group members including foreign SAMs (VBScript)
Original author: Alejandro Campos Magencio
Posting date: 2008-08-05T03:21:00+00:00


Hi all,


We may have a group in our Active Directory with members from a foreign domain. We may try to retrieve all those members with ADSI and a code like this: Using IADs::GetInfoEx for Range Retrieval. The issue with this code is that we will only be able to see the SID of foreign principals (i.e. CN=S-1-5-21-1234567890...-123,CN=ForeignSecurityPrincipals,DC=domain,DC=com), which may not be very useful for us.


The following sample (based on above Range Retrieval sample) will retrieve all members in a group (including foreign principals) and show their sAMAccountName:

' PARAMETERS.
'
strFileName = "c:\List.txt"
strGroupDN = "CN=groupName,CN=Users,DC=domainName,DC=com"

' Bind to the group with the current credentials.
'
Set oGroup = GetObject("LDAP://" & strGroupDN)

' Create file for results.
'
Set fs = CreateObject("Scripting.FileSystemObject")
Set usersFile = fs.CreateTextFile(strFileName, True)

' For compatibility with all operating systems, the number of objects
' retrieved by each query should not exceed 999. The number of objects
' to retrieve should be as close as possible to 999 to reduce the number
' of round trips to the server necessary to retrieve the objects.
rangeStep = 999
lowRange = 0
highRange = lowRange + rangeStep

Do
' Use the "member;range=<lowRange>-<highRange>" syntax.
'
strCommandText = "member;range=" & lowRange & "-" & highRange
usersFile.WriteLine(vbCrLf & "Current search command: " & _
strCommandText & vbCrLf)

' Load the specified range of members into the local cache. This
' will throw an error if the range exceeds the properties contained
' in the object. The "On Error GoTo quit" error handler will cause
' the loop to terminate when this happens.
'
On Error Resume Next
oGroup.GetInfoEx Array(strCommandText), 0
If Err.Number = &H80072020 Then
Exit Do
End If
On Error Goto 0

' Enumerate the retrieved members.
'
oMembers = oGroup.Get("member")
If vbArray And VarType(oMembers) Then
For Each oMember In oMembers
' Add the member.
'
Set oUser = GetObject("LDAP://" & oMember)
If (oUser.Class = "foreignSecurityPrincipal") Then

usersFile.WriteLine(GetForeignSAM(oUser))
Else
usersFile.WriteLine(oUser.sAMAccountName)
End If
nRetrieved = nRetrieved + 1
Next
Else
' oGroup.Get returned only one member, so add it to the list.
'
Set oUser = GetObject("LDAP://" & oMembers)
If (oUser.Class = "foreignSecurityPrincipal") Then

usersFile.WriteLine(GetForeignSAM(oUser))
Else
usersFile.WriteLine(oUser.sAMAccountName)
End If

nRetrieved = nRetrieved + 1
End If

' Increment the high and low ranges to query for the next block of
' objects.
'
lowRange = highRange + 1
highRange = lowRange + rangeStep
Loop While True

MsgBox "File """ & strFileName & """ created"

'-----------------------------------------------------------------------
' HELPER FUNCTIONS
'-----------------------------------------------------------------------
function GetForeignSAM (oUser)
' CONSTANTS.
'
ADS_SID_RAW = 0
ADS_SID_HEXSTRING = ADS_SID_RAW + 1
ADS_SID_SAM = ADS_SID_HEXSTRING + 1
ADS_SID_UPN = ADS_SID_SAM + 1
ADS_SID_SDDL = ADS_SID_UPN + 1
ADS_SID_WINNT_PATH = ADS_SID_SDDL + 1

' Get the SID
'

' Now, resolve the SID into its sAMAcountName.
'
set oADsSID = CreateObject("ADsSID")
oADsSID.SetAs ADS_SID_RAW, oUser.Get("objectSid")

' Requesting the Sam Account Name
'
GetForeignSAM = oADsSID.GetAs(ADS_SID_SAM)

end function


Note: ADsSID object is implemented in ADsSecurity.dll.


I hope this helps.


Regards,



Alex (Alejandro Campos Magencio)