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