Получение корневого сертификата
Такая ситуация: необходимо проверить подписаную dll на валидность цифровой подписи.
Как это правильней сделать? Сейчас уже получаеться проверить имя компании на соответсвие той что написана в сертификате, но мне кажеться это не являеться гарантией поставленого выше условия, ведь злоумышленик может подписать файл своей подписью и указать ту же компанию. Хотелось бы добраться как-то до рутового сертификата, но не разобрться как. Текущий код приведен ниже
Код:
function IsCompanySigningCertificate(const Filename, CompanyName :string): Boolean;
var
hExe: HMODULE;
Cert: PWinCertificate;
CertContext: PCCERT_CONTEXT;
iCert: PCCERT_CONTEXT;
CertCount: DWORD;
CertName: AnsiString;
CertNameLen: DWORD;
VerifyParams: CRYPT_VERIFY_MESSAGE_PARA;
RootStore: HCERTSTORE;
flags: DWORD;
begin
// Returns TRUE if the SubjectName on the certificate used to sign the exe is
// "Company Name". Should prevent a cracker from modifying the file and
// re-signing it with their own certificate.
//
// Microsoft has an example that does this using CryptQueryObject and
// CertFindCertificateInStore instead of CryptVerifyMessageSignature, but
// CryptQueryObject is NT-only. Using CertCreateCertificateContext doesn't work
// either, though I don't know why.
Result := False;
// Verify that the exe was signed by our private key
hExe := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ,
nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_RANDOM_ACCESS, 0);
if hExe = INVALID_HANDLE_VALUE then
Exit;
try
// There should only be one certificate associated with the exe
if (not ImageEnumerateCertificates(hExe, CERT_SECTION_TYPE_ANY, CertCount, nil, 0)) or
(CertCount <> 1) then
Exit;
// Read the certificate header so we can get the size needed for the full cert
GetMem(Cert, SizeOf(TWinCertificate) + 3); // ImageGetCertificateHeader writes an DWORD at bCertificate for some reason
try
Cert.dwLength := 0;
Cert.wRevision := WIN_CERT_REVISION_1_0;
if not ImageGetCertificateHeader(hExe, 0, Cert^) then
Exit;
// Read the full certificate
ReallocMem(Cert, SizeOf(TWinCertificate) + Cert.dwLength);
if not ImageGetCertificateData(hExe, 0, Cert, Cert.dwLength) then
Exit;
// Get the certificate context. CryptVerifyMessageSignature has the
// side effect of creating a context for the signing certificate.
FillChar(VerifyParams, SizeOf(VerifyParams), 0);
VerifyParams.cbSize := SizeOf(VerifyParams);
VerifyParams.dwMsgAndCertEncodingType := X509_ASN_ENCODING or PKCS_7_ASN_ENCODING;
if not CryptVerifyMessageSignature(VerifyParams, 0, @Cert.bCertificate,
Cert.dwLength, nil, nil, @CertContext) then
Exit;
try
// Extract and compare the certificate's subject names. Don't
// compare the entire certificate or the public key as those will
// change when the certificate is renewed.
flags:= CERT_STORE_SIGNATURE_FLAG or
CERT_STORE_TIME_VALIDITY_FLAG;
RootStore:= CertOpenSystemStore (0, 'ROOT');
// try to obtain issuer certificate from root
(*
iCert:= CertGetIssuerCertificateFromStore(RootStore, CertContext, nil, @flags);
if iCert <> nil then
begin
*)
CertNameLen := CertGetNameStringA(CertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, nil, 0);
SetLength(CertName, CertNameLen - 1);
CertGetNameStringA(CertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
nil, PAnsiChar(CertName), CertNameLen);
if CertName <> CompanyName then
Exit;
(*
end else
Exit;
*)
finally
CertFreeCertificateContext(CertContext)
end;
finally
FreeMem(Cert);
end;
finally
CloseHandle(hExe);
end;
Result := True;
end;
var
hExe: HMODULE;
Cert: PWinCertificate;
CertContext: PCCERT_CONTEXT;
iCert: PCCERT_CONTEXT;
CertCount: DWORD;
CertName: AnsiString;
CertNameLen: DWORD;
VerifyParams: CRYPT_VERIFY_MESSAGE_PARA;
RootStore: HCERTSTORE;
flags: DWORD;
begin
// Returns TRUE if the SubjectName on the certificate used to sign the exe is
// "Company Name". Should prevent a cracker from modifying the file and
// re-signing it with their own certificate.
//
// Microsoft has an example that does this using CryptQueryObject and
// CertFindCertificateInStore instead of CryptVerifyMessageSignature, but
// CryptQueryObject is NT-only. Using CertCreateCertificateContext doesn't work
// either, though I don't know why.
Result := False;
// Verify that the exe was signed by our private key
hExe := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ,
nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_RANDOM_ACCESS, 0);
if hExe = INVALID_HANDLE_VALUE then
Exit;
try
// There should only be one certificate associated with the exe
if (not ImageEnumerateCertificates(hExe, CERT_SECTION_TYPE_ANY, CertCount, nil, 0)) or
(CertCount <> 1) then
Exit;
// Read the certificate header so we can get the size needed for the full cert
GetMem(Cert, SizeOf(TWinCertificate) + 3); // ImageGetCertificateHeader writes an DWORD at bCertificate for some reason
try
Cert.dwLength := 0;
Cert.wRevision := WIN_CERT_REVISION_1_0;
if not ImageGetCertificateHeader(hExe, 0, Cert^) then
Exit;
// Read the full certificate
ReallocMem(Cert, SizeOf(TWinCertificate) + Cert.dwLength);
if not ImageGetCertificateData(hExe, 0, Cert, Cert.dwLength) then
Exit;
// Get the certificate context. CryptVerifyMessageSignature has the
// side effect of creating a context for the signing certificate.
FillChar(VerifyParams, SizeOf(VerifyParams), 0);
VerifyParams.cbSize := SizeOf(VerifyParams);
VerifyParams.dwMsgAndCertEncodingType := X509_ASN_ENCODING or PKCS_7_ASN_ENCODING;
if not CryptVerifyMessageSignature(VerifyParams, 0, @Cert.bCertificate,
Cert.dwLength, nil, nil, @CertContext) then
Exit;
try
// Extract and compare the certificate's subject names. Don't
// compare the entire certificate or the public key as those will
// change when the certificate is renewed.
flags:= CERT_STORE_SIGNATURE_FLAG or
CERT_STORE_TIME_VALIDITY_FLAG;
RootStore:= CertOpenSystemStore (0, 'ROOT');
// try to obtain issuer certificate from root
(*
iCert:= CertGetIssuerCertificateFromStore(RootStore, CertContext, nil, @flags);
if iCert <> nil then
begin
*)
CertNameLen := CertGetNameStringA(CertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nil, nil, 0);
SetLength(CertName, CertNameLen - 1);
CertGetNameStringA(CertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
nil, PAnsiChar(CertName), CertNameLen);
if CertName <> CompanyName then
Exit;
(*
end else
Exit;
*)
finally
CertFreeCertificateContext(CertContext)
end;
finally
FreeMem(Cert);
end;
finally
CloseHandle(hExe);
end;
Result := True;
end;
Проблема решена - для однозначной идентификации сертификата достаточно сравнивать его серийный номер и имя издателя. Все необходимые функции есть в CryptAPI.