The project address

Github.com/xiangyuecn/…

The main support

  • DKIM signature for mail, support with attachments
  • Verify the DKIM signature for the entire message content (.eml file)
  • rightMailMessage,SmtpClientAfter a encapsulation, it is easy to send mail and can be directly delivered to the other party’s server after DKIM signature (without own mail server).

DKIM signature and verification rules

For DKIM’s signature and verification rules, the article “DKIM Guidelines” of QQ mailbox has been written in sufficient detail, so it will not be carried.

Still not good can also refer to the DKIM.Net library for signature implementation.

For example

Var DKIM = new EMail_DKIM("domain.com", "dkimSelector", new rsa. RSA(/*"-----BEGIN RSA PRIVATE KEY-----....") , true*/ 1024)); Using (var EMail = new EMail("mx1.qq.com", 25)) {// Use the signature email.tryusedkim (dkim); email.FromEmail = "[email protected]"; email.ToEmail("[email protected]"); Var res = email.send (" title ", "content "); Console.WriteLine(res.IsError ? "Send failed :" + res.errorMessage :" send succeeded "); Var MSG = new MailMessage("[email protected]", "[email protected]"); msg.SubjectEncoding = msg.BodyEncoding = msg.HeadersEncoding = Encoding.UTF8; MSG.Subject = ""; MSG.Body = "content "; Msg.attachments.Add(New Attachment(new MemoryStream(encoding.utf8.getBytes (" ABC text 123")), "text.txt ")); WriteLine(dkim.sign (MSG).iserror? "Signature failed" : "signature completed "); Var eml = email_dkim_mailMessagetext.toraw (MSG).value; Console.WriteLine(dkim.verify (emL)? "Verification passed" : "verification failed "); Console.WriteLine(eml.raw);Copy the code

Output:

Sending failure: Sending error: The mailbox is unavailable. The server responds :Mailbox not found. http://servi ce.mail.qq.com/cgi-bin/help? Subtype =1&& ID =20022&&no=1000728 MIME -version: 1.0 From:test@test.test
To: [email protected]
Date: Sun, 11 Nov 2018 05:31:55 +000
Subject: =?utf-8?B?5qCH6aKY?=
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=domain.com;
 s=dkimSelector; q=dns/txt; t=1541914315; h=Date:From:Subject:To;
 bh=iKgtfjx6cvO8YCUPyjjnbHU9jziQ+q1c/Hrz0aRDb98=;
 b=CidpxecyNHkZGsIQGnUD8eQwrEGS+Nx09RUOff6hU/7H1DV50m/h0xqRLFlgskiqm1r0exDTPf/zS
CKui1WWNO5iKXSZt9/3s0YN9fhliP72c0GRIJ8DM3tQilVYgFnayK61jmvCW0gtrPd3biDdMp/s+Arq8
lWD6CbQfBMIPmQ=
Content-Type: multipart/mixed; boundary=--boundaryhRN0aXVHKzDLi76qUZTq


----boundaryhRN0aXVHKzDLi76qUZTq
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: base64

5YaF5a65
----boundaryhRN0aXVHKzDLi76qUZTq
Content-Type: application/octet-stream; name="=? utf-8? B? 5paH5pysLnR4dA==? ="
Content-Transfer-Encoding: base64
Content-Disposition: attachment

YWJj5paH5pys5YaF5a65MTIz
----boundaryhRN0aXVHKzDLi76qUZTq--
Copy the code

run

When clone is used with vs, it should be able to open it directly. The files that look like no eggs are ignored by SVN :ignore.

Preface, self-statement, and what else

When I realized email sending, I found that even if I did not send the mail to my own mail server (the mail service server sends the mail to the other party), some mailbox (QQ mailbox) would not refuse, but some mailbox directly refused (netease mailbox). The dkim-signature header is missing when comparing the contents of emails sent by the mail server and those sent directly.

All right, if it’s missing, add it. But.net’s MailMessage and SmtpClient are so simple that they don’t expose the interface for sending a message to a Stream (writing to a folder doesn’t give file names but supports file names) that they don’t support signatures. That’s self-fulfilling.

Studied RFC 6376 long text can not understand (mainly did not give a simple implementation steps), and then QQ to simple to understand much (streaming. Clear). The signature and validation algorithms are clear.

Found at DKIM.Net

How to hash the body? A bunch of attachments, a bunch of transcoding…… MailMessage and SmtpClient do not support fetching body. Then find a library, DKIM.Net, which implements the method to get the entire message content. Simply call the MailMessage private method to do it.

Then, DKIM.Net failed to solve the problem. For emails with Attachments or AlternateViews, the signatures of the emails obtained each time are invalid due to inconsistent boundary delimiters. DKIM.Net is a blunt rejection of multipart signatures. NET Framework MimeMultiPart source code to find the following code:

internal string GetNextBoundary() {
	int b = Interlocked.Increment(ref boundary) - 1;
	string boundaryString = "--boundary_" + b.ToString(CultureInfo.InvariantCulture)+"_"+Guid.NewGuid().ToString(null, CultureInfo.InvariantCulture);

	return boundaryString;
}
Copy the code

This method is called only once when MimeMultiPart is initialized, and MimeMultiPart is initialized with mailMessage.send, via mailMessage.setContent. The private mailmessage. Send method is called when an email is sent, and this is how we get the content of the email. . If we make through means MimeMultiPart GetNextBoundary return the boundary of the same, then every time get email content will be the same (same Date).

Found DotNetDetour

Then is the search for control MimeMultiPart. GetNextBoundary function method. Can reflection replace a class instance method with another method? Then follow the search of C# hook, find several articles of the same content, or read the original “I wrote a library can hook.net method”, the content itself is not cool (do not understand), but the end sentence but hook generally need DLL injection to match, because hook itself process is not meaningful. Hook someone else’s process makes sense, hey, do your own, interesting, and then take a look at the code, yes! This is what I want, to change one method of the class to another method! DotNetDetour library.

The Date hidden trouble

Because the signature time and the sending time are different, the signature time is 8:05.999 and the sending time is 8:06.001. As a result, the signature with the Date header fails. However, you are advised to carry the Date header with the signature.

So hook System.Net.Mail.Message.PrepareHeaders can solve this problem, after completion of each process original function we get System.Net.Mail.Message.Headers, then put the Date header to delete, And then write values that we can control.

Changes to DotNetDetour

When testing DotNetDetour, it was found that it could not hook non-public methods, so it changed monitor. cs to add flags to retrieve all methods of the class.

We added a void SetMethod(MethodInfo method) method to the IMethodMonitor interface to pass raw method information to our own methods, simplifying reflection operations in our own functions. Net framework type type string is more complex, with MethodInfo is a property call matter).

