();
var iter = new MimeIterator (message);
// collect our list of attachments and their parent multiparts
while (iter.MoveNext ()) {
var multipart = iter.Parent as Multipart;
var part = iter.Current as MimePart;
if (multipart != null && part != null && part.IsAttachment) {
// keep track of each attachment's parent multipart
multiparts.Add (multipart);
attachments.Add (part);
}
}
// now remove each attachment from its parent multipart...
for (int i = 0; i < attachments.Count; i++)
multiparts[i].Remove (attachments[i]);
```
### Quick and Dirty Enumeration of Message Body Parts
If you would rather skip the proper way of traversing a MIME tree, another option that MimeKit provides
is a simple enumerator over the message's body parts in a flat (depth-first) list.
You can access this flat list via the `BodyParts` property, like so:
```csharp
foreach (var part in message.BodyParts) {
// do something
}
```
Another helper property on the MimeMessage class is the `Attachments` property which works
much the same way as the `BodyParts` property except that it will only contain MIME parts
which have a `Content-Disposition` header value that is set to `attachment`.
### Getting the Decoded Content of a MIME Part
At some point, you're going to want to extract the decoded content of a `MimePart` (such as an image) and
save it to disk or feed it to a UI control to display it.
Once you've found the `MimePart` object that you'd like to extract the content of, here's how you can
save the decoded content to a file:
```csharp
// This will get the name of the file as specified by the sending mail client.
// Note: this value *may* be null, so you'll want to handle that case in your code.
var fileName = part.FileName;
using (var stream = File.Create (fileName)) {
part.Content.DecodeTo (stream);
}
```
You can also get access to the original raw content by "opening" the `Content`. This might be useful
if you want to pass the content off to a UI control that can do its own loading from a stream.
```csharp
using (var stream = part.Content.Open ()) {
// At this point, you can now read from the stream as if it were the original,
// raw content. Assuming you have an image UI control that could load from a
// stream, you could do something like this:
imageControl.Load (stream);
}
```
There are a number of useful filters that can be applied to a `FilteredStream`, so if you find this type of
interface appealing, I suggest taking a look at the available filters in the `MimeKit.IO.Filters` namespace
or even write your own! The possibilities are limited only by your imagination.
### Creating a Simple Message
Creating MIME messages using MimeKit is really trivial.
```csharp
var message = new MimeMessage ();
message.From.Add (new MailboxAddress ("Joey", "joey@friends.com"));
message.To.Add (new MailboxAddress ("Alice", "alice@wonderland.com"));
message.Subject = "How you doin?";
message.Body = new TextPart ("plain") {
Text = @"Hey Alice,
What are you up to this weekend? Monica is throwing one of her parties on
Saturday and I was hoping you could make it.
Will you be my +1?
-- Joey
"
};
```
A `TextPart` is a leaf-node MIME part with a text media-type. The first argument to the `TextPart` constructor
specifies the media-subtype, in this case, "plain". Another media subtype you are probably familiar with
is the "html" subtype. Some other examples include "enriched", "rtf", and "csv".
The `Text` property is the easiest way to both get and set the string content of the MIME part.
### Creating a Message with Attachments
Attachments are just like any other `MimePart`, the only difference is that they typically have
a `Content-Disposition` header with a value of "attachment" instead of "inline" or no
`Content-Disposition` header at all.
Typically, when a mail client adds attachments to a message, it will create a `multipart/mixed`
part and add the text body part and all of the file attachments to the `multipart/mixed.`
Here's how you can do that with MimeKit:
```csharp
var message = new MimeMessage ();
message.From.Add (new MailboxAddress ("Joey", "joey@friends.com"));
message.To.Add (new MailboxAddress ("Alice", "alice@wonderland.com"));
message.Subject = "How you doin?";
// create our message text, just like before (except don't set it as the message.Body)
var body = new TextPart ("plain") {
Text = @"Hey Alice,
What are you up to this weekend? Monica is throwing one of her parties on
Saturday and I was hoping you could make it.
Will you be my +1?
-- Joey
"
};
// create an image attachment for the file located at path
var attachment = new MimePart ("image", "gif") {
Content = new MimeContent (File.OpenRead (path), ContentEncoding.Default),
ContentDisposition = new ContentDisposition (ContentDisposition.Attachment),
ContentTransferEncoding = ContentEncoding.Base64,
FileName = Path.GetFileName (path)
};
// now create the multipart/mixed container to hold the message text and the
// image attachment
var multipart = new Multipart ("mixed");
multipart.Add (body);
multipart.Add (attachment);
// now set the multipart/mixed as the message body
message.Body = multipart;
```
Of course, that is just a simple example. A lot of modern mail clients such as Outlook or Thunderbird will
send out both a `text/html` and a `text/plain` version of the message text. To do this, you'd create a
`TextPart` for the `text/plain` part and another `TextPart` for the `text/html` part and then add them to a
`multipart/alternative` like so:
```csharp
var attachment = CreateAttachment ();
var plain = CreateTextPlainPart ();
var html = CreateTextHtmlPart ();
// Note: it is important that the text/html part is added second, because it is the
// most expressive version and (probably) the most faithful to the sender's WYSIWYG
// editor.
var alternative = new Multipart ("alternative");
alternative.Add (plain);
alternative.Add (html);
// now create the multipart/mixed container to hold the multipart/alternative
// and the image attachment
var multipart = new Multipart ("mixed");
multipart.Add (alternative);
multipart.Add (attachment);
// now set the multipart/mixed as the message body
message.Body = multipart;
```
### Creating a Message Using a BodyBuilder (not Arnold Schwarzenegger)
If you are used to System.Net.Mail's API for creating messages, you will probably find using a `BodyBuilder`
much more friendly than manually creating the tree of MIME parts. Here's how you could create a message body
using a `BodyBuilder`:
```csharp
var message = new MimeMessage ();
message.From.Add (new MailboxAddress ("Joey", "joey@friends.com"));
message.To.Add (new MailboxAddress ("Alice", "alice@wonderland.com"));
message.Subject = "How you doin?";
var builder = new BodyBuilder ();
// Set the plain-text version of the message text
builder.TextBody = @"Hey Alice,
What are you up to this weekend? Monica is throwing one of her parties on
Saturday and I was hoping you could make it.
Will you be my +1?
-- Joey
";
// generate a Content-Id for the image we'll be referencing
var contentId = MimeUtils.GenerateMessageId ();
// Set the html version of the message text
builder.HtmlBody = string.Format (@"Hey Alice,
What are you up to this weekend? Monica is throwing one of her parties on
Saturday and I was hoping you could make it.
Will you be my +1?
-- Joey
", contentId);
// Since selfie.jpg is referenced from the html text, we'll need to add it
// to builder.LinkedResources and then set the Content-Id header value
builder.LinkedResources.Add (@"C:\Users\Joey\Documents\Selfies\selfie.jpg");
builder.LinkedResources[0].ContentId = contentId;
// We may also want to attach a calendar event for Monica's party...
builder.Attachments.Add (@"C:\Users\Joey\Documents\party.ics");
// Now we just need to set the message body and we're done
message.Body = builder.ToMessageBody ();
```
### Preparing to use MimeKit's S/MIME support
Before you can begin using MimeKit's S/MIME support, you will need to decide which
database to use for certificate storage.
If you are targetting any of the Xamarin platforms (or Linux), you won't need to do
anything (although you certainly can if you want to) because, by default, I've
configured MimeKit to use the Mono.Data.Sqlite binding to SQLite.
If you are on any of the Windows platforms, however, you'll need to decide on whether
to use one of the conveniently available backends such as the `WindowsSecureMimeContext`
backend or the `TemporarySecureMimeContext` backend or else you'll need to pick a
System.Data provider such as
[System.Data.SQLite](https://www.nuget.org/packages/System.Data.SQLite) to use with
the `DefaultSecureMimeContext` base class.
If you opt for using the `DefaultSecureMimeContext` backend, you'll need to implement
your own `DefaultSecureMimeContext` subclass. Luckily, it's very simple to do.
Assuming you've chosen System.Data.SQLite, here's how you'd implement your own
`DefaultSecureMimeContext` class:
```csharp
using System.Data.SQLite;
using MimeKit.Cryptography;
using MyAppNamespace {
class MySecureMimeContext : DefaultSecureMimeContext
{
public MySecureMimeContext () : base (OpenDatabase ("C:\\wherever\\certdb.sqlite"))
{
}
static IX509CertificateDatabase OpenDatabase (string fileName)
{
var builder = new SQLiteConnectionStringBuilder ();
builder.DateTimeFormat = SQLiteDateFormats.Ticks;
builder.DataSource = fileName;
if (!File.Exists (fileName))
SQLiteConnection.CreateFile (fileName);
var sqlite = new SQLiteConnection (builder.ConnectionString);
sqlite.Open ();
return new SqliteCertificateDatabase (sqlite, "password");
}
}
}
```
Now that you've implemented your own `SecureMimeContext`, you'll want to register it with MimeKit:
```csharp
CryptographyContext.Register (typeof (MySecureMimeContext));
```
Now you are ready to encrypt, decrypt, sign and verify S/MIME messages!
Note: If you choose to use the `WindowsSecureMimeContext` or `TemporarySecureMimeContext` backend,
you should register that class instead.
### Preparing to use MimeKit's PGP/MIME support
Like with S/MIME support, you also need to register your own `OpenPgpContext`. Unlike S/MIME, however,
you don't need to choose a database if you subclass `GnuPGContext` because it uses GnuPG's PGP keyrings
to load and store public and private keys. If you choose to subclass `GnuPGContext`, the only thing you
you need to do is implement a password callback method:
```csharp
using MimeKit.Cryptography;
namespace MyAppNamespace {
class MyGnuPGContext : GnuPGContext
{
public MyGnuPgContext () : base ()
{
}
protected override string GetPasswordForKey (PgpSecretKey key)
{
// prompt the user (or a secure password cache) for the password for the specified secret key.
return "password";
}
}
}
```
Once again, to register your `OpenPgpContext`, you can use the following code snippet:
```csharp
CryptographyContext.Register (typeof (MyGnuPGContext));
```
Now you are ready to encrypt, decrypt, sign and verify PGP/MIME messages!
### Encrypting Messages with S/MIME
S/MIME uses an `application/pkcs7-mime` MIME part to encapsulate encrypted content (as well as other things).
```csharp
var joey = new MailboxAddress ("Joey", "joey@friends.com");
var alice = new MailboxAddress ("Alice", "alice@wonderland.com");
var message = new MimeMessage ();
message.From.Add (joey);
message.To.Add (alice);
message.Subject = "How you doin?";
// create our message body (perhaps a multipart/mixed with the message text and some
// image attachments, for example)
var body = CreateMessageBody ();
// now to encrypt our message body using our custom S/MIME cryptography context
using (var ctx = new MySecureMimeContext ()) {
// Note: this assumes that "Alice" has an S/MIME certificate with an X.509
// Subject Email identifier that matches her email address. If she doesn't,
// try using a SecureMailboxAddress which allows you to specify the
// fingerprint of her certificate to use for lookups.
message.Body = ApplicationPkcs7Mime.Encrypt (ctx, message.To.Mailboxes, body);
}
```
### Decrypting S/MIME Messages
As mentioned earlier, S/MIME uses an `application/pkcs7-mime` part with an "smime-type" parameter with a value of
"enveloped-data" to encapsulate the encrypted content.
The first thing you must do is find the `ApplicationPkcs7Mime` part (see the section on traversing MIME parts).
```csharp
if (entity is ApplicationPkcs7Mime) {
var pkcs7 = (ApplicationPkcs7Mime) entity;
if (pkcs7.SecureMimeType == SecureMimeType.EnvelopedData)
return pkcs7.Decrypt ();
}
```
### Encrypting Messages with PGP/MIME
Unlike S/MIME, PGP/MIME uses `multipart/encrypted` to encapsulate its encrypted data.
```csharp
var joey = new MailboxAddress ("Joey", "joey@friends.com");
var alice = new MailboxAddress ("Alice", "alice@wonderland.com");
var message = new MimeMessage ();
message.From.Add (joey);
message.To.Add (alice);
message.Subject = "How you doin?";
// create our message body (perhaps a multipart/mixed with the message text and some
// image attachments, for example)
var body = CreateMessageBody ();
// now to encrypt our message body using our custom PGP/MIME cryptography context
using (var ctx = new MyGnuPGContext ()) {
// Note: this assumes that "Alice" has a public PGP key that matches her email
// address. If she doesn't, try using a SecureMailboxAddress which allows you
// to specify the fingerprint of her public PGP key to use for lookups.
message.Body = MultipartEncrypted.Encrypt (ctx, message.To.Mailboxes, body);
}
```
### Decrypting PGP/MIME Messages
As mentioned earlier, PGP/MIME uses a `multipart/encrypted` part to encapsulate the encrypted content.
A `multipart/encrypted` contains exactly 2 parts: the first `MimeEntity` is the version information while the
second `MimeEntity` is the actual encrypted content and will typically be an `application/octet-stream`.
The first thing you must do is find the `MultipartEncrypted` part (see the section on traversing MIME parts).
```csharp
if (entity is MultipartEncrypted) {
var encrypted = (MultipartEncrypted) entity;
return encrypted.Decrypt ();
}
```
### Digitally Signing Messages with S/MIME or PGP/MIME
Both S/MIME and PGP/MIME use a `multipart/signed` to contain the signed content and the detached signature data.
Here's how you might digitally sign a message using S/MIME:
```csharp
var joey = new MailboxAddress ("Joey", "joey@friends.com");
var alice = new MailboxAddress ("Alice", "alice@wonderland.com");
var message = new MimeMessage ();
message.From.Add (joey);
message.To.Add (alice);
message.Subject = "How you doin?";
// create our message body (perhaps a multipart/mixed with the message text and some
// image attachments, for example)
var body = CreateMessageBody ();
// now to digitally sign our message body using our custom S/MIME cryptography context
using (var ctx = new MySecureMimeContext ()) {
// Note: this assumes that "Joey" has an S/MIME signing certificate and private key
// with an X.509 Subject Email identifier that matches Joey's email address.
message.Body = MultipartSigned.Create (ctx, joey, DigestAlgorithm.Sha1, body);
}
```
For S/MIME, if you have a way for the user to configure which S/MIME certificate to use
as their signing certificate, you could also do something more like this:
```csharp
// now to digitally sign our message body using our custom S/MIME cryptography context
using (var ctx = new MySecureMimeContext ()) {
var certificate = GetJoeysX509Certificate ();
var signer = new CmsSigner (certificate);
signer.DigestAlgorithm = DigestAlgorithm.Sha1;
message.Body = MultipartSigned.Create (ctx, signer, body);
}
```
If you'd prefer to use PGP instead of S/MIME, things work almost exactly the same except that you
would use an OpenPGP cryptography context. For example, you might use a subclass of the
`GnuPGContext` that comes with MimeKit if you want to re-use the user's GnuPG keyrings (you can't
use `GnuPGContext` directly because it has no way of prompting the user for their passphrase).
For the sake of this example, let's pretend that you've written a minimal subclass of
`MimeKit.Cryptography.GnuPGContext` that only overrides the `GetPassword()` method and
that this subclass is called `MyGnuPGContext`.
```csharp
// now to digitally sign our message body using our custom OpenPGP cryptography context
using (var ctx = new MyGnuPGContext ()) {
// Note: this assumes that "Joey" has a PGP key that matches his email address.
message.Body = MultipartSigned.Create (ctx, joey, DigestAlgorithm.Sha1, body);
}
```
Just like S/MIME, however, you can also do your own PGP key lookups instead of
relying on email addresses to match up with the user's private key.
```csharp
// now to digitally sign our message body using our custom OpenPGP cryptography context
using (var ctx = new MyGnuPGContext ()) {
var key = GetJoeysPrivatePgpKey ();
message.Body = MultipartSigned.Create (ctx, key, DigestAlgorithm.Sha1, body);
}
```
### Verifying S/MIME and PGP/MIME Digital Signatures
As mentioned earlier, both S/MIME and PGP/MIME typically use a `multipart/signed` part to contain the
signed content and the detached signature data.
A `multipart/signed` contains exactly 2 parts: the first `MimeEntity` is the signed content while the second
`MimeEntity` is the detached signature and, by default, will either be an `ApplicationPgpSignature` part or
an `ApplicationPkcs7Signature` part (depending on whether the sending client signed using OpenPGP or S/MIME).
Because the `multipart/signed` part may have been signed by multiple signers, it is important to
verify each of the digital signatures (one for each signer) that are returned by the
`MultipartSigned.Verify()` method:
```csharp
if (entity is MultipartSigned) {
var signed = (MultipartSigned) entity;
foreach (var signature in signed.Verify ()) {
try {
bool valid = signature.Verify ();
// If valid is true, then it signifies that the signed content has not been
// modified since this particular signer signed the content.
//
// However, if it is false, then it indicates that the signed content has
// been modified.
} catch (DigitalSignatureVerifyException) {
// There was an error verifying the signature.
}
}
}
```
It should be noted, however, that while most S/MIME clients will use the preferred `multipart/signed`
approach, it is possible that you may encounter an `application/pkcs7-mime` part with an "smime-type"
parameter set to "signed-data". Luckily, MimeKit can handle this format as well:
```csharp
if (entity is ApplicationPkcs7Mime) {
var pkcs7 = (ApplicationPkcs7Mime) entity;
if (pkcs7.SecureMimeType == SecureMimeType.SignedData) {
// extract the original content and get a list of signatures
MimeEntity extracted;
// Note: if you are rendering the message, you'll want to render the
// extracted mime part rather than the application/pkcs7-mime part.
foreach (var signature in pkcs7.Verify (out extracted)) {
try {
bool valid = signature.Verify ();
// If valid is true, then it signifies that the signed content has not
// been modified since this particular signer signed the content.
//
// However, if it is false, then it indicates that the signed content
// has been modified.
} catch (DigitalSignatureVerifyException) {
// There was an error verifying the signature.
}
}
}
}
```
### Signing Messages with DKIM
In addition to OpenPGP and S/MIME, MimeKit also supports DKIM signatures. To sign a message using DKIM,
you'll first need a private key. In the following example, assume that the private key is saved in a
file called **privatekey.pem**:
```csharp
var headers = new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.Date };
var signer = new DkimSigner ("privatekey.pem", "example.com", "brisbane", DkimSignatureAlgorithm.RsaSha256) {
HeaderCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple,
BodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple,
AgentOrUserIdentifier = "@eng.example.com",
QueryMethod = "dns/txt",
};
// Prepare the message body to be sent over a 7bit transport (such as older versions of SMTP).
// Note: If the SMTP server you will be sending the message over supports the 8BITMIME extension,
// then you can use `EncodingConstraint.EightBit` instead.
message.Prepare (EncodingConstraint.SevenBit);
signer.Sign (message, headers);
```
As you can see, it's fairly straight forward.
### Verifying DKIM Signatures
Verifying DKIM signatures is slightly more involved than creating them because you'll need to write a custom
implementation of the `IDkimPublicKeyLocator` interface. Typically, this custom class will need to download
the DKIM public keys via your chosen DNS library as they are requested by MimeKit during verification of
DKIM signature headers.
Once you've implemented a custom `IDkimPublicKeyLocator`, verifying signatures is fairly trivial. Most of the work
needed will be in the `IDkimPublicKeyLocator` implementation. As an example of how to implement this interface,
here is one possible implementation using the [Heijden.DNS](https://www.nuget.org/packages/Heijden.Dns/) library:
```csharp
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Heijden.DNS;
using Org.BouncyCastle.Crypto;
using MimeKit;
using MimeKit.Cryptography;
namespace DkimVerifierExample
{
// Note: By using the DkimPublicKeyLocatorBase, we avoid having to parse the DNS TXT records
// in order to get the public key ourselves.
class DkimPublicKeyLocator : DkimPublicKeyLocatorBase
{
readonly Dictionary cache;
readonly Resolver resolver;
public DkimPublicKeyLocator ()
{
cache = new Dictionary ();
resolver = new Resolver ("8.8.8.8") {
TransportType = TransportType.Udp,
UseCache = true,
Retries = 3
};
}
AsymmetricKeyParameter DnsLookup (string domain, string selector, CancellationToken cancellationToken)
{
var query = selector + "._domainkey." + domain;
AsymmetricKeyParameter pubkey;
// checked if we've already fetched this key
if (cache.TryGetValue (query, out pubkey))
return pubkey;
// make a DNS query
var response = resolver.Query (query, QType.TXT);
var builder = new StringBuilder ();
// combine the TXT records into 1 string buffer
foreach (var record in response.RecordsTXT) {
foreach (var text in record.TXT)
builder.Append (text);
}
var txt = builder.ToString ();
// DkimPublicKeyLocatorBase provides us with this helpful method.
pubkey = GetPublicKey (txt);
cache.Add (query, pubkey);
return pubkey;
}
public AsymmetricKeyParameter LocatePublicKey (string methods, string domain, string selector, CancellationToken cancellationToken = default (CancellationToken))
{
var methodList = methods.Split (new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < methodList.Length; i++) {
if (methodList[i] == "dns/txt")
return DnsLookup (domain, selector, cancellationToken);
}
throw new NotSupportedException (string.Format ("{0} does not include any suported lookup methods.", methods));
}
public Task LocatePublicKeyAsync (string methods, string domain, string selector, CancellationToken cancellationToken = default (CancellationToken))
{
return Task.Run (() => {
return LocatePublicKey (methods, domain, selector, cancellationToken);
}, cancellationToken);
}
}
class Program
{
public static void Main (string[] args)
{
if (args.Length == 0) {
Help ();
return;
}
for (int i = 0; i < args.Length; i++) {
if (args[i] == "--help") {
Help ();
return;
}
}
var locator = new DkimPublicKeyLocator ();
var verifier = new DkimVerifier (locator);
for (int i = 0; i < args.Length; i++) {
if (!File.Exists (args[i])) {
Console.Error.WriteLine ("{0}: No such file.", args[i]);
continue;
}
Console.Write ("{0} -> ", args[i]);
var message = MimeMessage.Load (args[i]);
var index = message.Headers.IndexOf (HeaderId.DkimSignature);
if (index == -1) {
Console.WriteLine ("NO SIGNATURE");
continue;
}
var dkim = message.Headers[index];
if (verifier.Verify (message, dkim)) {
// the DKIM-Signature header is valid!
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine ("VALID");
Console.ResetColor ();
} else {
// the DKIM-Signature is invalid!
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine ("INVALID");
Console.ResetColor ();
}
}
}
static void Help ()
{
Console.WriteLine ("Usage is: DkimVerifier [options] [messages]");
Console.WriteLine ();
Console.WriteLine ("Options:");
Console.WriteLine (" --help This help menu.");
}
}
}
```
### Signing Messages with ARC
Signing with ARC is similar to DKIM but quite a bit more involved. In order to sign with
ARC, you must first validate that the existing message is authentictic and produce
an ARC-Authentication-Results header containing the methods that you used to
authenticate the message as well as their results.
The abstract [ArcSigner](https://www.mimekit.net/docs/html/T_MimeKit_Cryptography_ArcSigner.htm)
class provided by MimeKit will need to be subclassed before it can be used. An example subclass
that provides 2 different implementations for generating the ARC-Authentication-Results header
can be seen below:
```csharp
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using MimeKit;
using MimeKit.Cryptography;
namespace ArcSignerExample
{
class MyArcSigner : ArcSigner
{
public MyArcSigner (string fileName, string domain, string selector, DkimSigningAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)
: base (fileName, domain, selector, algorithm)
{
}
///
/// Generate the ARC-Authentication-Results header.
///
///
/// The ARC-Authentication-Results header contains information detailing the results of
/// authenticating/verifying the message via ARC, DKIM, SPF, etc.
///
/// In the following implementation, we assume that all of these authentication results
/// have already been determined by other mail software that has added some Authentication-Results
/// headers containing this information.
///
/// Note: This method is used when ArcSigner.Sign() is called instead of ArcSigner.SignAsync().
///
protected override AuthenticationResults GenerateArcAuthenticationResults (FormatOptions options, MimeMessage message, CancellationToken cancellationToken)
{
const string AuthenticationServiceIdentifier = "lists.example.com";
var results = new AuthenticationResults (AuthenticationServiceIdentifier);
for (int i = 0; i < message.Headers.Count; i++) {
var header = message.Headers[i];
if (header.Id != HeaderId.AuthenticationResults)
continue;
if (!AuthenticationResults.TryParse (header.RawValue, out AuthenticationResults authres))
continue;
if (authres.AuthenticationServiceIdentifier != AuthenticationServiceIdentifier)
continue;
// Merge any authentication results that aren't already known.
foreach (var result in authres.Results) {
if (!results.Results.Any (r => r.Method == result.Method))
results.Results.Add (result);
}
}
return results;
}
///
/// Generate the ARC-Authentication-Results asynchronously.
///
///
/// The ARC-Authentication-Results header contains information detailing the results of
/// authenticating/verifying the message via ARC, DKIM, SPF, etc.
///
/// In the following implementation, we assume that we have to verify all of the various
/// authentication methods ourselves.
///
/// Note: This method is used when ArcSigner.SignAsync() is called instead of ArcSigner.Sign().
///
protected override async Task GenerateArcAuthenticationResultsAsync (FormatOptions options, MimeMessage message, CancellationToken cancellationToken)
{
const string AuthenticationServiceIdentifier = "lists.example.com";
var results = new AuthenticationResults (AuthenticationServiceIdentifier);
var locator = new DkimPublicKeyLocator (); // from the DKIM example above
var dkimVerifier = new DkimVerifier (locator);
var arcVerifier = new ArcVerifier (locator);
AuthenticationMethodResult method;
// Add the ARC authentication results
try {
var arc = await arcVerifier.VerifyAsync (message, cancellationToken);
var result = arc.Chain.ToString ().ToLowerInvariant ();
method = new AuthenticationMethodResult ("arc", result);
results.Results.Add (method);
} catch {
// Likely a DNS error
method = new AuthenticationMethodResult ("arc", "fail");
method.Reason = "DNS error";
results.Results.Add (method);
}
// Add authentication results for each DKIM signature
foreach (var dkimHeader in message.Headers.Where (h => h.Id == HeaderId.DkimSignature)) {
string result;
try {
if (await dkimVerifier.VerifyAsync (message, cancellationToken)) {
result = "pass";
} else {
result = "fail";
}
} catch {
result = "fail";
}
method = new AuthenticationMethodResult ("dkim", result);
// Parse the DKIM-Signature header so that we can add some
// properties to our method result.
var params = dkimHeader.Value.Replace (" ", "").Split (new char[] { ';' });
var i = params.FirstOrDefault (p => p.StartsWith ("i=", StringComparison.Ordinal));
var b = params.FirstOrDefault (p => p.StartsWith ("b=", StringComparison.Ordinal));
if (i != null)
method.Parameters.Add ("header.i", i.Substring (2));
if (b != null)
method.Parameters.Add ("header.b", b.Substring (2, 8));
results.Results.Add (method);
}
return results;
}
}
}
```
Once you have a custom `ArcSigner` class, the actual logic for signing is almost identical to DKIM.
Note: As with the DKIM signing example above, assume that the private key is saved in a
file called **privatekey.pem**:
```csharp
var headers = new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.Date };
var signer = new MyArcSigner ("privatekey.pem", "example.com", "brisbane", DkimSignatureAlgorithm.RsaSha256) {
HeaderCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Relaxed,
BodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Relaxed,
AgentOrUserIdentifier = "@eng.example.com"
};
// Prepare the message body to be sent over a 7bit transport (such as older versions of SMTP).
// Note: If the SMTP server you will be sending the message over supports the 8BITMIME extension,
// then you can use `EncodingConstraint.EightBit` instead.
message.Prepare (EncodingConstraint.SevenBit);
signer.Sign (message, headers); // or SignAsync
```
### Verifying ARC Signatures
Just like with verifying DKIM signatures, you will need to implement the `IDkimPublicKeyLocator`
interface. To see an example of how to implement this interface, see the DKIM signature verification
example above.
The `ArcVerifier` works exactly the same as the `DkimVerifier` except that it is not necessary
to provide a `Header` argument to the `Verify` or `VerifyAsync` method.
```csharp
var verifier = new ArcVerifier (new DkimPublicKeyLocator ());
var results = await verifier.VerifyAsync (message);
// The Chain results are the only real important results.
Console.WriteLine ("ARC results: {0}", results.Chain);
```
## Contributing
The first thing you'll need to do is fork MimeKit to your own GitHub repository. For instructions on how to
do that, see the section titled **Getting the Source Code**.
If you use [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/) or [MonoDevelop](https://monodevelop.com),
all of the solution files are configured with the coding style used by MimeKit. If you use Visual Studio on Windows or
some other editor, please try to maintain the existing coding style as best as you can.
Once you've got some changes that you'd like to submit upstream to the official MimeKit repository,
send me a **Pull Request** and I will try to review your changes in a timely manner.
If you'd like to contribute but don't have any particular features in mind to work on, check out the issue
tracker and look for something that might pique your interest!
## Reporting Bugs
Have a bug or a feature request? Please open a new
[bug report](https://github.com/jstedfast/MimeKit/issues/new?template=bug_report.md)
or
[feature request](https://github.com/jstedfast/MimeKit/issues/new?template=feature_request.md).
Before opening a new issue, please search through any [existing issues](https://github.com/jstedfast/MimeKit/issues)
to avoid submitting duplicates. It may also be worth checking the
[FAQ](https://github.com/jstedfast/MimeKit/blob/master/FAQ.md) for common questions that other developers
have had.
If you are getting an exception from somewhere within MimeKit, don't just provide the `Exception.Message`
string. Please include the `Exception.StackTrace` as well. The `Message`, by itself, is often useless.
## Documentation
API documentation can be found at [https://www.mimekit.net/docs](https://www.mimekit.net/docs).
A copy of the XML-formatted API reference documentation is also included in the NuGet package.
## .NET Foundation
MimeKit is a [.NET Foundation](https://www.dotnetfoundation.org/projects) project.
This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. For more information, see the [.NET Foundation Code of Conduct](https://www.dotnetfoundation.org/code-of-conduct).
General .NET OSS discussions: [.NET Foundation forums](https://forums.dotnetfoundation.org)