/// <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)); } }
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)); } }
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); } }
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); } }
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)); } }
/// <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); } }
/// <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); } }
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); } }