Microsoft Cryptography API (CAPI)라는 넘이 있는데, 요넘은 거의 모든 암호화 및 PKI 관련 기술에 사용되는 근간이 되는 것으로, COM으로 노출시킨 CAPICOM이라는 것을 배포하고 있다. 이것을 .NET에서 참조하여 사용하면 .NET으로도 똑같은 코드를 작성할 수 있는데, COM으로 노출시켜놓은 형식의 제한성 때문에 사용하는 데 몇 가지 문제점이 있다. 대표적인 문제점이 문자열 표현방식인데, .NET에서는 기본 인코딩을 UTF8을 사용하지만 CAPICOM에서는 Unicode를 사용하도록 되어 있다. 따라서 그대로 사용하면 정상적으로 작동되지 않고 원하지 않은 결과가 나타난다.

이것을 보정하기 위한 여러가지 방법이 있는데, 여기서는 COM을 직접 참조할 때 사용되는 RCW 중간언어의 디컴파일과 재컴파일을 통해 원하는 형식으로 맞추는 방법을 살펴본다.

 

-----

 

As you see, root of the problem is because CAPICOM manipulates only Unicode strings while validating and generating digital signatures. CAPICOM is actually designed by Microsoft to be used in languages like VB, so the method parameters in CAPICOM Type library is defined as BSTR which is actually VB version of Unicode. BSTR is actually a 32-bit pointer to a Unicode character array, preceded by 4-byte long and terminated by a 2-byte null character (ANSI 0). Based on my experiments in VB 6, I found out that none of these problems happen there, i.e., methods of the Utilities class does not truncate the last character, and everything works just fine. This actually brought my attention to the Runtime Callable Wrapper (RCW) that needs to be generated to be able to use CAPICOM from .NET world. It looks like when I tried to pass or retrieve ANSI BSTR (Binary String) from .NET to RCW, it was the guy who actually causes truncation.

Based on all these, I decided to modify the generated RCW so that no marshalling occurs between managed world and unmanaged COM world. I will still use the Utilities class to generate and retrieve Binary Strings, but I will be preventing any BSTR to/from .NET string conversion. The best candidate would be using .NET IntPtr instead of BSTR marshalling as BSTR is actually a pointer.

As a first step, I used ILDASM.EXE to decompile .NET RCW CAPICOM.dll into IL code, using the command below:

C:\CapicomWork>ILDASM CAPICOM.dll /OUT:CAPICOM.IL


ILDASM generated two files CAPICOM.IL which contains IL code for CAPICOM RCW, and CAPICOM.res resource file. I opened the CAPICOM.IL and located the places for Content field declarations of SignedDataClass as below:

.method public hidebysig newslot specialname virtual

 instance void set_Content([in] string marshal( bstr) pVal)

 runtime managed internalcall

{

 .custom instance void

 [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32)

 = ( 01 00 00 00 00 00 00 00 )

 .override CAPICOM.ISignedData::set_Content

} // end of method SignedDataClass::set_Content


.method public hidebysig newslot specialname virtual

 instance string marshal( bstr)  get_Content()

 runtime managed internalcall

{

 .custom instance void

 [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32)

 = ( 01 00 00 00 00 00 00 00 )

 .override CAPICOM.ISignedData::get_Content

} // end of method SignedDataClass::get_Content


Since Content is a property, IL code includes two methods for get and set operations; get_Content, and set_Content. From the IL code above, Content field is actually interpreted as string and marshaled as BSTR. I changed the IL code above with the following:

.method public hidebysig newslot specialname virtual

 instance void set_Content([in] native int pVal)

 runtime managed internalcall

{

 .custom instance void

 [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32)

 = ( 01 00 00 00 00 00 00 00 )

 .override CAPICOM.ISignedData::set_Content

} // end of method SignedDataClass::set_Content

.method public hidebysig newslot specialname virtual

 instance native int get_Content()

 runtime managed internalcall

{

 .custom instance void

 [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32)

 = ( 01 00 00 00 00 00 00 00 )

 .override CAPICOM.ISignedData::get_Content

} // end of method SignedDataClass::get_Content


As you have noticed, I replaced the string marshal (bstr) with native int which is actually IntPtr in C#. I had to do the same changes on ISignedData and SignedData interfaces not to cause any conflict.

However, this change alone will not be enough as it is difficult to generate a proper BSTR IntPtr in .NET side. The other problem was to retrieve the correct string in case of Attached signature content extraction. I should be able to get the content in byte array to overcome potential data truncation.

