I'm trying to build a tool that will mass sign a bunch of files based on Kernel-Mode Code Signing requirements. I know that signtool can take an additional certificate for cross-signatures trust via the /ac argument, but have not been able to figure out how to do the same using SignerSign or SignerSignEx. I've even spied on signtool's API calls, and mirroring them does not seems to produce the same affect.
Be aware, signtool or other command-line utilities cannot be used for this purpose due to project constraints.
Is there any documentation or examples on how to accomplish this?
Okay, after working on this for awhile, I've finally figured our how to do signing with a cross certificate. First, you will need four, or five depending on your version of signtool, certificates that are embedded in the signtool EXE's resources under the CERTIFICATE
resource type, they all start with MS
. Now I made my version pull all certificates from files so the following example pseudo code will explain how to do this. It's not purely the CryptoAPI calls, but does explain the basic process used by signtool. All certificate usage validation has also been left out for simplicity.
// inputs: string pfx_file_path, string cross_cert_file_path, string password, string file_to_sign
Certificate_Store signer_collection = CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, CERT_STORE_CREATE_NEW_FLAG, NULL);
Certificate_Store signer_store = CertOpenStore(CERT_STORE_PROV_MEMORY, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, NULL,
CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG | CERT_STORE_READONLY_FLAG, NULL);
File pfx_file = File::open(pifx_file_path, GENERIC_READ, OPEN_EXISTING);
File_Mapping<BYTE> pfx_mapping = File_Mapping<BYTE>::map(pfx_file);
CRYPT_DATA_BLOB pfx_blob = { pfx_mapping.size(), pfx_mapping.data() };
Certificate_Store signer_pfx = PFXImportCertStore(&pfx_blob, password, CRYPT_USER_KEYSET);
// CertEnumCertificatesInStore
for (Certificate certificate: signer_pfx) {
signer_store.add(certificate); // CertAddCertificateContextToStore
}
signer_collection.add(signer_store); // CertAddStoreToCollection
Certificate signer;
for (Certificate certificate: signer_collection) {
// Assumes first certificate is the signer, need better validation.
signer = CertDuplicateCertificateContext(certificate);
break;
}
if (signer != NULL) {
throw NoSginerException();
}
Certificate_Store additional_collection;
if (cross_cert_file_path != NULL) {
Certificate_Store cross_collection;
Certificate_Store cross_store;
Certificate cross_certificate = Certificate::load(cross_cert_file_path);
// CryptQueryObject(CERT_QUERY_OBJECT_FILE, cross_cert_file_path,
// CERT_QUERY_CONTENT_FLAG_CERT, CERT_QUERY_FORMAT_FLAG_ALL,
// 0, NULL, NULL, NULL, NULL, NULL, &cross_certificate);
cross_collection = CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, CERT_STORE_CREATE_NEW_FLAG, NULL);
cross_collection.add(signer_collection);
cross_store = CertOpenStore(CERT_STORE_PROV_MEMORY, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, NULL,
CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG | CERT_STORE_READONLY_FLAG, NULL);
cross_store.add(cross_certificate);
cross_collection.add(cross_store);
Certificate_Store ms_root_store = CertOpenStore(sz_CERT_STORE_PROV_MEMORY, 0, NULL,
CERT_STORE_CREATE_NEW_FLAG | CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, NULL);
// This is where the embedded certificates from the MS Code Validation roots are collectioned.
for (Resource resource: Program_Resources::resources_under("CERTIFICATE")) { // EnumResourceNames, Find/Load/Lock|Resource
Certificate certificate = Certificate::from_blob(resource.size(), resource.data());
// CERT_BLOB blob = { resource.size(), resources.data() };
// CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &blob,
// CERT_QUERY_CONTENT_FLAG_CERT | CERT_QUERY_CONTENT_FLAG_SERIALIZED_CERT,
// CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, NULL, NULL, NULL, NULL,
// &certificate);
ms_root_store.add(certificate);
}
cross_collection.add(certificate);
static const DWORD CHAIN_FLAGS = CERT_CHAIN_DISABLE_PASS1_QUALITY_FILTERING |
CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS |
CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
static const CERT_CHAIN_PARA CHAIN_PARAMS = { sizeof(CERT_CHAIN_PARA) };
Certificate_Chain chain = Certificate_Chain::get(HCCE_LOCAL_MACHINE, signer, NULL,
cross_collection, &CHAIN_PARAMS, CHAIN_FLAGS, NULL);
// CertGetCertificateChain(HCCE_LOCAL_MACHINE, signer, NULL,
// cross_collection, &CHAIN_PARAMS, CHAIN_FLAGS, NULL, &chain);
Certificate_Store additional_store = CertOpenStore(CERT_STORE_PROV_MEMORY,
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG |
CERT_STORE_READONLY_FLAG, NULL);
for (DWORD l = 0; l != chain->cLowerQualityChainContext; ++l) {
PCCERT_CHAIN_CONTEXT low_chain = pChain->rgpLowerQualityChainContext[l];
for (DWORD c = 0; c != low_chain->cChain; ++c) {
PCERT_SIMPLE_CHAIN simple_chain = low_chain->rgpChain[c];
for (DWORD e = 0; e != simple_chain->cElement; ++e) {
PCERT_CHAIN_ELEMENT element = simple_chain->rgpElement[e];
additional_store.add(element->pCertContext);
}
}
}
additional_collection = CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, CERT_STORE_CREATE_NEW_FLAG, NULL);
additional_collection.add(additional_store);
}
SIGNER_FILE_INFO file_info = { sizeof(SIGNER_FILE_INFO) };
file_info.pwszFileName = file_to_sign;
DWORD index = 0;
SIGNER_SUBJECT_INFO subject_info = { sizeof(SIGNER_SUBJECT_INFO) };
subject_info.pdwIndex = &index;
subject_info.dwSubjectChoice = SIGNER_SUBJECT_FILE;
subject_info.pSignerFileInfo = &file_info;
SIGNER_CERT_STORE_INFO store_info = { sizeof(SIGNER_CERT_STORE_INFO) };
store_info.dwCertPolicy = SIGNER_CERT_POLICY_STORE;
store_info.pSigningCert = signer;
store_info.hCertStore = additional_collection;
SIGNER_CERT cert_info = { sizeof(SIGNER_CERT) };
cert_info.dwCertChoice = SIGNER_CERT_STORE;
cert_info.pCertStoreInfo = &store_info;
SIGNER_ATTR_AUTHCODE authcode_attr = { sizeof(SIGNER_ATTR_AUTHCODE) };
SIGNER_SIGNATURE_INFO signature_info = { sizeof(SIGNER_SIGNATURE_INFO) };
signature_info.algidHash = CALG_SHA;
signature_info.dwAttrChoice = SIGNER_AUTHCODE_ATTR;
signature_info.pAttrAuthcode = &authcode_attr;
SIGNER_PROVIDER_INFO provider_info = { sizeof(SIGNER_PROVIDER_INFO) };
provider_info.pwszProviderName = L"";
provider_info.dwPvkChoice = PVK_TYPE_KEYCONTAINER;
provider_info.pwszKeyContainer = L"";
HRESULT hr = SignerSign(&subject_info, &cert_info, &signature_info, &provider_info, NULL, NULL, NULL);