/***************************************************************************** * 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 #include #include #include #include #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] [ ]\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 - r=relaxed [DEFAULT], s=simple, t=relaxed/simple, u=simple/relaxed\n"); fprintf( FDLOG, "\t-d - Signing Domain Identifier (if not provided it will be determined from " "the sender/from header)\n"); fprintf( FDLOG, "\t-i - 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 - the expire time in seconds since epoch (optional, DEFAULT = current " "time + 604800)\n"); fprintf(FDLOG, "\t-y - set RSA selector (DEFAULT: default)\n"); fprintf(FDLOG, "\t-Y - set Ed25519 selector (DEFAULT: default)\n"); fprintf( FDLOG, "\t-z - 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; }