Utilities class was the best candidate to help me in these challenges as I can pass byte array and get BSTR pointer with no extra work using ByteArrayToBinaryString method. Similarly, using BinaryStringToByteArray method would let me handle the second issue of retrieving proper message attached in the message. I changed the UtilitiesClass (as well as the IUtilities interface) in IL as below:

method public hidebysig newslot virtual

 instance object marshal( struct) BinaryStringToByteArray([in] native int BinaryString)

 runtime managed internalcall

{

 .custom instance void

 [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32)

 = ( 01 00 06 00 00 00 00 00 )

 .override CAPICOM.IUtilities::BinaryStringToByteArray

} // end of method UtilitiesClass::BinaryStringToByteArray

.method public hidebysig newslot virtual

 instance native int  ByteArrayToBinaryString([in] object marshal( struct) varByteArray)

 runtime managed internalcall

{

 .custom instance void

 [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32)

 = ( 01 00 07 00 00 00 00 00 )

 .override CAPICOM.IUtilities::ByteArrayToBinaryString

} // end of method UtilitiesClass::ByteArrayToBinaryString


Then save the CAPICOM.IL and run the following command from the command prompt to re-build the RCW CAPICOM.dll using .NET's ILASM tool:

C:\CapicomWork>ilasm /dll CAPICOM.IL / resource=CAPICOM.res /KEY=mykey.snk /output=Capicom.dll



The final code


After the successful build with ILASM, I rewrote the Digital signature routines as below:

public string SignFromText(string plaintextMessage, bool bDetached, Encoding encodingType)

{

 CAPICOM.SignedData signedData = new CAPICOM.SignedDataClass();

 CAPICOM.Utilities u = new CAPICOM.UtilitiesClass();

 signedData.set_Content(u.ByteArrayToBinaryString( encodingType.GetBytes(plaintextMessage)));

 CAPICOM.Signer signer = new CAPICOM.Signer();

 signer.Certificate = ClientCert;

 this._signedContent = signedData.Sign(signer, bDetached, CAPICOM.CAPICOM_ENCODING_TYPE.CAPICOM_ENCODE_BASE64);

 return _signedContent;

}

public bool VerifyDetachedSignature(string plaintextMessage, string signedContent, Encoding encodingType)

{

 try

 {

 this._clearText = plaintextMessage;

 this._signedContent = signedContent;

 CAPICOM.SignedData signedData = new CAPICOM.SignedDataClass();

 CAPICOM.Utilities u = new CAPICOM.UtilitiesClass();

 signedData.set_Content(u.ByteArrayToBinaryString( encodingType.GetBytes(plaintextMessage)));

 signedData.Verify(_signedContent,true, CAPICOM.CAPICOM_SIGNED_DATA_VERIFY_FLAG .CAPICOM_VERIFY_SIGNATURE_ONLY);

 SignerCert=null;

 CAPICOM.Signer s = (CAPICOM.Signer) signedData.Signers[1];

 SignerCert = (CAPICOM.Certificate)s.Certificate; return true;

 }

 catch(COMException e) { return false; }

}

public bool VerifyAttachedSignature(string signedContent, Encoding encodingType)

{

 try

 {

 this._signedContent = signedContent;

 CAPICOM.Utilities u = new CAPICOM.Utilities();

 CAPICOM.SignedData signedData = new CAPICOM.SignedData();

 signedData.Verify(_signedContent,false, CAPICOM.CAPICOM_SIGNED_DATA_VERIFY_FLAG.CAPICOM_VERIFY_SIGNATURE_ONLY);

 SignerCert=null;

 CAPICOM.Signer s = (CAPICOM.Signer) signedData.Signers[1];

 SignerCert = (CAPICOM.Certificate)s.Certificate;

 this._clearText = encodingType.GetString( (byte[])u.BinaryStringToByteArray(signedData.get_Content()));

 return true;

 }

 catch(COMException e) { return false; }

}

The bold lines above actually show the differences in the code after applying changes made in CAPICOM RCW. We can not use SignedData.Content property anymore as C# does not support properties with IntPtr return type so we have to explicitly specify the get_Content or set_Content methods whenever needed. encodingType parameter enables us to pass or retrieve the content whichever encoding we like. The SignFromText accepts bDetached parameter for distinguishing detached/attached signing requests, and should be set to true if you want to generate detached signature.





Posted by 떼르미
,


자바스크립트를 허용해주세요!
Please Enable JavaScript![ Enable JavaScript ]