ready

DKIM.Net provides the idea to get the content of the mail, move to DotNetDetour hook modify.net system class method, mail DKIM signature is easy ~

Reference article:

“C# sends dkim-signed mail” : discover DKIM.Net

RFC 6376: DKIM signature rule

DKIM Guidelines: DOCUMENTS of DKIM signature and verification rules provided by QQ

Discover DotNetDetour

DKIM Test: tests signatures. Prerequisites: You must have your own domain name and configure the DKIM public key of the email domain name

Methods the document

EMail_DKIM.cs

All the code for DKIM signing and validation of the email is in there.

EMail_DKIM class: provides signature and verification Verify.

EMail_DKIM_RAW_EML class: provides ParseOrNull to parse the contents of an. Eml file.

EMail_DKIM_MailMessageText class: Provides the ToRAW used to retrieve the entire contents of a MailMessage in the EMail_DKIM_RAW_EML format.

EMail.cs

Encapsulate a mail sending function.

Mainly provide TimeoutMillisecond, ClientName Settings, a pile of add attachments method AddAttachment (x, x, x), and finally the Send email.

EMail_Unit.cs

Some generic methods of encapsulation, such as Base64. It’s all about peripheral functions.

/ Lib/RSA – csharp directory

This directory contains my RSA-csharp repository code, which is used to parse PEM key pairs.

/ Lib/DotNetDetour directory

Inside this directory is the DotNetDetour library, which uses this version of the code. Hooks that have been modified to support private methods.

The attached

Any mailbox address query

For example, QQ mailbox, smtp.qq.com is the address used for sending mail, not for receiving mail. The address for receiving mail needs mx query. If you have a mailing address, you can send any email to him. Whether he receives it or not is another matter.

Mx query method:

For example, query the mailing address of QQ mailbox

> nslookup
> set typeQq.com mx preference =30, mail exchanger = mx1.qq.com
qq.com  MX preference = 20, mail exchanger = mx2.qq.com
qq.com  MX preference = 10, mail exchanger = mx3.qq.com
Copy the code

Then the reply sender is the receiver address, and the sender is used for spam.

Email domain name DKIM Public key query

To verify the signature of a message, you need to obtain the public key first (if you have a private key, you can verify it with the private key). Give a mailbox and then query the public key method (such as QQ mailbox) :

Step 1: open the mail source code to obtain the s parameter of dkim-signature, and QQ is s201512. Step 2: Combine the NS TXT record name with QQ mailbox: s201512._domainkey.qq.com

> nslookup
> set typeS201512._domainkey.qq.com: "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPsFIOSteMStsN615gUWK2RpNJ/B/ekmm4jVlu2fNzXADFkjF8mCMgh0uYe8w46FVqxUS97habZq6P5jmCj/WvtPGZAX49j
mdaB38hzZ5cUmwYZkdue6dM17sWocPZO8e7HVdq7bQwfGuUjVuMKfeTB3iNeo6/hFhb9TmUgnwjpQIDA
QAB"
Copy the code

Then the p parameter in the text of the response is the public key, which can be copied into PEM format and used for DKIM verification.

Online DKIM signature test

The test needs to have a domain name and configure the corresponding NS DKIM TXT record.

The test example code:

Var rsa = new rsa. rsa (@"-----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- ", true); var rsa = new rsa. rsa (@"-----BEGIN RSA PRIVATE KEY----- PRIVATE KEY content -----END RSA PRIVATE KEY----- ", true); var mail = new EMail("mail.appmaildev.com", 25); mail.TryUseDKIM(new EMail_DKIM("email.jiebian.life", "email", rsa)); mail.FromEmail = "[email protected]"; mail.ToEmail("[email protected]"); Var res=mail.Send(" test ", "test content "); Console.WriteLine(res.IsError?" Send failed :"+ res.errorMessage :" send succeeded ");Copy the code

Report of this test: see images/report-7ea72484.txt

The screenshots

Online Test results:

Start online test:

Console run: