diff options
author | Jannis Hoffmann <jannis@fehcom.de> | 2024-07-09 11:44:11 +0200 |
---|---|---|
committer | Jannis Hoffmann <jannis@fehcom.de> | 2024-07-09 11:44:11 +0200 |
commit | f1b71c9fe7dbb4886588a036399cf5ebe16b7c47 (patch) | |
tree | e07786aa479c9fb6ee3e537078470aaab5454f80 /src/dkimsign.cpp | |
parent | a293489ee83c8b05d845a162dc2a4de026f3775d (diff) |
removed top level directory
Diffstat (limited to 'src/dkimsign.cpp')
-rw-r--r-- | src/dkimsign.cpp | 1106 |
1 files changed, 1106 insertions, 0 deletions
diff --git a/src/dkimsign.cpp b/src/dkimsign.cpp new file mode 100644 index 0000000..03b03e2 --- /dev/null +++ b/src/dkimsign.cpp @@ -0,0 +1,1106 @@ +/***************************************************************************** +* 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 <string.h> +#include <map> + +#include "dkim.h" +#include "dkimsign.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<string,list<string>::reverse_iterator> IterMap; + map<string,list<string>::reverse_iterator>::iterator IterMapIter; + list<string>::reverse_iterator riter; + list<string>::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; +} |