/* * Copyright 2008 Juan Lang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * */ #include #include #include "windef.h" #include "winbase.h" #include "wincrypt.h" #include "wine/debug.h" #include "crypt32_private.h" WINE_DEFAULT_DEBUG_CHANNEL(crypt); static void CTL_free(context_t *context) { ctl_t *ctl = (ctl_t*)context; CryptMsgClose(ctl->ctx.hCryptMsg); CryptMemFree(ctl->ctx.pbCtlEncoded); CryptMemFree(ctl->ctx.pbCtlContext); LocalFree(ctl->ctx.pCtlInfo); } static context_t *CTL_clone(context_t *context, WINECRYPT_CERTSTORE *store, BOOL use_link) { ctl_t *ctl; if(!use_link) { FIXME("Only links supported\n"); return NULL; } ctl = (ctl_t*)Context_CreateLinkContext(sizeof(CTL_CONTEXT), context, store); if(!ctl) return NULL; ctl->ctx.hCertStore = store; return &ctl->base; } static const context_vtbl_t ctl_vtbl = { CTL_free, CTL_clone }; BOOL WINAPI CertAddCTLContextToStore(HCERTSTORE hCertStore, PCCTL_CONTEXT pCtlContext, DWORD dwAddDisposition, PCCTL_CONTEXT* ppStoreContext) { WINECRYPT_CERTSTORE *store = hCertStore; BOOL ret = TRUE; PCCTL_CONTEXT toAdd = NULL, existing = NULL; TRACE("(%p, %p, %08x, %p)\n", hCertStore, pCtlContext, dwAddDisposition, ppStoreContext); if (dwAddDisposition != CERT_STORE_ADD_ALWAYS) { existing = CertFindCTLInStore(hCertStore, 0, 0, CTL_FIND_EXISTING, pCtlContext, NULL); } switch (dwAddDisposition) { case CERT_STORE_ADD_ALWAYS: toAdd = CertDuplicateCTLContext(pCtlContext); break; case CERT_STORE_ADD_NEW: if (existing) { TRACE("found matching CTL, not adding\n"); SetLastError(CRYPT_E_EXISTS); ret = FALSE; } else toAdd = CertDuplicateCTLContext(pCtlContext); break; case CERT_STORE_ADD_NEWER: if (existing) { LONG newer = CompareFileTime(&existing->pCtlInfo->ThisUpdate, &pCtlContext->pCtlInfo->ThisUpdate); if (newer < 0) toAdd = CertDuplicateCTLContext(pCtlContext); else { TRACE("existing CTL is newer, not adding\n"); SetLastError(CRYPT_E_EXISTS); ret = FALSE; } } else toAdd = CertDuplicateCTLContext(pCtlContext); break; case CERT_STORE_ADD_NEWER_INHERIT_PROPERTIES: if (existing) { LONG newer = CompareFileTime(&existing->pCtlInfo->ThisUpdate, &pCtlContext->pCtlInfo->ThisUpdate); if (newer < 0) { toAdd = CertDuplicateCTLContext(pCtlContext); Context_CopyProperties(existing, pCtlContext); } else { TRACE("existing CTL is newer, not adding\n"); SetLastError(CRYPT_E_EXISTS); ret = FALSE; } } else toAdd = CertDuplicateCTLContext(pCtlContext); break; case CERT_STORE_ADD_REPLACE_EXISTING: toAdd = CertDuplicateCTLContext(pCtlContext); break; case CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES: toAdd = CertDuplicateCTLContext(pCtlContext); if (existing) Context_CopyProperties(toAdd, existing); break; case CERT_STORE_ADD_USE_EXISTING: if (existing) { Context_CopyProperties(existing, pCtlContext); if (ppStoreContext) *ppStoreContext = CertDuplicateCTLContext(existing); } else toAdd = CertDuplicateCTLContext(pCtlContext); break; default: FIXME("Unimplemented add disposition %d\n", dwAddDisposition); ret = FALSE; } if (toAdd) { if (store) { context_t *ret_ctx; ret = store->vtbl->ctls.addContext(store, context_from_ptr(toAdd), existing ? context_from_ptr(existing) : NULL, ppStoreContext ? &ret_ctx : NULL, TRUE); if(ret && ppStoreContext) *ppStoreContext = context_ptr(ret_ctx); }else if (ppStoreContext) { *ppStoreContext = CertDuplicateCTLContext(toAdd); } CertFreeCTLContext(toAdd); } CertFreeCTLContext(existing); TRACE("returning %d\n", ret); return ret; } BOOL WINAPI CertAddEncodedCTLToStore(HCERTSTORE hCertStore, DWORD dwMsgAndCertEncodingType, const BYTE *pbCtlEncoded, DWORD cbCtlEncoded, DWORD dwAddDisposition, PCCTL_CONTEXT *ppCtlContext) { PCCTL_CONTEXT ctl = CertCreateCTLContext(dwMsgAndCertEncodingType, pbCtlEncoded, cbCtlEncoded); BOOL ret; TRACE("(%p, %08x, %p, %d, %08x, %p)\n", hCertStore, dwMsgAndCertEncodingType, pbCtlEncoded, cbCtlEncoded, dwAddDisposition, ppCtlContext); if (ctl) { ret = CertAddCTLContextToStore(hCertStore, ctl, dwAddDisposition, ppCtlContext); CertFreeCTLContext(ctl); } else ret = FALSE; return ret; } PCCTL_CONTEXT WINAPI CertEnumCTLsInStore(HCERTSTORE hCertStore, PCCTL_CONTEXT pPrev) { ctl_t *prev = pPrev ? ctl_from_ptr(pPrev) : NULL, *ret; WINECRYPT_CERTSTORE *hcs = hCertStore; TRACE("(%p, %p)\n", hCertStore, pPrev); if (!hCertStore) ret = NULL; else if (hcs->dwMagic != WINE_CRYPTCERTSTORE_MAGIC) ret = NULL; else ret = (ctl_t*)hcs->vtbl->ctls.enumContext(hcs, prev ? &prev->base : NULL); return ret ? &ret->ctx : NULL; } typedef BOOL (*CtlCompareFunc)(PCCTL_CONTEXT pCtlContext, DWORD dwType, DWORD dwFlags, const void *pvPara); static BOOL compare_ctl_any(PCCTL_CONTEXT pCtlContext, DWORD dwType, DWORD dwFlags, const void *pvPara) { return TRUE; } static BOOL compare_ctl_by_md5_hash(PCCTL_CONTEXT pCtlContext, DWORD dwType, DWORD dwFlags, const void *pvPara) { BOOL ret; BYTE hash[16]; DWORD size = sizeof(hash); ret = CertGetCTLContextProperty(pCtlContext, CERT_MD5_HASH_PROP_ID, hash, &size); if (ret) { const CRYPT_HASH_BLOB *pHash = pvPara; if (size == pHash->cbData) ret = !memcmp(pHash->pbData, hash, size); else ret = FALSE; } return ret; } static BOOL compare_ctl_by_sha1_hash(PCCTL_CONTEXT pCtlContext, DWORD dwType, DWORD dwFlags, const void *pvPara) { BOOL ret; BYTE hash[20]; DWORD size = sizeof(hash); ret = CertGetCTLContextProperty(pCtlContext, CERT_SHA1_HASH_PROP_ID, hash, &size); if (ret) { const CRYPT_HASH_BLOB *pHash = pvPara; if (size == pHash->cbData) ret = !memcmp(pHash->pbData, hash, size); else ret = FALSE; } return ret; } static BOOL compare_ctl_existing(PCCTL_CONTEXT pCtlContext, DWORD dwType, DWORD dwFlags, const void *pvPara) { BOOL ret; if (pvPara) { PCCTL_CONTEXT ctl = pvPara; if (pCtlContext->cbCtlContext == ctl->cbCtlContext) { if (ctl->cbCtlContext) ret = !memcmp(pCtlContext->pbCtlContext, ctl->pbCtlContext, ctl->cbCtlContext); else ret = TRUE; } else ret = FALSE; } else ret = FALSE; return ret; } PCCTL_CONTEXT WINAPI CertFindCTLInStore(HCERTSTORE hCertStore, DWORD dwCertEncodingType, DWORD dwFindFlags, DWORD dwFindType, const void *pvFindPara, PCCTL_CONTEXT pPrevCtlContext) { PCCTL_CONTEXT ret; CtlCompareFunc compare; TRACE("(%p, %d, %d, %d, %p, %p)\n", hCertStore, dwCertEncodingType, dwFindFlags, dwFindType, pvFindPara, pPrevCtlContext); switch (dwFindType) { case CTL_FIND_ANY: compare = compare_ctl_any; break; case CTL_FIND_SHA1_HASH: compare = compare_ctl_by_sha1_hash; break; case CTL_FIND_MD5_HASH: compare = compare_ctl_by_md5_hash; break; case CTL_FIND_EXISTING: compare = compare_ctl_existing; break; default: FIXME("find type %08x unimplemented\n", dwFindType); compare = NULL; } if (compare) { BOOL matches = FALSE; ret = pPrevCtlContext; do { ret = CertEnumCTLsInStore(hCertStore, ret); if (ret) matches = compare(ret, dwFindType, dwFindFlags, pvFindPara); } while (ret != NULL && !matches); if (!ret) SetLastError(CRYPT_E_NOT_FOUND); } else { SetLastError(CRYPT_E_NOT_FOUND); ret = NULL; } return ret; } BOOL WINAPI CertDeleteCTLFromStore(PCCTL_CONTEXT pCtlContext) { WINECRYPT_CERTSTORE *hcs; ctl_t *ctl = ctl_from_ptr(pCtlContext); BOOL ret; TRACE("(%p)\n", pCtlContext); if (!pCtlContext) return TRUE; hcs = pCtlContext->hCertStore; if (hcs->dwMagic != WINE_CRYPTCERTSTORE_MAGIC) return FALSE; ret = hcs->vtbl->ctls.delete(hcs, &ctl->base); if (ret) ret = CertFreeCTLContext(pCtlContext); return ret; } PCCTL_CONTEXT WINAPI CertCreateCTLContext(DWORD dwMsgAndCertEncodingType, const BYTE *pbCtlEncoded, DWORD cbCtlEncoded) { ctl_t *ctl = NULL; HCRYPTMSG msg; BOOL ret; BYTE *content = NULL; DWORD contentSize = 0, size; PCTL_INFO ctlInfo = NULL; TRACE("(%08x, %p, %d)\n", dwMsgAndCertEncodingType, pbCtlEncoded, cbCtlEncoded); if (GET_CERT_ENCODING_TYPE(dwMsgAndCertEncodingType) != X509_ASN_ENCODING) { SetLastError(E_INVALIDARG); return NULL; } if (!pbCtlEncoded || !cbCtlEncoded) { SetLastError(ERROR_INVALID_DATA); return NULL; } msg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0, 0, NULL, NULL); if (!msg) return NULL; ret = CryptMsgUpdate(msg, pbCtlEncoded, cbCtlEncoded, TRUE); if (!ret) { SetLastError(ERROR_INVALID_DATA); goto end; } /* Check that it's really a CTL */ ret = CryptMsgGetParam(msg, CMSG_INNER_CONTENT_TYPE_PARAM, 0, NULL, &size); if (ret) { char *innerContent = CryptMemAlloc(size); if (innerContent) { ret = CryptMsgGetParam(msg, CMSG_INNER_CONTENT_TYPE_PARAM, 0, innerContent, &size); if (ret) { if (strcmp(innerContent, szOID_CTL)) { SetLastError(ERROR_INVALID_DATA); ret = FALSE; } } CryptMemFree(innerContent); } else { SetLastError(ERROR_OUTOFMEMORY); ret = FALSE; } } if (!ret) goto end; ret = CryptMsgGetParam(msg, CMSG_CONTENT_PARAM, 0, NULL, &contentSize); if (!ret) goto end; content = CryptMemAlloc(contentSize); if (content) { ret = CryptMsgGetParam(msg, CMSG_CONTENT_PARAM, 0, content, &contentSize); if (ret) { ret = CryptDecodeObjectEx(dwMsgAndCertEncodingType, PKCS_CTL, content, contentSize, CRYPT_DECODE_ALLOC_FLAG, NULL, &ctlInfo, &size); if (ret) { ctl = (ctl_t*)Context_CreateDataContext(sizeof(CTL_CONTEXT), &ctl_vtbl, &empty_store); if (ctl) { BYTE *data = CryptMemAlloc(cbCtlEncoded); if (data) { memcpy(data, pbCtlEncoded, cbCtlEncoded); ctl->ctx.dwMsgAndCertEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; ctl->ctx.pbCtlEncoded = data; ctl->ctx.cbCtlEncoded = cbCtlEncoded; ctl->ctx.pCtlInfo = ctlInfo; ctl->ctx.hCertStore = &empty_store; ctl->ctx.hCryptMsg = msg; ctl->ctx.pbCtlContext = content; ctl->ctx.cbCtlContext = contentSize; } else { SetLastError(ERROR_OUTOFMEMORY); ret = FALSE; } } else { SetLastError(ERROR_OUTOFMEMORY); ret = FALSE; } } } } else { SetLastError(ERROR_OUTOFMEMORY); ret = FALSE; } end: if (!ret) { if(ctl) Context_Release(&ctl->base); ctl = NULL; LocalFree(ctlInfo); CryptMemFree(content); CryptMsgClose(msg); return NULL; } return &ctl->ctx; } PCCTL_CONTEXT WINAPI CertDuplicateCTLContext(PCCTL_CONTEXT pCtlContext) { TRACE("(%p)\n", pCtlContext); if (pCtlContext) Context_AddRef(&ctl_from_ptr(pCtlContext)->base); return pCtlContext; } BOOL WINAPI CertFreeCTLContext(PCCTL_CONTEXT pCTLContext) { TRACE("(%p)\n", pCTLContext); if (pCTLContext) Context_Release(&ctl_from_ptr(pCTLContext)->base); return TRUE; } DWORD WINAPI CertEnumCTLContextProperties(PCCTL_CONTEXT pCTLContext, DWORD dwPropId) { ctl_t *ctl = ctl_from_ptr(pCTLContext); DWORD ret; TRACE("(%p, %d)\n", pCTLContext, dwPropId); if (ctl->base.properties) ret = ContextPropertyList_EnumPropIDs(ctl->base.properties, dwPropId); else ret = 0; return ret; } static BOOL CTLContext_SetProperty(ctl_t *ctl, DWORD dwPropId, DWORD dwFlags, const void *pvData); static BOOL CTLContext_GetHashProp(ctl_t *ctl, DWORD dwPropId, ALG_ID algID, const BYTE *toHash, DWORD toHashLen, void *pvData, DWORD *pcbData) { BOOL ret = CryptHashCertificate(0, algID, 0, toHash, toHashLen, pvData, pcbData); if (ret && pvData) { CRYPT_DATA_BLOB blob = { *pcbData, pvData }; ret = CTLContext_SetProperty(ctl, dwPropId, 0, &blob); } return ret; } static BOOL CTLContext_GetProperty(ctl_t *ctl, DWORD dwPropId, void *pvData, DWORD *pcbData) { BOOL ret; CRYPT_DATA_BLOB blob; TRACE("(%p, %d, %p, %p)\n", ctl, dwPropId, pvData, pcbData); if (ctl->base.properties) ret = ContextPropertyList_FindProperty(ctl->base.properties, dwPropId, &blob); else ret = FALSE; if (ret) { if (!pvData) *pcbData = blob.cbData; else if (*pcbData < blob.cbData) { SetLastError(ERROR_MORE_DATA); *pcbData = blob.cbData; ret = FALSE; } else { memcpy(pvData, blob.pbData, blob.cbData); *pcbData = blob.cbData; } } else { /* Implicit properties */ switch (dwPropId) { case CERT_SHA1_HASH_PROP_ID: ret = CTLContext_GetHashProp(ctl, dwPropId, CALG_SHA1, ctl->ctx.pbCtlEncoded, ctl->ctx.cbCtlEncoded, pvData, pcbData); break; case CERT_MD5_HASH_PROP_ID: ret = CTLContext_GetHashProp(ctl, dwPropId, CALG_MD5, ctl->ctx.pbCtlEncoded, ctl->ctx.cbCtlEncoded, pvData, pcbData); break; default: SetLastError(CRYPT_E_NOT_FOUND); } } TRACE("returning %d\n", ret); return ret; } BOOL WINAPI CertGetCTLContextProperty(PCCTL_CONTEXT pCTLContext, DWORD dwPropId, void *pvData, DWORD *pcbData) { BOOL ret; TRACE("(%p, %d, %p, %p)\n", pCTLContext, dwPropId, pvData, pcbData); switch (dwPropId) { case 0: case CERT_CERT_PROP_ID: case CERT_CRL_PROP_ID: case CERT_CTL_PROP_ID: SetLastError(E_INVALIDARG); ret = FALSE; break; case CERT_ACCESS_STATE_PROP_ID: if (!pvData) { *pcbData = sizeof(DWORD); ret = TRUE; } else if (*pcbData < sizeof(DWORD)) { SetLastError(ERROR_MORE_DATA); *pcbData = sizeof(DWORD); ret = FALSE; } else { ret = CertGetStoreProperty(pCTLContext->hCertStore, dwPropId, pvData, pcbData); } break; default: ret = CTLContext_GetProperty(ctl_from_ptr(pCTLContext), dwPropId, pvData, pcbData); } return ret; } static BOOL CTLContext_SetProperty(ctl_t *ctl, DWORD dwPropId, DWORD dwFlags, const void *pvData) { BOOL ret; TRACE("(%p, %d, %08x, %p)\n", ctl, dwPropId, dwFlags, pvData); if (!ctl->base.properties) ret = FALSE; else if (!pvData) { ContextPropertyList_RemoveProperty(ctl->base.properties, dwPropId); ret = TRUE; } else { switch (dwPropId) { case CERT_AUTO_ENROLL_PROP_ID: case CERT_CTL_USAGE_PROP_ID: /* same as CERT_ENHKEY_USAGE_PROP_ID */ case CERT_DESCRIPTION_PROP_ID: case CERT_FRIENDLY_NAME_PROP_ID: case CERT_HASH_PROP_ID: case CERT_KEY_IDENTIFIER_PROP_ID: case CERT_MD5_HASH_PROP_ID: case CERT_NEXT_UPDATE_LOCATION_PROP_ID: case CERT_PUBKEY_ALG_PARA_PROP_ID: case CERT_PVK_FILE_PROP_ID: case CERT_SIGNATURE_HASH_PROP_ID: case CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID: case CERT_SUBJECT_NAME_MD5_HASH_PROP_ID: case CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID: case CERT_ENROLLMENT_PROP_ID: case CERT_CROSS_CERT_DIST_POINTS_PROP_ID: case CERT_RENEWAL_PROP_ID: { PCRYPT_DATA_BLOB blob = (PCRYPT_DATA_BLOB)pvData; ret = ContextPropertyList_SetProperty(ctl->base.properties, dwPropId, blob->pbData, blob->cbData); break; } case CERT_DATE_STAMP_PROP_ID: ret = ContextPropertyList_SetProperty(ctl->base.properties, dwPropId, pvData, sizeof(FILETIME)); break; default: FIXME("%d: stub\n", dwPropId); ret = FALSE; } } TRACE("returning %d\n", ret); return ret; } BOOL WINAPI CertSetCTLContextProperty(PCCTL_CONTEXT pCTLContext, DWORD dwPropId, DWORD dwFlags, const void *pvData) { BOOL ret; TRACE("(%p, %d, %08x, %p)\n", pCTLContext, dwPropId, dwFlags, pvData); /* Handle special cases for "read-only"/invalid prop IDs. Windows just * crashes on most of these, I'll be safer. */ switch (dwPropId) { case 0: case CERT_ACCESS_STATE_PROP_ID: case CERT_CERT_PROP_ID: case CERT_CRL_PROP_ID: case CERT_CTL_PROP_ID: SetLastError(E_INVALIDARG); return FALSE; } ret = CTLContext_SetProperty(ctl_from_ptr(pCTLContext), dwPropId, dwFlags, pvData); TRACE("returning %d\n", ret); return ret; }