static void TestUnicode(DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm, string expectedHash) { var headers = new [] { HeaderId.From, HeaderId.To, HeaderId.Subject, HeaderId.Date }; var signer = CreateSigner(signatureAlgorithm); var message = new MimeMessage(); message.From.Add(new MailboxAddress("", "*****@*****.**")); message.To.Add(new MailboxAddress("", "*****@*****.**")); message.Subject = "This is a unicode message"; message.Date = DateTimeOffset.Now; var builder = new BodyBuilder(); builder.TextBody = " تست "; builder.HtmlBody = " <div> تست </div> "; message.Body = builder.ToMessageBody(); ((Multipart)message.Body).Boundary = "=-MultipartAlternativeBoundary"; ((Multipart)message.Body)[1].ContentId = null; message.Body.Prepare(EncodingConstraint.EightBit); message.Sign(signer, headers, DkimCanonicalizationAlgorithm.Simple, bodyAlgorithm); var dkim = message.Headers[0]; Console.WriteLine("{0}", dkim.Value); VerifyDkimBodyHash(message, signatureAlgorithm, expectedHash); Assert.IsTrue(message.Verify(dkim, new DummyPublicKeyLocator(DkimKeys.Public)), "Failed to verify DKIM-Signature."); }
static void TestEmptyBody(DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm, string expectedHash) { var signer = CreateSigner(signatureAlgorithm, DkimCanonicalizationAlgorithm.Simple, bodyAlgorithm); var headers = new [] { HeaderId.From, HeaderId.To, HeaderId.Subject, HeaderId.Date }; var verifier = new DkimVerifier(new DummyPublicKeyLocator(DkimKeys.Public)); var message = new MimeMessage(); message.From.Add(new MailboxAddress("", "*****@*****.**")); message.To.Add(new MailboxAddress("", "*****@*****.**")); message.Subject = "This is an empty message"; message.Date = DateTimeOffset.Now; message.Body = new TextPart("plain") { Text = "" }; message.Prepare(EncodingConstraint.SevenBit); signer.Sign(message, headers); VerifyDkimBodyHash(message, signatureAlgorithm, expectedHash); var dkim = message.Headers[0]; if (signatureAlgorithm == DkimSignatureAlgorithm.RsaSha1) { Assert.IsFalse(verifier.Verify(message, dkim), "DKIM-Signature using rsa-sha1 should not verify."); // now enable rsa-sha1 to verify again, this time it should pass... verifier.Enable(DkimSignatureAlgorithm.RsaSha1); } Assert.IsTrue(verifier.Verify(message, dkim), "Failed to verify DKIM-Signature."); }
static void TestEmptyBody(DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm, string expectedHash) { var headers = new [] { HeaderId.From, HeaderId.To, HeaderId.Subject, HeaderId.Date }; var signer = CreateSigner(signatureAlgorithm); var message = new MimeMessage(); message.From.Add(new MailboxAddress("", "*****@*****.**")); message.To.Add(new MailboxAddress("", "*****@*****.**")); message.Subject = "This is an empty message"; message.Date = DateTimeOffset.Now; message.Body = new TextPart("plain") { Text = "" }; message.Body.Prepare(EncodingConstraint.SevenBit); message.Sign(signer, headers, DkimCanonicalizationAlgorithm.Simple, bodyAlgorithm); VerifyDkimBodyHash(message, signatureAlgorithm, expectedHash); var dkim = message.Headers[0]; Assert.IsTrue(message.Verify(dkim, new DummyPublicKeyLocator(DkimKeys.Public)), "Failed to verify DKIM-Signature."); }
public void Canonicalization2(string emailText, string canonicalizedHeaders, string canonicalizedBody, DkimCanonicalizationAlgorithm type) { var email = Email.Parse(emailText); Assert.AreEqual(canonicalizedBody, DkimCanonicalizer.CanonicalizeBody(email.Body, type), "body " + type); Assert.AreEqual(canonicalizedHeaders, DkimCanonicalizer.CanonicalizeHeaders(email.Headers, type, false, "A", "B"), "headers " + type); Assert.AreEqual(emailText, email.Raw); }
static DkimSigner CreateSigner(DkimSignatureAlgorithm algorithm, DkimCanonicalizationAlgorithm headerAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm) { return(new DkimSigner(Path.Combine(TestHelper.ProjectDir, "TestData", "dkim", "example.pem"), "example.com", "1433868189.example") { BodyCanonicalizationAlgorithm = bodyAlgorithm, HeaderCanonicalizationAlgorithm = headerAlgorithm, SignatureAlgorithm = algorithm, AgentOrUserIdentifier = "@eng.example.com", QueryMethod = "dns/txt" }); }
static void ValidateDkimSignatureParameters(IDictionary <string, string> parameters, out DkimSignatureAlgorithm algorithm, out DkimCanonicalizationAlgorithm headerAlgorithm, out DkimCanonicalizationAlgorithm bodyAlgorithm, out string d, out string s, out string q, out string[] headers, out string bh, out string b, out int maxLength) { bool containsFrom = false; if (!parameters.TryGetValue("v", out string v)) { throw new FormatException("Malformed DKIM-Signature header: no version parameter detected."); } if (v != "1") { throw new FormatException(string.Format("Unrecognized DKIM-Signature version: v={0}", v)); } ValidateCommonSignatureParameters("DKIM-Signature", parameters, out algorithm, out headerAlgorithm, out bodyAlgorithm, out d, out s, out q, out headers, out bh, out b, out maxLength); for (int i = 0; i < headers.Length; i++) { if (headers[i].Equals("from", StringComparison.OrdinalIgnoreCase)) { containsFrom = true; break; } } if (!containsFrom) { throw new FormatException("Malformed DKIM-Signature header: From header not signed."); } if (parameters.TryGetValue("i", out string id)) { string ident; int at; if ((at = id.LastIndexOf('@')) == -1) { throw new FormatException("Malformed DKIM-Signature header: no @ in the AUID value."); } ident = id.Substring(at + 1); if (!ident.Equals(d, StringComparison.OrdinalIgnoreCase) && !ident.EndsWith("." + d, StringComparison.OrdinalIgnoreCase)) { throw new FormatException("Invalid DKIM-Signature header: the domain in the AUID does not match the domain parameter."); } } }
static void TestUnicode(DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm, string expectedHash) { var signer = CreateSigner(signatureAlgorithm, DkimCanonicalizationAlgorithm.Simple, bodyAlgorithm); var headers = new [] { HeaderId.From, HeaderId.To, HeaderId.Subject, HeaderId.Date }; var verifier = new DkimVerifier(new DummyPublicKeyLocator(DkimKeys.Public)); var message = new MimeMessage(); message.From.Add(new MailboxAddress("", "*****@*****.**")); message.To.Add(new MailboxAddress("", "*****@*****.**")); message.Subject = "This is a unicode message"; message.Date = DateTimeOffset.Now; var builder = new BodyBuilder(); builder.TextBody = " تست "; builder.HtmlBody = " <div> تست </div> "; message.Body = builder.ToMessageBody(); ((Multipart)message.Body).Boundary = "=-MultipartAlternativeBoundary"; ((Multipart)message.Body)[1].ContentId = null; message.Prepare(EncodingConstraint.EightBit); signer.Sign(message, headers); var dkim = message.Headers[0]; VerifyDkimBodyHash(message, signatureAlgorithm, expectedHash); if (signatureAlgorithm == DkimSignatureAlgorithm.RsaSha1) { Assert.IsFalse(verifier.Verify(message, dkim), "DKIM-Signature using rsa-sha1 should not verify."); // now enable rsa-sha1 to verify again, this time it should pass... verifier.Enable(DkimSignatureAlgorithm.RsaSha1); } Assert.IsTrue(verifier.Verify(message, dkim), "Failed to verify DKIM-Signature."); }
/// <summary> /// Verify the signature of the message headers. /// </summary> /// <remarks> /// Verifies the signature of the message headers. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="message">The signed MIME message.</param> /// <param name="dkimSignature">The DKIM-Signature or ARC-Message-Signature header.</param> /// <param name="signatureAlgorithm">The algorithm used to sign the message headers.</param> /// <param name="key">The public key used to verify the signature.</param> /// <param name="headers">The list of headers that were signed.</param> /// <param name="canonicalizationAlgorithm">The algorithm used to canonicalize the headers.</param> /// <param name="signature">The expected signature of the headers encoded in base64.</param> /// <returns><c>true</c> if the calculated signature matches <paramref name="signature"/>; otherwise, <c>false</c>.</returns> protected bool VerifySignature(FormatOptions options, MimeMessage message, Header dkimSignature, DkimSignatureAlgorithm signatureAlgorithm, AsymmetricKeyParameter key, string[] headers, DkimCanonicalizationAlgorithm canonicalizationAlgorithm, string signature) { using (var stream = new DkimSignatureStream(CreateVerifyContext(signatureAlgorithm, key))) { using (var filtered = new FilteredStream(stream)) { filtered.Add(options.CreateNewLineFilter()); WriteHeaders(options, message, headers, canonicalizationAlgorithm, filtered); // now include the DKIM-Signature header that we are verifying, // but only after removing the "b=" signature value. var header = GetSignedSignatureHeader(dkimSignature); switch (canonicalizationAlgorithm) { case DkimCanonicalizationAlgorithm.Relaxed: WriteHeaderRelaxed(options, filtered, header, true); break; default: WriteHeaderSimple(options, filtered, header, true); break; } filtered.Flush(); } return(stream.VerifySignature(signature)); } }
/// <summary> /// Verify the hash of the message body. /// </summary> /// <remarks> /// Verifies the hash of the message body. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="message">The signed MIME message.</param> /// <param name="signatureAlgorithm">The algorithm used to sign the message.</param> /// <param name="canonicalizationAlgorithm">The algorithm used to canonicalize the message body.</param> /// <param name="maxLength">The max length of the message body to hash or <c>-1</c> to hash the entire message body.</param> /// <param name="bodyHash">The expected message body hash encoded in base64.</param> /// <returns><c>true</c> if the calculated body hash matches <paramref name="bodyHash"/>; otherwise, <c>false</c>.</returns> protected bool VerifyBodyHash(FormatOptions options, MimeMessage message, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm canonicalizationAlgorithm, int maxLength, string bodyHash) { var hash = Convert.ToBase64String(message.HashBody(options, signatureAlgorithm, canonicalizationAlgorithm, maxLength)); return(hash == bodyHash); }
internal static void WriteHeaders(FormatOptions options, MimeMessage message, IList <string> fields, DkimCanonicalizationAlgorithm headerCanonicalizationAlgorithm, Stream stream) { var counts = new Dictionary <string, int> (StringComparer.Ordinal); for (int i = 0; i < fields.Count; i++) { var headers = fields[i].StartsWith("Content-", StringComparison.OrdinalIgnoreCase) ? message.Body.Headers : message.Headers; var name = fields[i].ToLowerInvariant(); int index, count, n = 0; if (!counts.TryGetValue(name, out count)) { count = 0; } // Note: signers choosing to sign an existing header field that occurs more // than once in the message (such as Received) MUST sign the physically last // instance of that header field in the header block. Signers wishing to sign // multiple instances of such a header field MUST include the header field // name multiple times in the list of header fields and MUST sign such header // fields in order from the bottom of the header field block to the top. index = headers.LastIndexOf(name); // find the n'th header with this name while (n < count && --index >= 0) { if (headers[index].Field.Equals(name, StringComparison.OrdinalIgnoreCase)) { n++; } } if (index < 0) { continue; } var header = headers[index]; switch (headerCanonicalizationAlgorithm) { case DkimCanonicalizationAlgorithm.Relaxed: WriteHeaderRelaxed(options, stream, header, false); break; default: WriteHeaderSimple(options, stream, header, false); break; } counts[name] = ++count; } }
public static string CanonicalizeHeaders(List<EmailHeader> headers, DkimCanonicalizationAlgorithm type) { var sb = new StringBuilder(); switch (type) { /* * 3.4.1 The "simple" Header Canonicalization Algorithm * * The "simple" header canonicalization algorithm does not change header fields in any way. * Header fields MUST be presented to the signing or verification algorithm exactly as they * are in the message being signed or verified. In particular, header field names MUST NOT * be case folded and whitespace MUST NOT be changed. * * */ case DkimCanonicalizationAlgorithm.SIMPLE: { foreach (var h in headers) { sb.Append(h.Key); sb.Append(": "); sb.Append(h.Value); sb.Append(Email.NewLine); } sb.Length -= Email.NewLine.Length; break; } /* * * 3.4.2 The "relaxed" Header Canonicalization Algorithm * * The "relaxed" header canonicalization algorithm MUST apply the following steps in order: * * Convert all header field names (not the header field values) to lowercase. For example, * convert "SUBJect: AbC" to "subject: AbC". * * * Unfold all header field continuation lines as described in [RFC2822]; in particular, lines * with terminators embedded in continued header field values (that is, CRLF sequences followed * by WSP) MUST be interpreted without the CRLF. Implementations MUST NOT remove the CRLF at the * end of the header field value. * * * Convert all sequences of one or more WSP characters to a single SP character. WSP characters * here include those before and after a line folding boundary. * * * Delete all WSP characters at the end of each unfolded header field value. * * * Delete any WSP characters remaining before and after the colon separating the header field name * from the header field value. The colon separator MUST be retained. * * */ case DkimCanonicalizationAlgorithm.RELAXED: { foreach (var h in headers) { sb.Append(h.Key.Trim().ToLower()); sb.Append(':'); sb.Append(h.Value.Trim().Replace(Email.NewLine, string.Empty).ReduceWitespace()); sb.Append(Email.NewLine); } sb.Length -= Email.NewLine.Length; break; } default: { throw new ArgumentException("Invalid canonicalization type."); } } return sb.ToString(); }
static void ValidateDkimSignatureParameters (IDictionary<string, string> parameters, out DkimSignatureAlgorithm algorithm, out DkimCanonicalizationAlgorithm headerAlgorithm, out DkimCanonicalizationAlgorithm bodyAlgorithm, out string d, out string s, out string q, out string h, out string bh, out string b, out int maxLength) { string v, a, c, l; if (!parameters.TryGetValue ("v", out v)) throw new FormatException ("Malformed DKIM-Signature header: no version parameter detected."); if (v != "1") throw new FormatException (string.Format ("Unrecognized DKIM-Signature version: v={0}", v)); if (!parameters.TryGetValue ("a", out a)) throw new FormatException ("Malformed DKIM-Signature header: no signature algorithm parameter detected."); switch (a.ToLowerInvariant ()) { case "rsa-sha256": algorithm = DkimSignatureAlgorithm.RsaSha256; break; case "rsa-sha1": algorithm = DkimSignatureAlgorithm.RsaSha1; break; default: throw new FormatException (string.Format ("Unrecognized DKIM-Signature algorithm parameter: a={0}", a)); } if (!parameters.TryGetValue ("d", out d)) throw new FormatException ("Malformed DKIM-Signature header: no domain parameter detected."); if (!parameters.TryGetValue ("s", out s)) throw new FormatException ("Malformed DKIM-Signature header: no selector parameter detected."); if (!parameters.TryGetValue ("q", out q)) q = "dns/txt"; if (parameters.TryGetValue ("l", out l)) { if (!int.TryParse (l, out maxLength)) throw new FormatException (string.Format ("Malformed DKIM-Signature header: invalid length parameter: l={0}", l)); } else { maxLength = -1; } if (parameters.TryGetValue ("c", out c)) { var tokens = c.ToLowerInvariant ().Split ('/'); if (tokens.Length == 0 || tokens.Length > 2) throw new FormatException (string.Format ("Malformed DKIM-Signature header: invalid canonicalization parameter: c={0}", c)); switch (tokens[0]) { case "relaxed": headerAlgorithm = DkimCanonicalizationAlgorithm.Relaxed; break; case "simple": headerAlgorithm = DkimCanonicalizationAlgorithm.Simple; break; default: throw new FormatException (string.Format ("Malformed DKIM-Signature header: invalid canonicalization parameter: c={0}", c)); } if (tokens.Length == 2) { switch (tokens[1]) { case "relaxed": bodyAlgorithm = DkimCanonicalizationAlgorithm.Relaxed; break; case "simple": bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; break; default: throw new FormatException (string.Format ("Malformed DKIM-Signature header: invalid canonicalization parameter: c={0}", c)); } } else { bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; } } else { headerAlgorithm = DkimCanonicalizationAlgorithm.Simple; bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; } if (!parameters.TryGetValue ("h", out h)) throw new FormatException ("Malformed DKIM-Signature header: no signed header parameter detected."); if (!parameters.TryGetValue ("bh", out bh)) throw new FormatException ("Malformed DKIM-Signature header: no body hash parameter detected."); if (!parameters.TryGetValue ("b", out b)) throw new FormatException ("Malformed DKIM-Signature header: no signature parameter detected."); }
/// <summary> /// Digitally sign the message using a DomainKeys Identified Mail (DKIM) signature. /// </summary> /// <remarks> /// Digitally signs the message using a DomainKeys Identified Mail (DKIM) signature. /// </remarks> /// <param name="options">The formatting options.</param> /// <param name="signer">The DKIM signer.</param> /// <param name="headers">The list of header fields to sign.</param> /// <param name="headerCanonicalizationAlgorithm">The header canonicalization algorithm.</param> /// <param name="bodyCanonicalizationAlgorithm">The body canonicalization algorithm.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="options"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="signer"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="headers"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para><paramref name="headers"/> does not contain the 'From' header.</para> /// <para>-or-</para> /// <para><paramref name="headers"/> contains one or more of the following headers: Return-Path, /// Received, Comments, Keywords, Bcc, Resent-Bcc, or DKIM-Signature.</para> /// </exception> void Sign (FormatOptions options, DkimSigner signer, IList<HeaderId> headers, DkimCanonicalizationAlgorithm headerCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple) { if (options == null) throw new ArgumentNullException ("options"); if (signer == null) throw new ArgumentNullException ("signer"); if (headers == null) throw new ArgumentNullException ("headers"); if (!headers.Contains (HeaderId.From)) throw new ArgumentException ("The list of headers to sign MUST include the 'From' header."); var fields = new string[headers.Count]; for (int i = 0; i < headers.Count; i++) { if (DkimShouldNotInclude.Contains (headers[i])) throw new ArgumentException (string.Format ("The list of headers to sign SHOULD NOT include the '{0}' header.", headers[i].ToHeaderName ())); fields[i] = headers[i].ToHeaderName ().ToLowerInvariant (); } if (version == null && Body != null && Body.Headers.Count > 0) MimeVersion = new Version (1, 0); Prepare (EncodingConstraint.SevenBit, 78); var t = DateTime.Now - DateUtils.UnixEpoch; var value = new StringBuilder ("v=1"); byte[] signature, hash; Header dkim; options = options.Clone (); options.NewLineFormat = NewLineFormat.Dos; switch (signer.SignatureAlgorithm) { case DkimSignatureAlgorithm.RsaSha256: value.Append ("; a=rsa-sha256"); break; default: value.Append ("; a=rsa-sha1"); break; } value.AppendFormat ("; d={0}; s={1}", signer.Domain, signer.Selector); value.AppendFormat ("; c={0}/{1}", headerCanonicalizationAlgorithm.ToString ().ToLowerInvariant (), bodyCanonicalizationAlgorithm.ToString ().ToLowerInvariant ()); if (!string.IsNullOrEmpty (signer.QueryMethod)) value.AppendFormat ("; q={0}", signer.QueryMethod); if (!string.IsNullOrEmpty (signer.AgentOrUserIdentifier)) value.AppendFormat ("; i={0}", signer.AgentOrUserIdentifier); value.AppendFormat ("; t={0}", (long) t.TotalSeconds); using (var stream = new DkimSignatureStream (DkimGetDigestSigner (signer.SignatureAlgorithm, signer.PrivateKey))) { using (var filtered = new FilteredStream (stream)) { filtered.Add (options.CreateNewLineFilter ()); // write the specified message headers DkimWriteHeaders (options, fields, headerCanonicalizationAlgorithm, filtered); value.AppendFormat ("; h={0}", string.Join (":", fields.ToArray ())); hash = DkimHashBody (options, signer.SignatureAlgorithm, bodyCanonicalizationAlgorithm, -1); value.AppendFormat ("; bh={0}", Convert.ToBase64String (hash)); value.Append ("; b="); dkim = new Header (HeaderId.DkimSignature, value.ToString ()); Headers.Insert (0, dkim); switch (headerCanonicalizationAlgorithm) { case DkimCanonicalizationAlgorithm.Relaxed: DkimWriteHeaderRelaxed (options, filtered, dkim); break; default: DkimWriteHeaderSimple (options, filtered, dkim); break; } filtered.Flush (); } signature = stream.GenerateSignature (); dkim.Value += Convert.ToBase64String (signature); } }
public static string CanonicalizeHeaders(List <EmailHeader> headers, DkimCanonicalizationAlgorithm type) { var sb = new StringBuilder(); switch (type) { /* * 3.4.1 The "simple" Header Canonicalization Algorithm * * The "simple" header canonicalization algorithm does not change header fields in any way. * Header fields MUST be presented to the signing or verification algorithm exactly as they * are in the message being signed or verified. In particular, header field names MUST NOT * be case folded and whitespace MUST NOT be changed. * * */ case DkimCanonicalizationAlgorithm.SIMPLE: { foreach (var h in headers) { sb.Append(h.Key); sb.Append(": "); sb.Append(h.Value); sb.Append(Email.NewLine); } sb.Length -= Email.NewLine.Length; break; } /* * * 3.4.2 The "relaxed" Header Canonicalization Algorithm * * The "relaxed" header canonicalization algorithm MUST apply the following steps in order: * * Convert all header field names (not the header field values) to lowercase. For example, * convert "SUBJect: AbC" to "subject: AbC". * * * Unfold all header field continuation lines as described in [RFC2822]; in particular, lines * with terminators embedded in continued header field values (that is, CRLF sequences followed * by WSP) MUST be interpreted without the CRLF. Implementations MUST NOT remove the CRLF at the * end of the header field value. * * * Convert all sequences of one or more WSP characters to a single SP character. WSP characters * here include those before and after a line folding boundary. * * * Delete all WSP characters at the end of each unfolded header field value. * * * Delete any WSP characters remaining before and after the colon separating the header field name * from the header field value. The colon separator MUST be retained. * * */ case DkimCanonicalizationAlgorithm.RELAXED: { foreach (var h in headers) { sb.Append(h.Key.Trim().ToLower()); sb.Append(':'); sb.Append(h.Value.Trim().Replace(Email.NewLine, string.Empty).ReduceWitespace()); sb.Append(Email.NewLine); } sb.Length -= Email.NewLine.Length; break; } default: { throw new ArgumentException("Invalid canonicalization type."); } } return(sb.ToString()); }
static void TestUnicode (DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm, string expectedHash) { var headers = new [] { HeaderId.From, HeaderId.To, HeaderId.Subject, HeaderId.Date }; var signer = CreateSigner (signatureAlgorithm); var message = new MimeMessage (); message.From.Add (new MailboxAddress ("", "*****@*****.**")); message.To.Add (new MailboxAddress ("", "*****@*****.**")); message.Subject = "This is a unicode message"; message.Date = DateTimeOffset.Now; var builder = new BodyBuilder (); builder.TextBody = " تست "; builder.HtmlBody = " <div> تست </div> "; message.Body = builder.ToMessageBody (); ((Multipart) message.Body).Boundary = "=-MultipartAlternativeBoundary"; ((Multipart) message.Body)[1].ContentId = null; message.Body.Prepare (EncodingConstraint.EightBit); message.Sign (signer, headers, DkimCanonicalizationAlgorithm.Simple, bodyAlgorithm); var dkim = message.Headers[0]; Console.WriteLine ("{0}", dkim.Value); VerifyDkimBodyHash (message, signatureAlgorithm, expectedHash); Assert.IsTrue (message.Verify (dkim, new DummyPublicKeyLocator (DkimKeys.Public)), "Failed to verify DKIM-Signature."); }
public static string CanonicalizeHeaders( [NotNull] Dictionary<string, EmailHeader> headers, DkimCanonicalizationAlgorithm type, bool includeSignatureHeader, params string[] headersToSign) { if (headers == null) { throw new ArgumentNullException("headers"); } if (includeSignatureHeader) { if(headersToSign == null || headersToSign.Length == 0) { headersToSign = new[] {"From", DkimSigner.SignatureKey}; } else { var tmp = new string[headersToSign.Length + 1]; Array.Copy(headersToSign, 0, tmp, 0, headersToSign.Length); tmp[headersToSign.Length] = DkimSigner.SignatureKey; headersToSign = tmp; } } else { if(headersToSign == null || headersToSign.Length == 0) { headersToSign = new[] {"From"}; } } ValidateHeaders(headers, headersToSign); var sb = new StringBuilder(); switch (type) { /* * 3.4.1 The "simple" Header Canonicalization Algorithm * * The "simple" header canonicalization algorithm does not change header fields in any way. * Header fields MUST be presented to the signing or verification algorithm exactly as they * are in the message being signed or verified. In particular, header field names MUST NOT * be case folded and whitespace MUST NOT be changed. * * */ case DkimCanonicalizationAlgorithm.Simple: { foreach (var key in headersToSign) { if(key == null) { continue; } var h = headers[key]; sb.Append(h.Key); sb.Append(':'); sb.Append(h.Value); sb.Append(Email.NewLine); } if (includeSignatureHeader) { sb.Length -= Email.NewLine.Length; } break; } /* * * 3.4.2 The "relaxed" Header Canonicalization Algorithm * * The "relaxed" header canonicalization algorithm MUST apply the following steps in order: * * Convert all header field names (not the header field values) to lowercase. For example, * convert "SUBJect: AbC" to "subject: AbC". * * * Unfold all header field continuation lines as described in [RFC2822]; in particular, lines * with terminators embedded in continued header field values (that is, CRLF sequences followed * by WSP) MUST be interpreted without the CRLF. Implementations MUST NOT remove the CRLF at the * end of the header field value. * * * Convert all sequences of one or more WSP characters to a single SP character. WSP characters * here include those before and after a line folding boundary. * * * Delete all WSP characters at the end of each unfolded header field value. * * * Delete any WSP characters remaining before and after the colon separating the header field name * from the header field value. The colon separator MUST be retained. * * */ case DkimCanonicalizationAlgorithm.Relaxed: { foreach (var key in headersToSign) { if(key == null) { continue; } var h = headers[key]; sb.Append(h.Key.Trim().ToLower()); sb.Append(':'); sb.Append(h.FoldedValue ? h.Value.Trim().Replace(Email.NewLine, string.Empty).ReduceWitespace() : h.Value.Trim().ReduceWitespace()); sb.Append(Email.NewLine); } if (includeSignatureHeader) { sb.Length -= Email.NewLine.Length; } break; } default: { throw new ArgumentException("Invalid canonicalization type."); } } return sb.ToString(); }
byte[] DkimHashBody (FormatOptions options, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm, int maxLength) { using (var stream = new DkimHashStream (signatureAlgorithm, maxLength)) { using (var filtered = new FilteredStream (stream)) { DkimBodyFilter dkim; if (bodyCanonicalizationAlgorithm == DkimCanonicalizationAlgorithm.Relaxed) dkim = new DkimRelaxedBodyFilter (); else dkim = new DkimSimpleBodyFilter (); filtered.Add (options.CreateNewLineFilter ()); filtered.Add (dkim); if (Body != null) { try { Body.EnsureNewLine = compliance == RfcComplianceMode.Strict; Body.Headers.Suppress = true; Body.WriteTo (options, filtered, CancellationToken.None); } finally { Body.Headers.Suppress = false; Body.EnsureNewLine = false; } } filtered.Flush (); if (!dkim.LastWasNewLine) stream.Write (options.NewLineBytes, 0, options.NewLineBytes.Length); } return stream.GenerateHash (); } }
public void UpdateSettings(Settings config) { lock (settingsMutex) { // Load the list of domains domains.Clear(); DkimSignatureAlgorithm signatureAlgorithm; switch (config.SigningAlgorithm) { case DkimAlgorithmKind.RsaSha1: signatureAlgorithm = DkimSignatureAlgorithm.RsaSha1; break; case DkimAlgorithmKind.RsaSha256: signatureAlgorithm = DkimSignatureAlgorithm.RsaSha256; break; default: // ReSharper disable once NotResolvedInText throw new ArgumentOutOfRangeException("config.SigningAlgorithm"); } foreach (DomainElement domainElement in config.Domains) { string privateKey = domainElement.PrivateKeyPathAbsolute( Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); if (String.IsNullOrEmpty(privateKey) || !File.Exists(privateKey)) { Logger.LogError("The private key for domain " + domainElement.Domain + " wasn't found: " + privateKey + ". Ignoring domain."); } //check if the private key can be parsed try { KeyHelper.ParseKeyPair(privateKey); } catch (Exception ex) { Logger.LogError("Couldn't load private key for domain " + domainElement.Domain + ": " + ex.Message); continue; } MimeKit.Cryptography.DkimSigner signer; try { AsymmetricKeyParameter key = KeyHelper.ParsePrivateKey(privateKey); signer = new MimeKit.Cryptography.DkimSigner(key, domainElement.Domain, domainElement.Selector) { SignatureAlgorithm = signatureAlgorithm }; } catch (Exception ex) { Logger.LogError("Could not initialize MimeKit DkimSigner for domain " + domainElement.Domain + ": " + ex.Message); continue; } domains.Add(domainElement.Domain, new DomainElementSigner(domainElement, signer)); } headerCanonicalization = config.HeaderCanonicalization == DkimCanonicalizationKind.Relaxed ? DkimCanonicalizationAlgorithm.Relaxed : DkimCanonicalizationAlgorithm.Simple; bodyCanonicalization = config.BodyCanonicalization == DkimCanonicalizationKind.Relaxed ? DkimCanonicalizationAlgorithm.Relaxed : DkimCanonicalizationAlgorithm.Simple; List<HeaderId> headerList = new List<HeaderId>(); foreach (string headerToSign in config.HeadersToSign) { HeaderId headerId; #if EX_2007_SP3 || EX_2010 || EX_2010_SP1 || EX_2010_SP2 || EX_2010_SP3 if (!TryParseHeader(headerToSign, out headerId)) #else if (!Enum.TryParse(headerToSign, true, out headerId)) #endif { Logger.LogWarning("Invalid value for header to sign: '" + headerToSign + "'. This header will be ignored."); } headerList.Add(headerId); } // The From header must always be signed according to the // DKIM specification. if (!headerList.Contains(HeaderId.From)) { headerList.Add(HeaderId.From); } eligibleHeaders = headerList.ToArray(); } }
/// <summary> /// /// </summary> /// <param name="headers">The existing email headers</param> /// <param name="type">Canonicalization algorithm to be used</param> /// <param name="includeSignatureHeader">Include the 'DKIM-Signature' header </param> /// <param name="headersToSign">The headers to sign. When no heaaders are suppiled the required headers are used.</param> /// <returns></returns> public static string CanonicalizeHeaders( Dictionary <string, EmailHeader> headers, DkimCanonicalizationAlgorithm type, bool includeSignatureHeader, params string[] headersToSign) { if (headers == null) { throw new ArgumentNullException("headers"); } if (includeSignatureHeader) { if (headersToSign == null || headersToSign.Length == 0) { headersToSign = new[] { "From", Constant.DkimSignatureKey }; } else { var tmp = new string[headersToSign.Length + 1]; Array.Copy(headersToSign, 0, tmp, 0, headersToSign.Length); tmp[headersToSign.Length] = Constant.DkimSignatureKey; headersToSign = tmp; } } else { if (headersToSign == null || headersToSign.Length == 0) { headersToSign = new[] { "From" }; } } ValidateHeaders(headers, headersToSign); var sb = new StringBuilder(); switch (type) { /* * 3.4.1 The "simple" Header Canonicalization Algorithm * * The "simple" header canonicalization algorithm does not change header fields in any way. * Header fields MUST be presented to the signing or verification algorithm exactly as they * are in the message being signed or verified. In particular, header field names MUST NOT * be case folded and whitespace MUST NOT be changed. * * */ case DkimCanonicalizationAlgorithm.Simple: { foreach (var key in headersToSign) { if (key == null) { continue; } var h = headers[key]; sb.Append(h.Key); sb.Append(':'); sb.Append(h.Value); sb.Append(Email.NewLine); } if (includeSignatureHeader) { sb.Length -= Email.NewLine.Length; } break; } /* * * 3.4.2 The "relaxed" Header Canonicalization Algorithm * * The "relaxed" header canonicalization algorithm MUST apply the following steps in order: * * Convert all header field names (not the header field values) to lowercase. For example, * convert "SUBJect: AbC" to "subject: AbC". * * * Unfold all header field continuation lines as described in [RFC2822]; in particular, lines * with terminators embedded in continued header field values (that is, CRLF sequences followed * by WSP) MUST be interpreted without the CRLF. Implementations MUST NOT remove the CRLF at the * end of the header field value. * * * Convert all sequences of one or more WSP characters to a single SP character. WSP characters * here include those before and after a line folding boundary. * * * Delete all WSP characters at the end of each unfolded header field value. * * * Delete any WSP characters remaining before and after the colon separating the header field name * from the header field value. The colon separator MUST be retained. * * */ case DkimCanonicalizationAlgorithm.Relaxed: { foreach (var key in headersToSign) { if (key == null) { continue; } var h = headers[key]; sb.Append(h.Key.Trim().ToLower()); sb.Append(':'); sb.Append(h.FoldedValue ? h.Value.Trim().Replace(Email.NewLine, string.Empty).ReduceWitespace() : h.Value.Trim().ReduceWitespace()); sb.Append(Email.NewLine); } if (includeSignatureHeader) { sb.Length -= Email.NewLine.Length; } break; } default: { throw new ArgumentException("Invalid canonicalization type."); } } return(sb.ToString()); }
static void ValidateDkimSignatureParameters (IDictionary<string, string> parameters, out DkimSignatureAlgorithm algorithm, out DkimCanonicalizationAlgorithm headerAlgorithm, out DkimCanonicalizationAlgorithm bodyAlgorithm, out string d, out string s, out string q, out string[] headers, out string bh, out string b, out int maxLength) { bool containsFrom = false; string v, a, c, h, l, id; if (!parameters.TryGetValue ("v", out v)) throw new FormatException ("Malformed DKIM-Signature header: no version parameter detected."); if (v != "1") throw new FormatException (string.Format ("Unrecognized DKIM-Signature version: v={0}", v)); if (!parameters.TryGetValue ("a", out a)) throw new FormatException ("Malformed DKIM-Signature header: no signature algorithm parameter detected."); switch (a.ToLowerInvariant ()) { case "rsa-sha256": algorithm = DkimSignatureAlgorithm.RsaSha256; break; case "rsa-sha1": algorithm = DkimSignatureAlgorithm.RsaSha1; break; default: throw new FormatException (string.Format ("Unrecognized DKIM-Signature algorithm parameter: a={0}", a)); } if (!parameters.TryGetValue ("d", out d)) throw new FormatException ("Malformed DKIM-Signature header: no domain parameter detected."); if (parameters.TryGetValue ("i", out id)) { string ident; int at; if ((at = id.LastIndexOf ('@')) == -1) throw new FormatException ("Malformed DKIM-Signature header: no @ in the AUID value."); ident = id.Substring (at + 1); if (!ident.Equals (d, StringComparison.OrdinalIgnoreCase) && !ident.EndsWith ("." + d, StringComparison.OrdinalIgnoreCase)) throw new FormatException ("Invalid DKIM-Signature header: the domain in the AUID does not match the domain parameter."); } if (!parameters.TryGetValue ("s", out s)) throw new FormatException ("Malformed DKIM-Signature header: no selector parameter detected."); if (!parameters.TryGetValue ("q", out q)) q = "dns/txt"; if (parameters.TryGetValue ("l", out l)) { if (!int.TryParse (l, out maxLength)) throw new FormatException (string.Format ("Malformed DKIM-Signature header: invalid length parameter: l={0}", l)); } else { maxLength = -1; } if (parameters.TryGetValue ("c", out c)) { var tokens = c.ToLowerInvariant ().Split ('/'); if (tokens.Length == 0 || tokens.Length > 2) throw new FormatException (string.Format ("Malformed DKIM-Signature header: invalid canonicalization parameter: c={0}", c)); switch (tokens[0]) { case "relaxed": headerAlgorithm = DkimCanonicalizationAlgorithm.Relaxed; break; case "simple": headerAlgorithm = DkimCanonicalizationAlgorithm.Simple; break; default: throw new FormatException (string.Format ("Malformed DKIM-Signature header: invalid canonicalization parameter: c={0}", c)); } if (tokens.Length == 2) { switch (tokens[1]) { case "relaxed": bodyAlgorithm = DkimCanonicalizationAlgorithm.Relaxed; break; case "simple": bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; break; default: throw new FormatException (string.Format ("Malformed DKIM-Signature header: invalid canonicalization parameter: c={0}", c)); } } else { bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; } } else { headerAlgorithm = DkimCanonicalizationAlgorithm.Simple; bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; } if (!parameters.TryGetValue ("h", out h)) throw new FormatException ("Malformed DKIM-Signature header: no signed header parameter detected."); headers = h.Split (':'); for (int i = 0; i < headers.Length; i++) { if (headers[i].Equals ("from", StringComparison.OrdinalIgnoreCase)) { containsFrom = true; break; } } if (!containsFrom) throw new FormatException (string.Format ("Malformed DKIM-Signature header: From header not signed.")); if (!parameters.TryGetValue ("bh", out bh)) throw new FormatException ("Malformed DKIM-Signature header: no body hash parameter detected."); if (!parameters.TryGetValue ("b", out b)) throw new FormatException ("Malformed DKIM-Signature header: no signature parameter detected."); }
byte[] DkimHashBody (FormatOptions options, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm, int maxLength) { using (var stream = new DkimHashStream (signatureAlgorithm, maxLength)) { using (var filtered = new FilteredStream (stream)) { filtered.Add (options.CreateNewLineFilter ()); if (bodyCanonicalizationAlgorithm == DkimCanonicalizationAlgorithm.Relaxed) filtered.Add (new DkimRelaxedBodyFilter ()); else filtered.Add (new DkimSimpleBodyFilter ()); if (Body != null) { try { Body.Headers.Suppress = true; Body.WriteTo (options, stream, CancellationToken.None); } finally { Body.Headers.Suppress = false; } } filtered.Flush (); } return stream.GenerateHash (); } }
public static string CanonicalizeBody( [CanBeNull]string body, DkimCanonicalizationAlgorithm type) { if (body == null) { body = string.Empty; } var sb = new StringBuilder(body.Length + Email.NewLine.Length); switch (type) { /* * 3.4.4 The "relaxed" Body Canonicalization Algorithm * * The "relaxed" body canonicalization algorithm: * * Ignores all whitespace at the end of lines. Implementations MUST NOT remove the CRLF at the end of the line. * * Reduces all sequences of WSP within a line to a single SP character. * * Ignores all empty lines at the end of the message body. "Empty line" is defined in Section 3.4.3. * * INFORMATIVE NOTE: It should be noted that the relaxed body canonicalization algorithm may enable certain * types of extremely crude "ASCII Art" attacks where a message may be conveyed by adjusting the spacing * between words. If this is a concern, the "simple" body canonicalization algorithm should be used instead. * * */ case DkimCanonicalizationAlgorithm.Relaxed: { using (var reader = new StringReader(body)) { string line; int emptyLineCount = 0; while ((line = reader.ReadLine()) != null) { if (line == string.Empty) { emptyLineCount++; continue; } while (emptyLineCount > 0) { sb.AppendLine(); emptyLineCount--; } sb.AppendLine(line.TrimEnd().ReduceWitespace()); } } break; } /* * * 3.4.3 The "simple" Body Canonicalization Algorithm * * The "simple" body canonicalization algorithm ignores all empty lines at the end * of the message body. An empty line is a line of zero length after removal of the * line terminator. If there is no body or no trailing CRLF on the message body, a * CRLF is added. It makes no * * Note that a completely empty or missing body is canonicalized as a single "CRLF"; * that is, the canonicalized length will be 2 octets. * * */ case DkimCanonicalizationAlgorithm.Simple: { using (var reader = new StringReader(body)) { string line; int emptyLineCount = 0; while ((line = reader.ReadLine()) != null) { if (line == string.Empty) { emptyLineCount++; continue; } while (emptyLineCount > 0) { sb.AppendLine(); emptyLineCount--; } sb.AppendLine(line); } } if (sb.Length == 0) { sb.AppendLine(); } break; } default: { throw new ArgumentException("Invalid canonicalization type."); } } return sb.ToString(); }
public static string CanonicalizeBody(string body, DkimCanonicalizationAlgorithm type) { if (body == null) { body = string.Empty; } var sb = new StringBuilder(body.Length + Email.NewLine.Length); switch (type) { /* * 3.4.4 The "relaxed" Body Canonicalization Algorithm * * The "relaxed" body canonicalization algorithm: * * Ignores all whitespace at the end of lines. Implementations MUST NOT remove the CRLF at the end of the line. * * Reduces all sequences of WSP within a line to a single SP character. * * Ignores all empty lines at the end of the message body. "Empty line" is defined in Section 3.4.3. * * INFORMATIVE NOTE: It should be noted that the relaxed body canonicalization algorithm may enable certain * types of extremely crude "ASCII Art" attacks where a message may be conveyed by adjusting the spacing * between words. If this is a concern, the "simple" body canonicalization algorithm should be used instead. * * */ case DkimCanonicalizationAlgorithm.RELAXED: { using (var reader = new StringReader(body)) { string line; int emptyLineCount = 0; while ((line = reader.ReadLine()) != null) { if (line == string.Empty) { emptyLineCount++; continue; } while (emptyLineCount > 0) { sb.Append(Email.NewLine); emptyLineCount--; } sb.Append(line.TrimEnd().ReduceWitespace()); sb.Append(Email.NewLine); } } break; } /* * * 3.4.3 The "simple" Body Canonicalization Algorithm * * The "simple" body canonicalization algorithm ignores all empty lines at the end * of the message body. An empty line is a line of zero length after removal of the * line terminator. If there is no body or no trailing CRLF on the message body, a * CRLF is added. It makes no * * Note that a completely empty or missing body is canonicalized as a single "CRLF"; * that is, the canonicalized length will be 2 octets. * * */ case DkimCanonicalizationAlgorithm.SIMPLE: { using (var reader = new StringReader(body)) { string line; int emptyLineCount = 0; while ((line = reader.ReadLine()) != null) { if (line == string.Empty) { emptyLineCount++; continue; } while (emptyLineCount > 0) { sb.Append(Email.NewLine); emptyLineCount--; } sb.Append(line); sb.Append(Email.NewLine); } } if (sb.Length == 0) { sb.Append(Email.NewLine); } break; } default: { throw new ArgumentException("Invalid canonicalization type."); } } return(sb.ToString()); }
static void TestDkimSignVerify(MimeMessage message, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm headerAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm) { var headers = new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.Date }; var verifier = new DkimVerifier(new DummyPublicKeyLocator(DkimKeys.Public)); var signer = CreateSigner(signatureAlgorithm, headerAlgorithm, bodyAlgorithm); signer.Sign(message, headers); var dkim = message.Headers[0]; if (signatureAlgorithm == DkimSignatureAlgorithm.RsaSha1) { Assert.IsFalse(verifier.Verify(message, dkim), "DKIM-Signature using rsa-sha1 should not verify."); // now enable rsa-sha1 to verify again, this time it should pass... verifier.Enable(DkimSignatureAlgorithm.RsaSha1); } Assert.IsTrue(verifier.Verify(message, dkim), "Failed to verify DKIM-Signature."); message.Headers.RemoveAt(0); }
public void Canonicalization2(string emailText, string canonicalizedHeaders, string canonicalizedBody, DkimCanonicalizationAlgorithm type) { var email = Email.Parse(emailText); Assert.AreEqual(canonicalizedBody, DkimCanonicalizer.CanonicalizeBody(email.Body, type), "body " + type); Assert.AreEqual(canonicalizedHeaders, DkimCanonicalizer.CanonicalizeHeaders(email.Headers, type, false, "A", "B"), "headers " + type); Assert.AreEqual(emailText, email.Raw); }
static void TestEmptyBody (DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm, string expectedHash) { var headers = new [] { HeaderId.From, HeaderId.To, HeaderId.Subject, HeaderId.Date }; var signer = CreateSigner (signatureAlgorithm); var message = new MimeMessage (); message.From.Add (new MailboxAddress ("", "*****@*****.**")); message.To.Add (new MailboxAddress ("", "*****@*****.**")); message.Subject = "This is an empty message"; message.Date = DateTimeOffset.Now; message.Body = new TextPart ("plain") { Text = "" }; message.Body.Prepare (EncodingConstraint.SevenBit); message.Sign (signer, headers, DkimCanonicalizationAlgorithm.Simple, bodyAlgorithm); VerifyDkimBodyHash (message, signatureAlgorithm, expectedHash); var dkim = message.Headers[0]; Assert.IsTrue (message.Verify (dkim, new DummyPublicKeyLocator (DkimKeys.Public)), "Failed to verify DKIM-Signature."); }
static void TestDkimSignVerify(MimeMessage message, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm headerAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm) { var headers = new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.Date }; var signer = CreateSigner(signatureAlgorithm); message.Sign(signer, headers, headerAlgorithm, bodyAlgorithm); var dkim = message.Headers[0]; Assert.IsTrue(message.Verify(dkim, new DummyPublicKeyLocator(DkimKeys.Public)), "Failed to verify DKIM-Signature."); message.Headers.RemoveAt(0); }
static void TestDkimSignVerify (MimeMessage message, DkimSignatureAlgorithm signatureAlgorithm, DkimCanonicalizationAlgorithm headerAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm) { var headers = new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.Date }; var signer = CreateSigner (signatureAlgorithm); message.Sign (signer, headers, headerAlgorithm, bodyAlgorithm); var dkim = message.Headers[0]; Assert.IsTrue (message.Verify (dkim, new DummyPublicKeyLocator (DkimKeys.Public)), "Failed to verify DKIM-Signature."); message.Headers.RemoveAt (0); }
public void C(string orig, string expected, DkimCanonicalizationAlgorithm type) { Assert.AreEqual(expected, DkimCanonicalizer.CanonicalizeBody(orig, type)); }
void DkimWriteHeaders (FormatOptions options, IList<string> fields, DkimCanonicalizationAlgorithm headerCanonicalizationAlgorithm, Stream stream) { var counts = new Dictionary<string, int> (); Header header; for (int i = 0; i < fields.Count; i++) { var name = fields[i].ToLowerInvariant (); int index, count, n = 0; if (!counts.TryGetValue (name, out count)) count = 0; // Note: signers choosing to sign an existing header field that occurs more // than once in the message (such as Received) MUST sign the physically last // instance of that header field in the header block. Signers wishing to sign // multiple instances of such a header field MUST include the header field // name multiple times in the list of header fields and MUST sign such header // fields in order from the bottom of the header field block to the top. index = Headers.LastIndexOf (name); // find the n'th header with this name while (n < count && --index >= 0) { if (Headers[index].Field.Equals (name, StringComparison.OrdinalIgnoreCase)) n++; } if (index < 0) continue; header = Headers[index]; switch (headerCanonicalizationAlgorithm) { case DkimCanonicalizationAlgorithm.Relaxed: DkimWriteHeaderRelaxed (options, stream, header); break; default: DkimWriteHeaderSimple (options, stream, header); break; } counts[name] = ++count; } }
static void ValidateArcMessageSignatureParameters(IDictionary <string, string> parameters, out DkimSignatureAlgorithm algorithm, out DkimCanonicalizationAlgorithm headerAlgorithm, out DkimCanonicalizationAlgorithm bodyAlgorithm, out string d, out string s, out string q, out string[] headers, out string bh, out string b, out int maxLength) { ValidateCommonSignatureParameters("ARC-Message-Signature", parameters, out algorithm, out headerAlgorithm, out bodyAlgorithm, out d, out s, out q, out headers, out bh, out b, out maxLength); }
/// <summary> /// Digitally sign the message using a DomainKeys Identified Mail (DKIM) signature. /// </summary> /// <remarks> /// Digitally signs the message using a DomainKeys Identified Mail (DKIM) signature. /// </remarks> /// <param name="signer">The DKIM signer.</param> /// <param name="headers">The headers to sign.</param> /// <param name="headerCanonicalizationAlgorithm">The header canonicalization algorithm.</param> /// <param name="bodyCanonicalizationAlgorithm">The body canonicalization algorithm.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="signer"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="headers"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para><paramref name="headers"/> does not contain the 'From' header.</para> /// <para>-or-</para> /// <para><paramref name="headers"/> contains one or more of the following headers: Return-Path, /// Received, Comments, Keywords, Bcc, Resent-Bcc, or DKIM-Signature.</para> /// </exception> public void Sign (DkimSigner signer, IList<HeaderId> headers, DkimCanonicalizationAlgorithm headerCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple, DkimCanonicalizationAlgorithm bodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple) { Sign (FormatOptions.Default, signer, headers, headerCanonicalizationAlgorithm, bodyCanonicalizationAlgorithm); }
public void UpdateSettings(Settings config) { lock (settingsMutex) { // Load the list of domains domains.Clear(); DkimSignatureAlgorithm signatureAlgorithm; switch (config.SigningAlgorithm) { case DkimAlgorithmKind.RsaSha1: signatureAlgorithm = DkimSignatureAlgorithm.RsaSha1; break; case DkimAlgorithmKind.RsaSha256: signatureAlgorithm = DkimSignatureAlgorithm.RsaSha256; break; default: // ReSharper disable once NotResolvedInText throw new ArgumentOutOfRangeException("config.SigningAlgorithm"); } foreach (DomainElement domainElement in config.Domains) { string privateKey = domainElement.PrivateKeyPathAbsolute( Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); if (String.IsNullOrEmpty(privateKey) || !File.Exists(privateKey)) { Logger.LogError("The private key for domain " + domainElement.Domain + " wasn't found: " + privateKey + ". Ignoring domain."); } //check if the private key can be parsed try { KeyHelper.ParseKeyPair(privateKey); } catch (Exception ex) { Logger.LogError("Couldn't load private key for domain " + domainElement.Domain + ": " + ex.Message); continue; } MimeKit.Cryptography.DkimSigner signer; try { AsymmetricKeyParameter key = KeyHelper.ParsePrivateKey(privateKey); signer = new MimeKit.Cryptography.DkimSigner(key, domainElement.Domain, domainElement.Selector) { SignatureAlgorithm = signatureAlgorithm }; } catch (Exception ex) { Logger.LogError("Could not initialize MimeKit DkimSigner for domain " + domainElement.Domain + ": " + ex.Message); continue; } domains.Add(domainElement.Domain, new DomainElementSigner(domainElement, signer)); } headerCanonicalization = config.HeaderCanonicalization == DkimCanonicalizationKind.Relaxed ? DkimCanonicalizationAlgorithm.Relaxed : DkimCanonicalizationAlgorithm.Simple; bodyCanonicalization = config.BodyCanonicalization == DkimCanonicalizationKind.Relaxed ? DkimCanonicalizationAlgorithm.Relaxed : DkimCanonicalizationAlgorithm.Simple; List <HeaderId> headerList = new List <HeaderId>(); foreach (string headerToSign in config.HeadersToSign) { HeaderId headerId; #if EX_2007_SP3 || EX_2010 || EX_2010_SP1 || EX_2010_SP2 || EX_2010_SP3 if (!TryParseHeader(headerToSign, out headerId) || (headerId == HeaderId.Unknown)) #else if (!Enum.TryParse(headerToSign, true, out headerId) || (headerId == HeaderId.Unknown)) #endif { Logger.LogWarning("Invalid value for header to sign: '" + headerToSign + "'. This header will be ignored."); } headerList.Add(headerId); } // The From header must always be signed according to the DKIM specification. if (!headerList.Contains(HeaderId.From)) { headerList.Add(HeaderId.From); } eligibleHeaders = headerList.ToArray(); } }
internal static void ValidateCommonSignatureParameters(string header, IDictionary <string, string> parameters, out DkimSignatureAlgorithm algorithm, out DkimCanonicalizationAlgorithm headerAlgorithm, out DkimCanonicalizationAlgorithm bodyAlgorithm, out string d, out string s, out string q, out string[] headers, out string bh, out string b, out int maxLength) { ValidateCommonParameters(header, parameters, out algorithm, out d, out s, out q, out b); if (parameters.TryGetValue("l", out string l)) { if (!int.TryParse(l, NumberStyles.Integer, CultureInfo.InvariantCulture, out maxLength) || maxLength < 0) { throw new FormatException(string.Format("Malformed {0} header: invalid length parameter: l={1}", header, l)); } } else { maxLength = -1; } if (parameters.TryGetValue("c", out string c)) { var tokens = c.ToLowerInvariant().Split('/'); if (tokens.Length == 0 || tokens.Length > 2) { throw new FormatException(string.Format("Malformed {0} header: invalid canonicalization parameter: c={1}", header, c)); } switch (tokens[0]) { case "relaxed": headerAlgorithm = DkimCanonicalizationAlgorithm.Relaxed; break; case "simple": headerAlgorithm = DkimCanonicalizationAlgorithm.Simple; break; default: throw new FormatException(string.Format("Malformed {0} header: invalid canonicalization parameter: c={1}", header, c)); } if (tokens.Length == 2) { switch (tokens[1]) { case "relaxed": bodyAlgorithm = DkimCanonicalizationAlgorithm.Relaxed; break; case "simple": bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; break; default: throw new FormatException(string.Format("Malformed {0} header: invalid canonicalization parameter: c={1}", header, c)); } } else { bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; } } else { headerAlgorithm = DkimCanonicalizationAlgorithm.Simple; bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple; } if (!parameters.TryGetValue("h", out string h)) { throw new FormatException(string.Format("Malformed {0} header: no signed header parameter detected.", header)); } headers = h.Split(':'); if (!parameters.TryGetValue("bh", out bh)) { throw new FormatException(string.Format("Malformed {0} header: no body hash parameter detected.", header)); } }
public void C(string orig, string expected, DkimCanonicalizationAlgorithm type) { Assert.AreEqual(expected, DkimCanonicalizer.CanonicalizeBody(orig, type)); }