Пример #1
0
        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.");
        }
Пример #2
0
        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.");
        }
Пример #3
0
        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.");
        }
Пример #4
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);
		}
Пример #5
0
 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"
     });
 }
Пример #6
0
        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.");
                }
            }
        }
Пример #7
0
        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.");
        }
Пример #8
0
        /// <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));
            }
        }
Пример #9
0
        /// <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);
        }
Пример #10
0
        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;
            }
        }
Пример #11
0
        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();
        }
Пример #12
0
		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.");
		}
Пример #13
0
		/// <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);
			}
		}
Пример #14
0
        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());
        }
Пример #15
0
		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.");
		}
Пример #16
0
		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();
		}
Пример #17
0
		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 ();
			}
		}
Пример #18
0
        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();
            }
        }
Пример #19
0
        /// <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());
        }
Пример #20
0
		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.");
		}
Пример #21
0
		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 ();
			}
		}
Пример #22
0
		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();

		}
Пример #23
0
        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());
        }
Пример #24
0
        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);
        }
Пример #25
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);
        }
Пример #26
0
		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.");
		}
Пример #27
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);
        }
Пример #28
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);
		}
Пример #29
0
		public void C(string orig, string expected, DkimCanonicalizationAlgorithm type)
		{
			Assert.AreEqual(expected, DkimCanonicalizer.CanonicalizeBody(orig, type));
		}
Пример #30
0
		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;
			}
		}
Пример #31
0
 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);
 }
Пример #32
0
		/// <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);
		}
Пример #33
0
        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();
            }
        }
Пример #34
0
        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));
            }
        }
Пример #35
0
 public void C(string orig, string expected, DkimCanonicalizationAlgorithm type)
 {
     Assert.AreEqual(expected, DkimCanonicalizer.CanonicalizeBody(orig, type));
 }