summaryrefslogtreecommitdiff
path: root/src/qmail-dkim.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmail-dkim.cpp')
-rw-r--r--src/qmail-dkim.cpp343
1 files changed, 343 insertions, 0 deletions
diff --git a/src/qmail-dkim.cpp b/src/qmail-dkim.cpp
new file mode 100644
index 0000000..fba94fe
--- /dev/null
+++ b/src/qmail-dkim.cpp
@@ -0,0 +1,343 @@
+/*****************************************************************************
+* 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
+*
+* Comment: Awful mixture of C and C++ making use of the worst parts of it.
+* Style: Partial Hungarian notation (see Torvalds comments)
+* C++: Obsolete classes, allocators, virtual constructors w/o destructors
+* C: Stdio interface routines
+* OpenSSL: Brain demaged EVP_Digest calls with memory leaks.
+* Network: Sigh, exchanged internal DNS routines by fehQlibs resolver
+*
+*****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "dkim.h"
+extern "C" {
+#include "dns.h"
+}
+
+// change these to your selector name, domain name, etc
+#define MYRSASELECTOR "default"
+#define MYECCSELECTOR "eddy"
+#define MYDOMAIN "" //"bardenhagen.com"
+#define MYIDENTITY "" //"dkimtest@bardenhagen.com"
+
+#define strnicmp strncasecmp
+#define FDLOG stderr /* writing to another FD requires a method */
+
+int DKIM_CALL SignThisHeader(const char* szHeader)
+{
+ if (strnicmp(szHeader,"X-",2) == 0 ) { return 0; }
+ return 1;
+}
+
+int DKIM_CALL SelectorCallback(const char* szFQDN,char* szBuffer,int nBufLen)
+{
+ return 0;
+}
+
+void usage()
+{
+ char version[] = "1.4.0";
+ fprintf(FDLOG,"qmail-dkim %s \n",version);
+ fprintf(FDLOG,"Usage: qmail-dkim [-h|-v|-s] [tags] <msgfile> [<RSAkeyfile> <outfile> <Ed25519keyfile>]\n\n");
+ fprintf(FDLOG, "Options:\n\t-h show this help\n");
+ fprintf(FDLOG, "\t-s sign the message \n");
+ fprintf(FDLOG, "\t-v verify the message\n");
+ fprintf(FDLOG, "\t-V verify the message and write result to output file (Pass/Fail)\n\n");
+ fprintf(FDLOG, "These tags are available:\n");
+ fprintf(FDLOG, "\t-c<canonicalization> - r=relaxed [DEFAULT], s=simple, t=relaxed/simple, u=simple/relaxed\n");
+ fprintf(FDLOG, "\t-d<sdid> - Signing Domain Identifier (if not provided it will be determined from the sender/from header)\n");
+ fprintf(FDLOG, "\t-i<auid> - Agent User Identifier, usually the sender's email address (optional)\n");
+ fprintf(FDLOG, "\t-l - include body length tag (optional)\n");
+ fprintf(FDLOG, "\t-q - include query method tag\n");
+ fprintf(FDLOG, "\t-t - include a timestamp tag (optional)\n");
+ fprintf(FDLOG, "\t-x<expire_time> - the expire time in seconds since epoch (optional, DEFAULT = current time + 604800)\n");
+ fprintf(FDLOG, "\t-y<selector> - set RSA selector (DEFAULT: default)\n");
+ fprintf(FDLOG, "\t-Y<selector> - set Ed25519 selector (DEFAULT: default)\n");
+ fprintf(FDLOG, "\t-z<hash> - set signature algorithm type (1=rsa-sha1, 2=rsa-sha256, 3=both, 4=ed25519, 5=hybrid)\n");
+}
+
+int main(int argc, char* argv[])
+{
+ int n;
+ const char* RSAKeyFile = "rsa.pem";
+ const char* ECCKeyFile = "ed25519.pem";
+ const char* MsgFile = "test.msg";
+ const char* OutFile = "signed.msg";
+ int nKeyLen;
+ char RSAPrivKey[4196]; // storge for private key FILE including header and DER envelope
+ char ECCPrivKey[128];
+ char Buffer[1000];
+ int BufLen;
+ char szSignature[8192];
+ time_t t;
+ DKIMContext ctxt;
+ DKIMSignOptions opts = {0};
+
+ opts.nHash = DKIM_HASH_SHA256; // default
+
+ time(&t);
+
+ opts.nCanon = DKIM_SIGN_RELAXED;
+ opts.nIncludeBodyLengthTag = 0;
+ opts.nIncludeQueryMethod = 0;
+ opts.nIncludeTimeStamp = 0;
+ opts.expireTime = t + 604800; // expires in 1 week
+ strcpy(opts.szSelector,MYRSASELECTOR);
+ strcpy(opts.szSelectorE,MYECCSELECTOR);
+ strcpy(opts.szDomain,MYDOMAIN);
+ strcpy(opts.szIdentity,MYIDENTITY);
+ opts.pfnHeaderCallback = SignThisHeader;
+ strcpy(opts.szRequiredHeaders,"NonExistant");
+ opts.nIncludeCopiedHeaders = 0;
+
+ int nArgParseState = 0;
+ bool bSign = true;
+ bool bRes = false;
+
+ if (argc < 2){
+ usage();
+ exit(1);
+ }
+
+ for (n = 1; n < argc; n++) {
+ if (argv[n][0] == '-' && strlen(argv[n]) > 1) {
+ switch (argv[n][1]) {
+ case 'c': // canonicalization
+ if (argv[n][2] == 'r') { opts.nCanon = DKIM_SIGN_RELAXED; }
+ else if (argv[n][2] == 's') { opts.nCanon = DKIM_SIGN_SIMPLE; }
+ else if (argv[n][2] == 't') { opts.nCanon = DKIM_SIGN_RELAXED_SIMPLE; }
+ else if (argv[n][2] == 'u') { opts.nCanon = DKIM_SIGN_SIMPLE_RELAXED; }
+ break;
+ case 'd':
+ strncpy(opts.szDomain,(const char*)(argv[n] + 2),sizeof(opts.szDomain) - 1);
+ break;
+ case 'l': // body length tag
+ opts.nIncludeBodyLengthTag = 1;
+ break;
+ case 'h':
+ usage();
+ return 0;
+ case 'i': // identity
+ if (argv[n][2] == '-') { opts.szIdentity[0] = '\0'; }
+ else { strncpy(opts.szIdentity, argv[n] + 2,sizeof(opts.szIdentity) - 1); }
+ break;
+ case 'q': // query method tag
+ opts.nIncludeQueryMethod = 1;
+ break;
+ case 's': // sign with and use potentially Ed25519 private key
+ bSign = true;
+ break;
+ case 't': // timestamp tag
+ opts.nIncludeTimeStamp = 1;
+ break;
+ case 'v': // verify
+ bSign = false;
+ break;
+ case 'V': // verify and write result to OutFile
+ bSign = false;
+ bRes = true;
+ break;
+ case 'x': // expire time
+ if (argv[n][2] == '-') { opts.expireTime = 0; }
+ else { opts.expireTime = t + atoi(argv[n] + 2); }
+ break;
+ case 'y':
+ strncpy(opts.szSelector,argv[n] + 2,sizeof(opts.szSelector) - 1);
+ break;
+ case 'Y':
+ strncpy(opts.szSelectorE,argv[n] + 2,sizeof(opts.szSelectorE) - 1);
+ break;
+ case 'z': // sign w/ sha1, sha256, both, ed25519, hybrid
+ opts.nHash = atoi(&argv[n][2]);
+ }
+ }
+ else {
+ switch (nArgParseState) {
+ case 0:
+ MsgFile = argv[n];
+ break;
+ case 1:
+ RSAKeyFile = argv[n];
+ break;
+ case 2:
+ OutFile = argv[n];
+ break;
+ case 3:
+ ECCKeyFile = argv[n];
+ break;
+ }
+ nArgParseState++;
+ }
+ }
+
+/** Go for DKIM signing ... **/
+
+ if (bSign) {
+ if (opts.nHash != 4) {
+ FILE* RSAPrivKeyFP = fopen(RSAKeyFile,"r");
+ if (RSAPrivKeyFP == NULL) {
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: can't open private key file (%s) \n",RSAKeyFile);
+#endif
+ exit(1);
+ }
+ nKeyLen = fread(RSAPrivKey,1,sizeof(RSAPrivKey),RSAPrivKeyFP); // we read sizeof(RSAPrivKey) members with size of 1 byte each; sigh
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: private key file (%s) - length %i \n",RSAKeyFile,nKeyLen);
+#endif
+ if (nKeyLen >= sizeof(RSAPrivKey)) { /* (TC9) on return, we get the number of members read! */
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: private key buffer isn't big enough for private key length %i \n",nKeyLen);
+#endif
+ exit(1);
+ }
+ RSAPrivKey[nKeyLen] = '\0';
+ fclose(RSAPrivKeyFP);
+ }
+
+/** Ed25519 signing **/
+
+ if (opts.nHash == 4 || opts.nHash == 5) {
+ FILE* ECCPrivKeyFP = fopen(ECCKeyFile,"r");
+ if (ECCPrivKeyFP == NULL) {
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: can't open Ed25519 private key file (%s) \n",ECCKeyFile);
+#endif
+ exit(1);
+ }
+ nKeyLen = fread(ECCPrivKey,1,sizeof(ECCPrivKey),ECCPrivKeyFP);
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: Ed25519 private key file (%s) - length %i \n",ECCKeyFile,nKeyLen);
+#endif
+ if (nKeyLen >= sizeof(ECCPrivKey)) {
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: ECC private key buffer isn't big enough for ECC private key length %i \n",nKeyLen);
+#endif
+ exit(1);
+ }
+ ECCPrivKey[nKeyLen] = '\0';
+ fclose(ECCPrivKeyFP);
+ }
+
+/** Input message for signing **/
+
+ FILE* MsgFP = fopen(MsgFile,"rb");
+ if (MsgFP == NULL) {
+#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: can't open msg file (%s) \n",MsgFile);
+#endif
+ exit(1);
+ }
+
+ n = DKIMSignInit(&ctxt,&opts);
+
+ while (1) {
+ BufLen = fread(Buffer,1,sizeof(Buffer),MsgFP);
+ if (BufLen > 0) { DKIMSignProcess(&ctxt,Buffer,BufLen); }
+ else { break; }
+ }
+ fclose(MsgFP);
+
+ char* pSig = NULL;
+
+/** Do the actual signing **/
+
+ n = DKIMSignGetSig2(&ctxt,RSAPrivKey,ECCPrivKey,&pSig);
+
+ strcpy(szSignature,pSig);
+
+ DKIMSignFree(&ctxt);
+
+ FILE* in = fopen(MsgFile,"rb");
+ FILE* out = fopen(OutFile,"wb+");
+
+#ifdef SHOWLOG
+ fprintf(FDLOG," outfile written %s \n",OutFile);
+#endif
+
+ fwrite(szSignature,1,strlen(szSignature),out);
+ fwrite("\r\n",1,2,out);
+
+ while (1) {
+ BufLen = fread(Buffer,1,sizeof(Buffer),in);
+ if (BufLen > 0) { fwrite(Buffer,1,BufLen,out); }
+ else { break; }
+ }
+ fclose(in);
+
+ }
+
+/** Now go for verification **/
+
+ else {
+ FILE* in = fopen(MsgFile,"rb");
+ if (in == NULL) {
+//#ifdef SHOWLOG
+ fprintf(FDLOG," qmail-dkim: can't open input file\n");
+//#endif
+ return 0; // bad option -- no CTX set up yet
+ }
+
+ DKIMVerifyOptions vopts = {0};
+ vopts.pfnSelectorCallback = NULL; //SelectorCallback;
+
+ n = DKIMVerifyInit(&ctxt,&vopts);
+
+ while (1) {
+ BufLen = fread(Buffer,1,sizeof(Buffer),in);
+ if (BufLen > 0) { DKIMVerifyProcess(&ctxt,Buffer,BufLen); }
+ else { break; }
+ }
+
+ n = DKIMVerifyResults(&ctxt);
+
+ int nSigCount = 0;
+ DKIMVerifyDetails* pDetails;
+ char szPolicy[512];
+
+ n = DKIMVerifyGetDetails(&ctxt,&nSigCount,&pDetails,szPolicy);
+
+ for (int i = 0; i < nSigCount; i++) {
+ const char s[] = "pass";
+ const char f[] = "fail";
+ const char* error = DKIM_ErrorResult(pDetails[i].nResult);
+ if (!bRes)
+ fprintf(FDLOG," Signature #%d: ",i + 1);
+ if (pDetails[i].nResult >= 0 ) {
+ if (bRes) {
+ _DKIM_ReportResult(OutFile,s,0);
+ } else
+ printf(" Pass\n");
+ } else { // fail
+ if (bRes) {
+ _DKIM_ReportResult(OutFile,f,error);
+ } else
+ printf(" Fail %s \n",error);
+ }
+ }
+ DKIMVerifyFree(&ctxt);
+ }
+ return 0;
+}