/***************************************************************************** * Copyright 2005 Alt-N Technologies, Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * This code incorporates intellectual property owned by Yahoo! and licensed * pursuant to the Yahoo! DomainKeys Patent License Agreement. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Changes done by ¢feh@fehcom.de obeying the above license * *****************************************************************************/ #define _strnicmp strncasecmp #define _stricmp strcasecmp #define LOWORD(l) ((unsigned)(l) & 0xffff) #define HIWORD(l) ((unsigned)(l) >> 16) #include "dkimsign.h" #include #include #include "dkim.h" /***************************************************************************** * * Generating Ed25519 signed message: * * 1. RSA SHA1/SHA256 signatures are generated in streaming mode together with * their hashes. Two different 'contexts' (ctx) are used here: * m_Hdr_shaXctx => Used for signing (EVP_Sign...) -- covering the header only * m_[B,E]dy_shaXctx => Used for hashing (EVP_Digest..) -- covering the body only * * 2. Private keys * For hybrid signing we need two distinct keys: * - RSAKey * - ECCKey * These private keys needs to be passed concurrently to the signature functions. * Given those keys, the signature operation itself is executed in one step. * * 3. Public keys * The 'public keys' need to be deployed in the DNS: * - The RSA public key is DER-header enriched base64-encoded; thus is 9 byte larger * than the 'naked' public key, which size depends on the given parameters. * - The Ed25519 public key is also base64-encoded with a constant length of 60 byte. * * 4. DKIM message preparation scheme * According to RFC 6376 Sec. 3.7, we have a conducted hash for * - the previously available headers in the message; * selected and given in order by h=..., * - any existing DKIM signature fields b=..., * - except for previous added 'X-Authentication ...' header fields, * - and all (new) synthezised DKIM header tokens; except of course for the * signature itself - treated as 'null string': b="". * All this is subject of canonicalization (adding/removing CRLF, whitespaces ...). + As a result, the input for further calculations depends on this order given. * * Results following the 'preparation scheme': * - The message body hash is included in the DKIM header => bh=[m_[B,E]dy_shaXctx]. * - The message signature (including the result of bh=...) => b=[m_Hdr_shaXctx] * * We consider SHA256 as default hash function and SHA1 as exception (on demand). * * 5. Generating (ECC) signatures * According to RFC 8032 Sect 4., we have two possible Ed25519 signature schemes: * * a) PureEd25519, as a one shot signature calculation swallowing the * complete message and employing a shortened SHA-512 hash input. * b) HashEd25519 working again in 'streaming mode' and permitting a choice * for the hash function - which is in RFC 8463 - defined to be SHA-256. * * RFC 8463 in Sect 3 is a bit ambiguous about the signing function: * Ed25519-256 vs. PureEd25519. * In fact (after consulting John Levine), it is PureEd25519. * * In order to allow parallel RSA/Ed25519 processing, we need to generate: * m_Hdr_sha256ctx => Used for RSA signatures * m_Bdy_sha256ctx => The SHA256 hash of selected header parts and body (RSA) * m_Edy_sha256ctx => The SHA256 hash of selected header parts and body (Ed25519) * m_Hdr_ed25519ctx => The signature of the messsage header using PureEd25519 * following the 'preparation' scheme * * Now, two cryptographic informations are provided in the header: * bh=[m_Edy_sha256ctx] => The SHA256 digest of the message (BodyHash), * b=[m_Hdr_ed25519ctx] => The PureED25519 signature. * including the value of bh=... (EmailSignature) * having a length of 512 bits => 64 bytes. * * 6. Hybrid signatures (RSA and Ed25519) * They involve * m_Hdr_sha256ctx => Used for RSA signatures * m_Hdr_ed25519ctx => PureED25519 signature * m_Bdy_sha256ctx => SHA256 digest of the message (BodyHash) for RSA * m_Edy_sha256ctx => SHA256 digest of the message (BodyHash) for Ed25519 * * The EVP_DigestFinal routine has to be replaced by EVP_DigestFinal_ex. * However; after the first call, its content seems to be garbeled. * A common MD for both RSA and Ed2551 seems to be infeasible. * * ------ * * The particular function and variable names chosen here do not obviously match * what they are intended to do. However, in order to keep traceablility of the * changes, I left those untouched. * *****************************************************************************/ CDKIMSign::CDKIMSign() { m_EmptyLineCount = 0; m_pfnHdrCallback = NULL; #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_SignInit(&m_Hdr_sha1ctx, EVP_sha1()); EVP_SignInit(&m_Hdr_sha256ctx, EVP_sha256()); EVP_DigestInit(&m_Bdy_sha1ctx, EVP_sha1()); EVP_DigestInit(&m_Bdy_sha256ctx, EVP_sha256()); #else m_Hdr_sha1ctx = EVP_MD_CTX_create(); EVP_SignInit_ex(m_Hdr_sha1ctx, EVP_sha1(), NULL); m_Hdr_sha256ctx = EVP_MD_CTX_create(); EVP_SignInit_ex(m_Hdr_sha256ctx, EVP_sha256(), NULL); m_Bdy_sha1ctx = EVP_MD_CTX_create(); EVP_DigestInit_ex(m_Bdy_sha1ctx, EVP_sha1(), NULL); m_Bdy_sha256ctx = EVP_MD_CTX_create(); EVP_DigestInit_ex(m_Bdy_sha256ctx, EVP_sha256(), NULL); m_Hdr_ed25519ctx = EVP_MD_CTX_create(); m_Edy_sha256ctx = EVP_MD_CTX_create(); EVP_DigestInit_ex(m_Edy_sha256ctx, EVP_sha256(), NULL); #endif } CDKIMSign::~CDKIMSign() { #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_MD_CTX_cleanup(&m_Hdr_sha1ctx); EVP_MD_CTX_cleanup(&m_Hdr_sha256ctx); EVP_MD_CTX_cleanup(&m_Bdy_sha1ctx); EVP_MD_CTX_cleanup(&m_Bdy_sha256ctx); #else EVP_MD_CTX_free(m_Hdr_sha1ctx); EVP_MD_CTX_free(m_Hdr_sha256ctx); EVP_MD_CTX_free(m_Hdr_ed25519ctx); EVP_MD_CTX_free(m_Bdy_sha1ctx); EVP_MD_CTX_free(m_Bdy_sha256ctx); EVP_MD_CTX_free(m_Edy_sha256ctx); #endif } //////////////////////////////////////////////////////////////////////////////// // // Init - save the options // //////////////////////////////////////////////////////////////////////////////// int CDKIMSign::Init(DKIMSignOptions *pOptions) { int nRet = CDKIMBase::Init(); m_Canon = pOptions->nCanon; // as of draft 01, these are the only allowed signing types: if ((m_Canon != DKIM_SIGN_SIMPLE_RELAXED) && (m_Canon != DKIM_SIGN_RELAXED) && (m_Canon != DKIM_SIGN_RELAXED_SIMPLE)) { m_Canon = DKIM_SIGN_SIMPLE; } sSelector.assign(pOptions->szSelector); eSelector.assign(pOptions->szSelectorE); m_pfnHdrCallback = pOptions->pfnHeaderCallback; sDomain.assign(pOptions->szDomain); m_IncludeBodyLengthTag = (pOptions->nIncludeBodyLengthTag != 0); m_nBodyLength = 0; m_ExpireTime = pOptions->expireTime; sIdentity.assign(pOptions->szIdentity); m_nIncludeTimeStamp = pOptions->nIncludeTimeStamp; m_nIncludeQueryMethod = pOptions->nIncludeQueryMethod; m_nIncludeCopiedHeaders = pOptions->nIncludeCopiedHeaders; // NOTE: the following line is not backwards compatible with MD 8.0.3 // because the szRequiredHeaders member was added after the release //sRequiredHeaders.assign(pOptions->szRequiredHeaders); //make sure there is a colon after the last header in the list if ((sRequiredHeaders.size() > 0) && sRequiredHeaders.at(sRequiredHeaders.size() - 1) != ':') { sRequiredHeaders.append(":"); } m_nHash = pOptions->nHash; m_bReturnedSigAssembled = false; m_sCopiedHeaders.erase(); // Initializes ED25519 header fields SigHdrs #if ((OPENSSL_VERSION_NUMBER > 0x10101000L)) SigHdrs.assign(""); m_SigHdrs = 0; #endif return nRet; } //////////////////////////////////////////////////////////////////////////////// // // Hash - update the hash // //////////////////////////////////////////////////////////////////////////////// void CDKIMSign::Hash(const char *szBuffer, int nBufLength, bool bHdr) { /** START DEBUG CODE ** if (nBufLength == 2 && szBuffer[0] == '\r' && szBuffer[1] == '\n') { printf("[CRLF]\n"); } else { char *szDbg = new char[nBufLength+1]; strncpy(szDbg,szBuffer,nBufLength); szDbg[nBufLength] = '\0'; printf("[%s]\n",szDbg); } *** if (fpdebug == NULL) { fpdebug = fopen("canon.msg", "wb"); } fwrite(szBuffer,1,nBufLength,fpdebug); ** END DEBUG CODE **/ if (bHdr) { /* Generate signature: b=... */ if ((m_nHash == DKIM_HASH_SHA1) || (m_nHash == DKIM_HASH_SHA1_AND_SHA256)) #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_SignUpdate(&m_Hdr_sha1ctx, szBuffer, nBufLength); #else EVP_SignUpdate(m_Hdr_sha1ctx, szBuffer, nBufLength); #endif if ((m_nHash == DKIM_HASH_SHA256) || (m_nHash == DKIM_HASH_SHA1_AND_SHA256) || (m_nHash == DKIM_HASH_RSA256_AND_ED25519)) #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_SignUpdate(&m_Hdr_sha256ctx, szBuffer, nBufLength); #else EVP_SignUpdate(m_Hdr_sha256ctx, szBuffer, nBufLength); #endif #if ((OPENSSL_VERSION_NUMBER > 0x10101000L)) if ((m_nHash == DKIM_HASH_ED25519) || (m_nHash == DKIM_HASH_RSA256_AND_ED25519)) { SigHdrs.append(szBuffer, nBufLength); m_SigHdrs += nBufLength; } #endif } else { /* lets go for body hash values: bh=... (either SHA1 or SHA256) */ if ((m_nHash == DKIM_HASH_SHA1) || (m_nHash == DKIM_HASH_SHA1_AND_SHA256)) #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_DigestUpdate(&m_Bdy_sha1ctx, szBuffer, nBufLength); #else EVP_DigestUpdate(m_Bdy_sha1ctx, szBuffer, nBufLength); #endif if (m_nHash != DKIM_HASH_SHA1) #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_DigestUpdate(&m_Bdy_sha256ctx, szBuffer, nBufLength); #else EVP_DigestUpdate(m_Bdy_sha256ctx, szBuffer, nBufLength); #endif #if ((OPENSSL_VERSION_NUMBER > 0x10101000L)) if ((m_nHash == DKIM_HASH_ED25519) || (m_nHash == DKIM_HASH_RSA256_AND_ED25519)) EVP_DigestUpdate(m_Edy_sha256ctx, szBuffer, nBufLength); #endif } } //////////////////////////////////////////////////////////////////////////////// // // SignThisTag - return boolean whether or not to sign this tag // //////////////////////////////////////////////////////////////////////////////// bool CDKIMSign::SignThisTag(const string& sTag) { bool bRet = true; if (_strnicmp(sTag.c_str(), "X-", 2) == 0 || _stricmp(sTag.c_str(), "Authentication-Results:") == 0 || _stricmp(sTag.c_str(), "Return-Path:") == 0) { bRet = false; } return bRet; } bool ConvertHeaderToQuotedPrintable(const char *source, char *dest) { bool bConvert = false; // do quoted printable static unsigned char hexchars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; unsigned char *d = (unsigned char *)dest; for (const unsigned char *s = (const unsigned char *)source; *s != '\0'; s++) { if (*s >= 33 && *s <= 126 && *s != '=' && *s != ':' && *s != ';' && *s != '|') { *d++ = *s; } else { bConvert = true; *d++ = '='; *d++ = hexchars[*s >> 4]; *d++ = hexchars[*s & 15]; } } *d = '\0'; return bConvert; } //////////////////////////////////////////////////////////////////////////////// // // GetHeaderParams - Extract any needed header parameters // //////////////////////////////////////////////////////////////////////////////// void CDKIMSign::GetHeaderParams(const string& sHdr) { if (_strnicmp(sHdr.c_str(), "X", 1) == 0) return; if (_strnicmp(sHdr.c_str(), "From:", 5) == 0) { sFrom.assign(sHdr.c_str() + 5); } if (_strnicmp(sHdr.c_str(), "Sender:", 7) == 0) { sSender.assign(sHdr.c_str() + 7); } if (m_nIncludeCopiedHeaders) { string::size_type pos = sHdr.find(':'); if (pos != string::npos) { string sTag, sValue; char *workBuffer = new char[sHdr.size() * 3 + 1]; sTag.assign(sHdr.substr(0, pos)); sValue.assign(sHdr.substr(pos + 1, string::npos)); ConvertHeaderToQuotedPrintable(sTag.c_str(), workBuffer); if (!m_sCopiedHeaders.empty()) { m_sCopiedHeaders.append("|"); } m_sCopiedHeaders.append(workBuffer); m_sCopiedHeaders.append(":"); ConvertHeaderToQuotedPrintable(sValue.c_str(), workBuffer); m_sCopiedHeaders.append(workBuffer); delete[] workBuffer; } } } //////////////////////////////////////////////////////////////////////////////// // // ProcessHeaders - sign headers and save needed parameters (this is a lie) // //////////////////////////////////////////////////////////////////////////////// int CDKIMSign::ProcessHeaders(void) { map::reverse_iterator> IterMap; map::reverse_iterator>::iterator IterMapIter; list::reverse_iterator riter; list::iterator iter; string sTag; bool bFromHeaderFound = false; // walk the header list for (iter = HeaderList.begin(); iter != HeaderList.end(); iter++) { sTag.assign(*iter); // look for a colon string::size_type pos = sTag.find(':'); if (pos != string::npos) { int nSignThisTag = 1; // hack off anything past the colon sTag.erase(pos + 1, string::npos); // is this the From: header? if (_stricmp(sTag.c_str(), "From:") == 0) { bFromHeaderFound = true; nSignThisTag = 1; IsRequiredHeader(sTag); // remove from required header list } // is this in the list of headers that must be signed? else if (IsRequiredHeader(sTag)) { nSignThisTag = 1; } else { if (m_pfnHdrCallback) { nSignThisTag = m_pfnHdrCallback(iter->c_str()); } else { nSignThisTag = SignThisTag(sTag) ? 1 : 0; } } // save header parameters GetHeaderParams(*iter); if (nSignThisTag > 0) { // add this tag to h= hParam.append(sTag); IterMapIter = IterMap.find(sTag); riter = (IterMapIter == IterMap.end()) ? HeaderList.rbegin() : IterMapIter->second; // walk the list in reverse looking for the last instance of this header while (riter != HeaderList.rend()) { if (_strnicmp(riter->c_str(), sTag.c_str(), sTag.size()) == 0) { ProcessHeader(*riter); // save the reverse iterator position for this tag riter++; IterMap[sTag] = riter; break; } riter++; } } } } if (!bFromHeaderFound) { string sFrom("From:"); hParam.append(sFrom); IsRequiredHeader(sFrom); // remove from required header list // Hash("\r\n",2); } hParam.append(sRequiredHeaders); // string::size_type end = sRequiredHeaders.find(':'); // while (end != string::npos) // { // Hash("\r\n",2); // end = sRequiredHeaders.find(':', end+1); // } // remove the last colon from h= if (hParam.at(hParam.size() - 1) == ':') hParam.erase(hParam.size() - 1, string::npos); return DKIM_SUCCESS; } void CDKIMSign::ProcessHeader(const string& sHdr) { switch (HIWORD(m_Canon)) { case DKIM_CANON_SIMPLE: Hash(sHdr.c_str(), sHdr.size(), true); Hash("\r\n", 2, true); break; case DKIM_CANON_NOWSP: { string sTemp = sHdr; RemoveSWSP(sTemp); // convert characters before ':' to lower case for (char *s = (char *)sTemp.c_str(); *s != '\0' && *s != ':'; s++) { if (*s >= 'A' && *s <= 'Z') *s += 'a' - 'A'; } Hash(sTemp.c_str(), sTemp.size(), true); Hash("\r\n", 2, true); } break; case DKIM_CANON_RELAXED: { string sTemp = RelaxHeader(sHdr); Hash(sTemp.c_str(), sTemp.length(), true); Hash("\r\n", 2, true); } break; } } int CDKIMSign::ProcessBody(char *szBuffer, int nBufLength, bool bEOF) { switch (LOWORD(m_Canon)) { case DKIM_CANON_SIMPLE: if (nBufLength > 0) { while (m_EmptyLineCount > 0) { Hash("\r\n", 2, false); m_nBodyLength += 2; m_EmptyLineCount--; } Hash(szBuffer, nBufLength, false); Hash("\r\n", 2, false); m_nBodyLength += nBufLength + 2; } else { m_EmptyLineCount++; if (bEOF) { Hash("\r\n", 2, false); m_nBodyLength += 2; } } break; case DKIM_CANON_NOWSP: RemoveSWSP(szBuffer, nBufLength); if (nBufLength > 0) { Hash(szBuffer, nBufLength, false); m_nBodyLength += nBufLength; } break; case DKIM_CANON_RELAXED: CompressSWSP(szBuffer, nBufLength); if (nBufLength > 0) { while (m_EmptyLineCount > 0) { Hash("\r\n", 2, false); m_nBodyLength += 2; m_EmptyLineCount--; } Hash(szBuffer, nBufLength, false); m_nBodyLength += nBufLength; if (!bEOF) { Hash("\r\n", 2, false); m_nBodyLength += 2; } } else m_EmptyLineCount++; break; } return DKIM_SUCCESS; } bool CDKIMSign::ParseFromAddress(void) { string::size_type pos; string sAddress; if (!sFrom.empty()) { sAddress.assign(sFrom); } else if (!sSender.empty()) { sAddress.assign(sSender); } else { return false; } // simple for now, beef it up later // remove '<' and anything before it pos = sAddress.find('<'); if (pos != string::npos) sAddress.erase(0, pos); // remove '>' and anything after it pos = sAddress.find('>'); if (pos != string::npos) sAddress.erase(pos, string::npos); // look for '@' symbol pos = sAddress.find('@'); if (pos == string::npos) return false; if (sDomain.empty()) { sDomain.assign(sAddress.c_str() + pos + 1); RemoveSWSP(sDomain); } return true; } //////////////////////////////////////////////////////////////////////////////// // // InitSig - initialize signature folding algorithm // //////////////////////////////////////////////////////////////////////////////// void CDKIMSign::InitSig(void) { m_sSig.reserve(1024); m_sSig.assign("DKIM-Signature:"); m_nSigPos = m_sSig.size(); } //////////////////////////////////////////////////////////////////////////////// // // AddTagToSig - add tag and value to signature folding if necessary // if bFold, fold at cbrk char // //////////////////////////////////////////////////////////////////////////////// void CDKIMSign::AddTagToSig(const char *const Tag, const string& sValue, char cbrk, bool bFold) { int nTagLen = strlen(Tag); AddInterTagSpace((!bFold) ? sValue.size() + nTagLen + 2 : nTagLen + 2); m_sSig.append(Tag); m_sSig.append("="); m_nSigPos += 1 + nTagLen; if (!bFold) { m_sSig.append(sValue); m_nSigPos += sValue.size(); } else { AddFoldedValueToSig(sValue, cbrk); } m_sSig.append(";"); m_nSigPos++; } //////////////////////////////////////////////////////////////////////////////// // // AddTagToSig - add tag and numeric value to signature folding if necessary // //////////////////////////////////////////////////////////////////////////////// void CDKIMSign::AddTagToSig(const char *const Tag, unsigned long nValue) { char szValue[64]; sprintf(szValue, "%lu", nValue); AddTagToSig(Tag, szValue, 0, false); } //////////////////////////////////////////////////////////////////////////////// // // AddInterTagSpace - add space or fold here // //////////////////////////////////////////////////////////////////////////////// void CDKIMSign::AddInterTagSpace(int nSizeOfNextTag) { if (m_nSigPos + nSizeOfNextTag + 1 > OptimalHeaderLineLength) { // m_sSig.append("\r\n\t"); m_sSig.append("\r\n "); /* s/qmail style */ m_nSigPos = 1; } else { m_sSig.append(" "); m_nSigPos++; } } //////////////////////////////////////////////////////////////////////////////// // // AddTagToSig - add value to signature folding if necessary // if cbrk == 0 fold anywhere, otherwise fold only at cbrk // //////////////////////////////////////////////////////////////////////////////// void CDKIMSign::AddFoldedValueToSig(const string& sValue, char cbrk) { string::size_type pos = 0; if (cbrk == 0) { // fold anywhere while (pos < sValue.size()) { string::size_type len = OptimalHeaderLineLength - m_nSigPos; if (len > sValue.size() - pos) len = sValue.size() - pos; m_sSig.append(sValue.substr(pos, len)); m_nSigPos += len; pos += len; if (pos < sValue.size()) { // m_sSig.append("\r\n\t"); m_sSig.append("\r\n "); /* s/qmail style */ m_nSigPos = 1; } } } else { // fold only at cbrk while (pos < sValue.size()) { string::size_type len = OptimalHeaderLineLength - m_nSigPos; string::size_type brkpos; if (sValue.size() - pos < len) { brkpos = sValue.size(); } else { brkpos = sValue.rfind(cbrk, pos + len); } if (brkpos == string::npos || brkpos < pos) { brkpos = sValue.find(cbrk, pos); if (brkpos == string::npos) { brkpos = sValue.size(); } } len = brkpos - pos + 1; m_sSig.append(sValue.substr(pos, len)); m_nSigPos += len; pos += len; if (pos < sValue.size()) { // m_sSig.append("\r\n\t"); m_sSig.append("\r\n "); /* s/qmail style */ m_nSigPos = 1; } } } } //////////////////////////////////////////////////////////////////////////////// // // GetSig - compute hash and return signature header in szSignature // //////////////////////////////////////////////////////////////////////////////// int CDKIMSign::GetSig2(char *szRSAKey, char *szECCKey, char **pszSignature) { if (szRSAKey == NULL && szECCKey == NULL) { return DKIM_BAD_PRIVATE_KEY; } if (pszSignature == NULL) { return DKIM_BUFFER_TOO_SMALL; } int nRet = AssembleReturnedSig(szRSAKey, szECCKey); if (nRet != DKIM_SUCCESS) return nRet; *pszSignature = (char *)m_sReturnedSig.c_str(); return DKIM_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // // IsRequiredHeader - Check if header in required list. If so, delete // header from list. // //////////////////////////////////////////////////////////////////////////////// bool CDKIMSign::IsRequiredHeader(const string& sTag) { string::size_type start = 0; string::size_type end = sRequiredHeaders.find(':'); while (end != string::npos) { // check for a zero-length header if (start == end) { sRequiredHeaders.erase(start, 1); } else { if (_stricmp(sTag.c_str(), sRequiredHeaders.substr(start, end - start + 1).c_str()) == 0) { sRequiredHeaders.erase(start, end - start + 1); return true; } else { start = end + 1; } } end = sRequiredHeaders.find(':', start); } return false; } //////////////////////////////////////////////////////////////////////////////// // // ConstructSignature // // Here, we don't construct the 'signature' but rather the DKIM header // multiply and indidually crafted for each distinct nSigAlg method // // nSigAlg: DKIM_HASH_SHA1, DKIM_HASH_SHA256, DKIM_HASH_ED25519 // //////////////////////////////////////////////////////////////////////////////// int CDKIMSign::ConstructSignature(char *szPrivKey, int nSigAlg) { string sSignedSig; unsigned char *sig; EVP_PKEY *pkey = 0; BIO *bio, *b64; unsigned int siglen; int size; int len; char *buf; int nSignRet; /* construct the DKIM-Signature: header and add to hash */ InitSig(); AddTagToSig("v", "1", 0, false); switch (nSigAlg) { case DKIM_HASH_SHA1: AddTagToSig("a", "rsa-sha1", 0, false); break; case DKIM_HASH_SHA256: AddTagToSig("a", "rsa-sha256", 0, false); break; case DKIM_HASH_ED25519: AddTagToSig("a", "ed25519-sha256", 0, false); break; } switch (m_Canon) { case DKIM_SIGN_SIMPLE: AddTagToSig("c", "simple/simple", 0, false); break; case DKIM_SIGN_SIMPLE_RELAXED: AddTagToSig("c", "simple/relaxed", 0, false); break; case DKIM_SIGN_RELAXED: AddTagToSig("c", "relaxed/relaxed", 0, false); break; case DKIM_SIGN_RELAXED_SIMPLE: AddTagToSig("c", "relaxed/simple", 0, false); break; } AddTagToSig("d", sDomain, 0, false); if (nSigAlg == DKIM_HASH_ED25519) AddTagToSig("s", eSelector, 0, false); else AddTagToSig("s", sSelector, 0, false); if (m_IncludeBodyLengthTag) { AddTagToSig("l", m_nBodyLength); } if (m_nIncludeTimeStamp != 0) { time_t t; time(&t); AddTagToSig("t", t); } if (m_ExpireTime != 0) { AddTagToSig("x", m_ExpireTime); } if (!sIdentity.empty()) { AddTagToSig("i", sIdentity, 0, false); } if (m_nIncludeQueryMethod) { AddTagToSig("q", "dns/txt", 0, false); } AddTagToSig("h", hParam, ':', true); // copied headers follow the ':' if (m_nIncludeCopiedHeaders) { AddTagToSig("z", m_sCopiedHeaders, 0, true); } /* Set up context for (body) hash */ unsigned char Hash[4096]; unsigned int nHashLen = 0; switch (nSigAlg) { case DKIM_HASH_SHA1: #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_DigestFinal(&m_Bdy_sha1ctx, Hash, &nHashLen); break; #else EVP_DigestFinal_ex(m_Bdy_sha1ctx, Hash, &nHashLen); break; #endif case DKIM_HASH_SHA256: #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_DigestFinal(&m_Bdy_sha256ctx, Hash, &nHashLen); break; #else EVP_DigestFinal_ex(m_Bdy_sha256ctx, Hash, &nHashLen); break; #endif #if (OPENSSL_VERSION_NUMBER > 0x10101000L) case DKIM_HASH_ED25519: EVP_DigestFinal_ex(m_Edy_sha256ctx, Hash, &nHashLen); break; #endif } bio = BIO_new(BIO_s_mem()); if (!bio) return DKIM_OUT_OF_MEMORY; b64 = BIO_new(BIO_f_base64()); if (!b64) { BIO_free(bio); return DKIM_OUT_OF_MEMORY; } BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO_push(b64, bio); if (BIO_write(b64, Hash, nHashLen) < (int)nHashLen) { BIO_free_all(b64); return DKIM_OUT_OF_MEMORY; } BIO_flush(b64); len = nHashLen * 2; buf = new char[len]; if (buf == NULL) { BIO_free_all(b64); return DKIM_OUT_OF_MEMORY; } size = BIO_read(bio, buf, len); BIO_free_all(b64); // this should never happen if (size >= len) { delete[] buf; return DKIM_OUT_OF_MEMORY; } buf[size] = '\0'; AddTagToSig("bh", buf, 0, true); delete[] buf; AddInterTagSpace(3); m_sSig.append("b="); m_nSigPos += 2; // Force a full copy - no reference copies please sSignedSig.assign(m_sSig.c_str()); // note that since we're not calling hash here, need to dump this // to the debug file if you want the full canonical form string sTemp; if (HIWORD(m_Canon) == DKIM_CANON_RELAXED) { sTemp = RelaxHeader(sSignedSig); } else { sTemp = sSignedSig.c_str(); } /* Update streaming signatures */ switch (nSigAlg) { case DKIM_HASH_SHA1: #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_SignUpdate(&m_Hdr_sha1ctx, sTemp.c_str(), sTemp.size()); break; #else EVP_SignUpdate(m_Hdr_sha1ctx, sTemp.c_str(), sTemp.size()); break; #endif case DKIM_HASH_SHA256: #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) EVP_SignUpdate(&m_Hdr_sha256ctx, sTemp.c_str(), sTemp.size()); break; #else EVP_SignUpdate(m_Hdr_sha256ctx, sTemp.c_str(), sTemp.size()); break; #endif #if ((OPENSSL_VERSION_NUMBER > 0x10101000L)) case DKIM_HASH_ED25519: SigHdrs.append(sTemp.c_str(), sTemp.size()); m_SigHdrs += sTemp.size(); break; #endif } bio = BIO_new_mem_buf(szPrivKey, -1); if (bio == NULL) return DKIM_OUT_OF_MEMORY; pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); // FIXME - done BIO_free(bio); if (!pkey) { return DKIM_BAD_PRIVATE_KEY; } siglen = EVP_PKEY_size(pkey); sig = (unsigned char *)OPENSSL_malloc(siglen); if (sig == NULL) { EVP_PKEY_free(pkey); return DKIM_OUT_OF_MEMORY; } /* Finish streaming signature and potentially go for Ed25519 signatures */ size_t sig_len; unsigned char *SignMsg; switch (nSigAlg) { case DKIM_HASH_SHA1: #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) nSignRet = EVP_SignFinal(&m_Hdr_sha1ctx, sig, &siglen, pkey); break; #else nSignRet = EVP_SignFinal(m_Hdr_sha1ctx, sig, &siglen, pkey); break; #endif case DKIM_HASH_SHA256: #if ( \ (OPENSSL_VERSION_NUMBER < 0x10100000L) \ || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L)) nSignRet = EVP_SignFinal(&m_Hdr_sha256ctx, sig, &siglen, pkey); break; #else nSignRet = EVP_SignFinal(m_Hdr_sha256ctx, sig, &siglen, pkey); break; #endif #if (OPENSSL_VERSION_NUMBER > 0x10101000L) case DKIM_HASH_ED25519: EVP_DigestSignInit(m_Hdr_ed25519ctx, NULL, NULL, NULL, pkey); SignMsg = (unsigned char *)SigHdrs.c_str(); EVP_DigestSign(m_Hdr_ed25519ctx, NULL, &sig_len, SignMsg, m_SigHdrs); sig = (unsigned char *)OPENSSL_malloc(sig_len); nSignRet = EVP_DigestSign(m_Hdr_ed25519ctx, sig, &sig_len, SignMsg, m_SigHdrs); siglen = (unsigned int)sig_len; break; #endif } EVP_PKEY_free(pkey); if (!nSignRet) { OPENSSL_free(sig); return DKIM_BAD_PRIVATE_KEY; // key too small } bio = BIO_new(BIO_s_mem()); if (!bio) { return DKIM_OUT_OF_MEMORY; } b64 = BIO_new(BIO_f_base64()); if (!b64) { BIO_free(bio); return DKIM_OUT_OF_MEMORY; } BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO_push(b64, bio); if (BIO_write(b64, sig, siglen) < (int)siglen) { OPENSSL_free(sig); BIO_free_all(b64); return DKIM_OUT_OF_MEMORY; } BIO_flush(b64); OPENSSL_free(sig); len = siglen * 2; buf = new char[len]; if (buf == NULL) { BIO_free_all(b64); return DKIM_OUT_OF_MEMORY; } size = BIO_read(bio, buf, len); BIO_free_all(b64); // this should never happen if (size >= len) { delete[] buf; return DKIM_OUT_OF_MEMORY; } buf[size] = '\0'; AddFoldedValueToSig(buf, 0); delete[] buf; return DKIM_SUCCESS; } //////////////////////////////////////////////////////////////////////////////// // // AssembleReturnSig // // calls ConstructSignature // for all different hashes and signature key files // //////////////////////////////////////////////////////////////////////////////// int CDKIMSign::AssembleReturnedSig(char *szRSAKey, char *szECCKey) { int nRet; if (m_bReturnedSigAssembled) return DKIM_SUCCESS; ProcessFinal(); if (ParseFromAddress() == false) { return DKIM_NO_SENDER; } string ed25519Sig, sha256Sig, sha1Sig; if ((m_nHash == DKIM_HASH_ED25519) || (m_nHash == DKIM_HASH_RSA256_AND_ED25519)) { nRet = ConstructSignature(szECCKey, DKIM_HASH_ED25519); if (nRet == DKIM_SUCCESS) { ed25519Sig.assign(m_sSig); } else { return nRet; } } if ((m_nHash == DKIM_HASH_SHA256) || (m_nHash == DKIM_HASH_SHA1_AND_SHA256) || (m_nHash == DKIM_HASH_RSA256_AND_ED25519)) { nRet = ConstructSignature(szRSAKey, DKIM_HASH_SHA256); if (nRet == DKIM_SUCCESS) { sha256Sig.assign(m_sSig); } else { return nRet; } } if ((m_nHash == DKIM_HASH_SHA1) || (m_nHash == DKIM_HASH_SHA1_AND_SHA256)) { nRet = ConstructSignature(szRSAKey, DKIM_HASH_SHA1); if (nRet == DKIM_SUCCESS) { sha1Sig.assign(m_sSig); } else { return nRet; } } // fclose(fpdebug); // fpdebug = NULL; if (!ed25519Sig.empty()) { /* if (!m_sReturnedSig.empty()) { m_sReturnedSig.append("\r\n"); } */ m_sReturnedSig.assign(ed25519Sig); } if (!sha1Sig.empty()) { if (!m_sReturnedSig.empty()) { m_sReturnedSig.append("\r\n"); } m_sReturnedSig.append(sha1Sig); } if (!sha256Sig.empty()) { if (!m_sReturnedSig.empty()) { m_sReturnedSig.append("\r\n"); } m_sReturnedSig.append(sha256Sig); } m_bReturnedSigAssembled = true; return DKIM_SUCCESS; }