/// <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));
            }
        }
Exemple #2
0
        async Task <bool> VerifyArcSealAsync(FormatOptions options, ArcHeaderSet[] sets, int i, bool doAsync, CancellationToken cancellationToken)
        {
            DkimSignatureAlgorithm algorithm;
            AsymmetricKeyParameter key;
            string d, s, q, b;

            ValidateArcSealParameters(sets[i].ArcSealParameters, out algorithm, out d, out s, out q, out b);

            if (!IsEnabled(algorithm))
            {
                return(false);
            }

            if (doAsync)
            {
                key = await PublicKeyLocator.LocatePublicKeyAsync(q, d, s, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                key = PublicKeyLocator.LocatePublicKey(q, d, s, cancellationToken);
            }

            if ((key is RsaKeyParameters rsa) && rsa.Modulus.BitLength < MinimumRsaKeyLength)
            {
                return(false);
            }

            options = options.Clone();
            options.NewLineFormat = NewLineFormat.Dos;

            using (var stream = new DkimSignatureStream(CreateVerifyContext(algorithm, key))) {
                using (var filtered = new FilteredStream(stream)) {
                    filtered.Add(options.CreateNewLineFilter());

                    for (int j = 0; j < i; j++)
                    {
                        WriteHeaderRelaxed(options, filtered, sets[j].ArcAuthenticationResult, false);
                        WriteHeaderRelaxed(options, filtered, sets[j].ArcMessageSignature, false);
                        WriteHeaderRelaxed(options, filtered, sets[j].ArcSeal, false);
                    }

                    WriteHeaderRelaxed(options, filtered, sets[i].ArcAuthenticationResult, false);
                    WriteHeaderRelaxed(options, filtered, sets[i].ArcMessageSignature, false);

                    // now include the ARC-Seal header that we are verifying,
                    // but only after removing the "b=" signature value.
                    var seal = GetSignedSignatureHeader(sets[i].ArcSeal);

                    WriteHeaderRelaxed(options, filtered, seal, true);

                    filtered.Flush();
                }

                return(stream.VerifySignature(b));
            }
        }
Exemple #3
0
        Header GenerateArcMessageSignature(FormatOptions options, MimeMessage message, int instance, TimeSpan t, IList <string> headers)
        {
            if (message.MimeVersion == null && message.Body != null && message.Body.Headers.Count > 0)
            {
                message.MimeVersion = new Version(1, 0);
            }

            var value = CreateArcHeaderBuilder(instance);

            byte[] signature, hash;
            Header ams;

            value.AppendFormat("; d={0}; s={1}", Domain, Selector);
            value.AppendFormat("; c={0}/{1}",
                               HeaderCanonicalizationAlgorithm.ToString().ToLowerInvariant(),
                               BodyCanonicalizationAlgorithm.ToString().ToLowerInvariant());
            value.AppendFormat("; t={0}", (long)t.TotalSeconds);

            using (var stream = new DkimSignatureStream(CreateSigningContext())) {
                using (var filtered = new FilteredStream(stream)) {
                    filtered.Add(options.CreateNewLineFilter());

                    // write the specified message headers
                    DkimVerifierBase.WriteHeaders(options, message, headers, HeaderCanonicalizationAlgorithm, filtered);

                    value.AppendFormat("; h={0}", string.Join(":", headers.ToArray()));

                    hash = message.HashBody(options, SignatureAlgorithm, BodyCanonicalizationAlgorithm, -1);
                    value.AppendFormat("; bh={0}", Convert.ToBase64String(hash));
                    value.Append("; b=");

                    ams = new Header(HeaderId.ArcMessageSignature, value.ToString());

                    switch (HeaderCanonicalizationAlgorithm)
                    {
                    case DkimCanonicalizationAlgorithm.Relaxed:
                        DkimVerifierBase.WriteHeaderRelaxed(options, filtered, ams, true);
                        break;

                    default:
                        DkimVerifierBase.WriteHeaderSimple(options, filtered, ams, true);
                        break;
                    }

                    filtered.Flush();
                }

                signature = stream.GenerateSignature();

                ams.Value += Convert.ToBase64String(signature);

                return(ams);
            }
        }
Exemple #4
0
        Header GenerateArcSeal(FormatOptions options, int instance, TimeSpan t, ArcHeaderSet[] sets, int count, Header aar, Header ams)
        {
            var value = CreateArcHeaderBuilder(instance);

            byte[] signature;
            Header seal;

            // FIXME: where should this value come from?
            value.Append("; cv=pass");

            value.AppendFormat("; d={0}; s={1}", Domain, Selector);
            value.AppendFormat("; t={0}", (long)t.TotalSeconds);

            using (var stream = new DkimSignatureStream(CreateSigningContext())) {
                using (var filtered = new FilteredStream(stream)) {
                    filtered.Add(options.CreateNewLineFilter());

                    for (int i = 0; i < count; i++)
                    {
                        DkimVerifierBase.WriteHeaderRelaxed(options, filtered, sets[i].ArcAuthenticationResult, false);
                        DkimVerifierBase.WriteHeaderRelaxed(options, filtered, sets[i].ArcMessageSignature, false);
                        DkimVerifierBase.WriteHeaderRelaxed(options, filtered, sets[i].ArcSeal, false);
                    }

                    DkimVerifierBase.WriteHeaderRelaxed(options, filtered, aar, false);
                    DkimVerifierBase.WriteHeaderRelaxed(options, filtered, ams, false);

                    value.Append("; b=");

                    seal = new Header(HeaderId.ArcSeal, value.ToString());
                    DkimVerifierBase.WriteHeaderRelaxed(options, filtered, seal, true);

                    filtered.Flush();
                }

                signature = stream.GenerateSignature();

                seal.Value += Convert.ToBase64String(signature);

                return(seal);
            }
        }
Exemple #5
0
        async Task <bool> VerifyAsync(FormatOptions options, MimeMessage message, Header dkimSignature, bool doAsync, CancellationToken cancellationToken)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            if (dkimSignature == null)
            {
                throw new ArgumentNullException(nameof(dkimSignature));
            }

            if (dkimSignature.Id != HeaderId.DkimSignature)
            {
                throw new ArgumentException("The signature parameter MUST be a DKIM-Signature header.", nameof(dkimSignature));
            }

            var parameters = ParseParameterTags(dkimSignature.Id, dkimSignature.Value);
            DkimCanonicalizationAlgorithm headerAlgorithm, bodyAlgorithm;
            DkimSignatureAlgorithm        signatureAlgorithm;
            AsymmetricKeyParameter        key;
            string d, s, q, bh, b;

            string[] headers;
            int      maxLength;

            ValidateDkimSignatureParameters(parameters, out signatureAlgorithm, out headerAlgorithm, out bodyAlgorithm,
                                            out d, out s, out q, out headers, out bh, out b, out maxLength);

            if (!IsEnabled(signatureAlgorithm))
            {
                return(false);
            }

            if (doAsync)
            {
                key = await PublicKeyLocator.LocatePublicKeyAsync(q, d, s, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                key = PublicKeyLocator.LocatePublicKey(q, d, s, cancellationToken);
            }

            if ((key is RsaKeyParameters rsa) && rsa.Modulus.BitLength < MinimumRsaKeyLength)
            {
                return(false);
            }

            options = options.Clone();
            options.NewLineFormat = NewLineFormat.Dos;

            // first check the body hash (if that's invalid, then the entire signature is invalid)
            var hash = Convert.ToBase64String(message.HashBody(options, signatureAlgorithm, bodyAlgorithm, maxLength));

            if (hash != bh)
            {
                return(false);
            }

            using (var stream = new DkimSignatureStream(CreateVerifyContext(signatureAlgorithm, key))) {
                using (var filtered = new FilteredStream(stream)) {
                    filtered.Add(options.CreateNewLineFilter());

                    WriteHeaders(options, message, headers, headerAlgorithm, filtered);

                    // now include the DKIM-Signature header that we are verifying,
                    // but only after removing the "b=" signature value.
                    var header = GetSignedSignatureHeader(dkimSignature);

                    switch (headerAlgorithm)
                    {
                    case DkimCanonicalizationAlgorithm.Relaxed:
                        WriteHeaderRelaxed(options, filtered, header, true);
                        break;

                    default:
                        WriteHeaderSimple(options, filtered, header, true);
                        break;
                    }

                    filtered.Flush();
                }

                return(stream.VerifySignature(b));
            }
        }
Exemple #6
0
		/// <summary>
		/// Verify the specified DKIM-Signature header.
		/// </summary>
		/// <remarks>
		/// Verifies the specified DKIM-Signature header.
		/// </remarks>
		/// <param name="options">The formatting options.</param>
		/// <param name="dkimSignature">The DKIM-Signature header.</param>
		/// <param name="publicKeyLocator">The public key locator service.</param>
		/// <param name="cancellationToken">The cancellation token.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="options"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="dkimSignature"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="publicKeyLocator"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.ArgumentException">
		/// <paramref name="dkimSignature"/> is not a DKIM-Signature header.
		/// </exception>
		/// <exception cref="System.FormatException">
		/// The DKIM-Signature header value is malformed.
		/// </exception>
		/// <exception cref="System.OperationCanceledException">
		/// The operation was canceled via the cancellation token.
		/// </exception>
		bool Verify (FormatOptions options, Header dkimSignature, IDkimPublicKeyLocator publicKeyLocator, CancellationToken cancellationToken = default (CancellationToken))
		{
			if (options == null)
				throw new ArgumentNullException ("options");

			if (dkimSignature == null)
				throw new ArgumentNullException ("dkimSignature");

			if (dkimSignature.Id != HeaderId.DkimSignature)
				throw new ArgumentException ("The dkimSignature parameter MUST be a DKIM-Signature header.", "dkimSignature");

			if (publicKeyLocator == null)
				throw new ArgumentNullException ("publicKeyLocator");

			var parameters = ParseDkimSignature (dkimSignature.Value);
			DkimCanonicalizationAlgorithm headerAlgorithm, bodyAlgorithm;
			DkimSignatureAlgorithm signatureAlgorithm;
			AsymmetricKeyParameter key;
			string d, s, q, h, bh, b;
			int maxLength;

			ValidateDkimSignatureParameters (parameters, out signatureAlgorithm, out headerAlgorithm, out bodyAlgorithm,
				out d, out s, out q, out h, out bh, out b, out maxLength);

			key = publicKeyLocator.LocatePublicKey (q, d, s, cancellationToken);

			options = options.Clone ();
			options.NewLineFormat = NewLineFormat.Dos;

			// first check the body hash (if that's invalid, then the entire signature is invalid)
			var hash = Convert.ToBase64String (DkimHashBody (options, signatureAlgorithm, bodyAlgorithm, maxLength));

			if (hash != bh)
				return false;

			using (var stream = new DkimSignatureStream (DkimGetDigestSigner (signatureAlgorithm, key))) {
				using (var filtered = new FilteredStream (stream)) {
					filtered.Add (options.CreateNewLineFilter ());

					DkimWriteHeaders (options, h.Split (':'), headerAlgorithm, filtered);

					// now include the DKIM-Signature header that we are verifying,
					// but only after removing the "b=" signature value.
					var header = GetSignedDkimSignatureHeader (dkimSignature);

					switch (headerAlgorithm) {
					case DkimCanonicalizationAlgorithm.Relaxed:
						DkimWriteHeaderRelaxed (options, filtered, header);
						break;
					default:
						DkimWriteHeaderSimple (options, filtered, header);
						break;
					}

					filtered.Flush ();
				}

				return stream.VerifySignature (b);
			}
		}
Exemple #7
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);
			}
		}
