Subject Interface Packages - Part 2
In part 1 of Subject Interface Packages, we left off being able to sign a custom file type, but not verify it. Here we are going to implement the two functions to perform verification.
This is in some ways, the reverse of what we did in the previous part. Instead of injecting the signature data in to the file, we need to extract it, and instead of creating a digest, we need to create and compare the digest.
Get Signed Data Message
The last part we did was “putting” the signature data, now we need to do the reverse. Given a file handle, get the signed data message.
BOOL WINAPI PngCryptSIPGetSignedDataMsg(
SIP_SUBJECTINFO *pSubjectInfo,
DWORD* pdwEncodingType,
DWORD dwIndex,
DWORD *pcbSignedDataMsg,
BYTE *pbSignedDataMsg
)
The purpose of this function is that upon successful completion,
pbSignedDataMsg
will point to the signed data message that we embedded in the
file with CryptSIPPutSignedDataMsg
.
Other things that will need to be set is the pcbSignedDataMsg
which is the
size of the signed data message, and pdwEncodingType
which is the encoding
type.
We can knock out pdwEncodingType
easily because we can set it to an either/or
as we see in many of the other CMS APIs:
//TODO: validation
*pdwEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
The authenticode process will call this function twice. Once with
pbSignedDataMsg
pointing to NULL, and it is expected that pcbSignedDataMsg
will be set with the size of the buffer that Win32 should allocate. The second
call to the function will have pbSignedDataMsg
pointing to a buffer of
memory that is at least as big as the the indicated size from the first call.
A pseudo-code implementation would look something like this:
BOOL WINAPI PngCryptSIPGetSignedDataMsg(
SIP_SUBJECTINFO *pSubjectInfo,
DWORD* pdwEncodingType,
DWORD dwIndex,
DWORD *pcbSignedDataMsg,
BYTE *pbSignedDataMsg
) {
//TODO: validation
*pdwEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
if (NULL == pbSignedDataMsg) {
DWORD size;
if (GetSignedDataMsgSize(pSubjectInfo->hFile, &size)) {
*pcbSignedDataMsg = size;
return TRUE;
}
return FALSE;
}
return GetSignedDataMsg(pSubjectInfo->hFile, pcbSignedDataMsg, pbSignedDataMsg));
}
Where GetSignedDataMsg
will fill pbSignedDataMsg
with the data message.
You don’t have to do any verification with this. Internally, Win32 will use
CryptVerifyMessageSignature
to verify the message and integrity of the
signature itself.
If you are having trouble at this step, it’s worth pointing out that you can
call CryptVerifyMessageSignature
yourself at this point to verify that you’re
extracting the signature from the file correctly. You should also be able to run
this through an ASN.1 decoder and see properly decoded output.
It should also be byte-for-byte identical to the “put” operation in part 1, so you can compare at these two steps.
Verify Indirect Data
The last step is to verify the hash that was signed.
BOOL WINAPI PngCryptSIPVerifyIndirectData(
SIP_SUBJECTINFO *pSubjectInfo,
SIP_INDIRECT_DATA *pIndirectData)
The first parameter gives us information about the file being verified. In this
step you will need to re-hash the file, just the same way that was done in the
very beginning. You then need to compare this hash with
pIndirectData->Digest.pbData
.
If the hashes match, you should return TRUE and use SetLastError
to
ERROR_SUCCESS
to indicate the hash is correct. If the hashes are not equal,
you should return FALSE and use TRUST_E_SUBJECT_NOT_TRUSTED
with
SetLastError
to indicate that there was no unexpected error, just that the
signatures do not match.
pIndirectData->Digest
will contain the digest algorithm. Valid that it is
correct, and that the parameters are what you expect. In cases for digests used
for authenticode digests, the parameters will either be a literal NULL or more
likely, {0, 5} as an ASN.1 NULL.
Final Thoughts
This is a rough beginning on writing a SIP. As mentioned in the first post, a GitHub project for a PNG SIP exists and can perform these basic operations. As a reminder, this code exists for demonstration purposes, and not for any real-world use.
There is still plenty to do in later parts. As of now, we cannot:
- Remove a signature
- Timestamp
- Dual Sign
- Seal
I hope to get to these soon for this project. More curiously, a slightly-related subject has me to believe this can be done in Rust. I don’t think it’s a stretch either, or an abuse. Rust seems well suited for the task without going down to C or C++.
The last part, sealing, is a ways off, because the details of sealing signatures is not public yet, and are known only due to some careful inspection.