将.NET的SecureString传递给COM互操作

3
如何手动将BSTR从.NET转换为COM?
我正在尝试将存储在SecureString中的密码从我的.NET应用程序传递到COM对象的方法。我想避免将SecureString转换为String,因为这不是使用SecureString的正确方法。密码会以明文形式保存在托管内存中,并在那里长时间存在。相反,我正在尝试以正确的方式进行转换:将其转换为未托管内存中的BSTR,将其传递给COM方法,然后使用Marshal.ZeroFreeBSTR清除BSTR,以便密码仅暴露一段有限的时间。

TblImp.exe生成的用于此COM对象的.NET接口期望将密码作为String传递,然后将其封送到BSTR。这不是我想要的,因为它意味着我必须将我的密码放在String中,所以根据调用COM的精益方法,我创建了自己的接口,以便我可以自定义它并摆脱String。以下是我开始使用的内容:

[ComImport, Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITheComObjectDispatcher
{                                                     // +--- the naughty string
    [return: MarshalAs(UnmanagedType.BStr)]           // |
    [DispId(1)]                                       // V
    string TheMethod([In, MarshalAs(UnmanagedType.BStr)] string secret);
}

[ComImport, Guid("YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY")]
public class TheComObject
{}

这个方法是可行的 - 但我必须将 SecureString 转换为 String 才能使用它,像这样:
private string UseTheMethod(SecureString secret)
{
    var comObject = new TheComObject();
    var comObjectDispatcher = (ITheComObjectDispatcher)comObject;
    var secretAsString = NaughtilyConvertSecureStringToString(secret);
    return comObjectDispatcher.TheMethod(secretAsString);
}

private static string NaughtilyConvertSecureStringToString(SecureString value)
{
    var valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
    try
    {
        return Marshal.PtrToStringUni(valuePtr); // now the secret is in managed memory :(
    }
    finally
    {
        Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
    }
}

相反,我希望使用类似于Marshal.SecureStringToBSTR的方法并跳过转换为String。我已经尝试过这个...

    [return: MarshalAs(UnmanagedType.BStr)]
    [DispId(1)]
    string TheMethod([In, MarshalAs(UnmanagedType.BStr)] IntPtr secret);

...和这个...

    [return: MarshalAs(UnmanagedType.BStr)]
    [DispId(1)]
    string TheMethod([In] IntPtr secret);

...使用此调用代码...

private string UseTheMethod(SecureString secret)
{
    var comObject = new TheComObject();
    var comObjectDispatcher = (ITheComObjectDispatcher)comObject;
    var secretPtr = Marshal.SecureStringToBSTR(secret);
    try
    {
        return comObjectDispatcher.TheMethod(secretPtr);
    }
    finally
    {
        Marshal.ZeroFreeBSTR(secretPtr);
    }
}

...但它不起作用:错误的值被传递给了COM方法。它没有出现错误,只是给出了不同的结果。

问题:

  1. 我可以像这样直接将BSTR作为IntPtr传递吗?我做错了什么?
  2. 有没有一种调试封送过程以查看传递的值的方法?

你确定ITheComObjectDispatcher是仅限于IDispatch的接口吗?如果是这样,那么你将无法轻松地完成它。你是否有ITheComObjectDispatcher的原始COM(idl或.h)定义? - Simon Mourier
@SimonMourier - 我有IDL - 看起来是“双重”的。 - Daniel Schilling
1个回答

3

假设您在.idl文件中有以下内容:

interface ITheComObjectDispatcher : IDispatch
{
    HRESULT TheMethod(BSTR secret, [out, retval] BSTR *pOut);
};

这将使用.NET的tlbimp(如您所见)变成这样:
[ComImport, TypeLibType((short) 0x10c0), Guid("D4089F1D-5D83-4D1C-92CD-5941B35D43AA")]
public interface ITheComObjectDispatcher
{
    [return: MarshalAs(UnmanagedType.BStr)]
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(0x60020000)]
    string TheMethod([MarshalAs(UnmanagedType.BStr)] string secret);
}

如果您不想使用不安全的string参数,就不能直接使用此方法。解决方案是在C#中重新定义COM接口,例如:

(请注意InterfaceIsDual选项):

[InterfaceType(ComInterfaceType.InterfaceIsDual), Guid("D4089F1D-5D83-4D1C-92CD-5941B35D43AA")]
public interface ITheComObjectDispatcher2
{
    [DispId(0x60020000)]
    IntPtr TheMethod(IntPtr secret);
}

然后像这样使用:

var comObject = new TheComObject();
var comObjectDispatcher = (ITheComObjectDispatcher2)comObject;
var ptr = Marshal.SecureStringToBSTR(mySecureString);
try
{
    var outPtr = doc.TheMethod(ptr);

    // get the output string
    var output = Marshal.PtrToStringBSTR(outPtr);

    // free the callee-allocated BSTR
    Marshal.FreeBSTR(outPtr);
}
finally
{
    // free the secure string allocated BSTR
    Marshal.ZeroFreeBSTR(ptr);
}

是的!这样就解决了。为什么将接口类型更改为双重可以解决它? - Daniel Schilling
1
因为它确实是一个双接口(也称为“早期绑定”和“后期绑定”,或者使用IUnknown + IDispatch描述方法),而不是纯IDispatch(也称为“后期绑定”,没有任何C/C++方法)接口。 - Simon Mourier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接