Exemple #8
0
        void DkimSign(FormatOptions options, MimeMessage message, IList <string> headers)
        {
            var value = new StringBuilder("v=1");
            var t     = GetTimestamp();

            byte[] signature, hash;
            Header dkim;

            options = options.Clone();
            options.NewLineFormat = NewLineFormat.Dos;
            options.EnsureNewLine = true;

            switch (SignatureAlgorithm)
            {
            case DkimSignatureAlgorithm.Ed25519Sha256:
                value.Append("; a=ed25519-sha256");
                break;

            case DkimSignatureAlgorithm.RsaSha256:
                value.Append("; a=rsa-sha256");
                break;

            default:
                value.Append("; a=rsa-sha1");
                break;
            }

            value.AppendFormat("; d={0}; s={1}", Domain, Selector);
            value.AppendFormat("; c={0}/{1}",
                               HeaderCanonicalizationAlgorithm.ToString().ToLowerInvariant(),
                               BodyCanonicalizationAlgorithm.ToString().ToLowerInvariant());
            if (!string.IsNullOrEmpty(QueryMethod))
            {
                value.AppendFormat("; q={0}", QueryMethod);
            }
            if (!string.IsNullOrEmpty(AgentOrUserIdentifier))
            {
                value.AppendFormat("; i={0}", AgentOrUserIdentifier);
            }
            value.AppendFormat("; t={0}", t);

            using (var stream = new DkimSignatureStream(CreateSigningContext())) {
                using (var filtered = new FilteredStream(stream)) {
                    filtered.Add(options.CreateNewLineFilter());

                    // write the specified message headers
                    DkimVerifierBase.WriteHeaders(options, message, headers, HeaderCanonicalizationAlgorithm, filtered);

                    value.AppendFormat("; h={0}", string.Join(":", headers.ToArray()));

                    hash = message.HashBody(options, SignatureAlgorithm, BodyCanonicalizationAlgorithm, -1);
                    value.AppendFormat("; bh={0}", Convert.ToBase64String(hash));
                    value.Append("; b=");

                    dkim = new Header(HeaderId.DkimSignature, value.ToString());
                    message.Headers.Insert(0, dkim);

                    switch (HeaderCanonicalizationAlgorithm)
                    {
                    case DkimCanonicalizationAlgorithm.Relaxed:
                        DkimVerifierBase.WriteHeaderRelaxed(options, filtered, dkim, true);
                        break;

                    default:
                        DkimVerifierBase.WriteHeaderSimple(options, filtered, dkim, true);
                        break;
                    }

                    filtered.Flush();
                }

                signature = stream.GenerateSignature();

                dkim.Value += Convert.ToBase64String(signature);
            }
        }