s/qmail 4.2.29a
Next generation secure email transport
Loading...
Searching...
No Matches
dkimsign.cpp
Go to the documentation of this file.
1/*****************************************************************************
2* Copyright 2005 Alt-N Technologies, Ltd.
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* This code incorporates intellectual property owned by Yahoo! and licensed
11* pursuant to the Yahoo! DomainKeys Patent License Agreement.
12*
13* Unless required by applicable law or agreed to in writing, software
14* distributed under the License is distributed on an "AS IS" BASIS,
15* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16* See the License for the specific language governing permissions and
17* limitations under the License.
18*
19* Changes done by ¢feh@fehcom.de obeying the above license
20*
21*****************************************************************************/
22
23#define _strnicmp strncasecmp
24#define _stricmp strcasecmp
25#define LOWORD(l) ((unsigned)(l) & 0xffff)
26#define HIWORD(l) ((unsigned)(l) >> 16)
27
28#include <string.h>
29#include <map>
30
31#include "dkim.h"
32#include "dkimsign.h"
33
34/*****************************************************************************
35*
36* Generating Ed25519 signed message:
37*
38* 1. RSA SHA1/SHA256 signatures are generated in streaming mode together with
39* their hashes. Two different 'contexts' (ctx) are used here:
40* m_Hdr_shaXctx => Used for signing (EVP_Sign...) -- covering the header only
41* m_[B,E]dy_shaXctx => Used for hashing (EVP_Digest..) -- covering the body only
42*
43* 2. Private keys
44* For hybrid signing we need two distinct keys:
45* - RSAKey
46* - ECCKey
47* These private keys needs to be passed concurrently to the signature functions.
48* Given those keys, the signature operation itself is executed in one step.
49*
50* 3. Public keys
51* The 'public keys' need to be deployed in the DNS:
52* - The RSA public key is DER-header enriched base64-encoded; thus is 9 byte larger
53* than the 'naked' public key, which size depends on the given parameters.
54* - The Ed25519 public key is also base64-encoded with a constant length of 60 byte.
55*
56* 4. DKIM message preparation scheme
57* According to RFC 6376 Sec. 3.7, we have a conducted hash for
58* - the previously available headers in the message;
59* selected and given in order by h=...,
60* - any existing DKIM signature fields b=...,
61* - except for previous added 'X-Authentication ...' header fields,
62* - and all (new) synthezised DKIM header tokens; except of course for the
63* signature itself - treated as 'null string': b="".
64* All this is subject of canonicalization (adding/removing CRLF, whitespaces ...).
65+ As a result, the input for further calculations depends on this order given.
66*
67* Results following the 'preparation scheme':
68* - The message body hash is included in the DKIM header => bh=[m_[B,E]dy_shaXctx].
69* - The message signature (including the result of bh=...) => b=[m_Hdr_shaXctx]
70*
71* We consider SHA256 as default hash function and SHA1 as exception (on demand).
72*
73* 5. Generating (ECC) signatures
74* According to RFC 8032 Sect 4., we have two possible Ed25519 signature schemes:
75*
76* a) PureEd25519, as a one shot signature calculation swallowing the
77* complete message and employing a shortened SHA-512 hash input.
78* b) HashEd25519 working again in 'streaming mode' and permitting a choice
79* for the hash function - which is in RFC 8463 - defined to be SHA-256.
80*
81* RFC 8463 in Sect 3 is a bit ambiguous about the signing function:
82* Ed25519-256 vs. PureEd25519.
83* In fact (after consulting John Levine), it is PureEd25519.
84*
85* In order to allow parallel RSA/Ed25519 processing, we need to generate:
86* m_Hdr_sha256ctx => Used for RSA signatures
87* m_Bdy_sha256ctx => The SHA256 hash of selected header parts and body (RSA)
88* m_Edy_sha256ctx => The SHA256 hash of selected header parts and body (Ed25519)
89* m_Hdr_ed25519ctx => The signature of the messsage header using PureEd25519
90* following the 'preparation' scheme
91*
92* Now, two cryptographic informations are provided in the header:
93* bh=[m_Edy_sha256ctx] => The SHA256 digest of the message (BodyHash),
94* b=[m_Hdr_ed25519ctx] => The PureED25519 signature.
95* including the value of bh=... (EmailSignature)
96* having a length of 512 bits => 64 bytes.
97*
98* 6. Hybrid signatures (RSA and Ed25519)
99* They involve
100* m_Hdr_sha256ctx => Used for RSA signatures
101* m_Hdr_ed25519ctx => PureED25519 signature
102* m_Bdy_sha256ctx => SHA256 digest of the message (BodyHash) for RSA
103* m_Edy_sha256ctx => SHA256 digest of the message (BodyHash) for Ed25519
104*
105* The EVP_DigestFinal routine has to be replaced by EVP_DigestFinal_ex.
106* However; after the first call, its content seems to be garbeled.
107* A common MD for both RSA and Ed2551 seems to be infeasible.
108*
109* ------
110*
111* The particular function and variable names chosen here do not obviously match
112* what they are intended to do. However, in order to keep traceablility of the
113* changes, I left those untouched.
114*
115*****************************************************************************/
116
118{
120 m_pfnHdrCallback = NULL;
121
122#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
123 EVP_SignInit(&m_Hdr_sha1ctx,EVP_sha1());
124 EVP_SignInit(&m_Hdr_sha256ctx,EVP_sha256());
125 EVP_DigestInit(&m_Bdy_sha1ctx,EVP_sha1());
126 EVP_DigestInit(&m_Bdy_sha256ctx,EVP_sha256());
127#else
128 m_Hdr_sha1ctx = EVP_MD_CTX_create();
129 EVP_SignInit_ex(m_Hdr_sha1ctx,EVP_sha1(),NULL);
130
131 m_Hdr_sha256ctx = EVP_MD_CTX_create();
132 EVP_SignInit_ex(m_Hdr_sha256ctx,EVP_sha256(),NULL);
133
134 m_Bdy_sha1ctx = EVP_MD_CTX_create();
135 EVP_DigestInit_ex(m_Bdy_sha1ctx,EVP_sha1(),NULL);
136
137 m_Bdy_sha256ctx = EVP_MD_CTX_create();
138 EVP_DigestInit_ex(m_Bdy_sha256ctx,EVP_sha256(),NULL);
139
140 m_Hdr_ed25519ctx = EVP_MD_CTX_create();
141
142 m_Edy_sha256ctx = EVP_MD_CTX_create();
143 EVP_DigestInit_ex(m_Edy_sha256ctx,EVP_sha256(),NULL);
144#endif
145}
146
148{
149#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
150 EVP_MD_CTX_cleanup(&m_Hdr_sha1ctx);
151 EVP_MD_CTX_cleanup(&m_Hdr_sha256ctx);
152 EVP_MD_CTX_cleanup(&m_Bdy_sha1ctx);
153 EVP_MD_CTX_cleanup(&m_Bdy_sha256ctx);
154#else
155 EVP_MD_CTX_free(m_Hdr_sha1ctx);
156 EVP_MD_CTX_free(m_Hdr_sha256ctx);
157 EVP_MD_CTX_free(m_Hdr_ed25519ctx);
158 EVP_MD_CTX_free(m_Bdy_sha1ctx);
159 EVP_MD_CTX_free(m_Bdy_sha256ctx);
160 EVP_MD_CTX_free(m_Edy_sha256ctx);
161#endif
162}
163
165//
166// Init - save the options
167//
170{
171 int nRet = CDKIMBase::Init();
172
173 m_Canon = pOptions->nCanon;
174
175 // as of draft 01, these are the only allowed signing types:
180 }
181
182 sSelector.assign(pOptions->szSelector);
183 eSelector.assign(pOptions->szSelectorE);
184
186
187 sDomain.assign(pOptions->szDomain);
188
190
191 m_nBodyLength = 0;
192
193 m_ExpireTime = pOptions->expireTime;
194
195 sIdentity.assign(pOptions->szIdentity);
196
200
201 // NOTE: the following line is not backwards compatible with MD 8.0.3
202 // because the szRequiredHeaders member was added after the release
203 //sRequiredHeaders.assign(pOptions->szRequiredHeaders);
204
205 //make sure there is a colon after the last header in the list
206 if ((sRequiredHeaders.size() > 0) &&
207 sRequiredHeaders.at(sRequiredHeaders.size() - 1) != ':') {
208 sRequiredHeaders.append(":");
209 }
210
211 m_nHash = pOptions->nHash;
213 m_sCopiedHeaders.erase();
214
215 // Initializes ED25519 header fields SigHdrs
216#if ((OPENSSL_VERSION_NUMBER > 0x10101000L))
217 SigHdrs.assign("");
218 m_SigHdrs = 0;
219#endif
220
221 return nRet;
222}
223
225//
226// Hash - update the hash
227//
229void CDKIMSign::Hash(const char *szBuffer,int nBufLength,bool bHdr)
230{
231
250 if (bHdr) { /* Generate signature: b=... */
251 if ((m_nHash == DKIM_HASH_SHA1) ||
253#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
254 EVP_SignUpdate(&m_Hdr_sha1ctx,szBuffer,nBufLength);
255#else
256 EVP_SignUpdate(m_Hdr_sha1ctx,szBuffer,nBufLength);
257#endif
258 if ((m_nHash == DKIM_HASH_SHA256) ||
261#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
262 EVP_SignUpdate(&m_Hdr_sha256ctx,szBuffer,nBufLength);
263#else
264 EVP_SignUpdate(m_Hdr_sha256ctx,szBuffer,nBufLength);
265#endif
266#if ((OPENSSL_VERSION_NUMBER > 0x10101000L))
267 if ((m_nHash == DKIM_HASH_ED25519) ||
269 SigHdrs.append(szBuffer,nBufLength);
270 m_SigHdrs += nBufLength;
271 }
272#endif
273 } else { /* lets go for body hash values: bh=... (either SHA1 or SHA256) */
274 if ((m_nHash == DKIM_HASH_SHA1) ||
276#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
277 EVP_DigestUpdate(&m_Bdy_sha1ctx,szBuffer,nBufLength);
278#else
279 EVP_DigestUpdate(m_Bdy_sha1ctx,szBuffer,nBufLength);
280#endif
281 if (m_nHash != DKIM_HASH_SHA1)
282#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
283 EVP_DigestUpdate(&m_Bdy_sha256ctx,szBuffer,nBufLength);
284#else
285 EVP_DigestUpdate(m_Bdy_sha256ctx,szBuffer,nBufLength);
286#endif
287#if ((OPENSSL_VERSION_NUMBER > 0x10101000L))
288 if ((m_nHash == DKIM_HASH_ED25519) ||
290 EVP_DigestUpdate(m_Edy_sha256ctx,szBuffer,nBufLength);
291#endif
292 }
293}
294
296//
297// SignThisTag - return boolean whether or not to sign this tag
298//
300bool CDKIMSign::SignThisTag(const string& sTag)
301{
302 bool bRet = true;
303
304 if (_strnicmp(sTag.c_str(),"X-",2) == 0 ||
305 _stricmp(sTag.c_str(),"Authentication-Results:") == 0 ||
306 _stricmp(sTag.c_str(),"Return-Path:") == 0) {
307 bRet = false;
308 }
309
310 return bRet;
311}
312
313bool ConvertHeaderToQuotedPrintable(const char* source, char* dest)
314{
315 bool bConvert = false;
316
317 // do quoted printable
318 static unsigned char hexchars[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
319
320 unsigned char *d = (unsigned char*)dest;
321 for (const unsigned char *s = (const unsigned char *)source; *s != '\0'; s++)
322 {
323 if (*s >= 33 && *s <= 126 && *s != '=' && *s != ':' && *s != ';' && *s != '|') {
324 *d++ = *s;
325 } else {
326 bConvert = true;
327 *d++ = '=';
328 *d++ = hexchars[*s >> 4];
329 *d++ = hexchars[*s & 15];
330 }
331 }
332 *d = '\0';
333
334 return bConvert;
335}
336
338//
339// GetHeaderParams - Extract any needed header parameters
340//
342void CDKIMSign::GetHeaderParams(const string &sHdr)
343{
344 if (_strnicmp(sHdr.c_str(),"X",1) == 0) return;
345 if (_strnicmp(sHdr.c_str(),"From:",5) == 0) { sFrom.assign(sHdr.c_str() + 5); }
346 if (_strnicmp(sHdr.c_str(),"Sender:",7) == 0) { sSender.assign(sHdr.c_str() + 7); }
347
349 string::size_type pos = sHdr.find(':');
350
351 if (pos != string::npos) {
352 string sTag, sValue;
353 char *workBuffer = new char[sHdr.size() * 3 + 1];
354
355 sTag.assign(sHdr.substr(0,pos));
356 sValue.assign(sHdr.substr(pos + 1,string::npos));
357
358 ConvertHeaderToQuotedPrintable(sTag.c_str(),workBuffer);
359 if (!m_sCopiedHeaders.empty()) { m_sCopiedHeaders.append("|"); }
360 m_sCopiedHeaders.append(workBuffer); m_sCopiedHeaders.append(":");
361 ConvertHeaderToQuotedPrintable(sValue.c_str(),workBuffer);
362 m_sCopiedHeaders.append(workBuffer);
363
364 delete[] workBuffer;
365 }
366 }
367}
368
370//
371// ProcessHeaders - sign headers and save needed parameters (this is a lie)
372//
375{
376 map<string,list<string>::reverse_iterator> IterMap;
377 map<string,list<string>::reverse_iterator>::iterator IterMapIter;
378 list<string>::reverse_iterator riter;
379 list<string>::iterator iter;
380 string sTag;
381 bool bFromHeaderFound = false;
382
383 // walk the header list
384 for (iter = HeaderList.begin(); iter != HeaderList.end(); iter++) {
385 sTag.assign(*iter);
386
387 // look for a colon
388 string::size_type pos = sTag.find(':');
389
390 if (pos != string::npos) {
391 int nSignThisTag = 1;
392
393 // hack off anything past the colon
394 sTag.erase(pos + 1,string::npos);
395
396 // is this the From: header?
397 if (_stricmp(sTag.c_str(),"From:") == 0) {
398 bFromHeaderFound = true;
399 nSignThisTag = 1;
400 IsRequiredHeader(sTag); // remove from required header list
401 }
402 // is this in the list of headers that must be signed?
403 else if (IsRequiredHeader(sTag)) {
404 nSignThisTag = 1;
405 }
406 else {
407 if(m_pfnHdrCallback) {
408 nSignThisTag = m_pfnHdrCallback(iter->c_str());
409 } else {
410 nSignThisTag = SignThisTag(sTag) ? 1 : 0;
411 }
412 }
413
414 // save header parameters
415 GetHeaderParams(*iter);
416
417 if (nSignThisTag > 0) {
418 // add this tag to h=
419 hParam.append(sTag);
420
421 IterMapIter = IterMap.find(sTag);
422
423 riter = (IterMapIter == IterMap.end()) ? HeaderList.rbegin() : IterMapIter->second;
424
425 // walk the list in reverse looking for the last instance of this header
426 while (riter != HeaderList.rend()) {
427 if (_strnicmp(riter->c_str(),sTag.c_str(),sTag.size()) == 0) {
428 ProcessHeader(*riter);
429
430 // save the reverse iterator position for this tag
431 riter++;
432 IterMap[sTag] = riter;
433 break;
434 }
435 riter++;
436 }
437 }
438 }
439 }
440
441 if(!bFromHeaderFound) {
442 string sFrom("From:");
443 hParam.append(sFrom);
444 IsRequiredHeader(sFrom); // remove from required header list
445// Hash("\r\n",2);
446 }
447
448 hParam.append(sRequiredHeaders);
449
450// string::size_type end = sRequiredHeaders.find(':');
451// while (end != string::npos)
452// {
453// Hash("\r\n",2);
454// end = sRequiredHeaders.find(':', end+1);
455// }
456
457 // remove the last colon from h=
458 if (hParam.at(hParam.size() - 1) == ':')
459 hParam.erase(hParam.size() - 1,string::npos);
460
461 return DKIM_SUCCESS;
462}
463
464void CDKIMSign::ProcessHeader(const string &sHdr)
465{
466 switch (HIWORD(m_Canon)) {
468 Hash(sHdr.c_str(),sHdr.size(),true);
469 Hash("\r\n",2,true);
470 break;
471
472 case DKIM_CANON_NOWSP: {
473 string sTemp = sHdr;
474 RemoveSWSP(sTemp);
475
476 // convert characters before ':' to lower case
477 for (char *s = (char*)sTemp.c_str(); *s != '\0' && *s != ':'; s++) {
478 if (*s >= 'A' && *s <= 'Z')
479 *s += 'a' - 'A';
480 }
481
482 Hash(sTemp.c_str(),sTemp.size(),true);
483 Hash("\r\n",2,true);
484 }
485 break;
486
487 case DKIM_CANON_RELAXED: {
488 string sTemp = RelaxHeader(sHdr);
489 Hash(sTemp.c_str(),sTemp.length(),true);
490 Hash("\r\n",2,true);
491 }
492 break;
493 }
494}
495
496int CDKIMSign::ProcessBody(char *szBuffer,int nBufLength,bool bEOF)
497{
498 switch(LOWORD(m_Canon)) {
500 if (nBufLength > 0) {
501 while (m_EmptyLineCount > 0) {
502 Hash("\r\n",2,false);
503 m_nBodyLength += 2;
505 }
506 Hash(szBuffer,nBufLength,false);
507 Hash("\r\n",2,false);
508 m_nBodyLength += nBufLength + 2;
509 } else {
511 if (bEOF) {
512 Hash("\r\n",2,false);
513 m_nBodyLength += 2;
514 }
515 }
516 break;
517 case DKIM_CANON_NOWSP:
518 RemoveSWSP(szBuffer,nBufLength);
519 if (nBufLength > 0) {
520 Hash(szBuffer,nBufLength,false);
521 m_nBodyLength += nBufLength;
522 }
523 break;
525 CompressSWSP(szBuffer,nBufLength);
526 if (nBufLength > 0) {
527 while (m_EmptyLineCount > 0) {
528 Hash("\r\n",2,false);
529 m_nBodyLength += 2;
531 }
532 Hash(szBuffer,nBufLength,false);
533 m_nBodyLength += nBufLength;
534 if (!bEOF) {
535 Hash("\r\n",2,false);
536 m_nBodyLength += 2;
537 }
538 } else
540 break;
541 }
542
543 return DKIM_SUCCESS;
544}
545
547{
548 string::size_type pos;
549 string sAddress;
550
551 if (!sFrom.empty()) {
552 sAddress.assign(sFrom);
553 } else if (!sSender.empty()) {
554 sAddress.assign(sSender);
555 } else {
556 return false;
557 }
558
559 // simple for now, beef it up later
560
561 // remove '<' and anything before it
562 pos = sAddress.find('<');
563 if(pos != string::npos)
564 sAddress.erase(0,pos);
565
566 // remove '>' and anything after it
567 pos = sAddress.find('>');
568 if (pos != string::npos)
569 sAddress.erase(pos,string::npos);
570
571 // look for '@' symbol
572 pos = sAddress.find('@');
573 if (pos == string::npos)
574 return false;
575
576 if (sDomain.empty()) {
577 sDomain.assign (sAddress.c_str() + pos + 1);
579 }
580
581 return true;
582}
583
585//
586// InitSig - initialize signature folding algorithm
587//
590{
591 m_sSig.reserve(1024);
592 m_sSig.assign("DKIM-Signature:");
593 m_nSigPos = m_sSig.size();
594}
595
597//
598// AddTagToSig - add tag and value to signature folding if necessary
599// if bFold, fold at cbrk char
600//
602void CDKIMSign::AddTagToSig(const char* const Tag,const string &sValue,char cbrk,bool bFold)
603{
604 int nTagLen = strlen(Tag);
605
606 AddInterTagSpace((!bFold) ? sValue.size() + nTagLen + 2 : nTagLen + 2);
607
608 m_sSig.append(Tag);
609 m_sSig.append("=");
610 m_nSigPos += 1 + nTagLen;
611
612 if (!bFold) {
613 m_sSig.append(sValue);
614 m_nSigPos += sValue.size();
615 } else {
616 AddFoldedValueToSig(sValue,cbrk);
617 }
618 m_sSig.append(";");
619 m_nSigPos++;
620}
621
623//
624// AddTagToSig - add tag and numeric value to signature folding if necessary
625//
627void CDKIMSign::AddTagToSig(const char* const Tag,unsigned long nValue)
628{
629 char szValue[64];
630 sprintf(szValue,"%lu",nValue);
631 AddTagToSig(Tag,szValue,0,false);
632}
633
635//
636// AddInterTagSpace - add space or fold here
637//
639void CDKIMSign::AddInterTagSpace(int nSizeOfNextTag)
640{
641 if (m_nSigPos + nSizeOfNextTag + 1 > OptimalHeaderLineLength) {
642// m_sSig.append("\r\n\t");
643 m_sSig.append("\r\n "); /* s/qmail style */
644 m_nSigPos = 1;
645 } else {
646 m_sSig.append(" ");
647 m_nSigPos++;
648 }
649}
650
652//
653// AddTagToSig - add value to signature folding if necessary
654// if cbrk == 0 fold anywhere, otherwise fold only at cbrk
655//
657void CDKIMSign::AddFoldedValueToSig(const string &sValue,char cbrk)
658{
659 string::size_type pos = 0;
660
661 if (cbrk == 0) {
662 // fold anywhere
663 while (pos < sValue.size()) {
664 string::size_type len = OptimalHeaderLineLength - m_nSigPos;
665 if (len > sValue.size() - pos)
666 len = sValue.size() - pos;
667 m_sSig.append(sValue.substr(pos,len));
668 m_nSigPos += len;
669 pos += len;
670
671 if (pos < sValue.size()) {
672// m_sSig.append("\r\n\t");
673 m_sSig.append("\r\n "); /* s/qmail style */
674 m_nSigPos = 1;
675 }
676 }
677 } else {
678 // fold only at cbrk
679 while (pos < sValue.size()) {
680 string::size_type len = OptimalHeaderLineLength - m_nSigPos;
681 string::size_type brkpos;
682
683 if (sValue.size() - pos < len) {
684 brkpos = sValue.size();
685 } else {
686 brkpos = sValue.rfind(cbrk,pos + len);
687 }
688
689 if (brkpos == string::npos || brkpos < pos) {
690 brkpos = sValue.find(cbrk,pos);
691 if (brkpos == string::npos) {
692 brkpos = sValue.size();
693 }
694 }
695
696 len = brkpos - pos + 1;
697
698 m_sSig.append(sValue.substr(pos,len));
699
700 m_nSigPos += len;
701 pos += len;
702
703 if (pos < sValue.size()) {
704// m_sSig.append("\r\n\t");
705 m_sSig.append("\r\n "); /* s/qmail style */
706 m_nSigPos = 1;
707 }
708 }
709 }
710}
711
713//
714// GetSig - compute hash and return signature header in szSignature
715//
717int CDKIMSign::GetSig2(char* szRSAKey,char* szECCKey,char** pszSignature)
718{
719 if (szRSAKey == NULL && szECCKey == NULL) {
721 }
722
723 if (pszSignature == NULL) {
725 }
726
727 int nRet = AssembleReturnedSig(szRSAKey,szECCKey);
728
729 if (nRet != DKIM_SUCCESS)
730 return nRet;
731
732 *pszSignature = (char*)m_sReturnedSig.c_str();
733
734 return DKIM_SUCCESS;
735}
736
737
739//
740// IsRequiredHeader - Check if header in required list. If so, delete
741// header from list.
742//
744bool CDKIMSign::IsRequiredHeader(const string& sTag)
745{
746 string::size_type start = 0;
747 string::size_type end = sRequiredHeaders.find(':');
748
749 while (end != string::npos) {
750 // check for a zero-length header
751 if(start == end) {
752 sRequiredHeaders.erase(start,1);
753 } else {
754 if (_stricmp(sTag.c_str(),sRequiredHeaders.substr(start,end - start + 1).c_str()) == 0) {
755 sRequiredHeaders.erase(start,end - start + 1);
756 return true;
757 } else {
758 start = end + 1;
759 }
760 }
761
762 end = sRequiredHeaders.find(':',start);
763 }
764
765 return false;
766}
768//
769// ConstructSignature
770//
771// Here, we don't construct the 'signature' but rather the DKIM header
772// multiply and indidually crafted for each distinct nSigAlg method
773//
774// nSigAlg: DKIM_HASH_SHA1, DKIM_HASH_SHA256, DKIM_HASH_ED25519
775//
777int CDKIMSign::ConstructSignature(char* szPrivKey,int nSigAlg)
778{
779 string sSignedSig;
780 unsigned char* sig;
781 EVP_PKEY *pkey = 0;
782 BIO *bio, *b64;
783 unsigned int siglen;
784 int size;
785 int len;
786 char* buf;
787 int nSignRet;
788
789 /* construct the DKIM-Signature: header and add to hash */
790 InitSig();
791
792 AddTagToSig("v","1",0,false);
793
794 switch (nSigAlg) {
795 case DKIM_HASH_SHA1:
796 AddTagToSig("a","rsa-sha1",0,false); break;
797 case DKIM_HASH_SHA256:
798 AddTagToSig("a","rsa-sha256",0,false); break;
799 case DKIM_HASH_ED25519:
800 AddTagToSig("a","ed25519-sha256",0,false); break;
801 }
802
803 switch (m_Canon) {
804 case DKIM_SIGN_SIMPLE:
805 AddTagToSig("c","simple/simple",0,false); break;
807 AddTagToSig("c","simple/relaxed",0,false); break;
809 AddTagToSig("c","relaxed/relaxed",0,false); break;
811 AddTagToSig("c","relaxed/simple",0,false); break;
812 }
813
814 AddTagToSig("d",sDomain,0,false);
815 if (nSigAlg == DKIM_HASH_ED25519)
816 AddTagToSig("s",eSelector,0,false);
817 else
818 AddTagToSig("s",sSelector,0,false);
820 if (m_nIncludeTimeStamp != 0) { time_t t; time(&t); AddTagToSig("t",t); }
821 if (m_ExpireTime != 0) { AddTagToSig("x",m_ExpireTime); }
822 if (!sIdentity.empty()) { AddTagToSig("i",sIdentity,0,false); }
823 if (m_nIncludeQueryMethod) { AddTagToSig("q","dns/txt",0,false); }
824
825 AddTagToSig("h",hParam,':',true); // copied headers follow the ':'
827
828 /* Set up context for (body) hash */
829
830 unsigned char Hash[4096];
831 unsigned int nHashLen = 0;
832
833 switch (nSigAlg) {
834 case DKIM_HASH_SHA1:
835#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
836 EVP_DigestFinal(&m_Bdy_sha1ctx,Hash,&nHashLen); break;
837#else
838 EVP_DigestFinal_ex(m_Bdy_sha1ctx,Hash,&nHashLen); break;
839#endif
840 case DKIM_HASH_SHA256:
841#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
842 EVP_DigestFinal(&m_Bdy_sha256ctx,Hash,&nHashLen); break;
843#else
844 EVP_DigestFinal_ex(m_Bdy_sha256ctx,Hash,&nHashLen); break;
845#endif
846#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
847 case DKIM_HASH_ED25519:
848 EVP_DigestFinal_ex(m_Edy_sha256ctx,Hash,&nHashLen); break;
849#endif
850 }
851
852 bio = BIO_new(BIO_s_mem());
853 if (!bio) return DKIM_OUT_OF_MEMORY;
854
855 b64 = BIO_new(BIO_f_base64());
856 if (!b64) {
857 BIO_free(bio);
858 return DKIM_OUT_OF_MEMORY;
859 }
860 BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
861 BIO_push(b64,bio);
862 if (BIO_write(b64,Hash,nHashLen) < (int)nHashLen) {
863 BIO_free_all(b64);
864 return DKIM_OUT_OF_MEMORY;
865 }
866 BIO_flush(b64);
867
868 len = nHashLen * 2;
869 buf = new char[len];
870
871 if (buf == NULL) {
872 BIO_free_all(b64);
873 return DKIM_OUT_OF_MEMORY;
874 }
875
876 size = BIO_read(bio,buf,len);
877 BIO_free_all(b64);
878
879 // this should never happen
880 if (size >= len) {
881 delete[] buf;
882 return DKIM_OUT_OF_MEMORY;
883 }
884
885 buf[size] = '\0';
886 AddTagToSig("bh",buf,0,true);
887 delete[] buf;
888
890
891 m_sSig.append("b=");
892 m_nSigPos += 2;
893
894 // Force a full copy - no reference copies please
895 sSignedSig.assign(m_sSig.c_str());
896
897 // note that since we're not calling hash here, need to dump this
898 // to the debug file if you want the full canonical form
899
900 string sTemp;
901
903 sTemp = RelaxHeader(sSignedSig);
904 } else {
905 sTemp = sSignedSig.c_str();
906 }
907
908 /* Update streaming signatures */
909
910 switch (nSigAlg) {
911 case DKIM_HASH_SHA1:
912#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
913 EVP_SignUpdate(&m_Hdr_sha1ctx,sTemp.c_str(),sTemp.size()); break;
914#else
915 EVP_SignUpdate(m_Hdr_sha1ctx,sTemp.c_str(),sTemp.size()); break;
916#endif
917 case DKIM_HASH_SHA256:
918#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
919 EVP_SignUpdate(&m_Hdr_sha256ctx,sTemp.c_str(),sTemp.size()); break;
920#else
921 EVP_SignUpdate(m_Hdr_sha256ctx,sTemp.c_str(),sTemp.size()); break;
922#endif
923#if ((OPENSSL_VERSION_NUMBER > 0x10101000L))
924 case DKIM_HASH_ED25519:
925 SigHdrs.append(sTemp.c_str(),sTemp.size());
926 m_SigHdrs += sTemp.size(); break;
927#endif
928 }
929
930 bio = BIO_new_mem_buf(szPrivKey, -1);
931 if (bio == NULL) return DKIM_OUT_OF_MEMORY;
932
933 pkey = PEM_read_bio_PrivateKey(bio,NULL,NULL,NULL); // FIXME - done
934 BIO_free(bio);
935
936 if (!pkey) { return DKIM_BAD_PRIVATE_KEY; }
937 siglen = EVP_PKEY_size(pkey);
938
939 sig = (unsigned char*) OPENSSL_malloc(siglen);
940 if (sig == NULL) {
941 EVP_PKEY_free(pkey);
942 return DKIM_OUT_OF_MEMORY;
943 }
944
945 /* Finish streaming signature and potentially go for Ed25519 signatures */
946
947 size_t sig_len;
948 unsigned char* SignMsg;
949
950 switch (nSigAlg) {
951 case DKIM_HASH_SHA1:
952#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
953 nSignRet = EVP_SignFinal(&m_Hdr_sha1ctx,sig,&siglen,pkey); break;
954#else
955 nSignRet = EVP_SignFinal(m_Hdr_sha1ctx,sig,&siglen,pkey); break;
956#endif
957 case DKIM_HASH_SHA256:
958#if ((OPENSSL_VERSION_NUMBER < 0x10100000L) || (LIBRESSL_VERSION_NUMBER > 0 && LIBRESSL_VERSION_NUMBER < 0x20700000L))
959 nSignRet = EVP_SignFinal(&m_Hdr_sha256ctx,sig,&siglen,pkey); break;
960#else
961 nSignRet = EVP_SignFinal(m_Hdr_sha256ctx,sig,&siglen,pkey); break;
962#endif
963#if (OPENSSL_VERSION_NUMBER > 0x10101000L)
964 case DKIM_HASH_ED25519:
965 EVP_DigestSignInit(m_Hdr_ed25519ctx,NULL,NULL,NULL,pkey);
966 SignMsg = (unsigned char*) SigHdrs.c_str();
967 EVP_DigestSign(m_Hdr_ed25519ctx,NULL,&sig_len,SignMsg,m_SigHdrs);
968 sig = (unsigned char*) OPENSSL_malloc(sig_len);
969 nSignRet = EVP_DigestSign(m_Hdr_ed25519ctx,sig,&sig_len,SignMsg,m_SigHdrs);
970 siglen = (unsigned int) sig_len; break;
971#endif
972 }
973 EVP_PKEY_free(pkey);
974
975 if (!nSignRet) {
976 OPENSSL_free(sig);
977 return DKIM_BAD_PRIVATE_KEY; // key too small
978 }
979
980 bio = BIO_new(BIO_s_mem());
981 if (!bio) {
982 return DKIM_OUT_OF_MEMORY;
983 }
984
985 b64 = BIO_new(BIO_f_base64());
986 if (!b64) {
987 BIO_free(bio);
988 return DKIM_OUT_OF_MEMORY;
989 }
990
991 BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
992 BIO_push(b64,bio);
993
994 if (BIO_write(b64,sig,siglen) < (int) siglen) {
995 OPENSSL_free(sig);
996 BIO_free_all(b64);
997 return DKIM_OUT_OF_MEMORY;
998 }
999 BIO_flush(b64);
1000 OPENSSL_free(sig);
1001
1002 len = siglen * 2;
1003 buf = new char[len];
1004
1005 if (buf == NULL) {
1006 BIO_free_all(b64);
1007 return DKIM_OUT_OF_MEMORY;
1008 }
1009
1010 size = BIO_read(bio,buf,len);
1011 BIO_free_all(b64);
1012
1013 // this should never happen
1014 if (size >= len) {
1015 delete[] buf;
1016 return DKIM_OUT_OF_MEMORY;
1017 }
1018
1019 buf[size] = '\0';
1021 delete[] buf;
1022 return DKIM_SUCCESS;
1023}
1024
1026//
1027// AssembleReturnSig
1028//
1029// calls ConstructSignature
1030// for all different hashes and signature key files
1031//
1033int CDKIMSign::AssembleReturnedSig(char* szRSAKey,char* szECCKey)
1034{
1035 int nRet;
1036
1038 return DKIM_SUCCESS;
1039
1040 ProcessFinal();
1041
1042 if (ParseFromAddress() == false) {
1043 return DKIM_NO_SENDER;
1044 }
1045
1046 string ed25519Sig, sha256Sig, sha1Sig;
1047
1048 if ((m_nHash == DKIM_HASH_ED25519) ||
1050 nRet = ConstructSignature(szECCKey,DKIM_HASH_ED25519);
1051 if (nRet == DKIM_SUCCESS) {
1052 ed25519Sig.assign(m_sSig);
1053 } else {
1054 return nRet;
1055 }
1056 }
1057
1058 if ((m_nHash == DKIM_HASH_SHA256) ||
1061 nRet = ConstructSignature(szRSAKey,DKIM_HASH_SHA256);
1062 if (nRet == DKIM_SUCCESS) {
1063 sha256Sig.assign(m_sSig);
1064 } else {
1065 return nRet;
1066 }
1067 }
1068
1069 if ((m_nHash == DKIM_HASH_SHA1) ||
1071 nRet = ConstructSignature(szRSAKey,DKIM_HASH_SHA1);
1072 if (nRet == DKIM_SUCCESS) {
1073 sha1Sig.assign(m_sSig);
1074 } else {
1075 return nRet;
1076 }
1077 }
1078
1079// fclose(fpdebug);
1080// fpdebug = NULL;
1081
1082 if (!ed25519Sig.empty()) {
1083/* if (!m_sReturnedSig.empty()) {
1084 m_sReturnedSig.append("\r\n");
1085 }
1086 */
1087 m_sReturnedSig.assign(ed25519Sig);
1088 }
1089
1090 if (!sha1Sig.empty()) {
1091 if (!m_sReturnedSig.empty()) {
1092 m_sReturnedSig.append("\r\n");
1093 }
1094 m_sReturnedSig.append(sha1Sig);
1095 }
1096
1097 if (!sha256Sig.empty()) {
1098 if (!m_sReturnedSig.empty()) {
1099 m_sReturnedSig.append("\r\n");
1100 }
1101 m_sReturnedSig.append(sha256Sig);
1102 }
1103
1105 return DKIM_SUCCESS;
1106}
static string RelaxHeader(const string &sHeader)
Definition: dkimbase.cpp:293
int ProcessFinal(void)
Definition: dkimbase.cpp:167
static void CompressSWSP(char *pBuffer, int &nBufLength)
Definition: dkimbase.cpp:235
static void RemoveSWSP(char *szBuffer)
Definition: dkimbase.cpp:214
int Init(void)
Definition: dkimbase.cpp:49
list< string > HeaderList
Definition: dkimbase.h:75
string m_sSig
Definition: dkimsign.h:101
DKIMHEADERCALLBACK m_pfnHdrCallback
Definition: dkimsign.h:99
EVP_MD_CTX m_Bdy_sha256ctx
Definition: dkimsign.h:67
int GetSig2(char *szRSAPrivKey, char *szECCPrivKey, char **pszSignature)
Definition: dkimsign.cpp:717
bool ParseFromAddress(void)
Definition: dkimsign.cpp:546
int m_nIncludeCopiedHeaders
Definition: dkimsign.h:97
int m_nBodyLength
Definition: dkimsign.h:92
string hParam
Definition: dkimsign.h:82
string sDomain
Definition: dkimsign.h:87
void AddInterTagSpace(int nSizeOfNextTag)
Definition: dkimsign.cpp:639
void ProcessHeader(const string &sHdr)
Definition: dkimsign.cpp:464
void InitSig(void)
Definition: dkimsign.cpp:589
time_t m_ExpireTime
Definition: dkimsign.h:93
bool IsRequiredHeader(const string &sTag)
Definition: dkimsign.cpp:744
string sFrom
Definition: dkimsign.h:83
void GetHeaderParams(const string &sHdr)
Definition: dkimsign.cpp:342
bool m_IncludeBodyLengthTag
Definition: dkimsign.h:91
int m_nHash
Definition: dkimsign.h:96
EVP_MD_CTX m_Bdy_sha1ctx
Definition: dkimsign.h:66
int m_nSigPos
Definition: dkimsign.h:102
string SigHdrs
Definition: dkimsign.h:109
string eSelector
Definition: dkimsign.h:86
int m_SigHdrs
Definition: dkimsign.h:110
virtual int ProcessBody(char *szBuffer, int nBufLength, bool bEOF) override
Definition: dkimsign.cpp:496
int m_EmptyLineCount
Definition: dkimsign.h:80
void AddTagToSig(const char *const Tag, const string &sValue, char cbrk, bool bFold)
Definition: dkimsign.cpp:602
int ConstructSignature(char *szSignKey, int nSigAlg)
Definition: dkimsign.cpp:777
string sIdentity
Definition: dkimsign.h:88
void AddFoldedValueToSig(const string &sValue, char cbrk)
Definition: dkimsign.cpp:657
bool m_bReturnedSigAssembled
Definition: dkimsign.h:105
int AssembleReturnedSig(char *szRSAPrivKey, char *szECCPrivKey)
Definition: dkimsign.cpp:1033
EVP_MD_CTX m_Hdr_sha1ctx
Definition: dkimsign.h:63
virtual int ProcessHeaders(void) override
Definition: dkimsign.cpp:374
string m_sCopiedHeaders
Definition: dkimsign.h:107
bool SignThisTag(const string &sTag)
Definition: dkimsign.cpp:300
string sRequiredHeaders
Definition: dkimsign.h:89
void Hash(const char *szBuffer, int nBufLength, bool bHdr)
Definition: dkimsign.cpp:229
int m_nIncludeTimeStamp
Definition: dkimsign.h:94
string sSelector
Definition: dkimsign.h:85
int m_nIncludeQueryMethod
Definition: dkimsign.h:95
EVP_MD_CTX m_Hdr_sha256ctx
Definition: dkimsign.h:64
string m_sReturnedSig
Definition: dkimsign.h:104
string sSender
Definition: dkimsign.h:84
int m_Canon
Definition: dkimsign.h:78
@ OptimalHeaderLineLength
Definition: dkimsign.h:40
#define DKIM_BUFFER_TOO_SMALL
Definition: dkim.h:70
#define DKIM_CANON_SIMPLE
Definition: dkim.h:37
#define DKIM_NO_SENDER
Definition: dkim.h:68
#define DKIM_CANON_NOWSP
Definition: dkim.h:38
#define DKIM_OUT_OF_MEMORY
Definition: dkim.h:66
#define DKIM_CANON_RELAXED
Definition: dkim.h:39
#define DKIM_HASH_RSA256_AND_ED25519
Definition: dkim.h:34
#define DKIM_BAD_PRIVATE_KEY
Definition: dkim.h:69
#define DKIM_SIGN_RELAXED
Definition: dkim.h:43
#define DKIM_SIGN_SIMPLE
Definition: dkim.h:41
#define DKIM_HASH_SHA1_AND_SHA256
Definition: dkim.h:32
#define DKIM_SIGN_RELAXED_SIMPLE
Definition: dkim.h:44
#define DKIM_SIGN_SIMPLE_RELAXED
Definition: dkim.h:42
#define DKIM_HASH_ED25519
Definition: dkim.h:33
#define DKIM_HASH_SHA1
Definition: dkim.h:30
#define DKIM_HASH_SHA256
Definition: dkim.h:31
#define DKIM_SUCCESS
Definition: dkim.h:47
#define LOWORD(l)
Definition: dkimsign.cpp:25
#define _stricmp
Definition: dkimsign.cpp:24
#define _strnicmp
Definition: dkimsign.cpp:23
#define HIWORD(l)
Definition: dkimsign.cpp:26
bool ConvertHeaderToQuotedPrintable(const char *source, char *dest)
Definition: dkimsign.cpp:313
char buf[100+FMT_ULONG]
Definition: hier.c:10
int
Definition: qmail-mrtg.c:26
unsigned long size
Definition: qmail-qread.c:55
struct del * d[CHANNELS]
Definition: qmail-send.c:720
int nIncludeBodyLengthTag
Definition: dkim.h:99
DKIMHEADERCALLBACK pfnHeaderCallback
Definition: dkim.h:107
int nIncludeCopiedHeaders
Definition: dkim.h:111
unsigned long expireTime
Definition: dkim.h:106
char szDomain[256]
Definition: dkim.h:104
char szIdentity[256]
Definition: dkim.h:105
int nIncludeTimeStamp
Definition: dkim.h:100
char szSelector[64]
Definition: dkim.h:102
int nIncludeQueryMethod
Definition: dkim.h:101
char szSelectorE[64]
Definition: dkim.h:103