summaryrefslogtreecommitdiff
path: root/src/dkimsign.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dkimsign.cpp')
-rw-r--r--src/dkimsign.cpp1106
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;
+}