/* ** capistm.cpp ** ** Purpose: ** Implementation of a class to wrap around CAPI functionality ** ** History ** 1/26/98; (brucek) triple wrap support ** 2/07/97: (t-erikne) multipart/signed ** 1/06/97: (t-erikne) Moved into MimeOLE ** 11/14/96: (t-erikne) CAPI Post-SDR work ** 8/27/96: (t-erikne) Created. ** ** Copyright (C) Microsoft Corp. 1996-1998. */ /////////////////////////////////////////////////////////////////////////// // // Depends on // #include "pch.hxx" #include #include "olealloc.h" #include "containx.h" #include "smime.h" #include "capistm.h" #include "mimeapi.h" #include "inetconv.h" #include #ifndef MAC #include #endif // !MAC #include #include "strconst.h" #include "smimepol.h" BOOL FHideMsgWithDifferentLabels(); enum ECertErrorProcessLabel { CertErrorProcessLabelAnyway = 0, CertErrorProcessLabelGrant = 1, CertErrorProcessLabelDeny = 2 }; DWORD DwProcessLabelWithCertError(); HRESULT HrCheckLabelAccess(const DWORD dwFlags, const HWND hwnd, PSMIME_SECURITY_LABEL plabel, const PCCERT_CONTEXT pccertDecrypt, const PCCERT_CONTEXT pccertSigner, const HCERTSTORE hcertstor); #ifdef MAC #undef CertOpenStore EXTERN_C WINCRYPT32API HCERTSTORE WINAPI MacCertOpenStore(LPCSTR lpszStoreProvider, DWORD dwEncodingType, HCRYPTPROV hCryptProv, DWORD dwFlags, const void *pvPara); #define CertOpenStore MacCertOpenStore #endif // MAC // from dllmain.h extern DWORD g_dwSysPageSize; extern CMimeAllocator * g_pMoleAlloc; extern ULONG DllAddRef(void); extern ULONG DllRelease(void); extern void DebugDumpStreamToFile(LPSTREAM pstm, LPSTR lpszFile); // From smime.cpp extern HRESULT HrGetLastError(void); extern BOOL FIsMsasn1Loaded(); #ifdef WIN16 #define CRYPT_ACQUIRE_CONTEXT CryptAcquireContextA #else #define CRYPT_ACQUIRE_CONTEXT CryptAcquireContextW #endif /////////////////////////////////////////////////////////////////////////// // // defines // #define THIS_AS_UNK ((IUnknown *)(IStream *)this) #define CS_E_CANT_DECRYPT MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 0x2414) #define CS_E_MSG_INVALID MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 0x2415) const int CbCacheBufferSize = 4 * 1024; /////////////////////////////////////////////////////////////////////////// // // inlines // static INLINE void ReleaseCert(PCCERT_CONTEXT pc) { if (pc) CertFreeCertificateContext(pc); } // Streaming state diagram // // SF GT SD-FW-TN-SO-SF // | | /[encryption] // SO QT------------------- // [op encode] \ / [opaque decode] \[signed] // ------NB------ (FW-TN)-SO-SF // [det encode] / \ [detached decode] // DO DO // | \ // SF DF // | // SO // | // SF // // // New state diagram: // encrypt // SD--SO // opaque encode opaque decode / // SO QT--------------------[QTF]--- // \ / \ // ---NB----- SO signed // / \ // SO---DO DO ------------------ SO // detach encode detach decode // // #ifndef WIN16 enum CSstate { STREAM_NOT_BEGUN, STREAM_QUESTION_TIME, STREAM_QUESTION_TIME_FINAL, STREAM_SETUP_DECRYPT, STREAM_DETACHED_OCCURING, STREAM_OCCURING, // must be +1 of DF STREAM_ERROR, STREAM_GOTTYPE, CSTM_FIRST_WRITE = 32, CSTM_TEST_NESTING, CSTM_STREAMING, CSTM_STREAMING_DONE, CSTM_GOTTYPE, }; #endif // !WIN16 // low word is public. see .h file. #define CSTM_DECODE 0x00010000 #define CSTM_DONTRELEASEPROV 0x00020000 #define CSTM_RECURSED 0x00040000 #define CSTM_HAVECR 0x10000000 #define CSTM_HAVEEOL 0x20000000 static const char s_cszMy[] = "My"; static const char s_cszWABCertStore[] = "AddressBook"; static const char s_cszCA[] = "CA"; static const char s_cszMimeHeader[] = "Content-Type: application/x-pkcs7-mime" "; name=smime.p7m; smime-type="; static const char s_cszMimeHeader2[] = "Content-Disposition: attachment; " "filename=smime.p7m"; static const char s_cszOIDMimeHeader1[] = "Content-Type: oid/"; static const char s_cszOIDMimeHeader2[] = "\nContent-Transfer-Encoding: binary\n\n"; /////////////////////////////////////////////////////////////////////////// // // static prototypes // #if 0 #define IV_LENGTH 8 static BOOL _GetIV(BYTE rgbIV[IV_LENGTH]); static PBYTE _PVEncodeObject( LPCSTR lpszStructType, const void *pvStructInfo, DWORD *pcbEncoded); #endif static HRESULT _InitEncodedCert(IN HCERTSTORE hcertstor, OUT PCERT_BLOB * rgblobCert, OUT DWORD * pcCerts, OUT PCRL_BLOB * rgblobCRL, OUT DWORD * pcCrl); static HRESULT _InitEncodedCertIncludingSigners(IN HCERTSTORE hcertstor, DWORD cSigners, SignerData rgSigners[], PCERT_BLOB * prgblobCerts, DWORD * pcCerts, PCRL_BLOB * prgblobCrls, DWORD * pcCrl); static void _SMimeCapsFromHMsg(HCRYPTMSG, DWORD id, LPBYTE * ppb, DWORD * pcb); // ---------------------------- UTILITY FUNCTIONS -------------------------- HRESULT GetParameters(PCCERT_CONTEXT pccert, HCERTSTORE hstoreMsg, HCERTSTORE hstoreAll) { CRYPT_DATA_BLOB blob; DWORD dw; HRESULT hr = CRYPT_E_MISSING_PUBKEY_PARA; PCCERT_CONTEXT pccertX; // // Start by looking for the issuer certificate on your own. All that // matters is that we find a certificate which claims to be the issuer // and has parameters -- they will need to verify the parameters are // correct at a later date. // pccertX = NULL; while (hstoreMsg != NULL) { // // Find certificates by matching issuers -- ok for now as PKIX requires // all issuers to have DNs // dw = CERT_STORE_SIGNATURE_FLAG; pccertX = CertGetIssuerCertificateFromStore(hstoreMsg, pccert, pccertX, &dw); if (pccertX == NULL) { if (::GetLastError() == CRYPT_E_SELF_SIGNED) { return S_OK; } break; } // // Only accept the item if we manage a signature validation on it. // if ((dw & CERT_STORE_SIGNATURE_FLAG)) { // // We can't verify the signature, so get the issuers paramters and try again // hr = GetParameters(pccertX, hstoreMsg, hstoreAll); if (FAILED(hr)) { continue; } // // The issuing cert has parameters, try the signature check again againist it. // dw = CERT_STORE_SIGNATURE_FLAG; if (CertVerifySubjectCertificateContext(pccert, pccertX, &dw) && (dw == 0)) { break; } hr = CRYPT_E_MISSING_PUBKEY_PARA; } else { if (pccertX->pCertInfo->SubjectPublicKeyInfo.Algorithm.Parameters.cbData != 0) { hr = 0; break; } // // If we found one but it does not have the parameters, it must be // inheriting from it's issuer as well. // dw = CERT_STORE_SIGNATURE_FLAG; if (CertVerifySubjectCertificateContext(pccert, pccertX, &dw) && (dw == 0)) { hr = 0; break; } } } // // If we still do not have a certificate, then search all of the system stores // for an isuer. // if (pccertX == NULL) { while (hstoreAll != NULL) { // // Find certificates by matching issuers -- ok for now as PKIX requires // all issuers to have DNs // dw = CERT_STORE_SIGNATURE_FLAG; pccertX = CertGetIssuerCertificateFromStore(hstoreAll, pccert, pccertX, &dw); if (pccertX == NULL) { if (::GetLastError() == CRYPT_E_SELF_SIGNED) { return S_OK; } break; } // // Only accept the item if we manage a signature validation on it. // if ((dw & CERT_STORE_SIGNATURE_FLAG)) { // // We can't verify the signature, so get the issuers paramters and try again // hr = GetParameters(pccertX, hstoreMsg, hstoreAll); if (FAILED(hr)) { continue; } // // The issuing cert has parameters, try the signature check again againist it. // dw = CERT_STORE_SIGNATURE_FLAG; if (CertVerifySubjectCertificateContext(pccert, pccertX, &dw) && (dw == 0)) { break; } hr = CRYPT_E_MISSING_PUBKEY_PARA; } else { if (pccertX->pCertInfo->SubjectPublicKeyInfo.Algorithm.Parameters.cbData != 0) { hr = 0; break; } // // If we found one but it does not have the parameters, it must be inheriting // from it's issuer as well. // dw = CERT_STORE_SIGNATURE_FLAG; if (CertVerifySubjectCertificateContext(pccert, pccertX, &dw) && (dw == 0)) { hr = 0; break; } } } } #if 0 // // We found a certificate, set the parameters onto the context so that we // can successfully manage to validate the signature // if (pccertX != NULL) { CRYPT_DATA_BLOB * pdata = NULL; hr = HrGetCertificateParam(pccert, CERT_PUBKEY_ALG_PARA_PROP_ID, (LPVOID *) &pdata, NULL); if (FAILED(hr)) { CertFreeCertificateContext(pccertX); return hr; } CertSetCertificateContextProperty(pccert, CERT_PUBKEY_ALG_PARA_PROP_ID, 0, pdata); ReleaseMem(pdata); CertFreeCertificateContext(pccertX); return S_OK; } // // if we still have not found anything, then let the caller have a chance // to tell us what the parameters ought to be. // if (m_pSmimeCallback != NULL) { hr = m_pSmimeCallback->GetParameters(pSignerCert, NULL, &blob.cbData, &blob.pbData); if (SUCCEEDED(hr)) { if (!CertSetCertificateContextProperty(pccert, CERT_PUBKEY_ALG_PARA_PROP_ID, 0, &blob) { hr = HrGetLastError(); } } if (pb != NULL) { LocalFree(blob.pbData); } if (SUCCEEDED(hr)) { goto retry; } } #endif // 0 return hr; } //************************************************************************* // CCAPIStm //************************************************************************* /////////////////////////////////////////////////////////////////////////// // // ctor/dtor // /*************************************************************************** Name : constructor Purpose : Parameters: lpstmOut -> Output stream or NULL psld -> SECURITY_LAYER_DATA or NULL. If NULL, one will be created. Returns : void Comment : ***************************************************************************/ CCAPIStm::CCAPIStm(LPSTREAM lpstmOut) : m_pstmOut(lpstmOut), m_cRef(1) { DOUT("CCAPIStm::constructor() %#x -> %d", this, m_cRef); if (m_pstmOut) m_pstmOut->AddRef(); m_hProv = NULL; m_hMsg = NULL; // m_buffer = NULL; m_csStatus = STREAM_NOT_BEGUN; m_csStream = CSTM_FIRST_WRITE; m_rgStores = NULL; m_cStores = 0; m_pUserCertDecrypt = NULL; m_pCapiInner = NULL; m_pConverter = NULL; m_psldData = NULL; m_pattrAuth = NULL; #if defined(DEBUG) && !defined(MAC) { char szFileName[MAX_PATH + 1]; m_pstmDebugFile = NULL; // Create a debug output file name based on the CAPIStm pointer wnsprintfA(szFileName, ARRAYSIZE(szFileName), "c:\\capidump%08x.txt", this); OpenFileStream(szFileName, CREATE_ALWAYS, GENERIC_WRITE, &m_pstmDebugFile); } #endif m_hwnd = NULL; m_pSmimeCallback = NULL; m_dwFlagsSEF = 0; m_pwszKeyPrompt = NULL; m_pbBuffer = NULL; m_cbBuffer = 0; // m_dwFlags set in HrInitialize // m_cbBeginWrite initialized before use // m_cbBufUsed handled in the Begin* functions // m_cbBufAlloc handled in the Begin* functions } CCAPIStm::~CCAPIStm() { DOUT("CCAPIStm::destructor() %#x -> %d", this, m_cRef); if (m_hMsg) { CryptMsgClose(m_hMsg); } if (m_hProv) { CryptReleaseContext(m_hProv, 0); } m_hProv = NULL; ReleaseObj(m_pCapiInner); ReleaseObj(m_pstmOut); ReleaseObj(m_pConverter); if (m_pattrAuth) { MemFree(m_pattrAuth); } if (m_pUserCertDecrypt) { CertFreeCertificateContext(m_pUserCertDecrypt); } if (m_cStores) { Assert(m_rgStores); for (DWORD i=0; iRelease(); } if (m_pbBuffer != NULL) { MemFree(m_pbBuffer); } SafeMemFree(m_pwszKeyPrompt); ReleaseObj(m_pSmimeCallback); } /////////////////////////////////////////////////////////////////////////// // // IUnknown methods // STDMETHODIMP CCAPIStm::QueryInterface(REFIID riid, LPVOID *ppv) { if (!ppv) { return TrapError(E_INVALIDARG); } // Find IID if (IID_IUnknown == riid) { *ppv = THIS_AS_UNK; } else if (IID_IStream == riid) { *ppv = (IStream *)this; } else { *ppv = NULL; return E_NOINTERFACE; } ((IUnknown *)*ppv)->AddRef(); return S_OK; } STDMETHODIMP_(ULONG) CCAPIStm::AddRef(void) { DOUT("CCAPIStm::AddRef() %#x -> %d", this, m_cRef+1); InterlockedIncrement((LPLONG)&m_cRef); return m_cRef; } STDMETHODIMP_(ULONG) CCAPIStm::Release(void) { DOUT("CCAPIStm::Release() %#x -> %d", this, m_cRef-1); if (0 == InterlockedDecrement((LPLONG)&m_cRef)) { delete this; return 0; } return m_cRef; } /////////////////////////////////////////////////////////////////////////// // // IStream methods // STDMETHODIMP CCAPIStm::Seek(LARGE_INTEGER, DWORD, ULARGE_INTEGER *plibNewPosition) { if (!plibNewPosition) { return E_POINTER; } else { plibNewPosition->HighPart = 0; plibNewPosition->LowPart = 0; } return S_OK; } //// CCAPIStm::Write // // Description: // This function is called with the original message as the buffer // being written into this stream object. We then make the appropriate // calls into the NT Crypto system in order to encrypt/decrypt the message. // // Part of what this function needs to do is to interact with the // Crypto system in order to cause decryption of message to occur. #ifndef WIN16 STDMETHODIMP CCAPIStm::Write(const void *pv, ULONG cb, ULONG *pcbActual) #else STDMETHODIMP CCAPIStm::Write(const void HUGEP *pv, ULONG cb, ULONG *pcbActual) #endif // !WIN16 { HRESULT hr; // // Reset the return arg just incase // if (pcbActual != NULL) { *pcbActual = 0; } // // If the CMS object is not still open, then we are dead and need to return an error. // if (!m_hMsg) { hr = CAPISTM_E_MSG_CLOSED; goto exit; } // // Are we in the correct state to take anything. // switch (m_csStatus) { case STREAM_NOT_BEGUN: Assert(FALSE); // Should never get here hr = CAPISTM_E_NOT_BEGUN; goto exit; case STREAM_DETACHED_OCCURING: case STREAM_QUESTION_TIME: case STREAM_SETUP_DECRYPT: case STREAM_OCCURING: break; case STREAM_ERROR: Assert(FALSE); // Should never get here hr = CAPISTM_E_OVERDONE; goto exit; case STREAM_GOTTYPE: hr = CAPISTM_E_GOTTYPE; goto exit; // We should go from QT to QTF in this function, and never come back // until we have changed the state again. default: Assert(FALSE); case STREAM_QUESTION_TIME_FINAL: hr = E_UNEXPECTED; goto exit; } #if defined(DEBUG) && !defined(MAC) // // Flush the input buffer to disk so that we can debug it later if necessary // if (!m_pCapiInner && m_pstmDebugFile) { m_pstmDebugFile->Write((BYTE *)pv, cb, NULL); } #endif // // We need to start buffering data to make our messages shorter. The output // from the save code comes in one and two byte chucks often, we need to put // the data out in larger blocks // if (m_pbBuffer != NULL) { // // If we would overflow the buffer, then dump the cached buffer out // if (m_cbBuffer + cb > CbCacheBufferSize) { if (!CryptMsgUpdate(m_hMsg, m_pbBuffer, m_cbBuffer, FALSE)) { // CryptMsgUpdate failed Assert(S_OK != HrGetLastError()); hr = HrGetLastError(); if (FAILED(hr)) { m_csStatus = STREAM_ERROR; } goto exit; } m_cbBuffer = 0; } // // If this buffer will over flow, then dump out just that item. Otherwise // we are just going to cache the buffer. // if (cb >= CbCacheBufferSize) { if (!CryptMsgUpdate(m_hMsg, (BYTE *) pv, cb, FALSE)) { // CryptMsgUpdate failed Assert(S_OK != HrGetLastError()); hr = HrGetLastError(); if (FAILED(hr)) { m_csStatus = STREAM_ERROR; } goto exit; } } else { memcpy(m_pbBuffer + m_cbBuffer, pv, cb); m_cbBuffer += cb; } if (pcbActual != NULL) { *pcbActual = cb; } // // The only time we should be here is when we are creating a new CMS object // and thus all of the code below this is not relavent as we don't ever // need to ask questions about what type of this message. // hr = S_OK; goto exit; } else { // // Push the input buffer into the Crypto system. On failures from the // system we need to propigate the correct error state into our structure // and into the return value. // if (!CryptMsgUpdate(m_hMsg, (BYTE *)pv, cb, FALSE)) { // CryptMsgUpdate failed Assert(S_OK != HrGetLastError()); hr = HrGetLastError(); if (FAILED(hr)) { m_csStatus = STREAM_ERROR; } goto exit; } } // // Since the CryptMsgUpdate call succeeded, return // a nice out param (specifically that we have consumed all of the passed // in bytes) // if (pcbActual) { *pcbActual = cb; } hr = S_OK; // // If we are in a state where we need to ask questions about the message, // then proceed to do so. // if ((STREAM_QUESTION_TIME == m_csStatus) || (STREAM_QUESTION_TIME_FINAL == m_csStatus)) { DWORD cbDWORD, dwMsgType; // We should never be asking questions if encoding. Assert(m_dwFlagsStm & CSTM_DECODE); // // Find out what security services have been placed onto this // message object (if any). If not enough bytes have been processed // to find out what the encoding of the message is, then return // success so we can get more bytes and get the question answered // at a later date. // cbDWORD = sizeof(DWORD); if (!CryptMsgGetParam(m_hMsg, CMSG_TYPE_PARAM, 0, &dwMsgType, &cbDWORD)) { hr = HrGetLastError(); Assert (S_OK != hr); if (CRYPT_E_STREAM_MSG_NOT_READY == hr) { hr = S_OK; } goto exit; } // Since we are here, we must have a V1 type S/MIME message Assert(m_psldData); m_psldData->m_dwMsgEnhancement = MST_CLASS_SMIME_V1; hr = S_OK; // // Set the correct flags based on the message type of the object we are // decoding. // switch (dwMsgType) { case CMSG_ENVELOPED: m_psldData->m_dwMsgEnhancement |= MST_THIS_ENCRYPT; break; case CMSG_SIGNED: m_psldData->m_dwMsgEnhancement |= MST_THIS_BLOBSIGN; break; case CMSG_SIGNED_AND_ENVELOPED: m_psldData->m_dwMsgEnhancement |= MST_THIS_BLOBSIGN | MST_THIS_ENCRYPT; break; default: // K this is a little rude. not my iface error. hr = MIME_E_SECURITY_BADSECURETYPE; // just return the CAPI type if we don't recognize m_psldData->m_dwMsgEnhancement = dwMsgType; break; } // // If all we are asking for is a type and we don't have any other errors, // mark the fact that we got the type and return that fact as the // error (to prevent futher buffers being written into us.) // if (CSTM_TYPE_ONLY & m_dwFlagsStm) { CSSDOUT("Got Type on typeonly call."); CSSDOUT("You will now see 80041417 failures; they're okay."); m_csStatus = STREAM_GOTTYPE; if (SUCCEEDED(hr)) { hr = CAPISTM_E_GOTTYPE; } goto exit; } // // Change the object state based on the message type. If we need to // setup a decryption, then we need to mark the state for that. // If we are just signing, then we can just let the rest of the // streaming occur. // if (CMSG_ENVELOPED == dwMsgType) { m_csStatus = STREAM_SETUP_DECRYPT; } else { m_csStatus = STREAM_OCCURING; } } // // If we need to set-up the message for decryption, then do so at this // point. // Assert(SUCCEEDED(hr)); if (STREAM_SETUP_DECRYPT == m_csStatus) { // Can't decrypt detached messages Assert(!(m_dwFlagsStm & CSTM_DETACHED)); // We are now streaming the data out, on the assumption that the // decryption stats. m_csStatus = STREAM_OCCURING; hr = HandleEnveloped(); // If we failed to decrypt, then re-map some errors and change the // state back in the event that not all of the lock boxes have // been seen yet. if (FAILED(hr)) { if (CRYPT_E_STREAM_MSG_NOT_READY == hr) { m_csStatus = STREAM_SETUP_DECRYPT; hr = S_OK; } else if (CS_E_CANT_DECRYPT == hr) { hr = MIME_E_SECURITY_CANTDECRYPT; // m_csStatus = STREAM_FINAL; // M00QUEST } else { if (CS_E_MSG_INVALID == hr) { hr = MIME_E_SECURITY_CANTDECRYPT; } m_csStatus = STREAM_ERROR; } goto exit; } } hr = S_OK; exit: #ifdef DEBUG if (CAPISTM_E_GOTTYPE != hr) { return TrapError(hr); } else { return hr; // don't spew this } #else return hr; #endif } /////////////////////////////////////////////////////////////////////////// // // CCAPIStm public methods // /* HrInnerInitialize: ** ** Purpose: ** the standard "my constructor can't return errors" function ** Takes: ** dwFlagsSEF - Control Flags ** hwndParent - modal UI parents to this ** dwFlagsStm - see capistm.h ** Returns: ** OLE_E_INVALIDHWND if you give me a bad window ** MIME_E_SECURITY_NOOP if MST_NONE is the current psi type ** Notes: ** dwFlags is currently 0 for encode. do it this way. */ HRESULT CCAPIStm::HrInnerInitialize(DWORD dwFlagsSEF, const HWND hwndParent, DWORD dwFlagsStm, IMimeSecurityCallback * pCallback, PSECURITY_LAYER_DATA psld) { HRESULT hr = S_OK; // // Save the security layer data // if (psld) { psld->AddRef(); m_psldData = psld; } else { IF_NULLEXIT(m_psldData = new(SECURITY_LAYER_DATA)); } // // Save the flags // m_dwFlagsSEF = dwFlagsSEF; m_dwFlagsStm = dwFlagsStm; if (pCallback != NULL) { m_pSmimeCallback = pCallback; pCallback->AddRef(); } // // Make sure that if we have a window, it is a real window. // IF_TRUEEXIT((hwndParent && !IsWindow(hwndParent)), OLE_E_INVALIDHWND); // // Shove the hwnd into any crypto provider we openned up. // CryptSetProvParam(NULL, PP_CLIENT_HWND, (BYTE *)&hwndParent, 0); m_hwnd = hwndParent; exit: return hr; } /* HrInitialize: ** ** Purpose: ** the standard "my constructor can't return errors" function ** Takes: ** dwFlagsSEF - Control Flags ** hwndParent - modal UI parents to this ** fEncode - trivial ** psi - message state information. see smime.h ** dwFlagsStm - see capistm.h ** Returns: ** OLE_E_INVALIDHWND if you give me a bad window ** MIME_E_SECURITY_NOOP if MST_NONE is the current psi type ** Notes: ** dwFlags is currently 0 for encode. do it this way. */ HRESULT CCAPIStm::HrInitialize(DWORD dwFlagsSEF, const HWND hwndParent, const BOOL fEncode, SMIMEINFO *const psi, DWORD dwFlagsStm, IMimeSecurityCallback * pCallback, PSECURITY_LAYER_DATA psld) { HRESULT hr; // do the initialization common to all capi stream objects. CHECKHR(hr = HrInnerInitialize(dwFlagsSEF, hwndParent, dwFlagsStm, pCallback, psld)); if (fEncode) { hr = BeginEncodeStreaming(psi); } else { hr = BeginDecodeStreaming(psi); } exit: return TrapError(hr); } /* EndStreaming: ** ** Purpose: ** Push CAPI's message state forward a notch ** Returns: ** HRESULT */ HRESULT CCAPIStm::EndStreaming() { DWORD dwMsgEnhancement = m_psldData->m_dwMsgEnhancement; HRESULT hr = S_OK; PCMSG_ATTR pUnprotectedAttrs = NULL; Assert(m_hMsg); // If we are crurent in an error state then return if ((STREAM_ERROR == m_csStatus) || STREAM_GOTTYPE == m_csStatus) { goto exit; } // // If we are decoding -- and we are doing a detached message we need // to jump from the sign object to the real body here. // if ((CSTM_DECODE & m_dwFlagsStm) && (STREAM_DETACHED_OCCURING == m_csStatus)) { Assert(m_csStream == CSTM_STREAMING_DONE); // client has finished giving us the signature block m_csStatus = STREAM_OCCURING; m_csStream = CSTM_STREAMING; m_psldData->m_dwMsgEnhancement = MST_THIS_SIGN; CSSDOUT("Signature streaming finished."); if (! CryptMsgUpdate(m_hMsg, m_pbBuffer, m_cbBuffer, TRUE)) { if ((hr = HrGetLastError()) == 0x80070000) { // CAPI sometimes doesn't SetLastError hr = 0x80070000 | ERROR_ACCESS_DENIED; } } m_cbBuffer = 0; goto exit; } if (! CryptMsgUpdate(m_hMsg, m_pbBuffer, m_cbBuffer, TRUE)) { if ((hr = HrGetLastError()) == 0x80070000) { // CAPI sometimes doesn't SetLastError hr = 0x80070000 | ERROR_ACCESS_DENIED; } goto exit; } m_cbBuffer = 0; if (m_dwFlagsStm & CSTM_DETACHED) { m_csStatus = STREAM_OCCURING; } // // do final streaming and verification // if (CSTM_DECODE & m_dwFlagsStm) { if (MST_THIS_SIGN & dwMsgEnhancement) { hr = VerifySignedMessage(); if (FAILED(hr)) { goto exit; } } else { Assert(STREAM_OCCURING == m_csStatus); Assert(CSTM_STREAMING_DONE == m_csStream); if (g_FSupportV3 && (MST_THIS_ENCRYPT & dwMsgEnhancement)) { BOOL f; DWORD cbData = 0; LPBYTE pb = NULL; f = CryptMsgGetParam(m_hMsg, CMSG_UNPROTECTED_ATTR_PARAM, 0, NULL, &cbData); if (!f) { // Probably, message doesn't have a CMSG_UNPROTECTED_ATTR_PARAM hr = HrGetLastError(); if(hr != CRYPT_E_ATTRIBUTES_MISSING) goto exit; else { hr = S_OK; cbData = 0; } } if (cbData != 0) { if (!MemAlloc((LPVOID *) &pUnprotectedAttrs, cbData)) { hr = E_OUTOFMEMORY; goto exit; } f = CryptMsgGetParam(m_hMsg, CMSG_UNPROTECTED_ATTR_PARAM, 0, pUnprotectedAttrs, &cbData); Assert(f); if (!CryptEncodeObjectEx(X509_ASN_ENCODING, szOID_Microsoft_Attribute_Sequence, pUnprotectedAttrs, CRYPT_ENCODE_ALLOC_FLAG, &CryptEncodeAlloc, &pb, &cbData)) { hr = HrGetLastError(); goto exit; } m_psldData->m_blobUnprotectAttrs.cbData = cbData; m_psldData->m_blobUnprotectAttrs.pbData = pb; } } } } // // fill in some more of the data structure // if ((CSTM_DECODE & m_dwFlagsStm) && (dwMsgEnhancement & MST_THIS_ENCRYPT)) { _SMimeCapsFromHMsg(m_hMsg, CMSG_ENVELOPE_ALGORITHM_PARAM, &m_psldData->m_blobDecAlg.pBlobData, &m_psldData->m_blobDecAlg.cbSize); } if (m_pCapiInner) { hr = m_pCapiInner->EndStreaming(); } exit: SafeMemFree(pUnprotectedAttrs); if (hr == ERROR_ACCESS_DENIED) { hr = E_ACCESSDENIED; // convert CAPI error to OLE HRESULT } return(hr); } PSECURITY_LAYER_DATA CCAPIStm::GetSecurityLayerData() const { if (m_psldData) { m_psldData->AddRef(); } return(m_psldData); } /////////////////////////////////////////////////////////////////////////// // // Implementation methods // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // // Encode / Decode // HRESULT CCAPIStm::BeginEncodeStreaming(SMIMEINFO *const psi) { DWORD cb; DWORD cbData; DWORD cCrls = 0; DWORD cCerts = 0; DWORD cSigners = 0; HRESULT hr; DWORD i; DWORD dwMsgType; DWORD dwFlags = 0; PCRYPT_KEY_PROV_INFO pKPI; CMSG_STREAM_INFO cmsi; DWORD dwPsiType; DWORD iSigner; PCRYPT_ATTRIBUTES pattrsUnprot = NULL; PCRYPT_ATTRIBUTES * rgpattrAuth = NULL; PCRYPT_ATTRIBUTES * rgpattrUnauth = NULL; #ifndef SMIME_V3 PCRYPT_SMIME_CAPABILITIES pcaps = NULL; #endif // SMIME_V3 CMSG_RC2_AUX_INFO rc2Aux; CRL_BLOB* rgCrlBlob; PCRYPT_SMIME_CAPABILITIES * rgpcaps = NULL; CMSG_SIGNER_ENCODE_INFO * rgSigner; // #ifndef _WIN64 union { struct { // #endif // anything that comes first must be common (in size) // to both structures CERT_INFO** rgpCertInfo; CMSG_ENVELOPED_ENCODE_INFO ceei; // #ifndef _WIN64 }; struct { // #endif CERT_BLOB* rgCertBlob; CMSG_SIGNED_ENCODE_INFO csei; // #ifndef _WIN64 }; }; // #endif //////////// // can only return from here down m_csStatus = STREAM_ERROR; rgSigner = NULL; if (!psi) { return E_POINTER; } // // Get the security operations to be performed on this body layer. // we only care about the current body properties so mask out // other layers. // If we don't have any security to perform, then get out of here // dwPsiType = m_psldData->m_dwMsgEnhancement & MST_THIS_MASK; if (MST_NONE == dwPsiType) { AssertSz(dwPsiType != MST_NONE, "Why are we here if we have no security to apply?"); return TrapError(MIME_E_SECURITY_NOOP); } // // detached is the only allowed user settable flag // if ((m_dwFlagsStm & CSTM_ALLFLAGS) & ~CSTM_DETACHED) { return TrapError(E_INVALIDARG); } rgCertBlob = NULL; rgCrlBlob = NULL; pKPI = NULL; //////////// // can goto end from here down // // We should never be in a situation where we are going to both encrypt and sign // a message. // AssertSz((!!(dwPsiType & MST_THIS_SIGN) + !!(dwPsiType & MST_THIS_ENCRYPT)) == 1, "Encrypt and Sign Same Layer is not legal"); if (dwPsiType & MST_THIS_SIGN) { dwMsgType = CMSG_SIGNED; if (!(m_psldData->m_dwMsgEnhancement & MST_BLOB_FLAG)) { dwFlags |= CMSG_DETACHED_FLAG; } cSigners = m_psldData->m_cSigners; if (m_psldData->m_hcertstor != NULL) { hr = _InitEncodedCertIncludingSigners(m_psldData->m_hcertstor, cSigners, m_psldData->m_rgSigners, &rgCertBlob, &cCerts, &rgCrlBlob, &cCrls); if (FAILED(hr)) { goto exit; } } cb = sizeof(CMSG_SIGNER_ENCODE_INFO) * cSigners; if (!MemAlloc((LPVOID *) &rgSigner, cb)) { hr = E_OUTOFMEMORY; goto exit; } memset(rgSigner, 0, cb); cb = sizeof(PCRYPT_SMIME_CAPABILITIES) * cSigners; if (!MemAlloc((LPVOID *) &rgpcaps, cb)) { hr = E_OUTOFMEMORY; goto exit; } memset(rgpcaps, 0, cb); if (!MemAlloc((LPVOID *) &rgpattrAuth, cSigners*sizeof(PCRYPT_ATTRIBUTES))) { hr = E_OUTOFMEMORY; goto exit; } memset(rgpattrAuth, 0, cSigners*sizeof(PCRYPT_ATTRIBUTES)); if (!MemAlloc((LPVOID *) &rgpattrUnauth, cSigners*sizeof(PCRYPT_ATTRIBUTES))) { hr = E_OUTOFMEMORY; goto exit; } memset(rgpattrUnauth, 0, cSigners*sizeof(PCRYPT_ATTRIBUTES)); for (iSigner=0; iSignerm_rgSigners[iSigner].blobHashAlg.pBlobData, m_psldData->m_rgSigners[iSigner].blobHashAlg.cbSize, PKCS_SMIME_CAPABILITIES, 0, &cbData, (LPVOID *)&rgpcaps[iSigner]); if (FAILED(hr)) { goto exit; } // MOOBUG -- MEMORY LEAK ON PCAPS!!!! Assert(rgpcaps[iSigner] != NULL); Assert(rgpcaps[iSigner]->cCapability == 1); rgSigner[iSigner].HashAlgorithm.pszObjId = rgpcaps[iSigner]->rgCapability[0].pszObjId; rgSigner[iSigner].HashAlgorithm.Parameters.cbData = rgpcaps[iSigner]->rgCapability[0].Parameters.cbData; rgSigner[iSigner].HashAlgorithm.Parameters.pbData = rgpcaps[iSigner]->rgCapability[0].Parameters.pbData; // // Need to setup the attributes to attach to the signed message // if (m_psldData->m_rgSigners[iSigner].blobAuth.cbSize != 0) { cbData = 0; hr = HrDecodeObject(m_psldData->m_rgSigners[iSigner].blobAuth.pBlobData, m_psldData->m_rgSigners[iSigner].blobAuth.cbSize, szOID_Microsoft_Attribute_Sequence, 0, &cbData, (LPVOID *)&rgpattrAuth[iSigner]); if (FAILED(hr)) { goto exit; } if (rgpattrAuth[iSigner] != NULL) { rgSigner[iSigner].cAuthAttr = rgpattrAuth[iSigner]->cAttr; rgSigner[iSigner].rgAuthAttr = rgpattrAuth[iSigner]->rgAttr; Assert(m_pattrAuth == NULL); if (!g_FSupportV3) { // This code exists for old versions of crypt32. Prior to // the NT5 re-write the capi code did not copy the attributes // but assumed that we must have done so. m_pattrAuth = rgpattrAuth[iSigner]; rgpattrAuth[iSigner] = NULL; } } else { Assert(rgSigner[iSigner].cAuthAttr == 0); Assert(rgSigner[iSigner].rgAuthAttr == NULL); } } if (m_psldData->m_rgSigners[iSigner].blobUnauth.cbSize != 0) { cbData = 0; HrDecodeObject(m_psldData->m_rgSigners[iSigner].blobUnauth.pBlobData, m_psldData->m_rgSigners[iSigner].blobUnauth.cbSize, szOID_Microsoft_Attribute_Sequence, 0, &cbData, (LPVOID *)&rgpattrUnauth[iSigner]); if (FAILED(hr)) { goto exit; } if (rgpattrUnauth[iSigner] != NULL) { rgSigner[iSigner].cUnauthAttr = rgpattrUnauth[iSigner]->cAttr; rgSigner[iSigner].rgUnauthAttr = rgpattrUnauth[iSigner]->rgAttr; } else { Assert(rgSigner[iSigner].cUnauthAttr == 0); Assert(rgSigner[iSigner].rgUnauthAttr == NULL); } } // load the provider information from the signing cert and then // acquire that provider with the appropriate key container hr = HrGetCertificateParam(m_psldData->m_rgSigners[iSigner].pccert, CERT_KEY_PROV_INFO_PROP_ID, (LPVOID *) &pKPI, NULL); if (FAILED(hr)) { goto gle; } if (!m_hProv && ! CRYPT_ACQUIRE_CONTEXT(&m_hProv, pKPI->pwszContainerName, pKPI->pwszProvName, pKPI->dwProvType, pKPI->dwFlags)) { goto gle; } Assert(0 == pKPI->cProvParam); #ifdef SMIME_V3 if (psi->pwszKeyPrompt != NULL) { CryptSetProvParam(m_hProv, PP_UI_PROMPT, (LPBYTE) psi->pwszKeyPrompt, 0); } #endif // SMIME_V3 rgSigner[iSigner].pCertInfo = m_psldData->m_rgSigners[iSigner].pccert->pCertInfo; rgSigner[iSigner].hCryptProv = m_hProv; rgSigner[iSigner].dwKeySpec = pKPI->dwKeySpec; // // Need to change dsa to dsa-with-sha1 // if (strcmp(rgSigner[iSigner].pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId, szOID_OIWSEC_dsa) == 0) { rgSigner[iSigner].HashEncryptionAlgorithm.pszObjId = szOID_OIWSEC_dsaSHA1; } } csei.cbSize = sizeof(CMSG_SIGNED_ENCODE_INFO); csei.cSigners = m_psldData->m_cSigners; csei.rgSigners = rgSigner; csei.cCertEncoded = cCerts; csei.rgCertEncoded = rgCertBlob; csei.cCrlEncoded = cCrls; csei.rgCrlEncoded = rgCrlBlob; } // // If it is not signed, then it must be encrypted. Setup the calls for // performing an encryption operation. // else { Assert((dwPsiType & MST_THIS_ENCRYPT) != 0); dwMsgType = CMSG_ENVELOPED; // // If we are given a CSP, then we are going to pass it on to the Crypt32 code, // However it turns out that we are the ones who release the CSP so store it // locally into the class object. // Assert(m_hProv == NULL); m_hProv = psi->hProv; psi->hProv = NULL; // // Extract out the bulk encryption algorithm we are going to apply to // the body of the message. This algorithm is the same across all // the different key transfer algorthms. // // // Setup the structure containing all of the encryption parameters for the // Message Encode function. This structure gets setup differently // depending on the version of Crypt32 which we are running on. // memset(&ceei, 0, sizeof(ceei)); ceei.cbSize = sizeof(CMSG_ENVELOPED_ENCODE_INFO); ceei.hCryptProv = m_hProv; ceei.ContentEncryptionAlgorithm = m_psldData->m_ContentEncryptAlgorithm; ceei.pvEncryptionAuxInfo = m_psldData->m_pvEncryptAuxInfo; if (g_FSupportV3) { ceei.cRecipients = m_psldData->m_cEncryptItems; ceei.rgCmsRecipients = m_psldData->m_rgRecipientInfo; if (m_psldData->m_blobUnprotectAttrs.cbData > 0) { CHECKHR(hr = HrDecodeObject(m_psldData->m_blobUnprotectAttrs.pbData, m_psldData->m_blobUnprotectAttrs.cbData, szOID_Microsoft_Attribute_Sequence, 0, &cbData, (LPVOID *) &pattrsUnprot)); ceei.cUnprotectedAttr = pattrsUnprot->cAttr; ceei.rgUnprotectedAttr = pattrsUnprot->rgAttr; } // // Allow for certificates to be carried on the encryption package now // need this for Fortezza static-static implementation. // if (m_psldData->m_hstoreEncrypt != NULL) { hr = _InitEncodedCert(m_psldData->m_hstoreEncrypt, &rgCertBlob, &cCerts, &rgCrlBlob, &cCrls); if (FAILED(hr)) { goto exit; } ceei.cCertEncoded = cCerts; ceei.rgCertEncoded = rgCertBlob; ceei.cCrlEncoded = cCrls; ceei.rgCrlEncoded = rgCrlBlob; } } else { PCERT_INFO pinfo; if (!MemAlloc((LPVOID *) &ceei.rgpRecipients, (sizeof(CERT_INFO) + sizeof(PCERT_INFO)) * m_psldData->m_cEncryptItems)) { hr = E_OUTOFMEMORY; goto exit; } memset(ceei.rgpRecipients, 0, (sizeof(CERT_INFO) + sizeof(PCERT_INFO)) * m_psldData->m_cEncryptItems); ceei.cRecipients = m_psldData->m_cEncryptItems; pinfo = (PCERT_INFO) ((ceei.cRecipients * sizeof(PCERT_INFO)) + (LPBYTE) ceei.rgpRecipients); for (i=0; im_rgRecipientInfo[i].dwRecipientChoice == CMSG_KEY_TRANS_RECIPIENT); pinfo->SubjectPublicKeyInfo.Algorithm = m_psldData->m_rgRecipientInfo[i].pKeyTrans->KeyEncryptionAlgorithm; pinfo->SubjectPublicKeyInfo.PublicKey = m_psldData->m_rgRecipientInfo[i].pKeyTrans->RecipientPublicKey; Assert(m_psldData->m_rgRecipientInfo[i].pKeyTrans->RecipientId.dwIdChoice == CERT_ID_ISSUER_SERIAL_NUMBER); pinfo->Issuer = m_psldData->m_rgRecipientInfo[i].pKeyTrans->RecipientId.IssuerSerialNumber.Issuer; pinfo->SerialNumber = m_psldData->m_rgRecipientInfo[i].pKeyTrans->RecipientId.IssuerSerialNumber.SerialNumber; } } } // Do we need to recurse and wrap ourselves in an Outer Layer? if (m_psldData->m_psldOuter) { CSSDOUT("Streaming wrapped message (type: %x)", m_psldData->m_psldOuter->m_dwMsgEnhancement); hr = InitInner(psi, NULL, m_psldData->m_psldOuter); if (FAILED(hr)) { goto exit; } // This will write the header to the new inner CAPI stream if (m_pstmOut) { CONVINITINFO ci = {0}; // Create a conversion stream ci.ietEncoding = IET_BASE64; ci.fEncoder = TRUE; TrapError(HrCreateInternetConverter(&ci, &m_pConverter)); m_pstmOut->Write(s_cszMimeHeader, sizeof(s_cszMimeHeader)-1, NULL); if (m_psldData->m_dwMsgEnhancement & MST_THIS_ENCRYPT) { m_pstmOut->Write(STR_SMT_ENVELOPEDDATA, lstrlen(STR_SMT_ENVELOPEDDATA), NULL); } else { if ((psi->pszInnerContent != NULL) && (strcmp(psi->pszInnerContent, szOID_SMIME_ContentType_Receipt) == 0)) { m_pstmOut->Write(STR_SMT_SIGNEDRECEIPT, lstrlen(STR_SMT_SIGNEDRECEIPT), NULL); } else { m_pstmOut->Write(STR_SMT_SIGNEDDATA, lstrlen(STR_SMT_SIGNEDDATA), NULL); } } m_pstmOut->Write(c_szCRLF, lstrlen(c_szCRLF), NULL); m_pstmOut->Write(STR_HDR_CNTXFER, lstrlen(STR_HDR_CNTXFER), NULL); m_pstmOut->Write(c_szColonSpace, lstrlen(c_szColonSpace), NULL); if (m_pConverter) { m_pstmOut->Write(STR_ENC_BASE64, lstrlen(STR_ENC_BASE64), NULL); } else { // Failed to create the conversion stream. Try sending binary anyway. // (Netscape can't read it, but most others can.) m_pstmOut->Write(STR_ENC_BINARY, lstrlen(STR_ENC_BINARY), NULL); } m_pstmOut->Write(c_szCRLF, lstrlen(c_szCRLF), NULL); m_pstmOut->Write(s_cszMimeHeader2, sizeof(s_cszMimeHeader2)-1, NULL); m_pstmOut->Write(c_szCRLFCRLF, lstrlen(c_szCRLFCRLF), NULL); } } // // Since the write code is so bad for buffering, lets do the buffering here. // Ignore all errors on return, if the buffer is not allocated then we just // get the same poor performance as before. // MemAlloc((LPVOID *) &m_pbBuffer, CbCacheBufferSize); // // if (psi->pszInnerContent != NULL) { cmsi.cbContent = psi->cbInnerContent; } else { cmsi.cbContent = (DWORD) -1; // indefinite-lenght BER encoding } cmsi.pfnStreamOutput = CBStreamOutput; cmsi.pvArg = (void *)this; m_hMsg = CryptMsgOpenToEncode( CRYPT_ASN_ENCODING|PKCS_7_ASN_ENCODING, CMSG_CMS_ENCAPSULATED_CONTENT_FLAG | dwFlags, dwMsgType, // (dwMsgType == CMSG_SIGNED) ? ((void *) &csei) : ((void *) &ceei), // really depends on the union &ceei, psi->pszInnerContent, &cmsi); if (! m_hMsg) { goto gle; } // // Put the top level into either DETACHED or STREAM based on if we are // doing detached signing or blob signing/encryption. // // Put the low level stream into the write through state so it moves all // data out to the output stream. (If no output stream then mark as no // output streaming.) // m_csStatus = (m_dwFlagsStm & CSTM_DETACHED) ? STREAM_DETACHED_OCCURING : STREAM_OCCURING; m_csStream = m_pstmOut ? CSTM_STREAMING : CSTM_GOTTYPE; hr = S_OK; exit: if (!g_FSupportV3 && (dwPsiType & MST_THIS_ENCRYPT)) { MemFree(ceei.rgpRecipients); } ReleaseMem(pKPI); if (rgCertBlob) { g_pMoleAlloc->Free(rgCertBlob); //also rgpCertInfo } if (rgCrlBlob) { g_pMoleAlloc->Free(rgCrlBlob); } if (rgpcaps != NULL) { for (iSigner=0; iSignerhProv; psi->hProv = NULL; // Copy the array of cert stores up here if (psi->cStores) { m_rgStores = (HCERTSTORE*)g_pMalloc->Alloc(psi->cStores * sizeof(HCERTSTORE)); if (! m_rgStores) { return(E_OUTOFMEMORY); } for (DWORD i = 0; i < psi->cStores; i++) { m_rgStores[i] = CertDuplicateStore(psi->rgStores[i]); } m_cStores = psi->cStores; } // HACK!!! HACK!!!! for WIN64 #ifndef _WIN64 #ifdef SMIME_V3 UNALIGNED WCHAR *wsz = psi->pwszKeyPrompt; if (wsz != NULL) { m_pwszKeyPrompt = PszDupW((LPCWSTR) wsz); if (m_pwszKeyPrompt == NULL) { return(E_OUTOFMEMORY); } } #endif // SMIME_V3 #endif //_WIN64 } cmsi.cbContent = (DWORD)-1; // indefinite-length BER encoding cmsi.pfnStreamOutput = CBStreamOutput; cmsi.pvArg = (void *)this; m_hMsg = CryptMsgOpenToDecode( CRYPT_ASN_ENCODING | PKCS_7_ASN_ENCODING, (m_dwFlagsStm & CSTM_DETACHED) ? CMSG_DETACHED_FLAG : 0, 0, // don't know the type m_hProv, // needed for verify, but not decrypt NULL, // pRecipientInfo &cmsi); if (m_hMsg) { m_csStatus = (m_dwFlagsStm & CSTM_DETACHED) ? STREAM_DETACHED_OCCURING : STREAM_QUESTION_TIME; } else { m_csStatus = STREAM_ERROR; } Assert(m_hMsg || S_OK != HrGetLastError()); return m_hMsg ? S_OK : HrGetLastError(); } /////////////////////////////////////////////////////////////////////////// // // Callback and helpers/crackers // BOOL WINAPI CCAPIStm::CBStreamOutput( const void *pvArg, BYTE *pbData, DWORD cbData, BOOL fFinal) { Assert(pvArg); return((CCAPIStm*)pvArg)->StreamOutput(pbData, cbData, fFinal); } BOOL CCAPIStm::StreamOutput( BYTE * pbData, DWORD cbData, BOOL fFinal) { HRESULT hr = S_OK; int iEOH; #ifdef SMIME_V3 LPSTR szContentType = NULL; #endif // SMIME_V3 // m_csStream should be one of the CSTM states at this point, if not then // we are in error. Assert((m_csStream == CSTM_GOTTYPE) || (m_csStream == CSTM_FIRST_WRITE) || (m_csStream == CSTM_TEST_NESTING) || (m_csStream == CSTM_STREAMING)); // If all we are doing is looking for the type, then we know that // we already have one at this point. There is no need to put the // output of the Crypto code anyplace as it is not part of what we // are looking for. if (CSTM_GOTTYPE == m_csStream) { return TRUE; } // If we have no output stream, then all we need to do is the state // transistion on fFinal being true. if (m_pstmOut == NULL) { if (fFinal) { m_csStream = CSTM_STREAMING_DONE; } return TRUE; } // // Test for an enclosed opaque S/MIME message // the client doesn't care about this level of goo, so hide it and // stream this data into a new CAPIStm, letting it stream out the // real stuff. // if (CSTM_FIRST_WRITE == m_csStream) { // this is the position of the beginning of any // possible MIME header if (FAILED(HrGetStreamPos(m_pstmOut, &m_cbBeginWrite)) || FAILED(HrGetStreamSize(m_pstmOut, &m_cbBeginSize))) { m_cbBeginWrite = 0; m_cbBeginSize = 0; } else { // reset position HrStreamSeekSet(m_pstmOut, m_cbBeginWrite); } m_csStream = CSTM_TEST_NESTING; #ifdef SMIME_V3 if (szContentType = (LPSTR)PVGetMsgParam(m_hMsg, CMSG_INNER_CONTENT_TYPE_PARAM, NULL, NULL)) { if (lstrcmp(szOID_PKCS_7_DATA, szContentType)) { hr = m_pstmOut->Write(s_cszOIDMimeHeader1, strlen(s_cszOIDMimeHeader1), NULL); if (SUCCEEDED(hr)) { hr = m_pstmOut->Write(szContentType, strlen(szContentType), NULL); } if (SUCCEEDED(hr)) { hr = m_pstmOut->Write(s_cszOIDMimeHeader2, strlen(s_cszOIDMimeHeader2), NULL); } if (FAILED(hr)) { return FALSE; } m_csStream = CSTM_STREAMING; } } #endif // SMIME_V3 } if (CSTM_TEST_NESTING == m_csStream && (-1 != (iEOH = SniffForEndOfHeader(pbData, cbData)))) { CMimePropertyContainer *pContHeader; // get the position of the first char of the body iEOH = cbData - iEOH + 1; pContHeader = new CMimePropertyContainer; if (pContHeader) { hr = pContHeader->InitNew(); if (SUCCEEDED(hr)) { ULONG posCurrent; // write out the last bit of the header data // then move back to the header's start after // saving our current position hr = m_pstmOut->Write(pbData, iEOH, NULL); if (SUCCEEDED(hr)) { // fixup the amount of data in pbData so // only body stuff gets written to the stream // . . . we've already written the header pbData += iEOH; cbData -= iEOH; HrGetStreamPos(m_pstmOut, &posCurrent); HrStreamSeekSet(m_pstmOut, m_cbBeginWrite); hr = pContHeader->Load(m_pstmOut); #ifdef DEBUG BYTE *pbHeader; DWORD cbHeader; HrStreamToByte(m_pstmOut, &pbHeader, &cbHeader); SafeMemFree(pbHeader); #endif // if we don't have an inner message, need to reset // the stream back to where we were HrStreamSeekSet(m_pstmOut, posCurrent); } } if (SUCCEEDED(hr)) { CSSDOUT("Loaded an inner header."); if (IsOpaqueSecureContentType(pContHeader)) { CSSDOUT("Sniffed an inner PKCS#7."); // the HandleNesting call will reset m_pstmOut TrapError(HandleNesting(pContHeader)); } m_csStream = CSTM_STREAMING; } #ifdef DEBUG else { CSSDOUT("Load of inner header failed."); } #endif pContHeader->Release(); } } if (fFinal) { m_csStream = CSTM_STREAMING_DONE; } if (m_pConverter) { BLOB blob; blob.pBlobData = pbData; blob.cbSize = cbData; hr = m_pConverter->HrFillAppend(&blob); if (SUCCEEDED(hr)) { if (m_dwFlagsStm & CSTM_DECODE) { hr = m_pConverter->HrInternetDecode(fFinal); } else { hr = m_pConverter->HrInternetEncode(fFinal); } } if (SUCCEEDED(hr)) { hr = m_pConverter->HrWriteConverted(m_pstmOut); } else { hr = m_pstmOut->Write(pbData, cbData, NULL); } } else { hr = m_pstmOut->Write(pbData, cbData, NULL); } #ifdef SMIME_V3 MemFree(szContentType); #endif // SMIME_V3 return SUCCEEDED(hr) ? TRUE : FALSE; } /* SniffForEndOfHeader: ** ** Purpose: ** see if we have accumulated two blank lines in a row ** Takes: ** a buffer to scan and size of the buffer ** Returns: ** number of characters from the end of the second \n */ int CCAPIStm::SniffForEndOfHeader( BYTE * pbData, DWORD cbData) { BOOL fCR, fEOL; // state is saved b/c the double blank could cross // a buffer chunk's boundary // restore old state and also reset fCR = m_dwFlagsStm & CSTM_HAVECR; fEOL = m_dwFlagsStm & CSTM_HAVEEOL; if (fCR || fEOL) { m_dwFlagsStm &= ~(CSTM_HAVECR | CSTM_HAVEEOL); } while (cbData) { if (chCR == *pbData) { fCR = TRUE; } else if (fCR && (chLF == *pbData)) { if (fEOL) { // double blank line return cbData; } fCR = FALSE; fEOL = TRUE; } else { fCR = FALSE; fEOL = FALSE; } pbData++; cbData--; } // state was reset above. persist if we need to. if (fCR || fEOL) { m_dwFlagsStm |= (fCR ? CSTM_HAVECR : 0) | (fEOL ? CSTM_HAVEEOL : 0); } return -1; } HRESULT CCAPIStm::HandleNesting(CMimePropertyContainer *pContHeader) { ENCODINGTYPE iet; iet = pContHeader->GetEncodingType(); if (!(IET_BINARY == iet || IET_7BIT == iet || IET_8BIT == iet)) { CONVINITINFO ciiDecode; // we actually need to decode ciiDecode.dwFlags = 0; ciiDecode.ietEncoding = iet; ciiDecode.fEncoder = FALSE; TrapError(HrCreateInternetConverter(&ciiDecode, &m_pConverter)); } return InitInner(); } HRESULT CCAPIStm::InitInner() { SMIMEINFO siBuilt; ULARGE_INTEGER liSize; // Init siBuilt memset(&siBuilt, 0, sizeof(siBuilt)); // now also fixup the stream back to a near original // state. if for some reason the data written after // now is smaller than the header is, this // work will make sure we don't keep bits of the header HrStreamSeekSet(m_pstmOut, m_cbBeginWrite); liSize.LowPart = m_cbBeginSize; liSize.HighPart = m_cbBeginWrite; m_pstmOut->SetSize(liSize); siBuilt.hProv = m_hProv; #ifdef OLD_STUFF // BUGBUG: Is something like this needed? siBuilt.ssEncrypt.pcDecryptionCert = m_pUserCertDecrypt; #endif // OLD_STUFF siBuilt.cStores = m_cStores; siBuilt.rgStores = m_rgStores; return InitInner(&siBuilt); } HRESULT CCAPIStm::InitInner( SMIMEINFO *const psi, CCAPIStm * pOuter, PSECURITY_LAYER_DATA psldOuter) { HRESULT hr; if (! pOuter) { m_pCapiInner = new CCAPIStm(m_pstmOut); CHECKHR(hr = m_pCapiInner-> HrInnerInitialize(m_dwFlagsSEF, m_hwnd, m_dwFlagsStm, m_pSmimeCallback, psldOuter)); if (!psldOuter) { // Hook up the chain of Security Layer Data objects. Assert(! m_psldData->m_psldInner); m_pCapiInner->m_psldData->AddRef(); m_psldData->m_psldInner = m_pCapiInner->m_psldData; if (m_pCapiInner->m_psldData) { // Init the Up pointer of the new layer data m_pCapiInner->m_psldData->m_psldOuter = m_psldData; } } // recurse return m_pCapiInner->InitInner(psi, this, psldOuter); } Assert(!m_pCapiInner); Assert(pOuter); Assert(psi); m_dwFlagsStm = pOuter->m_dwFlagsStm & CSTM_ALLFLAGS; // This will get me involved ReleaseObj(pOuter->m_pstmOut); pOuter->m_pstmOut = (IStream*)this; AddRef(); // outer is holding 1 m_dwFlagsStm |= CSTM_RECURSED; if (pOuter->m_dwFlagsStm & CSTM_DECODE) { hr = BeginDecodeStreaming(psi); } else { // don't support detached inner CRYPTMSGs m_dwFlagsStm &= ~CSTM_DETACHED; hr = BeginEncodeStreaming(psi); } m_dwFlagsStm &= ~CSTM_RECURSED; exit: return hr; } // // Gets the immediate outermost decryption cert (if any). // PCCERT_CONTEXT CCAPIStm::GetOuterDecryptCert() { PCCERT_CONTEXT pccertDecrypt = NULL; PSECURITY_LAYER_DATA psldOuter = NULL; Assert(NULL != m_psldData); if (NULL != m_psldData) { psldOuter = m_psldData->m_psldOuter; } while (NULL != psldOuter) { if (NULL != psldOuter->m_pccertDecrypt) { Assert( MST_ENCRYPT_MASK & (psldOuter->m_dwMsgEnhancement) ); pccertDecrypt = psldOuter->m_pccertDecrypt; break; } psldOuter = psldOuter->m_psldOuter; } return pccertDecrypt; } HCERTSTORE OpenAllStore( IN DWORD cStores, IN HCERTSTORE rgStores[], IN OUT HCERTSTORE *phCertStoreAddr, IN OUT HCERTSTORE *phCertStoreCA, IN OUT HCERTSTORE *phCertStoreMy, IN OUT HCERTSTORE *phCertStoreRoot ) { HCERTSTORE hstoreAll; DWORD i; hstoreAll = CertOpenStore(CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING, NULL, 0, NULL); if (hstoreAll == NULL) { return NULL; } for (i=0; i 1. We can't deal. ** MIME_E_SECURITY_BADCONTENT if I don't understand the message type of the ** inner data ** else S_OK or E_FAIL */ HRESULT CCAPIStm::VerifySignedMessage() { CRYPT_SMIME_CAPABILITIES cap; DWORD cbData = 0; DWORD cCerts; DWORD cSigners = 0; DWORD dexStore; BOOL f; #ifdef SMIME_V3 BOOL fLookForReceiptRequest = TRUE; #endif // SMIME_V3 HCERTSTORE hCertStoreAddr = NULL; HCERTSTORE hCertStoreCA = NULL; HCERTSTORE hCertStoreMy = NULL; HCERTSTORE hCertStoreRoot = NULL; HCERTSTORE hMsgCertStore = NULL; HRESULT hr = S_OK; DWORD i; DWORD iSigner; #ifdef SMIME_V3 DWORD iSignData; CRYPT_ATTR_BLOB attrReceiptReq = {0}; CRYPT_ATTR_BLOB attrSecLabel = {0}; DWORD cblabel; CMSG_CMS_SIGNER_INFO cmsSignerInfo; DWORD dwCtrl; HCERTSTORE hstoreAll = NULL; PSMIME_RECEIPT_REQUEST preq = NULL; PSMIME_SECURITY_LABEL plabel = NULL; CMSG_CMS_SIGNER_INFO * pCmsSignerInfo = NULL; LPVOID pv; CMSG_CTRL_VERIFY_SIGNATURE_EX_PARA verifySignature; #endif // SMIME_V3 PCCERT_CONTEXT pccertSigner = NULL; SignerData * pSignerData = NULL; PCMSG_SIGNER_INFO pSignerInfo = NULL; CERT_INFO SignerId; LPSTR szContentType = NULL; Assert(m_hMsg); Assert(m_psldData->m_fCertInLayer == FALSE); // Get the number of signers cbData = sizeof(cSigners); f = CryptMsgGetParam(m_hMsg, CMSG_SIGNER_COUNT_PARAM, 0, &cSigners, &cbData); if (!f) { goto CryptoError; } if (cSigners == 0) { hr = MIME_E_NO_SIGNER; goto ErrorReturn; } // Allocate space to hold the signer information if (!MemAlloc((LPVOID *) &pSignerData, cSigners * sizeof(SignerData))) { hr = E_OUTOFMEMORY; goto ErrorReturn; } m_psldData->m_rgSigners = pSignerData; m_psldData->m_cSigners = cSigners; // Initialized to a known state memset(pSignerData, 0, cSigners * sizeof(SignerData)); for (i=0; im_hcertstor = CertDuplicateStore(hMsgCertStore); } // // Walk through each and every signature attempting to verify each signature // for (iSigner=0; iSignerdwVersion; cmsSignerInfo.SignerId.dwIdChoice = CERT_ID_ISSUER_SERIAL_NUMBER; cmsSignerInfo.SignerId.IssuerSerialNumber.Issuer = pSignerInfo->Issuer; cmsSignerInfo.SignerId.IssuerSerialNumber.SerialNumber = pSignerInfo->SerialNumber; cmsSignerInfo.HashAlgorithm = pSignerInfo->HashAlgorithm; cmsSignerInfo.HashEncryptionAlgorithm = pSignerInfo->HashEncryptionAlgorithm; cmsSignerInfo.EncryptedHash = pSignerInfo->EncryptedHash; cmsSignerInfo.AuthAttrs = pSignerInfo->AuthAttrs; cmsSignerInfo.UnauthAttrs = pSignerInfo->UnauthAttrs; pCmsSignerInfo = &cmsSignerInfo; // (post-SDR) // Build up IASN SignerId.Issuer = pSignerInfo->Issuer; SignerId.SerialNumber = pSignerInfo->SerialNumber; } // Our best bet to easily find a certificate is in the message provided // list of certificates. if (hMsgCertStore) { if (g_FSupportV3) { pccertSigner = CertFindCertificateInStore(hMsgCertStore, X509_ASN_ENCODING, 0, CERT_FIND_CERT_ID, &pCmsSignerInfo->SignerId, NULL); } else { pccertSigner = CertGetSubjectCertificateFromStore(hMsgCertStore, X509_ASN_ENCODING, &SignerId); } if (pccertSigner != NULL) { m_psldData->m_fCertInLayer = TRUE; } } if (pccertSigner == NULL) { if (g_FSupportV3) { hstoreAll = OpenAllStore( m_cStores, m_rgStores, &hCertStoreAddr, &hCertStoreCA, &hCertStoreMy, &hCertStoreRoot ); if (hstoreAll == NULL) goto CryptoError; pccertSigner = CertFindCertificateInStore(hstoreAll, X509_ASN_ENCODING, 0, CERT_FIND_CERT_ID, &pCmsSignerInfo->SignerId, NULL); } else { Assert(!g_FSupportV3); CSSDOUT("Couldn't find cert in message store"); // Look in the caller specified cert store before the hard coded stores. for (dexStore=0; dexStoreulValidity = MSV_UNVERIFIABLE; } else { pSignerData->pccert = CertDuplicateCertificateContext(pccertSigner); if (g_FSupportV3) { dwCtrl = CMSG_CTRL_VERIFY_SIGNATURE_EX; pv = &verifySignature; verifySignature.cbSize = sizeof(verifySignature); verifySignature.hCryptProv = NULL; verifySignature.dwSignerIndex = iSigner; verifySignature.dwSignerType = CMSG_VERIFY_SIGNER_CERT; verifySignature.pvSigner = (LPVOID) pccertSigner; } else { dwCtrl = CMSG_CTRL_VERIFY_SIGNATURE; pv = pccertSigner->pCertInfo; } retry: if (!CryptMsgControl(m_hMsg, 0, dwCtrl, pv)) { HRESULT hr2 = HrGetLastError(); CSSDOUT("Failed signer verify --> %lx", hr2); if (hr2 == CRYPT_E_MISSING_PUBKEY_PARA) { if (NULL == hstoreAll && g_FSupportV3) { hstoreAll = OpenAllStore( m_cStores, m_rgStores, &hCertStoreAddr, &hCertStoreCA, &hCertStoreMy, &hCertStoreRoot ); if (NULL == hstoreAll) goto CryptoError; } hr2 = GetParameters(pccertSigner, hMsgCertStore, hstoreAll); if (hr2 == S_OK) { goto retry; } pSignerData->ulValidity = MSV_UNVERIFIABLE; } else if (NTE_BAD_SIGNATURE == hr2 || CRYPT_E_HASH_VALUE == hr2) { pSignerData->ulValidity = MSV_BADSIGNATURE; } else if (NTE_BAD_ALGID == hr2) { pSignerData->ulValidity = MSV_UNKHASH; } else if (CRYPT_E_SIGNER_NOT_FOUND == hr2) { pSignerData->ulValidity = MSV_UNVERIFIABLE; } else if (NTE_FAIL == hr2) { // RSABASE returns errors. This might // be a failure or the hash might be changed. // Have to be cautious -> make it bad. pSignerData->ulValidity = MSV_BADSIGNATURE; } else { pSignerData->ulValidity = MSV_MALFORMEDSIG; } } else { CSSDOUT("Verify of signature succeeded."); pSignerData->ulValidity &= ~(MSV_SIGNATURE_MASK|MSV_SIGNING_MASK); } // Determine if certificate is expired if (0 != CertVerifyTimeValidityWithDelta(NULL, pccertSigner->pCertInfo, TIME_DELTA_SECONDS)) { pSignerData->ulValidity |= MSV_EXPIRED_SIGNINGCERT; } } if (szContentType = (LPSTR)PVGetMsgParam(m_hMsg, CMSG_INNER_CONTENT_TYPE_PARAM, NULL, NULL)) { if (lstrcmp(szOID_PKCS_7_DATA, szContentType)) { CSSDOUT("Guess what, we have nested PKCS7 data types (maybe)."); } } else { // CAPI failed... we are in trouble... pSignerData->ulValidity |= MSV_INVALID; hr = MIME_E_SECURITY_BADCONTENT; goto ErrorReturn; } // Grab the hashing alg cap.cCapability = 1; cap.rgCapability = (CRYPT_SMIME_CAPABILITY *) &pCmsSignerInfo->HashAlgorithm; if (!CryptEncodeObjectEx(X509_ASN_ENCODING, PKCS_SMIME_CAPABILITIES, &cap, CRYPT_ENCODE_ALLOC_FLAG, &CryptEncodeAlloc, &pSignerData->blobHashAlg.pBlobData, &pSignerData->blobHashAlg.cbSize)) { Assert(FALSE); } // // Get the attributes, authenicated and unauthenicated, and put into the // structure so we can push them back to the user later if (pCmsSignerInfo->AuthAttrs.cAttr != 0) { #ifdef SMIME_V3 for (i=0; iAuthAttrs.cAttr; i++) { // If we have a security label in this message, then we need to // perform access validation. if (g_FSupportV3 && FIsMsasn1Loaded()) { if (strcmp(pCmsSignerInfo->AuthAttrs.rgAttr[i].pszObjId, szOID_SMIME_Security_Label) == 0) { if ((pSignerData->ulValidity & MSV_SIGNATURE_MASK) != MSV_OK) { DWORD dw = DwProcessLabelWithCertError(); if (CertErrorProcessLabelGrant == dw) { hr = S_OK; continue; } else if (CertErrorProcessLabelDeny == dw) { hr = MIME_E_SECURITY_LABELACCESSDENIED; goto ErrorReturn; } // else continue processing the label. } if (pCmsSignerInfo->AuthAttrs.rgAttr[i].cValue != 1) { hr = MIME_E_SECURITY_LABELCORRUPT; goto ErrorReturn; } // Have we already seen a label? if (attrSecLabel.pbData != NULL) { // Check that the one we saw matches this one if ((attrSecLabel.cbData != pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].cbData) || memcmp(attrSecLabel.pbData, pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].pbData, attrSecLabel.cbData)) { if (FHideMsgWithDifferentLabels()) { hr = MIME_E_SECURITY_LABELCORRUPT; goto ErrorReturn; } else { continue; } } else { continue; } } else { // Save label. attrSecLabel.cbData = pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].cbData; if (!MemAlloc((LPVOID*) (& attrSecLabel.pbData), attrSecLabel.cbData)) { hr = MIME_E_SECURITY_LABELCORRUPT; goto ErrorReturn; } memcpy(attrSecLabel.pbData, pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].pbData, attrSecLabel.cbData); } // Clean-up from last loop if (plabel != NULL) CryptDecodeAlloc.pfnFree(plabel); // Crack the contents of the label if (!CryptDecodeObjectEx(X509_ASN_ENCODING, szOID_SMIME_Security_Label, pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].pbData, pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].cbData, CRYPT_ENCODE_ALLOC_FLAG, &CryptDecodeAlloc, &plabel, &cblabel)) { goto CryptoError; } // Query the policy. hr = HrCheckLabelAccess((m_dwFlagsSEF & SEF_NOUI) ? SMIME_POLICY_MODULE_NOUI: 0, m_hwnd, plabel, GetOuterDecryptCert(), pccertSigner, hMsgCertStore); // If security policy returned an error, then abort. if (FAILED(hr)) { goto ErrorReturn; } } } if (g_FSupportV3 && FIsMsasn1Loaded() && (fLookForReceiptRequest) && ((pSignerData->ulValidity & (MSV_SIGNATURE_MASK | MSV_SIGNING_MASK)) == MSV_OK)) { // If we have a receipt request in this message than we need to build // the receipt body now while we have a chance. if (strcmp(pCmsSignerInfo->AuthAttrs.rgAttr[i].pszObjId, szOID_SMIME_Receipt_Request) == 0) { if (pCmsSignerInfo->AuthAttrs.rgAttr[i].cValue != 1) { if (attrReceiptReq.pbData != NULL) { StopSendOfReceipt: for (iSignData=0; iSignData < iSigner; iSignData++) { SafeMemFree(m_psldData->m_rgSigners[iSignData].blobReceipt.pBlobData); SafeMemFree(m_psldData->m_rgSigners[iSignData].blobHash.pBlobData); m_psldData->m_rgSigners[iSignData].blobReceipt.cbSize = 0; m_psldData->m_rgSigners[iSignData].blobHash.cbSize = 0; } } fLookForReceiptRequest = FALSE; continue; } DWORD cb; DWORD cbReceipt; DWORD cbHash = 0; LPBYTE pbReceipt = NULL; LPBYTE pbHash = NULL; SMIME_RECEIPT receipt = {0}; // Clean-up from last loop if (preq != NULL) free(preq); // Have we already seen a receipt? if (attrReceiptReq.pbData != NULL) { // Check that the one we saw matches this one if ((attrReceiptReq.cbData != pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].cbData) || memcmp(attrReceiptReq.pbData, pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].pbData, attrReceiptReq.cbData)) { goto StopSendOfReceipt; } } else { // Save receipt request attrReceiptReq.cbData = pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].cbData; if (!MemAlloc((LPVOID*) (& attrReceiptReq.pbData), attrReceiptReq.cbData)) { // abort looking for receipt requests. goto StopSendOfReceipt; } memcpy(attrReceiptReq.pbData, pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].pbData, attrReceiptReq.cbData); } // Crack the contents of the receipt if (!CryptDecodeObjectEx(X509_ASN_ENCODING, szOID_SMIME_Receipt_Request, pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].pbData, pCmsSignerInfo->AuthAttrs.rgAttr[i].rgValue[0].cbData, CRYPT_ENCODE_ALLOC_FLAG, &CryptDecodeAlloc, &preq, &cb)) { goto StopSendOfReceipt; } // Encode the receipt receipt.Version = 1; receipt.pszOIDContent = szContentType; receipt.ContentIdentifier = preq->ContentIdentifier; receipt.OriginatorSignature.cbData = pCmsSignerInfo->EncryptedHash.cbData; receipt.OriginatorSignature.pbData = pCmsSignerInfo->EncryptedHash.pbData; if (!CryptEncodeObjectEx(X509_ASN_ENCODING, szOID_SMIME_ContentType_Receipt, &receipt, CRYPT_ENCODE_ALLOC_FLAG, &CryptEncodeAlloc, &pbReceipt, &cbReceipt)) { goto StopSendOfReceipt; } pSignerData->blobReceipt.cbSize = cbReceipt; pSignerData->blobReceipt.pBlobData = pbReceipt; pbHash = (LPBYTE)PVGetMsgParam(m_hMsg, CMSG_COMPUTED_HASH_PARAM, NULL, &cbHash); if (pbHash == NULL) { goto CryptoError; } pSignerData->blobHash.cbSize = cbHash; pSignerData->blobHash.pBlobData = pbHash; } } } #endif // SMIME_V3 // cbData = 0; LPBYTE pb; if (!CryptEncodeObjectEx(X509_ASN_ENCODING, szOID_Microsoft_Attribute_Sequence, &pCmsSignerInfo->AuthAttrs, CRYPT_ENCODE_ALLOC_FLAG, &CryptEncodeAlloc, &pb, &cbData)) { goto CryptoError; } pSignerData->blobAuth.cbSize = cbData; pSignerData->blobAuth.pBlobData = pb; } if (pCmsSignerInfo->UnauthAttrs.cAttr != 0) { LPBYTE pb; if (!CryptEncodeObjectEx(X509_ASN_ENCODING, szOID_Microsoft_Attribute_Sequence, &pCmsSignerInfo->UnauthAttrs, CRYPT_ENCODE_ALLOC_FLAG, &CryptEncodeAlloc, &pb, &cbData)) { goto CryptoError; } pSignerData->blobUnauth.cbSize = cbData; pSignerData->blobUnauth.pBlobData = pb; } CertFreeCertificateContext(pccertSigner); pccertSigner = NULL; } exit: #ifdef SMIME_V3 if (preq != NULL) CryptDecodeAlloc.pfnFree(preq); if (plabel != NULL) CryptDecodeAlloc.pfnFree(plabel); SafeMemFree(attrReceiptReq.pbData); SafeMemFree(attrSecLabel.pbData); #endif // SMIME_V3 if (hCertStoreAddr != NULL) CertCloseStore(hCertStoreAddr, 0); if (hCertStoreMy != NULL) CertCloseStore(hCertStoreMy, 0); if (hCertStoreCA != NULL) CertCloseStore(hCertStoreCA, 0); if (hCertStoreRoot != NULL) CertCloseStore(hCertStoreRoot, 0); if (hMsgCertStore != NULL) CertCloseStore(hMsgCertStore, 0); MemFree(szContentType); ReleaseMem(pSignerInfo); #ifdef SMIME_V3 if (pCmsSignerInfo != &cmsSignerInfo) { ReleaseMem(pCmsSignerInfo); } if (hstoreAll != NULL) CertCloseStore(hstoreAll, 0); #endif // SMIME_V3 ReleaseCert(pccertSigner); return hr; CryptoError: hr = HrGetLastError(); ErrorReturn: // On error, release the cert store if (m_psldData->m_hcertstor != NULL) { CertCloseStore(m_psldData->m_hcertstor, 0); m_psldData->m_hcertstor = NULL; } if (S_OK == hr) // our generic error message hr = TrapError(MIME_E_SECURITY_BADMESSAGE); goto exit; } static HRESULT GetCSP(PCCERT_CONTEXT pccert, HCRYPTPROV * phprov, DWORD * pdwKeyId) { HRESULT hr; PCRYPT_KEY_PROV_INFO pKPI = NULL; Assert(*phprov == NULL); Assert(*pdwKeyId == 0); // // hr = HrGetCertificateParam(pccert, CERT_KEY_PROV_INFO_PROP_ID, (LPVOID *) &pKPI, NULL); if (FAILED(hr)) { goto exit; } *pdwKeyId = pKPI->dwKeySpec; // If the cert specifies the base provider OR has no specification, // then try to acquire RSAENH, else get RSABASE. if ((PROV_RSA_FULL == pKPI->dwProvType) && (UnlocStrEqNW(pKPI->pwszProvName, MS_DEF_PROV_W, sizeof(MS_DEF_PROV_W)/sizeof(WCHAR)-5) || (*pKPI->pwszProvName == 0))) { if (!CRYPT_ACQUIRE_CONTEXT(phprov, pKPI->pwszContainerName, MS_ENHANCED_PROV_W, PROV_RSA_FULL, pKPI->dwFlags)) { CSSDOUT("CryptAcquireContext -> %x\n", HrGetLastError()); } } if (*phprov == NULL) { if (! CRYPT_ACQUIRE_CONTEXT(phprov, pKPI->pwszContainerName, pKPI->pwszProvName, pKPI->dwProvType, pKPI->dwFlags)) { CSSDOUT("CryptAcquireContext -> %x\n", HrGetLastError()); hr = HrGetLastError(); goto exit; } } hr = S_OK; exit: ReleaseMem(pKPI); return hr; } HRESULT CCAPIStm::FindKeyFor(HWND hwnd, DWORD dwFlags, DWORD dwRecipientIndex, const CMSG_CMS_RECIPIENT_INFO * pRecipInfo, HCERTSTORE hcertstore, DWORD * pdwCtrl, CMS_CTRL_DECRYPT_INFO * pDecryptInfo, PCCERT_CONTEXT * ppCertDecrypt) { HRESULT hr; HCRYPTPROV hProv = NULL; DWORD i; PCMSG_CTRL_DECRYPT_PARA pccdp; PCCERT_CONTEXT pCertDecrypt = NULL; PCCERT_CONTEXT pCertOrig = NULL; if (g_FSupportV3) { switch (pRecipInfo->dwRecipientChoice) { // // Given the certificate reference, see if we can find it in // the passed in certificate stores, if yes then we will attempt // to decrypt using that certificate // // This is a Key Transport recipient info object. The CAPI 2.0 // code can deal with both SKI and Issuer/Serial Number references // case CMSG_KEY_TRANS_RECIPIENT: pCertDecrypt = CertFindCertificateInStore(hcertstore, X509_ASN_ENCODING, 0, CERT_FIND_CERT_ID, &pRecipInfo->pKeyTrans->RecipientId, NULL); if (pCertDecrypt != NULL) { hr = GetCSP(pCertDecrypt, &pDecryptInfo->trans.hCryptProv, &pDecryptInfo->trans.dwKeySpec); if (SUCCEEDED(hr)) { // // We find a certificate for this lock box. Setup the // structure to be used in decrypting the message. // *pdwCtrl = CMSG_CTRL_KEY_TRANS_DECRYPT; pDecryptInfo->trans.cbSize = sizeof(pDecryptInfo->trans); // pDecryptInfo->trans.hCryptProv = hProv; // pDecryptInfo->trans.dwKeySpec = pKPI->dwKeySpec; pDecryptInfo->trans.pKeyTrans = pRecipInfo->pKeyTrans; pDecryptInfo->trans.dwRecipientIndex = dwRecipientIndex; } else { ReleaseCert(pCertDecrypt); pCertDecrypt = NULL; } } break; // // Given the certificate reference, see if we can find it in // the passed in certificate stores, if yes then we will attempt // to decrypt using that certificate // // This is a Key Agreement recipient info object. The CAPI 2.0 // code can deal with both SKI and Issuer/Serial Number references // // There may be multiple certificate references within a single // recipient info object // case CMSG_KEY_AGREE_RECIPIENT: for (i=0; ipKeyAgree->cRecipientEncryptedKeys; i++) { pCertDecrypt = CertFindCertificateInStore( hcertstore, X509_ASN_ENCODING, 0, CERT_FIND_CERT_ID, &pRecipInfo->pKeyAgree->rgpRecipientEncryptedKeys[i]->RecipientId, NULL); if (pCertDecrypt != NULL) { hr = GetCSP(pCertDecrypt, &pDecryptInfo->agree.hCryptProv, &pDecryptInfo->agree.dwKeySpec); if (SUCCEEDED(hr)) { // // We find a certificate for this lock box. Setup the // structure to be used in decrypting the message. // *pdwCtrl = CMSG_CTRL_KEY_AGREE_DECRYPT; pDecryptInfo->agree.cbSize = sizeof(pDecryptInfo->agree); pDecryptInfo->agree.pKeyAgree = pRecipInfo->pKeyAgree; pDecryptInfo->agree.dwRecipientIndex = dwRecipientIndex; pDecryptInfo->agree.dwRecipientEncryptedKeyIndex = i; // // Need to find the originator information // switch(pRecipInfo->pKeyAgree->dwOriginatorChoice) { case CMSG_KEY_AGREE_ORIGINATOR_CERT: pCertOrig = CertFindCertificateInStore( hcertstore, X509_ASN_ENCODING, 0, CERT_FIND_CERT_ID, &pRecipInfo->pKeyAgree->OriginatorCertId, NULL); if (pCertOrig == NULL) { hr = S_FALSE; goto exit; } hr = HrCopyCryptBitBlob(&pCertOrig->pCertInfo->SubjectPublicKeyInfo.PublicKey, &pDecryptInfo->agree.OriginatorPublicKey); if (FAILED(hr)) { goto exit; } break; case CMSG_KEY_AGREE_ORIGINATOR_PUBLIC_KEY: hr = HrCopyCryptBitBlob(&pRecipInfo->pKeyAgree->OriginatorPublicKeyInfo.PublicKey, &pDecryptInfo->agree.OriginatorPublicKey); if (FAILED(hr)) { goto exit; } break; default: hr = NTE_FAIL; goto exit; } break; } else { ReleaseCert(pCertDecrypt); pCertDecrypt = NULL; } } } break; // // We can't find this from a certificate // case CMSG_MAIL_LIST_RECIPIENT: break; default: hr = NTE_FAIL; goto exit; } } else { CERT_INFO * pCertInfo = (CERT_INFO *) pRecipInfo; for (i=0; ihCryptProv, &pccdp->dwKeySpec); if (SUCCEEDED(hr)) { // // We find a certificate for this lock box. Setup the // structure to be used in decrypting the message. // *pdwCtrl = CMSG_CTRL_DECRYPT; pccdp->cbSize = sizeof(CMSG_CTRL_DECRYPT_PARA); pccdp->dwRecipientIndex = dwRecipientIndex; } else { ReleaseCert(pCertDecrypt); pCertDecrypt = NULL; } break; } } } // // If we did not find a certificate, then return a failure code // if (pCertDecrypt == NULL) { hr = S_FALSE; goto exit; } // // If we have a certificate, then return it for the user to examine. // if (pCertDecrypt != NULL) { *ppCertDecrypt = pCertDecrypt; pCertDecrypt = NULL; } hProv = NULL; hr = S_OK; exit: ReleaseCert(pCertDecrypt); ReleaseCert(pCertOrig); if (hProv != NULL) CryptReleaseContext(hProv, 0); return hr; } BOOL CCAPIStm::HandleEnveloped() { DWORD cbData; DWORD cCerts; DWORD cRecips; CMS_CTRL_DECRYPT_INFO decryptInfo = {0}; DWORD dexRecip; DWORD dwCtrl; BOOL f; BOOL fGotoUser = FALSE; HCERTSTORE hcertstore = NULL; HCERTSTORE hMsgCertStore = NULL; HRESULT hr; DWORD i; PCCERT_CONTEXT pCertDecrypt = NULL; CMSG_CMS_RECIPIENT_INFO * pCmsCertInfo; LPVOID pv = NULL; // // If we are not suppose to display UI -- return an error about displaying UI now. // //////////////////////////////////////////////////////////////////////////////////////// // 591349 - Compiler Bug For Zero Initialization of Data Structure. Active WinNT 5.1 (Whistler) 1 Server RC1 // the line above doesn't zero the structure due to this compiler bug memset(&decryptInfo, 0, sizeof(decryptInfo)); if (m_dwFlagsSEF & SEF_NOUI) { return MIME_E_SECURITY_UIREQUIRED; } // // this call exists for one and only one purpose. We must // be sure that we have read and parsed all of the RecipientInfo structures // before we start processing them. Since the algorithm parameter is after // the last of the last of the recipient structures, this make sure of that. // pv = PVGetMsgParam(m_hMsg, CMSG_ENVELOPE_ALGORITHM_PARAM, 0, NULL); if (pv == NULL) { goto gle; } MemFree(pv); pv = NULL; // // Fetch the set of certificates on the message object // cbData = sizeof(cCerts); f = CryptMsgGetParam(m_hMsg, CMSG_CERT_COUNT_PARAM, 0, &cCerts, &cbData); Assert(f); if (f && (cCerts > 0)) { // since there are certs included, let's try them first when matching // certs with enryptors. // get the store set // make sure we keep hold of our provider hMsgCertStore = CertOpenStore(CERT_STORE_PROV_MSG, X509_ASN_ENCODING, m_hProv, 0, m_hMsg); if (hMsgCertStore) { m_dwFlagsStm |= CSTM_DONTRELEASEPROV; // given unto the store } // if it failed, we just don't have a store then Assert(hMsgCertStore != NULL); m_psldData->m_hstoreEncrypt = CertDuplicateStore(hMsgCertStore); } // // Retrieve the count of recipient infos on the message // cbData = sizeof(cRecips); if (!CryptMsgGetParam(m_hMsg, g_FSupportV3 ? CMSG_CMS_RECIPIENT_COUNT_PARAM : CMSG_RECIPIENT_COUNT_PARAM, 0, &cRecips, &cbData)) { goto gle; } // // If we were provided an actual certificate, see if this is it... // We will either search for the provided certificate or in the provided // certificate stores, but not both. // if (m_pUserCertDecrypt != NULL) { hcertstore = CertOpenStore(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, NULL); if (hcertstore == NULL) { hr = HrGetLastError(); goto exit; } if (!CertAddCertificateContextToStore(hcertstore, m_pUserCertDecrypt, CERT_STORE_ADD_ALWAYS, NULL)) { hr = HrGetLastError(); goto exit; } } else { if (g_FSupportV3) { hcertstore = CertOpenStore(CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING, NULL, 0, NULL); if (hcertstore == NULL) { hr = HrGetLastError(); goto exit; } for (i=0; iFindKeyFor(m_hwnd, 0, dexRecip, (CMSG_CMS_RECIPIENT_INFO *) pv, &dwCtrl, &decryptInfo, &pCertDecrypt); } else { hr = FindKeyFor(m_hwnd, 0, dexRecip, (CMSG_CMS_RECIPIENT_INFO *) pv, hcertstore, &dwCtrl, &decryptInfo, &pCertDecrypt); } if (FAILED(hr)) { goto exit; } if (hr == S_OK) { #ifdef SMIME_V3 if (m_pwszKeyPrompt != NULL) { PCMSG_CTRL_DECRYPT_PARA pccdp; switch (dwCtrl) { case CMSG_CTRL_KEY_TRANS_DECRYPT: CryptSetProvParam(decryptInfo.trans.hCryptProv, PP_UI_PROMPT, (LPBYTE) m_pwszKeyPrompt, 0); break; case CMSG_CTRL_KEY_AGREE_DECRYPT: CryptSetProvParam(decryptInfo.agree.hCryptProv, PP_UI_PROMPT, (LPBYTE) m_pwszKeyPrompt, 0); break; case CMSG_CTRL_DECRYPT: pccdp = (PCMSG_CTRL_DECRYPT_PARA) &decryptInfo; CryptSetProvParam(pccdp->hCryptProv, PP_UI_PROMPT, (LPBYTE) m_pwszKeyPrompt, 0); break; } } #endif // SMIME_V3 if (!CryptMsgControl(m_hMsg, CMSG_CRYPT_RELEASE_CONTEXT_FLAG, dwCtrl, &decryptInfo)) { hr = HrGetLastError(); // // Force any cleanups in the event of an error // switch (dwCtrl) { case CMSG_CTRL_KEY_TRANS_DECRYPT: CryptReleaseContext(decryptInfo.trans.hCryptProv, 0); break; case CMSG_CTRL_KEY_AGREE_DECRYPT: CryptReleaseContext(decryptInfo.agree.hCryptProv, 0); break; case CMSG_CTRL_MAIL_LIST_DECRYPT: Assert(FALSE); break; case CMSG_CTRL_DECRYPT: CryptReleaseContext(((PCMSG_CTRL_DECRYPT_PARA) &decryptInfo)->hCryptProv, 0); break; } goto exit; } goto DecryptDone; } // // Clean up the object returned describing the lock box, if we // were unsuccessful in finding a decryption key. // MemFree(pv); pv = NULL; } // // If we are completely unsuccessful and the user has provided // us a callback to play with, then give the user a shot at finding // the correct decryption parameters. // if (!fGotoUser && g_FSupportV3 && (m_pSmimeCallback != NULL)) { fGotoUser = TRUE; goto tryAgain; } CSSDOUT("Could not decrypt the message"); m_psldData->m_ulDecValidity = MSV_CANTDECRYPT; hr = CS_E_CANT_DECRYPT; goto exit; // // If we get here, then we // 1) found some parameters and // 2) the worked. // DecryptDone: Assert(m_psldData && (m_psldData->m_pccertDecrypt == NULL)); if (pCertDecrypt != NULL) { m_psldData->m_pccertDecrypt = CertDuplicateCertificateContext(pCertDecrypt); // Determine if certificate is expired if (0 != CertVerifyTimeValidityWithDelta(NULL, pCertDecrypt->pCertInfo, TIME_DELTA_SECONDS)) { m_psldData->m_ulDecValidity |= MSV_ENC_FOR_EXPIREDCERT; } } hr = S_OK; exit: if (pv != NULL) MemFree(pv); CertFreeCertificateContext(pCertDecrypt); if (hMsgCertStore != NULL) { CertCloseStore(hMsgCertStore, 0); } if (hcertstore != NULL) { CertCloseStore(hcertstore, 0); } if (FAILED(hr)) { #ifdef DEBUG if (NTE_BAD_DATA == hr) { CSSDOUT("Could not decrypt. Maybe due to ImportKeyError since"); CSSDOUT("NTE_BAD_DATA is the result."); // If this happens then it is somewhat likely that PKCS2Decrypt // failed inside the CSP. (assuming rsabase, rsaenh) } #endif switch (hr) { case CS_E_CANT_DECRYPT: case CRYPT_E_STREAM_MSG_NOT_READY: case HRESULT_FROM_WIN32(ERROR_CANCELLED): break; default: // I suppose many things could have gone wrong. We thought // we had a cert, though, so let's just say the message itself // is bogus. //N8 this is a bad idea if we are wrapping a signature // should be able to tell if the sig failed and display // a better error message. //N8 CAPI is simply going to return NTE_FAIL b/c they // are failing because our callback failed. the // innerCAPI should have some failure state in it. // Maybe we could use this to set MSV_BADINNERSIG or something. // It would be an encryption error (inside that mask) //N8 also this is not being used well enough, even for // decryption. the secUI should test this bit and // say something intelligent about the message. NS does. m_psldData->m_ulDecValidity = MSV_INVALID; hr = CS_E_MSG_INVALID; break; } } #ifdef DEBUG if (CRYPT_E_STREAM_MSG_NOT_READY != hr) { return TrapError(hr); } else { return hr; } #else return hr; #endif gle: hr = HrGetLastError(); Assert(S_OK != hr); goto exit; } /////////////////////////////////////////////////////////////////////////// // // Class-static utility functions // HRESULT CCAPIStm::DuplicateSecurityLayerData(const PSECURITY_LAYER_DATA psldIn, PSECURITY_LAYER_DATA *const ppsldOut) { if (!psldIn || !ppsldOut) { return E_POINTER; } // Just addref the original and return it psldIn->AddRef(); *ppsldOut = psldIn; return(S_OK); } void CCAPIStm::FreeSecurityLayerData(PSECURITY_LAYER_DATA psld) { if (! psld) { return; } psld->Release(); } /////////////////////////////////////////////////////////////////////////// // // Statics to file // static HRESULT _InitEncodedCert(IN HCERTSTORE hcertstor, PCERT_BLOB * prgblobCerts, DWORD * pcCerts, PCRL_BLOB * prgblobCrls, DWORD * pcCrl) { DWORD cbCerts = 0; DWORD cbCRLs = 0; DWORD cCerts = 0; DWORD cCRLs = 0; DWORD i; LPBYTE pbCert = NULL; LPBYTE pbCRL = NULL; PCCERT_CONTEXT pccert = NULL; PCCRL_CONTEXT pccrl = NULL; PCERT_BLOB rgblobCerts = NULL; PCRL_BLOB rgblobCRLs = NULL; while ((pccert = CertEnumCertificatesInStore(hcertstor, pccert)) != NULL) { cbCerts += LcbAlignLcb(pccert->cbCertEncoded); cCerts += 1; } while ((pccrl = CertEnumCRLsInStore(hcertstor, pccrl)) != NULL) { cbCRLs += LcbAlignLcb(pccrl->cbCrlEncoded); cCRLs += 1; } if (cCerts > 0) { rgblobCerts = (PCERT_BLOB) g_pMoleAlloc->Alloc(LcbAlignLcb(sizeof(CERT_BLOB) * cCerts + cbCerts)); if (rgblobCerts == NULL) { return E_OUTOFMEMORY; } } if (cCRLs > 0) { rgblobCRLs = (PCRL_BLOB) g_pMoleAlloc->Alloc(LcbAlignLcb(sizeof(CRL_BLOB) * cCRLs + cbCRLs)); if (rgblobCRLs == NULL) { g_pMoleAlloc->Free(rgblobCerts); return E_OUTOFMEMORY; } } if (cCerts > 0) { pbCert = (LPBYTE) &rgblobCerts[cCerts]; i = 0; while ((pccert = CertEnumCertificatesInStore(hcertstor, pccert)) != NULL) { memcpy(pbCert, pccert->pbCertEncoded, pccert->cbCertEncoded); rgblobCerts[i].pbData = pbCert; rgblobCerts[i].cbData = pccert->cbCertEncoded; pbCert += LcbAlignLcb(pccert->cbCertEncoded); i++; } Assert(i == cCerts); } if (cCRLs > 0) { pbCRL = (LPBYTE) &rgblobCRLs[cCRLs]; i = 0; while ((pccrl = CertEnumCRLsInStore(hcertstor, pccrl)) != NULL) { memcpy(pbCRL, pccrl->pbCrlEncoded, pccrl->cbCrlEncoded); rgblobCRLs[i].pbData = pbCRL; rgblobCRLs[i].cbData = pccrl->cbCrlEncoded; pbCRL += LcbAlignLcb(pccrl->cbCrlEncoded); i++; } Assert(i == cCRLs); } *prgblobCerts = rgblobCerts; *pcCerts = cCerts; *prgblobCrls = rgblobCRLs; *pcCrl = cCRLs; return S_OK; } // Ensures that the signer certificates are included in the returned // array of blobs. static HRESULT _InitEncodedCertIncludingSigners(IN HCERTSTORE hcertstor, DWORD cSigners, SignerData rgSigners[], PCERT_BLOB * prgblobCerts, DWORD * pcCerts, PCRL_BLOB * prgblobCrls, DWORD * pcCrl) { HRESULT hr; HCERTSTORE hCollection = NULL; DWORD i; // Loop through signers. Check that they are already included in the // certificate store. If not, then, create a collection and memory store // to include. for (i = 0; i < cSigners; i++) { PCCERT_CONTEXT pSignerCert = rgSigners[i].pccert; PCCERT_CONTEXT pStoreCert = NULL; while (NULL != (pStoreCert = CertEnumCertificatesInStore( hcertstor, pStoreCert))) { if (pSignerCert->cbCertEncoded == pStoreCert->cbCertEncoded && 0 == memcmp(pSignerCert->pbCertEncoded, pStoreCert->pbCertEncoded, pSignerCert->cbCertEncoded)) break; } if (pStoreCert) // Signer cert is already included in the store CertFreeCertificateContext(pStoreCert); else { if (NULL == hCollection) { // Create collection and memory store to contain the // signer certificate HCERTSTORE hMemory = NULL; BOOL fResult; hCollection = CertOpenStore( CERT_STORE_PROV_COLLECTION, X509_ASN_ENCODING, NULL, 0, NULL ); if (NULL == hCollection) goto CommonReturn; if (!CertAddStoreToCollection( hCollection, hcertstor, 0, // dwUpdateFlags 0 // dwPriority )) goto CommonReturn; hMemory = CertOpenStore( CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, NULL ); if (NULL == hMemory) goto CommonReturn; fResult = CertAddStoreToCollection( hCollection, hMemory, CERT_PHYSICAL_STORE_ADD_ENABLE_FLAG, 1 // dwPriority ); CertCloseStore(hMemory, 0); if (!fResult) goto CommonReturn; hcertstor = hCollection; } CertAddEncodedCertificateToStore( hCollection, pSignerCert->dwCertEncodingType, pSignerCert->pbCertEncoded, pSignerCert->cbCertEncoded, CERT_STORE_ADD_ALWAYS, NULL ); } } CommonReturn: hr = _InitEncodedCert(hcertstor, prgblobCerts, pcCerts, prgblobCrls, pcCrl); if (hCollection) CertCloseStore(hCollection, 0); return hr; } #ifndef SMIME_V3 static HRESULT _InitCertInfo( IN PCCERT_CONTEXT * rgpCerts, IN DWORD cCerts, OUT PCERT_INFO ** prgpCertInfo) { PCERT_INFO* rgpCertInfo = NULL; DWORD dwIdx; HRESULT hr = S_OK; Assert(prgpCertInfo); if (cCerts) { rgpCertInfo = (PCERT_INFO*)g_pMoleAlloc->Alloc(sizeof(CERT_BLOB) * cCerts); if (NULL == rgpCertInfo) { hr = E_OUTOFMEMORY; goto exit; } for (dwIdx = 0; dwIdx < cCerts; dwIdx++) { rgpCertInfo[dwIdx] = rgpCerts[dwIdx]->pCertInfo; } } exit: *prgpCertInfo = rgpCertInfo; return hr; } #endif // !SMIME_V3 void _SMimeCapsFromHMsg(HCRYPTMSG hMsg, DWORD idParam, LPBYTE * ppb, DWORD * pcb) { DWORD cbData = 0; CRYPT_SMIME_CAPABILITY cap; CRYPT_SMIME_CAPABILITIES caps; BOOL f; PCRYPT_ALGORITHM_IDENTIFIER paid = NULL; LPBYTE pb = NULL; f = CryptMsgGetParam(hMsg, idParam, 0, NULL, &cbData); if ((cbData == 0) || ! MemAlloc((LPVOID *) &paid, cbData)) { Assert(FALSE); goto error; } f = CryptMsgGetParam(hMsg, idParam, 0, paid, &cbData); Assert(f); caps.cCapability = 1; caps.rgCapability = ∩ cap.pszObjId = paid->pszObjId; cap.Parameters.cbData = paid->Parameters.cbData; cap.Parameters.pbData = paid->Parameters.pbData; cbData = 0; if (!CryptEncodeObjectEx(X509_ASN_ENCODING, szOID_RSA_SMIMECapabilities, &caps, CRYPT_ENCODE_ALLOC_FLAG, &CryptEncodeAlloc, &pb, &cbData)) { Assert(FALSE); goto error; } *ppb = pb; *pcb = cbData; exit: SafeMemFree(paid); return; error: *ppb = NULL; *pcb = 0; goto exit; } #ifdef SMIME_V3 //// HrBuildContentEncryptionAlg // // Description: // This function is used to decode a smime capability and build the // structure we need to pass into the Crypt32 code. // HRESULT HrBuildContentEncryptionAlg(PSECURITY_LAYER_DATA psld, BLOB * pblob) { DWORD cbData; HRESULT hr; PCRYPT_SMIME_CAPABILITIES pcaps = NULL; CMSG_RC2_AUX_INFO * prc2Aux; // // Decode the capability which is the bulk encryption algorithm // hr = HrDecodeObject(pblob->pBlobData, pblob->cbSize, PKCS_SMIME_CAPABILITIES, 0, &cbData, (LPVOID *)&pcaps); if (FAILED(hr)) { goto exit; } Assert(pcaps->cCapability == 1); DWORD cchSize = (lstrlen(pcaps->rgCapability[0].pszObjId) + 1); if (!MemAlloc((LPVOID *) &psld->m_ContentEncryptAlgorithm.pszObjId, cchSize * sizeof(psld->m_ContentEncryptAlgorithm.pszObjId[0]))) { hr = E_OUTOFMEMORY; goto exit; } StrCpyN(psld->m_ContentEncryptAlgorithm.pszObjId, pcaps->rgCapability[0].pszObjId, cchSize); // // If this is the RC/2 algorithm, then we need to setup the aux info // to pass in the algorithm size. // if (lstrcmp(pcaps->rgCapability[0].pszObjId, szOID_RSA_RC2CBC) == 0) { psld->m_ContentEncryptAlgorithm.Parameters.cbData = 0; psld->m_ContentEncryptAlgorithm.Parameters.pbData = NULL; if (!MemAlloc((LPVOID *) &(psld->m_pvEncryptAuxInfo), sizeof(*prc2Aux))) { hr = E_OUTOFMEMORY; goto exit; } prc2Aux = (CMSG_RC2_AUX_INFO *) psld->m_pvEncryptAuxInfo; prc2Aux->cbSize = sizeof(*prc2Aux); if (pcaps->rgCapability[0].Parameters.cbData == 0) { prc2Aux->dwBitLen = 40; } else { switch(pcaps->rgCapability[0].Parameters.pbData[pcaps->rgCapability[0].Parameters.cbData-1]) { case 128: case 58: prc2Aux->dwBitLen = 128; break; case 64: case 120: prc2Aux->dwBitLen = 64; break; case 40: case 160: default: prc2Aux->dwBitLen = 40; break; } } } else if (pcaps->rgCapability[0].Parameters.cbData != 0) { if (!MemAlloc((LPVOID *) &psld->m_ContentEncryptAlgorithm.Parameters.pbData, pcaps->rgCapability[0].Parameters.cbData)) { hr = E_OUTOFMEMORY; goto exit; } memcpy(psld->m_ContentEncryptAlgorithm.Parameters.pbData, pcaps->rgCapability[0].Parameters.pbData, pcaps->rgCapability[0].Parameters.cbData); psld->m_ContentEncryptAlgorithm.Parameters.cbData = pcaps->rgCapability[0].Parameters.cbData; } hr = S_OK; exit: if (pcaps != NULL) MemFree(pcaps); return hr; } HRESULT HrDeriveKeyWrapAlg(PSECURITY_LAYER_DATA psld, CMSG_KEY_AGREE_RECIPIENT_ENCODE_INFO * pAgree) { LPCSTR pszObjId = psld->m_ContentEncryptAlgorithm.pszObjId; if (lstrcmp(pszObjId, szOID_RSA_RC2CBC) == 0) { pAgree->KeyWrapAlgorithm.pszObjId = szOID_RSA_SMIMEalgCMSRC2wrap; pAgree->pvKeyWrapAuxInfo = psld->m_pvEncryptAuxInfo; } else if (lstrcmp(pszObjId, szOID_RSA_DES_EDE3_CBC) == 0) { pAgree->KeyWrapAlgorithm.pszObjId = szOID_RSA_SMIMEalgCMS3DESwrap; pAgree->pvKeyWrapAuxInfo = NULL; } else if (lstrcmp(pszObjId, szOID_INFOSEC_mosaicConfidentiality) == 0) { pAgree->KeyWrapAlgorithm.pszObjId = "2.16.840.1.101.2.1.1.24"; pAgree->pvKeyWrapAuxInfo = NULL; } else { return NTE_NOT_FOUND; } return S_OK; } #endif // SMIME_V3 #ifdef SMIME_V3 // // Read in admin option that determines whether a msg with disparate // Labels is shown or not. // BOOL FHideMsgWithDifferentLabels() { DWORD cbData = 0; DWORD dwType = 0; DWORD dwValue = 0; BOOL fHideMsg = FALSE; HKEY hkey = NULL; LONG lRes; // Open the security label admin defaults key. lRes = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szSecLabelAdminRegKey, 0, KEY_READ, &hkey); if ( (ERROR_SUCCESS != lRes) || (NULL == hkey) ) { // No admin label options were found. goto exit; } cbData = sizeof(dwValue); lRes = RegQueryValueEx(hkey, c_szHideMsgWithDifferentLabels, NULL, &dwType, (LPBYTE) &dwValue, &cbData); if (ERROR_SUCCESS != lRes) { goto exit; } if (0x01 == dwValue) { fHideMsg = TRUE; } exit: if (NULL != hkey) RegCloseKey(hkey); return fHideMsg; } // // Read in admin option that determines how to process a label in a // signture with errors. // Returns 0, 1, 2 for ProcessAnyway, Grant, Deny(default). // DWORD DwProcessLabelWithCertError() { DWORD cbData = 0; DWORD dwType = 0; DWORD dwValue = CertErrorProcessLabelDeny; BOOL dwProcessMsg = 0; HKEY hkey = NULL; LONG lRes; // Open the security label admin defaults key. lRes = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szSecLabelAdminRegKey, 0, KEY_READ, &hkey); if ( (ERROR_SUCCESS != lRes) || (NULL == hkey) ) { // No admin label options were found. goto exit; } // Read in the admin option. cbData = sizeof(dwValue); lRes = RegQueryValueEx(hkey, c_szCertErrorWithLabel, NULL, &dwType, (LPBYTE) &dwValue, &cbData); if (ERROR_SUCCESS != lRes) { dwValue = CertErrorProcessLabelDeny; goto exit; } // If the value isn't one of the known ones, force it to the default value. if ( (CertErrorProcessLabelAnyway != dwValue) && (CertErrorProcessLabelGrant != dwValue) && (CertErrorProcessLabelDeny != dwValue) ) { dwValue = CertErrorProcessLabelDeny; } exit: if (NULL != hkey) RegCloseKey(hkey); return dwValue; } // // Given a label, queries the policy whether access is to be granted. // (If reqd policy doesn't exist, it also tries to query the default // policy, if one exists). // HRESULT HrCheckLabelAccess(const DWORD dwFlags, const HWND hwnd, PSMIME_SECURITY_LABEL plabel, const PCCERT_CONTEXT pccertDecrypt, const PCCERT_CONTEXT pccertSigner, const HCERTSTORE hcertstor) { HRESULT hr = MIME_E_SECURITY_LABELACCESSDENIED; HKEY hkey = NULL; HKEY hkeySub = NULL; HINSTANCE hinstDll = NULL; PFNGetSMimePolicy pfnGetSMimePolicy = NULL; ISMimePolicyCheckAccess *pspca = NULL; LONG lRes; DWORD dwType; DWORD cbData; CHAR szDllPath[MAX_PATH]; CHAR szExpandedDllPath[MAX_PATH]; CHAR szFuncName[MAX_FUNC_NAME]; if ((NULL == plabel) || (NULL == plabel->pszObjIdSecurityPolicy)) { hr = S_OK; // No label/policyoid => access granted. goto exit; } // Open the security policies key. lRes = RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szSecLabelPoliciesRegKey, 0, KEY_READ, &hkey); if ( (ERROR_SUCCESS != lRes) || (NULL == hkey) ) { // No security policies are registered. Deny access. goto ErrorReturn; } // Open the security policy (or default policy regkey). lRes = RegOpenKeyEx(hkey, plabel->pszObjIdSecurityPolicy, 0, KEY_READ, &hkeySub); if ((ERROR_SUCCESS != lRes) || (NULL == hkeySub)) { if (hkeySub != NULL) { RegCloseKey(hkeySub); hkeySub = NULL; } // Try opening the default policy, if one exists. lRes = RegOpenKeyEx(hkey, c_szDefaultPolicyOid, 0, KEY_READ, &hkeySub); if ((ERROR_SUCCESS != lRes) || (NULL == hkeySub)) { // couldn't find specified_and_default policy. deny access. goto ErrorReturn; } } Assert(NULL != hkeySub); // get the path to the policy dll, and load it. cbData = sizeof(szDllPath); lRes = RegQueryValueEx(hkeySub, c_szSecurityPolicyDllPath, NULL, &dwType, (LPBYTE)szDllPath, &cbData); if (ERROR_SUCCESS != lRes) { // policy not correctly registered. deny access. goto ErrorReturn; } szDllPath[ ARRAYSIZE(szDllPath) - 1 ] = '\0'; // expand environment strings (if any) in the dll path we read in. if (REG_EXPAND_SZ == dwType) { ZeroMemory(szExpandedDllPath, ARRAYSIZE(szExpandedDllPath)); ExpandEnvironmentStrings(szDllPath, szExpandedDllPath, ARRAYSIZE(szExpandedDllPath)); szExpandedDllPath[ARRAYSIZE(szExpandedDllPath) - 1] = '\0'; StrCpyN(szDllPath, szExpandedDllPath, ARRAYSIZE(szDllPath)); } hinstDll = LoadLibraryEx(szDllPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); if (NULL == hinstDll) { // couldn't load policy. deny access. goto ErrorReturn; } // get the entry func name. cbData = sizeof(szFuncName); lRes = RegQueryValueEx(hkeySub, c_szSecurityPolicyFuncName, NULL, &dwType, (LPBYTE)szFuncName, &cbData); if (ERROR_SUCCESS != lRes) { // policy not correctly registered. deny access. goto ErrorReturn; } pfnGetSMimePolicy = (PFNGetSMimePolicy) GetProcAddress(hinstDll, szFuncName); if (NULL == pfnGetSMimePolicy) { // couldn't get proc address. deny access. goto ErrorReturn; } hr = (pfnGetSMimePolicy) (0, plabel->pszObjIdSecurityPolicy, GetACP(), IID_ISMimePolicyCheckAccess, (LPUNKNOWN *) &pspca); if (FAILED(hr) || (NULL == pspca)) { // couldn't get required interface, goto ErrorReturn; } // Call into the policy module to find out if access is to be denied/granted. hr = pspca->IsAccessGranted(dwFlags, hwnd, plabel, pccertDecrypt, pccertSigner, hcertstor); // fall through to exit. exit: if (pspca) pspca->Release(); if (hinstDll) FreeLibrary(hinstDll); if (hkeySub) RegCloseKey(hkeySub); if (hkey) RegCloseKey(hkey); return hr; ErrorReturn: if (! FAILED(hr)) { // If we had an error, but didn't get a failure code, force a failure. hr |= 0x80000000; } goto exit; } #endif // SMIME_V3 /* * * END --- CAPISTM.CPP --- END * * */