In previous post we talked about digital signatures and how we can verify them in PowerShell (RSA signatures). I promised to continue this diving with unmanaged stuff.
As we already discussed, CryptoAPI has unmanaged structure CERT_SIGNED_CONTENT_INFO which represents a signed info, including actual data to be signed, algorithm identifier and signature value. In order to deal with this structure we need to use some encoders and decoders. In the decoding process a ASN.1-encoded raw byte array is converted to a structure and in encoding process, a structure is converted to a ASN.1-encoded byte array. CryptoAPI contains 2 (actually 4) functions for ASN.1 encoding/decoding:
Also we will have to define related structures (of which consist CERT_SIGNED_CONTENT_INFO structure):
I assume that you have at least basic experience with P/Invoke and dealing with unknown length data.
Note: what does means the data with unknown length? In certain cases, unmanaged function cannot determine the resulting size of the output value. Therefore, the function is called twice: first time with empty buffer (which receives the data) and function returns required buffer size. Then you allocate the buffer (with enough size) and specify this buffer during second call. And function will export resulted data. For more details you can check the following MSDN article: Retrieving Data of Unknown Length.
At first we need to write a definitions for CryptDecodeObject function and related structures:
$signature = @" [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern Boolean CryptDecodeObject( UInt32 dwCertEncodingType, UInt32 lpszStructType, IntPtr pbEncoded, UInt32 cbEncoded, UInt32 dwFlags, IntPtr pvStructInfo, ref UInt32 pcbStructInfo ); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CERT_SIGNED_CONTENT_INFO { public CRYPTOAPI_BLOB ToBeSigned; public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; public CRYPT_BIT_BLOB Signature; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRYPTOAPI_BLOB { public UInt32 cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRYPT_ALGORITHM_IDENTIFIER { [MarshalAs(UnmanagedType.LPStr)] public String pszObjId; public CRYPTOAPI_BLOB Parameters; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRYPT_BIT_BLOB { public UInt32 cbData; public IntPtr pbData; public UInt32 cUnusedBits; } "@ Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32
In the next examples we will convert ASN.1-encoded byte array and use CryptDecodeObject function to split this array to a signing parts (tbs data, algorithm identifier and signature).
Note: use Constants for CryptEncodeObject and CryptDecodeObject page for structure identifiers which will be passed to lpszStructType parameter. In our case, CERT_SIGNED_CONTENT_INFO.
In the first CryptDecodeObject call we specify the following parameters:
[↓] [vPodans] # retrieve X509Certificate2 object [↓] [vPodans] $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 .\Desktop\digicert.cer [↓] [vPodans] # extract raw byte array [↓] [vPodans] $raw = $cert.RawData [↓] [vPodans] # define variable that will receive resulting structure size [↓] [vPodans] $pcbStructInfo = 0 [↓] [vPodans] # call the function to determine resulting structure size [↓] [vPodans] [pki.crypt32]::CryptDecodeObject(65537,1,$raw,$raw.length,0x8,[IntPtr]::Zero,[ref]$pcbStructInfo) True [↓] [vPodans] # function returns True (success) and we can check required size: [↓] [vPodans] $pcbStructInfo 1760 [↓] [vPodans]
Now we need to allocate the buffer to receive the structure. Since unmanaged code cannot access managed memory, we need to allocate the buffer in unmanaged memory. For memory allocation we will use AllocHGlobal method in InteropServices.Marshal class:
[↓] [vPodans] $pvStructInfo = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($pcbStructInfo) [↓] [vPodans] $pvStructInfo 43069792 [↓] [vPodans]
We have a pointer. Now we call the CryptDecodeObject function again with the only change: in pvStructInfo we specify pointer to a allocated memory:
[↓] [vPodans] [pki.crypt32]::CryptDecodeObject(65537,1,$raw,$raw.length,0x8,$pvStructInfo,[ref]$pcbStructInfo) True [↓] [vPodans]
Now decoded structure is copied to allocated memory buffer. And how to extract it??? Marshal class has PtrToStructure method which will copy structure from unmanaged memory to a managed structure:
[↓] [vPodans] $struct = [System.Runtime.InteropServices.Marshal]::PtrToStructure($pvStructInfo,[Type][PKI.Crypt32+CERT_SIGNED_ CONTENT_INFO]) [↓] [vPodans] $struct ToBeSigned SignatureAlgorithm Signature ---------- ------------------ --------- PKI.Crypt32+CRYPTOAPI_BLOB PKI.Crypt32+CRYPT_ALGORITHM_IDENTIFIER PKI.Crypt32+CRYPT_BIT_BLOB [↓] [vPodans] $struct.ToBeSigned cbData pbData ------ ------ 1404 43069856 [↓] [vPodans] $struct.SignatureAlgorithm pszObjId Parameters -------- ---------- 1.2.840.113549.1.1.5 PKI.Crypt32+CRYPTOAPI_BLOB [↓] [vPodans] $struct.Signature cbData pbData cUnusedBits ------ ------ ----------- 256 43071296 0 [↓] [vPodans]
We see that our structure is filled with the data (at first look it looks as not what we expected, wait few minutes).
ToBeSigned property has two sub properties (inner CRYTPOAPI_BLOB), cbData and pbData. pbData is a pointer to a data to be signed. cbData is the size in bytes of the data contained in pbData. With Marshal.Copy we can copy byte array (without any transformation) to a managed byte array. Again, we need to allocate the buffer to receive the data. But now we need to allocate it in managed memory (because we no longer work with unmanaged functions):
[↓] [vPodans] $tbsData = New-Object byte[] -ArgumentList $struct.ToBeSigned.cbData [↓] [vPodans] [System.Runtime.InteropServices.Marshal]::Copy($struct.ToBeSigned.pbData,$tbsData,0,$tbsData.Length) [↓] [vPodans] $tbsData[0..5] 48 130 5 120 160 3 [↓] [vPodans]
And we repeat this trick with signature structure:
[↓] [vPodans] $signature = New-Object byte[] -ArgumentList $struct.Signature.cbData [↓] [vPodans] [System.Runtime.InteropServices.Marshal]::Copy($struct.Signature.pbData,$signature,0,$signature.Length) [↓] [vPodans] $signature[0..5] 9 94 76 81 68 128 [↓] [vPodans]
And what next? We missed signature algorithm:
[↓] [vPodans] $algorithm Value FriendlyName ----- ------------ 1.2.840.113549.1.1.5 sha1RSA [↓] [vPodans] $algorithm = (New-Object System.Security.Cryptography.Oid $struct.SignatureAlgorithm.pszObjId).FriendlyNam e [↓] [vPodans] # remove 'RSA' suffix to leave only algorithm name [↓] [vPodans] $algorithm = $algorithm.Replace("RSA",$null) [↓] [vPodans] $algorithm sha1 [↓] [vPodans]
And now we can verify signature with RSACryptoServiceProvider.VerifyData method:
[↓] [vPodans] $pubkey = $issuer.PublicKey.Key [↓] [vPodans] $pubkey.VerifyData($tbsData,$algorithm,$signature) True [↓] [vPodans]
Wow, here we go! Signature was verified successfully!
And the last note: when we finish our stuff, we must release unmanaged buffer. Buffer is allocated by FreeHGlobal method:
[↓] [vPodans] [System.Runtime.InteropServices.Marshal]::FreeHGlobal($pvStructInfo)
And here is PowerShell example which verifies whether the certificate was signed by other certificate's public key:
function Test-CertificateSignature { [CmdletBinding()] param( [Security.Cryptography.X509Certificates.X509Certificate2]$target, [Security.Cryptography.X509Certificates.X509Certificate2]$issuer ) $signature = @" [DllImport("Crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern Boolean CryptDecodeObject( UInt32 dwCertEncodingType, UInt32 lpszStructType, Byte[] pbEncoded, UInt32 cbEncoded, UInt32 dwFlags, IntPtr pvStructInfo, ref UInt32 pcbStructInfo ); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CERT_SIGNED_CONTENT_INFO { public CRYPTOAPI_BLOB ToBeSigned; public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; public CRYPT_BIT_BLOB Signature; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRYPTOAPI_BLOB { public UInt32 cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRYPT_ALGORITHM_IDENTIFIER { [MarshalAs(UnmanagedType.LPStr)] public String pszObjId; public CRYPTOAPI_BLOB Parameters; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct CRYPT_BIT_BLOB { public UInt32 cbData; public IntPtr pbData; public UInt32 cUnusedBits; } "@ Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32 $raw = $target.RawData $pcbStructInfo = 0 [void][pki.crypt32]::CryptDecodeObject(65537,1,$raw,$raw.length,0x8,[IntPtr]::Zero,[ref]$pcbStructInfo) $pvStructInfo = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($pcbStructInfo) [void][pki.crypt32]::CryptDecodeObject(65537,1,$raw,$raw.length,0x8,$pvStructInfo,[ref]$pcbStructInfo) $struct = [System.Runtime.InteropServices.Marshal]::PtrToStructure($pvStructInfo,[Type][PKI.Crypt32+CERT_SIGNED_CONTENT_INFO]) $tbsData = New-Object byte[] -ArgumentList $struct.ToBeSigned.cbData [System.Runtime.InteropServices.Marshal]::Copy($struct.ToBeSigned.pbData,$tbsData,0,$tbsData.Length) $signature = New-Object byte[] -ArgumentList $struct.Signature.cbData [System.Runtime.InteropServices.Marshal]::Copy($struct.Signature.pbData,$signature,0,$signature.Length) $algorithm = (New-Object System.Security.Cryptography.Oid $struct.SignatureAlgorithm.pszObjId).FriendlyName $algorithm = $algorithm.Replace("RSA",$null) $pubkey = $issuer.PublicKey.Key $pubkey.VerifyData($tbsData,$algorithm,$signature) [Runtime.InteropServices.Marshal]::FreeHGlobal($pvStructInfo) }
And here is a little example:
[↓] [vPodans] Test-CertificateSignature .\Desktop\digicert.cer .\Desktop\digicert_issuer.cer True [↓] [vPodans] Test-CertificateSignature .\Desktop\digicert.cer .\Desktop\pica-1.cer False [↓] [vPodans]
As you see, there is no magic, only the power of the Windows PowerShell!
Enjoy :)
Hi Vadims, can you add signature to a textfile using certificate from Smart card? I read a lot of your excellent blogs, but I still can't wrap my head around how I could do that (or if I understand logic correctly). Should you read the certificates from smartcard (as you are writing in another article), use some code (???) to attach that signature to the end of the file?
Thanks
Post your comment:
Comments: