I can find all sorts of stuff on how to program for DCOM, but practically nothing on how to set/check the security programmatically.
I'm not trying to recreate dcomcnfg, but if I knew how to reproduce all the functionality of dcomcnfg in C# (preferred, or VB.net) then my goal is in sight.
I can't seem to be able to find any good resource on this, no open source API's or even quick examples of how to do each step. Even here DCOM or dcomcnfg returns few results and none really about how to set/verify/list security.
If anybody has some pointers to an open API or some examples I would appreciate it.
The answer posted by Daniel was HUGELY helpful. Thank you so much, Daniel!
An issue with Microsoft's documentation is that they indicate that the registry values contain an ACL in binary form. So, for instance, if you were trying to set the machine's default access (rather than per-process), you would be accessing registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole\DefaultAccessPermission. However, in my initial attempts to access this key using the System.Security.AccessControl.RawACL class were failing.
As Daniel's code indicate's the value is not actually an ACL, but really is a SecurityDescriptor with the ACL in it.
So, even though I know this post is old, I'm going to post my solution for checking and setting the security settings and adding NetworkService for Default local access. Of course, you could take this and make it better I'm sure, but to get started you would simply need to change the key and the access mask.
static class ComACLRights{
public const int COM_RIGHTS_EXECUTE= 1;
public const int COM_RIGHTS_EXECUTE_LOCAL = 2;
public const int COM_RIGHTS_EXECUTE_REMOTE = 4;
public const int COM_RIGHTS_ACTIVATE_LOCAL = 8;
public const int COM_RIGHTS_ACTIVATE_REMOTE = 16;
}
class Program
{
static void Main(string[] args)
{
var value = Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", "DefaultAccessPermission", null);
RawSecurityDescriptor sd;
RawAcl acl;
if (value == null)
{
System.Console.WriteLine("Default Access Permission key has not been created yet");
sd = new RawSecurityDescriptor("");
}else{
sd = new RawSecurityDescriptor(value as byte[], 0);
}
acl = sd.DiscretionaryAcl;
bool found = false;
foreach (CommonAce ca in acl)
{
if (ca.SecurityIdentifier.IsWellKnown(WellKnownSidType.NetworkServiceSid))
{
//ensure local access is set
ca.AccessMask |= ComACLRights.COM_RIGHTS_EXECUTE | ComACLRights.COM_RIGHTS_EXECUTE_LOCAL | ComACLRights.COM_RIGHTS_ACTIVATE_LOCAL; //set local access. Always set execute
found = true;
break;
}
}
if(!found){
//Network Service was not found. Add it to the ACL
SecurityIdentifier si = new SecurityIdentifier(
WellKnownSidType.NetworkServiceSid, null);
CommonAce ca = new CommonAce(
AceFlags.None,
AceQualifier.AccessAllowed,
ComACLRights.COM_RIGHTS_EXECUTE | ComACLRights.COM_RIGHTS_EXECUTE_LOCAL | ComACLRights.COM_RIGHTS_ACTIVATE_LOCAL,
si,
false,
null);
acl.InsertAce(acl.Count, ca);
}
//re-set the ACL
sd.DiscretionaryAcl = acl;
byte[] binaryform = new byte[sd.BinaryLength];
sd.GetBinaryForm(binaryform, 0);
Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", "DefaultAccessPermission", binaryform, RegistryValueKind.Binary);
}
}
Facing similar circumstances (configuring DCOM security from an MSI) I managed to create a solution that does what I want by altering registry key values in HKEY_CLASSES_ROOT\AppID{APP-GUID-GOES-HERE}. Thanks to Arnout's answer for setting me on the right path.
In specific, I created a method to edit the security permissions for DCOM objects, which are stored in the LaunchPermission and AccessPermission registry key values. These are serialized security descriptors, which you can access by passing the binary data through RawSecurityDescriptor
. This class simplifies a lot of the details in a delicious .NET-y fashion, but you still have to attend to all the logical details regarding Windows ACL, and you have to make sure to write the security descriptor back to the registry by using RawSecurityDescriptor.GetBinaryForm
.
The method I created was called EditOrCreateACE
. This method will either edit an existing ACE for an account, or insert a new one, and ensure that the access mask has the passed flags set. I attach it here as an example, this is by no means any authority on how to deal with it, since I know very little of Windows ACL stuff still:
// These are constants for the access mask on LaunchPermission.
// I'm unsure of the exact constants for AccessPermission
private const int COM_RIGHTS_EXECUTE = 1;
private const int COM_RIGHTS_EXECUTE_LOCAL = 2;
private const int COM_RIGHTS_EXECUTE_REMOTE = 4;
private const int COM_RIGHTS_ACTIVATE_LOCAL = 8;
private const int COM_RIGHTS_ACTIVATE_REMOTE = 16;
void EditOrCreateACE(string keyname, string valuename,
string accountname, int mask)
{
// Get security descriptor from registry
byte[] keyval = (byte[]) Registry.GetValue(keyname, valuename,
new byte[] { });
RawSecurityDescriptor sd;
if (keyval.Length > 0) {
sd = new RawSecurityDescriptor(keyval, 0);
} else {
sd = InitializeEmptySecurityDescriptor();
}
RawAcl acl = sd.DiscretionaryAcl;
CommonAce accountACE = null;
// Look for the account in the ACL
int i = 0;
foreach (GenericAce ace in acl) {
if (ace.AceType == AceType.AccessAllowed) {
CommonAce c_ace = ace as CommonAce;
NTAccount account =
c_ace.SecurityIdentifier.Translate(typeof(NTAccount))
as NTAccount;
if (account.Value.Contains(accountname)) {
accountACE = c_ace;
}
i++;
}
}
// If no ACE found for the given account, insert a new one at the end
// of the ACL, otherwise just set the mask
if (accountACE == null) {
SecurityIdentifier ns_account =
(new NTAccount(accountname)).Translate(typeof(SecurityIdentifier))
as SecurityIdentifier;
CommonAce ns = new CommonAce(AceFlags.None, AceQualifier.AccessAllowed,
mask, ns_account, false, null);
acl.InsertAce(acl.Count, ns);
} else {
accountACE.AccessMask |= mask;
}
// Write security descriptor back to registry
byte[] binarySd = new byte[sd.BinaryLength];
sd.GetBinaryForm(binarySd, 0);
Registry.SetValue(keyname, valuename, binarySd);
}
private static RawSecurityDescriptor InitializeEmptySecurityDescriptor()
{
var localSystem =
new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
var new_sd =
new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent,
localSystem, localSystem, null,
new RawAcl(GenericAcl.AclRevision, 1));
return new_sd;
}
Do note that this code is by no means perfect. If the entire registry key value for these ACL is missing in the registry, the ACL that is synthesized will ONLY grant access to the passed account and nothing else. I'm also sure there are lots of error conditions I've not handled properly and details I've glossed over. Again, it's an example of how to deal with DCOM ACL in .NET.
I found this solution working:
public static void SetUp()
{
SetCOMSercurityAccess("DefaultAccessPermission");
SetCOMSercurityAccess("DefaultLaunchPermission");
}
private static void SetCOMSercurityAccess(string regKey)
{
//This is the magic permission!
byte[] binaryform = new string[]
{
"01","00","04","80","80","00","00","00","90","00","00","00","00","00","00","00","14","00","00","00","02","00","6c","00","04",
"00","00","00","00","00","14","00","1f","00","00","00","01","01","00","00","00","00","00","05","12","00","00","00","00","00",
"24","00","0b","00","00","00","01","05","00","00","00","00","00","05","15","00","00","00","a3","53","d8","c8","94","bd","63",
"84","88","bf","fa","cf","a7","2b","00","00","00","00","18","00","1f","00","00","00","01","02","00","00","00","00","00","05",
"20","00","00","00","20","02","00","00","00","00","14","00","1f","00","00","00","01","01","00","00","00","00","00","05","04",
"00","00","00","01","02","00","00","00","00","00","05","20","00","00","00","20","02","00","00","01","02","00","00","00","00",
"00","05","20","00","00","00","20","02","00","00"
}.Select(o=> Convert.ToByte(o,16)).ToArray();
Registry.SetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Ole", regKey, binaryform, RegistryValueKind.Binary);
}
In case it help others...