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:

  • dwCertEncodingType = 65537 (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING);
  • lpszStructType = 1 (as integer, not a string);
  • pbEncoded = raw encoded byte array (we will use RawData property from X509Certificate2 object);
  • cbEncoded = the length in bytes of raw array;
  • dwFlags = CRYPT_DECODE_NO_SIGNATURE_BYTE_REVERSAL_FLAG (0x8). We need to specify this flag, because CryptoAPI uses little-endian byte ordering, but .NET — big-endian;
  • pvStructInfo = IntPtr.Zero (with the first call we just determine resulting structure size).
  • pcbStructInfo = reference parameter. We need to define variable that will receive the size before we call the function.
[↓] [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 :)


Share this article:

Comments:

Jaro

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:

Please, solve this little equation and enter result below. Captcha