diff --git a/MimeKit/Cryptography/ArcSigner.cs b/MimeKit/Cryptography/ArcSigner.cs
index 088828c8e8..baa0cddd73 100644
--- a/MimeKit/Cryptography/ArcSigner.cs
+++ b/MimeKit/Cryptography/ArcSigner.cs
@@ -89,14 +89,11 @@ protected ArcSigner (string domain, string selector, DkimSignatureAlgorithm algo
///
/// is not a private key.
///
- protected ArcSigner (AsymmetricKeyParameter key, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256) : this (domain, selector, algorithm)
+ protected ArcSigner (IDkimPrivateKey key, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256) : this (domain, selector, algorithm)
{
if (key == null)
throw new ArgumentNullException (nameof (key));
- if (!key.IsPrivate)
- throw new ArgumentException ("The key must be a private key.", nameof (key));
-
PrivateKey = key;
}
@@ -109,7 +106,7 @@ protected ArcSigner (AsymmetricKeyParameter key, string domain, string selector,
///
///
///
- /// The file containing the private key.
+ /// The file containing the private key in PEM format.
/// The domain that the signer represents.
/// The selector subdividing the domain.
/// The signature algorithm.
@@ -157,7 +154,7 @@ protected ArcSigner (string fileName, string domain, string selector, DkimSignat
///
/// Creates a new .
///
- /// The stream containing the private key.
+ /// The stream containing the private key in PEM format.
/// The domain that the signer represents.
/// The selector subdividing the domain.
/// The signature algorithm.
@@ -269,7 +266,7 @@ Header GenerateArcMessageSignature (FormatOptions options, MimeMessage message,
builder.Append ("; t=");
builder.AppendInvariant (t);
- using (var stream = new DkimSignatureStream (CreateSigningContext ())) {
+ using (var stream = new DkimSignatureStream (PrivateKey.CreateSigningContext (SignatureAlgorithm))) {
using (var filtered = new FilteredStream (stream)) {
filtered.Add (options.CreateNewLineFilter ());
@@ -323,7 +320,7 @@ Header GenerateArcSeal (FormatOptions options, int instance, string cv, long t,
builder.Append ("; t=");
builder.AppendInvariant (t);
- using (var stream = new DkimSignatureStream (CreateSigningContext ())) {
+ using (var stream = new DkimSignatureStream (PrivateKey.CreateSigningContext (SignatureAlgorithm))) {
using (var filtered = new FilteredStream (stream)) {
filtered.Add (options.CreateNewLineFilter ());
diff --git a/MimeKit/Cryptography/ArcVerifier.cs b/MimeKit/Cryptography/ArcVerifier.cs
index c7c2e8717e..bd09452e46 100644
--- a/MimeKit/Cryptography/ArcVerifier.cs
+++ b/MimeKit/Cryptography/ArcVerifier.cs
@@ -385,8 +385,8 @@ async Task VerifyArcMessageSignatureAsync (FormatOptions options, MimeMess
{
DkimCanonicalizationAlgorithm headerAlgorithm, bodyAlgorithm;
DkimSignatureAlgorithm signatureAlgorithm;
- AsymmetricKeyParameter key;
string d, s, q, bh, b;
+ IDkimPublicKey key;
string[] headers;
int maxLength;
@@ -417,7 +417,7 @@ async Task VerifyArcMessageSignatureAsync (FormatOptions options, MimeMess
async Task VerifyArcSealAsync (FormatOptions options, ArcHeaderSet[] sets, int i, bool doAsync, CancellationToken cancellationToken)
{
DkimSignatureAlgorithm algorithm;
- AsymmetricKeyParameter key;
+ IDkimPublicKey key;
string d, s, q, b;
ValidateArcSealParameters (sets[i].ArcSealParameters, out algorithm, out d, out s, out q, out b);
@@ -430,13 +430,13 @@ async Task VerifyArcSealAsync (FormatOptions options, ArcHeaderSet[] sets,
else
key = PublicKeyLocator.LocatePublicKey (q, d, s, cancellationToken);
- if ((key is RsaKeyParameters rsa) && rsa.Modulus.BitLength < MinimumRsaKeyLength)
+ if (key.Algorithm == DkimPublicKeyAlgorithm.Rsa && key.KeySize < MinimumRsaKeyLength)
return false;
options = options.Clone ();
options.NewLineFormat = NewLineFormat.Dos;
- using (var stream = new DkimSignatureStream (CreateVerifyContext (algorithm, key))) {
+ using (var stream = new DkimSignatureStream (key.CreateVerifyContext (algorithm))) {
using (var filtered = new FilteredStream (stream)) {
filtered.Add (options.CreateNewLineFilter ());
diff --git a/MimeKit/Cryptography/BouncyCastleDkimKey.cs b/MimeKit/Cryptography/BouncyCastleDkimKey.cs
new file mode 100644
index 0000000000..fbe7f2429e
--- /dev/null
+++ b/MimeKit/Cryptography/BouncyCastleDkimKey.cs
@@ -0,0 +1,87 @@
+//
+// BouncyCastleDkimKey.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace MimeKit.Cryptography {
+ ///
+ /// A base class for and .
+ ///
+ ///
+ /// A base class for and .
+ ///
+ public abstract class BouncyCastleDkimKey
+ {
+ ///
+ /// Get the private key.
+ ///
+ ///
+ /// Gets the private key.
+ ///
+ public AsymmetricKeyParameter Key {
+ get; protected set;
+ }
+
+ ///
+ /// Create a DKIM signature context.
+ ///
+ ///
+ /// Creates a DKIM signature context.
+ ///
+ /// The DKIM signature algorithm.
+ /// If set to true, the context will be used for signing; otherwise, it will be used for verifying.
+ /// The DKIM signature context.
+ ///
+ /// The specified is not supported.
+ ///
+ protected IDkimSignatureContext CreateSignatureContext (DkimSignatureAlgorithm algorithm, bool sign)
+ {
+ ISigner signer;
+
+ switch (algorithm) {
+ case DkimSignatureAlgorithm.RsaSha1:
+ signer = new RsaDigestSigner (new Sha1Digest ());
+ break;
+ case DkimSignatureAlgorithm.RsaSha256:
+ signer = new RsaDigestSigner (new Sha256Digest ());
+ break;
+ case DkimSignatureAlgorithm.Ed25519Sha256:
+ signer = new Ed25519DigestSigner (new Sha256Digest ());
+ break;
+ default:
+ throw new NotSupportedException ($"{algorithm} is not supported.");
+ }
+
+ signer.Init (sign, Key);
+
+ return new BouncyCastleDkimSignatureContext (signer);
+ }
+ }
+}
diff --git a/MimeKit/Cryptography/BouncyCastleDkimPrivateKey.cs b/MimeKit/Cryptography/BouncyCastleDkimPrivateKey.cs
new file mode 100644
index 0000000000..53ffaf3338
--- /dev/null
+++ b/MimeKit/Cryptography/BouncyCastleDkimPrivateKey.cs
@@ -0,0 +1,157 @@
+//
+// BouncyCastleDkimPrivateKey.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.OpenSsl;
+
+namespace MimeKit.Cryptography {
+ ///
+ /// A DKIM private key implemented using BouncyCastle.
+ ///
+ ///
+ /// A DKIM private key implemented using BouncyCastle.
+ ///
+ public class BouncyCastleDkimPrivateKey : BouncyCastleDkimKey, IDkimPrivateKey
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new .
+ ///
+ /// The private key.
+ ///
+ /// is null.
+ ///
+ ///
+ /// is not a private key.
+ ///
+ public BouncyCastleDkimPrivateKey (AsymmetricKeyParameter key)
+ {
+ if (key is null)
+ throw new ArgumentNullException (nameof (key));
+
+ if (!key.IsPrivate)
+ throw new ArgumentException ("The key must be a private key.", nameof (key));
+
+ Key = key;
+ }
+
+ ///
+ /// Create a DKIM signature context suitable for signing.
+ ///
+ ///
+ /// Creates a DKIM signature context suitable for signing.
+ ///
+ /// The DKIM signature algorithm.
+ /// The DKIM signature context.
+ ///
+ /// The specified is not supported.
+ ///
+ public IDkimSignatureContext CreateSigningContext (DkimSignatureAlgorithm algorithm)
+ {
+ return CreateSignatureContext (algorithm, true);
+ }
+
+ static AsymmetricKeyParameter LoadPrivateKey (Stream stream)
+ {
+ AsymmetricKeyParameter key = null;
+
+ using (var reader = new StreamReader (stream)) {
+ var pem = new PemReader (reader);
+
+ var keyObject = pem.ReadObject ();
+
+ if (keyObject is AsymmetricCipherKeyPair pair) {
+ key = pair.Private;
+ } else if (keyObject is AsymmetricKeyParameter param) {
+ key = param;
+ }
+ }
+
+ if (key == null || !key.IsPrivate)
+ throw new FormatException ("Private key not found.");
+
+ return key;
+ }
+
+ ///
+ /// Load a private key from the specified stream.
+ ///
+ ///
+ /// Loads a private key from the specified stream.
+ ///
+ /// A stream containing the private DKIM key data.
+ /// A .
+ ///
+ /// is null.
+ ///
+ ///
+ /// The stream did not contain a private key in PEM format.
+ ///
+ ///
+ /// An I/O error occurred.
+ ///
+ public static BouncyCastleDkimPrivateKey Load (Stream stream)
+ {
+ if (stream is null)
+ throw new ArgumentNullException (nameof (stream));
+
+ var key = LoadPrivateKey (stream);
+
+ return new BouncyCastleDkimPrivateKey (key);
+ }
+
+ ///
+ /// Load a private key from the specified file.
+ ///
+ ///
+ /// Loads a private key from the specified file.
+ ///
+ /// A file containing the private DKIM key data.
+ /// A .
+ ///
+ /// is null.
+ ///
+ ///
+ /// The stream did not contain a private key in PEM format.
+ ///
+ ///
+ /// An I/O error occurred.
+ ///
+ public static BouncyCastleDkimPrivateKey Load (string fileName)
+ {
+ if (fileName is null)
+ throw new ArgumentNullException (nameof (fileName));
+
+ using (var stream = File.OpenRead (fileName))
+ return Load (stream);
+ }
+ }
+}
diff --git a/MimeKit/Cryptography/BouncyCastleDkimPublicKey.cs b/MimeKit/Cryptography/BouncyCastleDkimPublicKey.cs
new file mode 100644
index 0000000000..1dca7edf7a
--- /dev/null
+++ b/MimeKit/Cryptography/BouncyCastleDkimPublicKey.cs
@@ -0,0 +1,117 @@
+//
+// BouncyCastleDkimPublicKey.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace MimeKit.Cryptography {
+ ///
+ /// A DKIM public key implemented using BouncyCastle.
+ ///
+ ///
+ /// A DKIM public key implemented using BouncyCastle.
+ ///
+ public class BouncyCastleDkimPublicKey : BouncyCastleDkimKey, IDkimPublicKey
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Creates a new .
+ ///
+ /// The public key.
+ ///
+ /// is null.
+ ///
+ ///
+ /// is not a public key.
+ ///
+ public BouncyCastleDkimPublicKey (AsymmetricKeyParameter key)
+ {
+ if (key is null)
+ throw new ArgumentNullException (nameof (key));
+
+ if (key.IsPrivate)
+ throw new ArgumentException ("The key must be a public key.", nameof (key));
+
+ Key = key;
+ }
+
+ ///
+ /// Get the size, in bits, of the key modulus used by the asymmetric algorithm.
+ ///
+ ///
+ /// Gets the size, in bits, of the key modulus used by the asymmetric algorithm.
+ ///
+ /// The size, in bits, of the key modulus.
+ public int KeySize {
+ get {
+ if (Key is RsaKeyParameters rsa)
+ return rsa.Modulus.BitLength;
+
+ return 0;
+ }
+ }
+
+ ///
+ /// Get the public key algorithm.
+ ///
+ ///
+ /// Gets the public key algorithm.
+ ///
+ /// The public key algorithm.
+ public DkimPublicKeyAlgorithm Algorithm {
+ get {
+ if (Key is RsaKeyParameters)
+ return DkimPublicKeyAlgorithm.Rsa;
+
+ if (Key is Ed25519PublicKeyParameters)
+ return DkimPublicKeyAlgorithm.Ed25519;
+
+ return DkimPublicKeyAlgorithm.Unknown;
+ }
+ }
+
+ ///
+ /// Create a DKIM signature context suitable for verifying a signature.
+ ///
+ ///
+ /// Creates a DKIM signature context suitable for verifying a signature.
+ ///
+ /// The DKIM signature algorithm.
+ /// The DKIM signature context.
+ ///
+ /// The specified is not supported.
+ ///
+ public IDkimSignatureContext CreateVerifyContext (DkimSignatureAlgorithm algorithm)
+ {
+ return CreateSignatureContext (algorithm, false);
+ }
+ }
+}
diff --git a/MimeKit/Cryptography/BouncyCastleDkimPublicKeyFactory.cs b/MimeKit/Cryptography/BouncyCastleDkimPublicKeyFactory.cs
new file mode 100644
index 0000000000..de04856f03
--- /dev/null
+++ b/MimeKit/Cryptography/BouncyCastleDkimPublicKeyFactory.cs
@@ -0,0 +1,163 @@
+//
+// BouncyCastleDkimPublicKeyFactory.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.OpenSsl;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace MimeKit.Cryptography {
+ ///
+ /// A DKIM public key factory implemented using BouncyCastle.
+ ///
+ ///
+ /// A DKIM public key factory implemented using BouncyCastle.
+ ///
+ public class BouncyCastleDkimPublicKeyFactory : IDkimPublicKeyFactory
+ {
+ ///
+ /// Instantiates a new instance of the class.
+ ///
+ ///
+ /// Creates a new .
+ ///
+ public BouncyCastleDkimPublicKeyFactory ()
+ {
+ }
+
+ ///
+ /// Create a new instance.
+ ///
+ /// The public key.
+ /// A new based on the public key provided.
+ ///
+ /// is null.
+ ///
+ ///
+ /// is not a public key.
+ ///
+ public IDkimPublicKey Create (AsymmetricKeyParameter key)
+ {
+ return new BouncyCastleDkimPublicKey (key);
+ }
+
+ static AsymmetricKeyParameter LoadRsaKey (string keyData)
+ {
+ var data = "-----BEGIN PUBLIC KEY-----\r\n" + keyData + "\r\n-----END PUBLIC KEY-----\r\n";
+ var rawData = Encoding.ASCII.GetBytes (data);
+
+ using (var stream = new MemoryStream (rawData, false)) {
+ using (var reader = new StreamReader (stream)) {
+ var pem = new PemReader (reader);
+
+ return pem.ReadObject () as AsymmetricKeyParameter;
+ }
+ }
+ }
+
+ ///
+ /// Create a new instance of an .
+ ///
+ ///
+ /// Creates a new instance of an based on the parameters provided.
+ /// The string should be the k parameter value from a DNS DKIM TXT
+ /// record while the string should be the p parameter value which in general
+ /// will be the base64 encoded key data.
+ ///
+ /// The public key algorithm.
+ /// The base64 encoded public key data.
+ /// A new instance of an .
+ ///
+ /// is null.
+ /// -or-
+ /// is null.
+ ///
+ ///
+ /// The is not supported.
+ ///
+ public IDkimPublicKey CreatePublicKey (string algorithm, string keyData)
+ {
+ AsymmetricKeyParameter pubkey = null;
+
+ if (algorithm.Equals ("ed25519", StringComparison.OrdinalIgnoreCase)) {
+ var decoded = Convert.FromBase64String (keyData);
+
+ pubkey = new Ed25519PublicKeyParameters (decoded, 0);
+ } else if (algorithm.Equals ("rsa", StringComparison.OrdinalIgnoreCase)) {
+ pubkey = LoadRsaKey (keyData);
+ }
+
+ if (pubkey is null)
+ throw new NotSupportedException ("The public key algorithm is not supported.");
+
+ return new BouncyCastleDkimPublicKey (pubkey);
+ }
+
+ ///
+ /// Create a new instance of an .
+ ///
+ ///
+ /// Creates a new instance of an based on the parameters provided.
+ /// The string should be the k parameter value from a DNS DKIM TXT
+ /// record while the string should be the p parameter value which in general
+ /// will be the base64 encoded key data.
+ ///
+ /// The public key algorithm.
+ /// The encoded content of the public key.
+ /// A new instance of an .
+ ///
+ /// is null.
+ /// -or-
+ /// is null.
+ ///
+ ///
+ /// The is not supported.
+ ///
+ public IDkimPublicKey CreatePublicKey (DkimPublicKeyAlgorithm algorithm, string keyData)
+ {
+ AsymmetricKeyParameter pubkey = null;
+
+ switch (algorithm) {
+ case DkimPublicKeyAlgorithm.Ed25519:
+ var decoded = Convert.FromBase64String (keyData);
+
+ pubkey = new Ed25519PublicKeyParameters (decoded, 0);
+ break;
+ case DkimPublicKeyAlgorithm.Rsa:
+ pubkey = LoadRsaKey (keyData);
+ break;
+ }
+
+ if (pubkey is null)
+ throw new NotSupportedException ("The public key algorithm is not supported.");
+
+ return new BouncyCastleDkimPublicKey (pubkey);
+ }
+ }
+}
diff --git a/MimeKit/Cryptography/BouncyCastleDkimSignatureContext.cs b/MimeKit/Cryptography/BouncyCastleDkimSignatureContext.cs
new file mode 100644
index 0000000000..3ba1eb3925
--- /dev/null
+++ b/MimeKit/Cryptography/BouncyCastleDkimSignatureContext.cs
@@ -0,0 +1,59 @@
+//
+// BouncyCastleDkimSignatureContext.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using Org.BouncyCastle.Crypto;
+
+namespace MimeKit.Cryptography {
+ class BouncyCastleDkimSignatureContext : IDkimSignatureContext
+ {
+ readonly ISigner signer;
+
+ public BouncyCastleDkimSignatureContext (ISigner signer)
+ {
+ this.signer = signer;
+ }
+
+ public byte[] GenerateSignature ()
+ {
+ return signer.GenerateSignature ();
+ }
+
+ public void Update (byte[] buffer, int offset, int length)
+ {
+ signer.BlockUpdate (buffer, offset, length);
+ }
+
+ public bool VerifySignature (byte[] signature)
+ {
+ return signer.VerifySignature (signature);
+ }
+
+ public void Dispose ()
+ {
+ signer.Reset ();
+ }
+ }
+}
diff --git a/MimeKit/Cryptography/DkimPublicKeyAlgorithm.cs b/MimeKit/Cryptography/DkimPublicKeyAlgorithm.cs
new file mode 100644
index 0000000000..08f0561f57
--- /dev/null
+++ b/MimeKit/Cryptography/DkimPublicKeyAlgorithm.cs
@@ -0,0 +1,51 @@
+//
+// DkimPublicKeyAlgorithm.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+namespace MimeKit.Cryptography {
+ ///
+ /// An enumeration of DKIM public key algorithms.
+ ///
+ ///
+ /// An enumeration of DKIM public key algorithms.
+ ///
+ public enum DkimPublicKeyAlgorithm
+ {
+ ///
+ /// The RSA algorithm.
+ ///
+ Rsa,
+
+ ///
+ /// The Ed25519 algorithm.
+ ///
+ Ed25519,
+
+ ///
+ /// An unknown algorithm.
+ ///
+ Unknown
+ }
+}
diff --git a/MimeKit/Cryptography/DkimPublicKeyLocatorBase.cs b/MimeKit/Cryptography/DkimPublicKeyLocatorBase.cs
index e5b1b89121..9316415c8c 100644
--- a/MimeKit/Cryptography/DkimPublicKeyLocatorBase.cs
+++ b/MimeKit/Cryptography/DkimPublicKeyLocatorBase.cs
@@ -158,7 +158,7 @@ protected static AsymmetricKeyParameter GetPublicKey (string txt)
/// The domain.
/// The selector.
/// The cancellation token.
- public abstract AsymmetricKeyParameter LocatePublicKey (string methods, string domain, string selector, CancellationToken cancellationToken = default);
+ public abstract IDkimPublicKey LocatePublicKey (string methods, string domain, string selector, CancellationToken cancellationToken = default);
///
/// Asynchronously locate and retrieve the public key for the given domain and selector.
@@ -179,6 +179,6 @@ protected static AsymmetricKeyParameter GetPublicKey (string txt)
/// The domain.
/// The selector.
/// The cancellation token.
- public abstract Task LocatePublicKeyAsync (string methods, string domain, string selector, CancellationToken cancellationToken = default);
+ public abstract Task LocatePublicKeyAsync (string methods, string domain, string selector, CancellationToken cancellationToken = default);
}
}
diff --git a/MimeKit/Cryptography/DkimRsaPrivateKey.cs b/MimeKit/Cryptography/DkimRsaPrivateKey.cs
new file mode 100644
index 0000000000..4d71d9b133
--- /dev/null
+++ b/MimeKit/Cryptography/DkimRsaPrivateKey.cs
@@ -0,0 +1,54 @@
+//
+// DkimRsaPrivateKey.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Security.Cryptography;
+
+namespace MimeKit.Cryptography {
+ class DkimRsaPrivateKey : IDkimPrivateKey
+ {
+ public DkimRsaPrivateKey (RSA key)
+ {
+ Key = key ?? throw new ArgumentNullException (nameof (key));
+ }
+
+ public RSA Key {
+ get; private set;
+ }
+
+ public IDkimSignatureContext CreateSigningContext (DkimSignatureAlgorithm algorithm)
+ {
+ switch (algorithm) {
+ case DkimSignatureAlgorithm.RsaSha1:
+ return new DkimRsaSignatureContext (Key, HashAlgorithmName.SHA1);
+ case DkimSignatureAlgorithm.RsaSha256:
+ return new DkimRsaSignatureContext (Key, HashAlgorithmName.SHA256);
+ default:
+ throw new NotSupportedException ($"{algorithm} is not supported.");
+ }
+ }
+ }
+}
diff --git a/MimeKit/Cryptography/DkimRsaPublicKey.cs b/MimeKit/Cryptography/DkimRsaPublicKey.cs
new file mode 100644
index 0000000000..009be67be0
--- /dev/null
+++ b/MimeKit/Cryptography/DkimRsaPublicKey.cs
@@ -0,0 +1,65 @@
+//
+// DkimRsaPublicKey.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Security.Cryptography;
+
+namespace MimeKit.Cryptography {
+ class DkimRsaPublicKey : IDkimPublicKey
+ {
+ public DkimRsaPublicKey (RSA key)
+ {
+ Key = key;
+ }
+
+ public RSA Key {
+ get; private set;
+ }
+
+ ///
+ /// Get the size, in bits, of the key modulus used by the asymmetric algorithm.
+ ///
+ ///
+ /// Gets the size, in bits, of the key modulus used by the asymmetric algorithm.
+ ///
+ /// The size, in bits, of the key modulus.
+ public int KeySize { get { return Key.KeySize; } }
+
+ public DkimPublicKeyAlgorithm Algorithm { get { return DkimPublicKeyAlgorithm.Rsa; } }
+
+ public IDkimSignatureContext CreateVerifyContext (DkimSignatureAlgorithm algorithm)
+ {
+ switch (algorithm) {
+ case DkimSignatureAlgorithm.RsaSha1:
+ return new DkimRsaSignatureContext (Key, HashAlgorithmName.SHA1);
+ case DkimSignatureAlgorithm.RsaSha256:
+ return new DkimRsaSignatureContext (Key, HashAlgorithmName.SHA256);
+ default:
+ throw new NotSupportedException ($"{algorithm} is not supported.");
+ }
+ }
+ }
+}
diff --git a/MimeKit/Cryptography/DkimRsaSignatureContext.cs b/MimeKit/Cryptography/DkimRsaSignatureContext.cs
new file mode 100644
index 0000000000..f4bf6f2873
--- /dev/null
+++ b/MimeKit/Cryptography/DkimRsaSignatureContext.cs
@@ -0,0 +1,69 @@
+//
+// DkimRsaSignatureContext.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Security.Cryptography;
+
+namespace MimeKit.Cryptography {
+ class DkimRsaSignatureContext : IDkimSignatureContext
+ {
+ readonly HashAlgorithmName hashAlgo;
+ readonly HashAlgorithm hash;
+ readonly RSA rsa;
+
+ public DkimRsaSignatureContext (RSA rsa, HashAlgorithmName hashAlgo)
+ {
+ this.hashAlgo = hashAlgo;
+ this.rsa = rsa;
+
+ hash = HashAlgorithm.Create (hashAlgo.Name);
+ }
+
+ public void Update (byte[] buffer, int offset, int length)
+ {
+ hash.TransformBlock (buffer, offset, length, null, 0);
+ }
+
+ public byte[] GenerateSignature ()
+ {
+ hash.TransformFinalBlock (Array.Empty (), 0, 0);
+
+ return rsa.SignHash (hash.Hash, hashAlgo, RSASignaturePadding.Pkcs1);
+ }
+
+ public bool VerifySignature (byte[] signature)
+ {
+ hash.TransformFinalBlock (Array.Empty (), 0, 0);
+
+ return rsa.VerifyHash (hash.Hash, signature, hashAlgo, RSASignaturePadding.Pkcs1);
+ }
+
+ public void Dispose ()
+ {
+ hash.Dispose ();
+ }
+ }
+}
diff --git a/MimeKit/Cryptography/DkimSignatureStream.cs b/MimeKit/Cryptography/DkimSignatureStream.cs
index 81aa27ce8e..7bc4c71a0e 100644
--- a/MimeKit/Cryptography/DkimSignatureStream.cs
+++ b/MimeKit/Cryptography/DkimSignatureStream.cs
@@ -27,8 +27,6 @@
using System;
using System.IO;
-using Org.BouncyCastle.Crypto;
-
namespace MimeKit.Cryptography {
///
/// A DKIM signature stream.
@@ -47,16 +45,16 @@ class DkimSignatureStream : Stream
///
/// Creates a new .
///
- /// The digest signer.
+ /// The signature context.
///
- /// is null.
+ /// is null.
///
- public DkimSignatureStream (ISigner signer)
+ public DkimSignatureStream (IDkimSignatureContext ctx)
{
- if (signer == null)
- throw new ArgumentNullException (nameof (signer));
+ if (ctx == null)
+ throw new ArgumentNullException (nameof (ctx));
- Signer = signer;
+ Context = ctx;
}
///
@@ -66,7 +64,7 @@ public DkimSignatureStream (ISigner signer)
/// Gets the digest signer.
///
/// The signer.
- public ISigner Signer {
+ public IDkimSignatureContext Context {
get; private set;
}
@@ -79,7 +77,7 @@ public ISigner Signer {
/// The signature.
public byte[] GenerateSignature ()
{
- return Signer.GenerateSignature ();
+ return Context.GenerateSignature ();
}
///
@@ -100,7 +98,7 @@ public bool VerifySignature (string signature)
var rawSignature = Convert.FromBase64String (signature);
- return Signer.VerifySignature (rawSignature);
+ return Context.VerifySignature (rawSignature);
}
void CheckDisposed ()
@@ -269,7 +267,7 @@ public override void Write (byte[] buffer, int offset, int count)
ValidateArguments (buffer, offset, count);
- Signer.BlockUpdate (buffer, offset, count);
+ Context.Update (buffer, offset, count);
length += count;
}
@@ -345,13 +343,7 @@ public override void SetLength (long value)
protected override void Dispose (bool disposing)
{
if (disposing && !disposed) {
-#if ENABLE_NATIVE_DKIM
- var sss = Signer as SystemSecuritySigner;
-
- if (sss != null)
- sss.Dispose ();
-#endif
-
+ Context.Dispose ();
disposed = true;
}
diff --git a/MimeKit/Cryptography/DkimSigner.cs b/MimeKit/Cryptography/DkimSigner.cs
index dead222736..39a86ecac6 100644
--- a/MimeKit/Cryptography/DkimSigner.cs
+++ b/MimeKit/Cryptography/DkimSigner.cs
@@ -95,14 +95,11 @@ protected DkimSigner (string domain, string selector, DkimSignatureAlgorithm alg
///
/// is not a private key.
///
- public DkimSigner (AsymmetricKeyParameter key, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256) : this (domain, selector, algorithm)
+ public DkimSigner (IDkimPrivateKey key, string domain, string selector, DkimSignatureAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256) : this (domain, selector, algorithm)
{
if (key == null)
throw new ArgumentNullException (nameof (key));
- if (!key.IsPrivate)
- throw new ArgumentException ("The key must be a private key.", nameof (key));
-
PrivateKey = key;
}
@@ -292,7 +289,7 @@ void DkimSign (FormatOptions options, MimeMessage message, IList headers
builder.AppendInvariant (x);
}
- using (var stream = new DkimSignatureStream (CreateSigningContext ())) {
+ using (var stream = new DkimSignatureStream (PrivateKey.CreateSigningContext (SignatureAlgorithm))) {
using (var filtered = new FilteredStream (stream)) {
filtered.Add (options.CreateNewLineFilter ());
diff --git a/MimeKit/Cryptography/DkimSignerBase.cs b/MimeKit/Cryptography/DkimSignerBase.cs
index 4ea63de08d..0ecbc88ac9 100644
--- a/MimeKit/Cryptography/DkimSignerBase.cs
+++ b/MimeKit/Cryptography/DkimSignerBase.cs
@@ -25,15 +25,6 @@
//
using System;
-using System.IO;
-#if ENABLE_NATIVE_DKIM
-using System.Security.Cryptography;
-#endif
-
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.OpenSsl;
-using Org.BouncyCastle.Crypto.Digests;
-using Org.BouncyCastle.Crypto.Signers;
namespace MimeKit.Cryptography {
///
@@ -168,149 +159,8 @@ public DkimCanonicalizationAlgorithm HeaderCanonicalizationAlgorithm {
/// The private key used for signing.
///
/// The private key.
- protected AsymmetricKeyParameter PrivateKey {
+ protected IDkimPrivateKey PrivateKey {
get; set;
}
-
- internal static AsymmetricKeyParameter LoadPrivateKey (Stream stream)
- {
- AsymmetricKeyParameter key = null;
-
- using (var reader = new StreamReader (stream)) {
- var pem = new PemReader (reader);
-
- var keyObject = pem.ReadObject ();
-
- if (keyObject is AsymmetricCipherKeyPair pair) {
- key = pair.Private;
- } else if (keyObject is AsymmetricKeyParameter param) {
- key = param;
- }
- }
-
- if (key == null || !key.IsPrivate)
- throw new FormatException ("Private key not found.");
-
- return key;
- }
-
- ///
- /// Create the digest signing context.
- ///
- ///
- /// Creates a new digest signing context.
- ///
- /// The digest signer.
- ///
- /// The is not supported.
- ///
- internal protected virtual ISigner CreateSigningContext ()
- {
-#if ENABLE_NATIVE_DKIM
- return new SystemSecuritySigner (SignatureAlgorithm, PrivateKey.AsAsymmetricAlgorithm ());
-#else
- ISigner signer;
-
- switch (SignatureAlgorithm) {
- case DkimSignatureAlgorithm.RsaSha1:
- signer = new RsaDigestSigner (new Sha1Digest ());
- break;
- case DkimSignatureAlgorithm.RsaSha256:
- signer = new RsaDigestSigner (new Sha256Digest ());
- break;
- case DkimSignatureAlgorithm.Ed25519Sha256:
- signer = new Ed25519DigestSigner (new Sha256Digest ());
- break;
- default:
- throw new NotSupportedException (string.Format ("{0} is not supported.", SignatureAlgorithm));
- }
-
- signer.Init (true, PrivateKey);
-
- return signer;
-#endif
- }
- }
-
-#if ENABLE_NATIVE_DKIM
- class SystemSecuritySigner : ISigner
- {
- readonly RSACryptoServiceProvider rsa;
- readonly HashAlgorithm hash;
- readonly string oid;
-
- public SystemSecuritySigner (DkimSignatureAlgorithm algorithm, AsymmetricAlgorithm key)
- {
- rsa = key as RSACryptoServiceProvider;
-
- switch (algorithm) {
- case DkimSignatureAlgorithm.RsaSha256:
- oid = SecureMimeContext.GetDigestOid (DigestAlgorithm.Sha256);
- AlgorithmName = "RSASHA256";
- hash = SHA256.Create ();
- break;
- default:
- oid = SecureMimeContext.GetDigestOid (DigestAlgorithm.Sha1);
- AlgorithmName = "RSASHA1";
- hash = SHA1.Create ();
- break;
- }
- }
-
- public string AlgorithmName {
- get; private set;
- }
-
- public void BlockUpdate (byte[] input, int inOff, int length)
- {
- hash.TransformBlock (input, inOff, length, null, 0);
- }
-
-#if NET6_0_OR_GREATER
- public void BlockUpdate (ReadOnlySpan input)
- {
- throw new NotImplementedException ();
- }
-#endif
-
- public byte[] GenerateSignature ()
- {
- hash.TransformFinalBlock (new byte[0], 0, 0);
-
- return rsa.SignHash (hash.Hash, oid);
- }
-
- public void Init (bool forSigning, ICipherParameters parameters)
- {
- throw new NotImplementedException ();
- }
-
- public int GetMaxSignatureSize ()
- {
- return hash.HashSize;
- }
-
- public void Reset ()
- {
- hash.Initialize ();
- }
-
- public void Update (byte input)
- {
- hash.TransformBlock (new byte[] { input }, 0, 1, null, 0);
- }
-
- public bool VerifySignature (byte[] signature)
- {
- hash.TransformFinalBlock (new byte[0], 0, 0);
-
- return rsa.VerifyHash (hash.Hash, oid, signature);
- }
-
- public void Dispose ()
- {
- rsa.Dispose ();
- }
}
-#endif
}
diff --git a/MimeKit/Cryptography/DkimVerifier.cs b/MimeKit/Cryptography/DkimVerifier.cs
index c9b4019689..239502eb3f 100644
--- a/MimeKit/Cryptography/DkimVerifier.cs
+++ b/MimeKit/Cryptography/DkimVerifier.cs
@@ -114,8 +114,8 @@ async Task VerifyAsync (FormatOptions options, MimeMessage message, Header
var parameters = ParseParameterTags (dkimSignature.Id, dkimSignature.Value);
DkimCanonicalizationAlgorithm headerAlgorithm, bodyAlgorithm;
DkimSignatureAlgorithm signatureAlgorithm;
- AsymmetricKeyParameter key;
string d, s, q, bh, b;
+ IDkimPublicKey key;
string[] headers;
int maxLength;
@@ -137,7 +137,7 @@ async Task VerifyAsync (FormatOptions options, MimeMessage message, Header
else
key = PublicKeyLocator.LocatePublicKey (q, d, s, cancellationToken);
- if ((key is RsaKeyParameters rsa) && rsa.Modulus.BitLength < MinimumRsaKeyLength)
+ if (key.Algorithm == DkimPublicKeyAlgorithm.Rsa && key.KeySize < MinimumRsaKeyLength)
return false;
return VerifySignature (options, message, dkimSignature, signatureAlgorithm, key, headers, headerAlgorithm, b);
diff --git a/MimeKit/Cryptography/DkimVerifierBase.cs b/MimeKit/Cryptography/DkimVerifierBase.cs
index 7d84e6469d..bb0695d304 100644
--- a/MimeKit/Cryptography/DkimVerifierBase.cs
+++ b/MimeKit/Cryptography/DkimVerifierBase.cs
@@ -363,42 +363,6 @@ internal static void WriteHeaderSimple (FormatOptions options, Stream stream, He
stream.Write (rawValue, 0, rawLength);
}
- ///
- /// Create the digest signing context.
- ///
- ///
- /// Creates a new digest signing context that uses the specified algorithm.
- ///
- /// The DKIM signature algorithm.
- /// The public key.
- /// The digest signer.
- internal virtual ISigner CreateVerifyContext (DkimSignatureAlgorithm algorithm, AsymmetricKeyParameter key)
- {
-#if ENABLE_NATIVE_DKIM
- return new SystemSecuritySigner (algorithm, key.AsAsymmetricAlgorithm ());
-#else
- ISigner signer;
-
- switch (algorithm) {
- case DkimSignatureAlgorithm.RsaSha1:
- signer = new RsaDigestSigner (new Sha1Digest ());
- break;
- case DkimSignatureAlgorithm.RsaSha256:
- signer = new RsaDigestSigner (new Sha256Digest ());
- break;
- case DkimSignatureAlgorithm.Ed25519Sha256:
- signer = new Ed25519DigestSigner (new Sha256Digest ());
- break;
- default:
- throw new NotSupportedException (string.Format ("{0} is not supported.", algorithm));
- }
-
- signer.Init (key.IsPrivate, key);
-
- return signer;
-#endif
- }
-
internal static void WriteHeaders (FormatOptions options, MimeMessage message, IList fields, DkimCanonicalizationAlgorithm headerCanonicalizationAlgorithm, Stream stream)
{
var counts = new Dictionary (StringComparer.Ordinal);
@@ -529,9 +493,9 @@ protected static bool VerifyBodyHash (FormatOptions options, MimeMessage message
/// The algorithm used to canonicalize the headers.
/// The expected signature of the headers encoded in base64.
/// true if the calculated signature matches ; otherwise, false.
- protected bool VerifySignature (FormatOptions options, MimeMessage message, Header dkimSignature, DkimSignatureAlgorithm signatureAlgorithm, AsymmetricKeyParameter key, string[] headers, DkimCanonicalizationAlgorithm canonicalizationAlgorithm, string signature)
+ protected bool VerifySignature (FormatOptions options, MimeMessage message, Header dkimSignature, DkimSignatureAlgorithm signatureAlgorithm, IDkimPublicKey key, string[] headers, DkimCanonicalizationAlgorithm canonicalizationAlgorithm, string signature)
{
- using (var stream = new DkimSignatureStream (CreateVerifyContext (signatureAlgorithm, key))) {
+ using (var stream = new DkimSignatureStream (key.CreateVerifyContext (signatureAlgorithm))) {
using (var filtered = new FilteredStream (stream)) {
filtered.Add (options.CreateNewLineFilter ());
diff --git a/MimeKit/Cryptography/IDkimPrivateKey.cs b/MimeKit/Cryptography/IDkimPrivateKey.cs
new file mode 100644
index 0000000000..cdf52934c6
--- /dev/null
+++ b/MimeKit/Cryptography/IDkimPrivateKey.cs
@@ -0,0 +1,49 @@
+//
+// IDkimPrivateKey.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+namespace MimeKit.Cryptography {
+ ///
+ /// An interface for a DKIM private key.
+ ///
+ ///
+ /// Represents a private key for use with DKIM and ARC message signature generation.
+ ///
+ public interface IDkimPrivateKey
+ {
+ ///
+ /// Create a DKIM signature context suitable for signing.
+ ///
+ ///
+ /// Creates a DKIM signature context suitable for signing.
+ ///
+ /// The DKIM signature algorithm.
+ /// The DKIM signature context.
+ ///
+ /// The specified is not supported.
+ ///
+ IDkimSignatureContext CreateSigningContext (DkimSignatureAlgorithm algorithm);
+ }
+}
diff --git a/MimeKit/Cryptography/IDkimPublicKey.cs b/MimeKit/Cryptography/IDkimPublicKey.cs
new file mode 100644
index 0000000000..c82efdb8c5
--- /dev/null
+++ b/MimeKit/Cryptography/IDkimPublicKey.cs
@@ -0,0 +1,67 @@
+//
+// IDkimPublicKey.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+namespace MimeKit.Cryptography {
+ ///
+ /// An interface for a DKIM public key.
+ ///
+ ///
+ /// Represents a public key for use with DKIM and ARC message signature verification.
+ ///
+ public interface IDkimPublicKey
+ {
+ ///
+ /// Get the size, in bits, of the key modulus used by the asymmetric algorithm.
+ ///
+ ///
+ /// Gets the size, in bits, of the key modulus used by the asymmetric algorithm.
+ ///
+ /// The size, in bits, of the key modulus.
+ int KeySize { get; }
+
+ ///
+ /// Get the public key algorithm.
+ ///
+ ///
+ /// Gets the public key algorithm.
+ ///
+ /// The public key algorithm.
+ DkimPublicKeyAlgorithm Algorithm { get; }
+
+ ///
+ /// Create a DKIM signature context suitable for verifying a signature.
+ ///
+ ///
+ /// Creates a DKIM signature context suitable for verifying a signature.
+ ///
+ /// The DKIM signature algorithm.
+ /// The DKIM signature context.
+ ///
+ /// The specified is not supported.
+ ///
+ IDkimSignatureContext CreateVerifyContext (DkimSignatureAlgorithm algorithm);
+ }
+}
diff --git a/MimeKit/Cryptography/IDkimPublicKeyFactory.cs b/MimeKit/Cryptography/IDkimPublicKeyFactory.cs
new file mode 100644
index 0000000000..541e65e7a1
--- /dev/null
+++ b/MimeKit/Cryptography/IDkimPublicKeyFactory.cs
@@ -0,0 +1,80 @@
+//
+// IDkimPublicKeyFactory.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+namespace MimeKit.Cryptography {
+ ///
+ /// An interface for a factory that creates instances.
+ ///
+ ///
+ /// An interface for a factory that creates instances.
+ ///
+ public interface IDkimPublicKeyFactory
+ {
+ ///
+ /// Create a new instance of an .
+ ///
+ ///
+ /// Creates a new instance of an based on the parameters provided.
+ /// The string should be the k parameter value from a DNS DKIM TXT
+ /// record while the string should be the p parameter value which in general
+ /// will be the base64 encoded key data.
+ ///
+ /// The public key algorithm.
+ /// The base64 encoded public key data.
+ /// A new instance of an .
+ ///
+ /// is null.
+ /// -or-
+ /// is null.
+ ///
+ ///
+ /// The is not supported.
+ ///
+ IDkimPublicKey CreatePublicKey (string algorithm, string keyData);
+
+ ///
+ /// Create a new instance of an .
+ ///
+ ///
+ /// Creates a new instance of an based on the parameters provided.
+ /// The string should be the k parameter value from a DNS DKIM TXT
+ /// record while the string should be the p parameter value which in general
+ /// will be the base64 encoded key data.
+ ///
+ /// The public key algorithm.
+ /// The base64 encoded public key data.
+ /// A new instance of an .
+ ///
+ /// is null.
+ /// -or-
+ /// is null.
+ ///
+ ///
+ /// The is not supported.
+ ///
+ IDkimPublicKey CreatePublicKey (DkimPublicKeyAlgorithm algorithm, string keyData);
+ }
+}
diff --git a/MimeKit/Cryptography/IDkimPublicKeyLocator.cs b/MimeKit/Cryptography/IDkimPublicKeyLocator.cs
index 9dbb5ef226..a943e1ce6e 100644
--- a/MimeKit/Cryptography/IDkimPublicKeyLocator.cs
+++ b/MimeKit/Cryptography/IDkimPublicKeyLocator.cs
@@ -27,8 +27,6 @@
using System.Threading;
using System.Threading.Tasks;
-using Org.BouncyCastle.Crypto;
-
namespace MimeKit.Cryptography {
///
/// An interface for a service which locates and retrieves DKIM public keys (probably via DNS).
@@ -68,7 +66,7 @@ public interface IDkimPublicKeyLocator
/// The domain.
/// The selector.
/// The cancellation token.
- AsymmetricKeyParameter LocatePublicKey (string methods, string domain, string selector, CancellationToken cancellationToken = default);
+ IDkimPublicKey LocatePublicKey (string methods, string domain, string selector, CancellationToken cancellationToken = default);
///
/// Asynchronously locate and retrieve the public key for the given domain and selector.
@@ -89,6 +87,6 @@ public interface IDkimPublicKeyLocator
/// The domain.
/// The selector.
/// The cancellation token.
- Task LocatePublicKeyAsync (string methods, string domain, string selector, CancellationToken cancellationToken = default);
+ Task LocatePublicKeyAsync (string methods, string domain, string selector, CancellationToken cancellationToken = default);
}
}
diff --git a/MimeKit/Cryptography/IDkimSignatureContext.cs b/MimeKit/Cryptography/IDkimSignatureContext.cs
new file mode 100644
index 0000000000..075129a415
--- /dev/null
+++ b/MimeKit/Cryptography/IDkimSignatureContext.cs
@@ -0,0 +1,67 @@
+//
+// IDkimSignatureContext.cs
+//
+// Author: Jeffrey Stedfast
+//
+// Copyright (c) 2013-2023 .NET Foundation and Contributors
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+
+namespace MimeKit.Cryptography {
+ ///
+ /// An interface for a context used for generating and verifying DKIM signatures.
+ ///
+ ///
+ /// Represents a context used for generating and verifying DKIM signatures.
+ ///
+ public interface IDkimSignatureContext : IDisposable
+ {
+ ///
+ /// Update the signature context.
+ ///
+ ///
+ /// Updates the internal hash state of the signature context with the contents of the buffer.
+ ///
+ /// The buffer.
+ /// The offset into the buffer.
+ /// The length of the content within the buffer.
+ void Update (byte[] buffer, int offset, int length);
+
+ ///
+ /// Generate the signature.
+ ///
+ ///
+ /// Generates the signature for the data that has been hashed by previous calls to .
+ ///
+ /// The signature.
+ byte[] GenerateSignature ();
+
+ ///
+ /// Verify the signature.
+ ///
+ ///
+ /// Verifies the signature for the data that has been hashed by previous calls to .
+ ///
+ /// true if the signature is valid; otherwise, false.
+ bool VerifySignature (byte[] signature);
+ }
+}
diff --git a/UnitTests/Cryptography/DkimTests.cs b/UnitTests/Cryptography/DkimTests.cs
index 4935e69976..dc6ab6de8d 100644
--- a/UnitTests/Cryptography/DkimTests.cs
+++ b/UnitTests/Cryptography/DkimTests.cs
@@ -80,7 +80,9 @@ static DkimTests ()
static DkimSigner CreateSigner (DkimSignatureAlgorithm algorithm, DkimCanonicalizationAlgorithm headerAlgorithm, DkimCanonicalizationAlgorithm bodyAlgorithm)
{
- return new DkimSigner (Path.Combine (TestHelper.ProjectDir, "TestData", "dkim", "example.pem"), "example.com", "1433868189.example") {
+ var privateKey = BouncyCastleDkimPrivateKey.Load (Path.Combine (TestHelper.ProjectDir, "TestData", "dkim", "example.pem"));
+
+ return new DkimSigner (privateKey, "example.com", "1433868189.example") {
BodyCanonicalizationAlgorithm = bodyAlgorithm,
HeaderCanonicalizationAlgorithm = headerAlgorithm,
SignatureAlgorithm = algorithm,
@@ -92,8 +94,10 @@ static DkimSigner CreateSigner (DkimSignatureAlgorithm algorithm, DkimCanonicali
[Test]
public void TestDkimSignerCtors ()
{
+ var privateKey = new BouncyCastleDkimPrivateKey (DkimKeys.Private);
+
Assert.DoesNotThrow (() => {
- var signer = new DkimSigner (Path.Combine (TestHelper.ProjectDir, "TestData", "dkim", "example.pem"), "example.com", "1433868189.example") {
+ var signer = new DkimSigner (privateKey, "example.com", "1433868189.example") {
SignatureAlgorithm = DkimSignatureAlgorithm.RsaSha256,
AgentOrUserIdentifier = "@eng.example.com",
QueryMethod = "dns/txt"
@@ -183,12 +187,12 @@ public void TestDkimHashStream ()
[Test]
public void TestDkimSignatureStream ()
{
- var signer = CreateSigner (DkimSignatureAlgorithm.RsaSha1, DkimCanonicalizationAlgorithm.Simple, DkimCanonicalizationAlgorithm.Simple);
+ var privateKey = new BouncyCastleDkimPrivateKey (DkimKeys.Private);
var buffer = new byte[128];
Assert.Throws (() => new DkimSignatureStream (null));
- using (var stream = new DkimSignatureStream (signer.CreateSigningContext ())) {
+ using (var stream = new DkimSignatureStream (privateKey.CreateSigningContext (DkimSignatureAlgorithm.RsaSha1))) {
Assert.That (stream.CanRead, Is.False);
Assert.That (stream.CanWrite, Is.True);
Assert.That (stream.CanSeek, Is.False);