Необходимо расшифровать файл smime p7m с закрытым ключом на смарт-карте от UWP - PullRequest
0 голосов
/ 30 ноября 2018

У меня есть требование расшифровать вложения p7m с помощью закрытого ключа, который хранится на смарт-карте.

В настоящее время я могу получить закрытый ключ в смарт-карте, перечислив все сертификаты в объект CryptogrphicKey следующим образом:

CryptographicKey key = null;
try {
key = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync(Cert, 
HashAlgorithmNames.Sha256, 
CryptographicPadding.RsaPkcs1V15);
}

и в настоящее время я читаю в p7mфайл из локальной папки, например:

StorageFile file = await openPicker.PickSingleFileAsync();
var filebytes = new byte[(int)stream.Length];
stream.Read(filebytes, 0, (int)stream.Length);
var ms = new MemoryStream(filebytes);
ApplicationPkcs7Mime p7m = new 
ApplicationPkcs7Mime(SecureMimeType.EnvelopedData, ms);

Хотелось бы узнать, достижимо ли то, что я делаю, с помощью Mimekit ...

Спасибо!

Редактировать:

После предложения Джеффа я сделал это:

CryptographyContext.Register(typeof(WindowsSecureMimeContext));

И тогда это был просто вопрос вызова:

if (p7m != null && p7m.SecureMimeType == SecureMimeType.EnvelopedData)
p7m = p7m.Decrypt() as ApplicationPkcs7Mime;   
if (p7m != null && p7m.SecureMimeType == SecureMimeType.CompressedData)
p7m = p7m.Decompress() as ApplicationPkcs7Mime;            
if (p7m != null && p7m.SecureMimeType == SecureMimeType.SignedData)
{
p7m.Verify(out MimeEntity entity);
MimeMessage mm = new MimeMessage(entity);
}

Однако проблема в том, что мне нужно бытьв состоянии сделать это из приложения UWP ... И я не могу получить доступ к WindowsSecureMimeContext оттуда.Есть предложения?

Спасибо!

1 Ответ

0 голосов
/ 30 ноября 2018

Если вы можете преобразовать CryptographicKey в ключ, используемый BouncyCastle, то должна быть возможность создать подкласс TemporarySecureMimeContext (или любой из других классов, производных от BouncyCastleSecureMimeContext )переопределите метод GetPrivateKey , и вы должны быть Good-To-Go (tm).

Параметр IX509Selector - это просто способ найти правильный закрытый ключ для использования, но еслиВы уже знаете это, а затем просто верните свой CryptographicKey, преобразованный в BouncyCastle AsymmetricKeyParameter, и все будет работать.

Если вы не можете преобразовать его в AsymmetricKeyParameter, то вам может понадобиться подкласс Вместо этого SecureMimeContext , но, к сожалению, это потребует намного больше работы, потому что вам нужно будет переопределить намного больше методов, которые предоставляют криптографическую логику.Но если какая-либо библиотека, которую вы используете для чтения смарт-карты, имеет свой собственный API-интерфейс шифрования, то это не должно быть слишком сложным.

Для чего бы это ни стоило, если вам нужно напрямую создать подкласс SecureMimeContext, чтобыиспользуйте другую криптографическую библиотеку, я рекомендовал бы взглянуть на исходный код WindowsSecureMimeContext , который является альтернативой реализации BouncyCastle, и может помочь вам реализовать свой собственный контекст S / MIME гораздо большелегко, если ваша библиотека смарт-карт имеет достойный API высокого уровня.Файл выглядит большим (~ 1200 строк), но это как минимум 50% комментариев, так что это действительно неплохо, и вы, вероятно, можете обойтись без реализации его половины (то есть логики для подписи или шифрования, поскольку вам нужно только расшифровать).

Обновление :

Я не очень знаком с пространством имен UWP Windows.Security, но вот то, что я придумал в качестве возможного решения (может потребоваться работа,этот код полностью не проверен, и вам, вероятно, придется немного его настроить, чтобы использовать правильные хранилища сертификатов и т. д.):

using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;

using Windows.Storage.Streams;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Security.Cryptography.Certificates;

using Org.BouncyCastle.Cms;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Asn1.Smime;
using Org.BouncyCastle.X509.Store;
using Org.BouncyCastle.Crypto.Parameters;

using MimeKit.IO;

namespace MimeKit.Cryptography
{
    public class UWPSecureMimeContext : SecureMimeContext
    {
        readonly CertificateStore userStore;

        public UWPSecureMimeContext (CertificateStore userStore)
        {
            this.userStore = userStore;

            // UWP does not support Camellia...
            Disable (EncryptionAlgorithm.Camellia256);
            Disable (EncryptionAlgorithm.Camellia192);
            Disable (EncryptionAlgorithm.Camellia192);

            // ... or Blowfish/Twofish...
            Disable (EncryptionAlgorithm.Blowfish);
            Disable (EncryptionAlgorithm.Twofish);

            // ...or CAST5...
            Disable (EncryptionAlgorithm.Cast5);

            // ...or IDEA...
            Disable (EncryptionAlgorithm.Idea);

            // ...or SEED...
            Disable (EncryptionAlgorithm.Seed);
        }

        public UWPSecureMimeContext (string userStore) : this (CertificateStores.GetStoreByName (userStore))
        {
        }

        public UWPSecureMimeContext () : this (CertificateStores.GetStoreByName ("MimeKit"))
        {
        }

        /// <summary>
        /// Check whether or not the cryptography context can encrypt to a particular recipient.
        /// </summary>
        /// <remarks>
        /// Checks whether or not the cryptography context can be used to encrypt to a particular recipient.
        /// </remarks>
        /// <returns><c>true</c> if the cryptography context can be used to encrypt to the designated recipient; otherwise, <c>false</c>.</returns>
        /// <param name="mailbox">The recipient's mailbox address.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="mailbox"/> is <c>null</c>.
        /// </exception>
        public override bool CanEncrypt (MailboxAddress mailbox)
        {
            if (mailbox == null)
                throw new ArgumentNullException (nameof (mailbox));

            return GetRecipientCertificate (mailbox) != null;
        }

        /// <summary>
        /// Check whether or not a particular mailbox address can be used for signing.
        /// </summary>
        /// <remarks>
        /// Checks whether or not as particular mailbocx address can be used for signing.
        /// </remarks>
        /// <returns><c>true</c> if the mailbox address can be used for signing; otherwise, <c>false</c>.</returns>
        /// <param name="signer">The signer.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="signer"/> is <c>null</c>.
        /// </exception>
        public override bool CanSign (MailboxAddress signer)
        {
            if (signer == null)
                throw new ArgumentNullException (nameof (signer));

            return GetSignerCertificate (signer) != null;
        }

        static byte ToXDigit (char c)
        {
            if (c >= 0x41) {
                if (c >= 0x61)
                    return (byte) (c - (0x61 - 0x0a));

                return (byte) (c - (0x41 - 0x0A));
            }

            return (byte) (c - 0x30);
        }

        static byte[] HexDecode (string value)
        {
            var decoded = new byte[value.Length / 2];

            for (int i = 0; i < decoded.Length; i++) {
                var b1 = ToXDigit (value[i * 2]);
                var b2 = ToXDigit (value[i * 2 + 1]);

                decoded[i] = (byte) ((b1 << 4) | b2);
            }

            return decoded;
        }

        async Task<Certificate> GetCertificate (MailboxAddress mailbox, bool sign)
        {
            var secure = mailbox as SecureMailboxAddress;
            var query = new CertificateQuery ();
            var now = DateTimeOffset.UtcNow;

            if (secure != null)
                query.Thumbprint = HexDecode (secure.Fingerprint);
            else
                query.FriendlyName = mailbox.Address;

            // TODO: filter on key usages

            foreach (var certificate in await CertificateStores.FindAllAsync (query)) {
                if (certificate.ValidFrom > now || certificate.ValidTo < now)
                    continue;

                return certificate;
            }

            return null;
        }

        /// <summary>
        /// Get the certificate for the specified recipient.
        /// </summary>
        /// <remarks>
        /// <para>Gets the certificate for the specified recipient.</para>
        /// <para>If the mailbox is a <see cref="SecureMailboxAddress"/>, the
        /// <see cref="SecureMailboxAddress.Fingerprint"/> property will be used instead of
        /// the mailbox address.</para>
        /// </remarks>
        /// <returns>The certificate to use for the recipient; otherwise, or <c>null</c>.</returns>
        /// <param name="mailbox">The recipient's mailbox address.</param>
        protected virtual Certificate GetRecipientCertificate (MailboxAddress mailbox)
        {
            return GetCertificate (mailbox, false).GetAwaiter ().GetResult ();
        }

        /// <summary>
        /// Get the certificate for the specified signer.
        /// </summary>
        /// <remarks>
        /// <para>Gets the certificate for the specified signer.</para>
        /// <para>If the mailbox is a <see cref="SecureMailboxAddress"/>, the
        /// <see cref="SecureMailboxAddress.Fingerprint"/> property will be used instead of
        /// the mailbox address.</para>
        /// </remarks>
        /// <returns>The certificate to use for the signer; otherwise, or <c>null</c>.</returns>
        /// <param name="mailbox">The signer's mailbox address.</param>
        protected virtual Certificate GetSignerCertificate (MailboxAddress mailbox)
        {
            return GetCertificate (mailbox, true).GetAwaiter ().GetResult ();
        }

        static byte[] ReadAllBytes (Stream stream)
        {
            if (stream is MemoryBlockStream)
                return ((MemoryBlockStream) stream).ToArray ();

            if (stream is MemoryStream)
                return ((MemoryStream) stream).ToArray ();

            using (var memory = new MemoryBlockStream ()) {
                stream.CopyTo (memory, 4096);
                return memory.ToArray ();
            }
        }

        static async Task<byte[]> ReadAllBytesAsync (Stream stream, CancellationToken cancellationToken)
        {
            if (stream is MemoryBlockStream)
                return ((MemoryBlockStream) stream).ToArray ();

            if (stream is MemoryStream)
                return ((MemoryStream) stream).ToArray ();

            using (var memory = new MemoryBlockStream ()) {
                await stream.CopyToAsync (memory, 4096, cancellationToken).ConfigureAwait (false);
                return memory.ToArray ();
            }
        }

        static AsymmetricKeyParameter LoadPrivateKey (IBuffer buffer)
        {
            AsymmetricKeyParameter key = null;
            byte[] keyData;

            CryptographicBuffer.CopyToByteArray (buffer, out keyData);

            using (var memory = new MemoryStream (keyData, false)) {
                using (var reader = new StreamReader (memory)) {
                    var pem = new PemReader (reader);

                    var keyObject = pem.ReadObject ();

                    if (keyObject != null) {
                        key = keyObject as AsymmetricKeyParameter;

                        if (key == null) {
                            var pair = keyObject as AsymmetricCipherKeyPair;

                            if (pair != null)
                                key = pair.Private;
                        }
                    }
                }
            }

            if (key == null || !key.IsPrivate)
                return null;

            return key;
        }

        /// <summary>
        /// Gets the private key for the certificate matching the specified selector.
        /// </summary>
        /// <remarks>
        /// Gets the private key for the first certificate that matches the specified selector.
        /// </remarks>
        /// <returns>The private key on success; otherwise <c>null</c>.</returns>
        /// <param name="selector">The search criteria for the private key.</param>
        protected virtual async Task<AsymmetricKeyParameter> GetPrivateKeyAsync (IX509Selector selector)
        {
            // first we need to find the certificate...
            var match = selector as X509CertStoreSelector;
            var query = new CertificateQuery ();

            if (match == null)
                return null;

            if (match.Certificate != null)
                query.Thumbprint = HexDecode (match.Certificate.GetFingerprint ());

            if (match.Issuer != null)
                query.IssuerName = match.Issuer.ToString ();

            var certificates = await CertificateStores.FindAllAsync (query);
            var certificate = certificates.FirstOrDefault ();

            if (certificate == null)
                return null;

            // now get the key

            // TODO: what hash algo/padding do we want? does it matter?
            var key = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync (certificate, HashAlgorithmNames.Sha256, CryptographicPadding.RsaPkcs1V15);
            var buffer = key.Export (CryptographicPrivateKeyBlobType.Pkcs1RsaPrivateKey);

            return LoadPrivateKey (buffer);
        }

        /// <summary>
        /// Gets the private key for the certificate matching the specified selector.
        /// </summary>
        /// <remarks>
        /// Gets the private key for the first certificate that matches the specified selector.
        /// </remarks>
        /// <returns>The private key on success; otherwise <c>null</c>.</returns>
        /// <param name="selector">The search criteria for the private key.</param>
        protected virtual AsymmetricKeyParameter GetPrivateKey (IX509Selector selector)
        {
            return GetPrivateKeyAsync (selector).GetAwaiter ().GetResult ();
        }

        /// <summary>
        /// Decrypt the specified encryptedData.
        /// </summary>
        /// <remarks>
        /// Decrypts the specified encryptedData.
        /// </remarks>
        /// <returns>The decrypted <see cref="MimeKit.MimeEntity"/>.</returns>
        /// <param name="encryptedData">The encrypted data.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="encryptedData"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="Org.BouncyCastle.Cms.CmsException">
        /// An error occurred in the cryptographic message syntax subsystem.
        /// </exception>
        /// <exception cref="System.OperationCanceledException">
        /// The operation was cancelled via the cancellation token.
        /// </exception>
        public override MimeEntity Decrypt (Stream encryptedData, CancellationToken cancellationToken = default (CancellationToken))
        {
            if (encryptedData == null)
                throw new ArgumentNullException (nameof (encryptedData));

            var parser = new CmsEnvelopedDataParser (encryptedData);
            var recipients = parser.GetRecipientInfos ();
            var algorithm = parser.EncryptionAlgorithmID;
            AsymmetricKeyParameter key;

            foreach (RecipientInformation recipient in recipients.GetRecipients ()) {
                if ((key = GetPrivateKey (recipient.RecipientID)) == null)
                    continue;

                var content = recipient.GetContent (key);
                var memory = new MemoryStream (content, false);

                return MimeEntity.Load (memory, true, cancellationToken);
            }

            throw new CmsException ("A suitable private key could not be found for decrypting.");
        }
        /// <summary>
        /// Decrypt the specified encryptedData to an output stream.
        /// </summary>
        /// <remarks>
        /// Decrypts the specified encryptedData to an output stream.
        /// </remarks>
        /// <param name="encryptedData">The encrypted data.</param>
        /// <param name="decryptedData">The output stream.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <para><paramref name="encryptedData"/> is <c>null</c>.</para>
        /// <para>-or-</para>
        /// <para><paramref name="decryptedData"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="Org.BouncyCastle.Cms.CmsException">
        /// An error occurred in the cryptographic message syntax subsystem.
        /// </exception>
        public override void DecryptTo (Stream encryptedData, Stream decryptedData)
        {
            if (encryptedData == null)
                throw new ArgumentNullException (nameof (encryptedData));

            if (decryptedData == null)
                throw new ArgumentNullException (nameof (decryptedData));

            var parser = new CmsEnvelopedDataParser (encryptedData);
            var recipients = parser.GetRecipientInfos ();
            var algorithm = parser.EncryptionAlgorithmID;
            AsymmetricKeyParameter key;

            foreach (RecipientInformation recipient in recipients.GetRecipients ()) {
                if ((key = GetPrivateKey (recipient.RecipientID)) == null)
                    continue;

                var content = recipient.GetContentStream (key);

                content.ContentStream.CopyTo (decryptedData, 4096);
                return;
            }

            throw new CmsException ("A suitable private key could not be found for decrypting.");
        }

        static string GetHashAlgorithmName (DigestAlgorithm digestAlgo)
        {
            switch (digestAlgo) {
            case DigestAlgorithm.MD5: return HashAlgorithmNames.Md5;
            case DigestAlgorithm.Sha1: return HashAlgorithmNames.Sha1;
            case DigestAlgorithm.Sha256: return HashAlgorithmNames.Sha256;
            case DigestAlgorithm.Sha384: return HashAlgorithmNames.Sha384;
            case DigestAlgorithm.Sha512: return HashAlgorithmNames.Sha512;
            default: throw new NotSupportedException ($"The {digestAlgo} digest algorithm is not supported on the Universal Windows Platform.");
            }
        }

        static Certificate GetCertificate (X509Certificate x509)
        {
            var buffer = CryptographicBuffer.CreateFromByteArray (x509.GetEncoded ());

            return new Certificate (buffer);
        }

        Stream Sign (Certificate certificate, IList<Certificate> chain, DigestAlgorithm digestAlgo, Stream content, bool detach)
        {
            var signerInfo = new CmsSignerInfo ();
            var signers = new CmsSignerInfo[1];
            byte[] signedData;
            IBuffer buffer;

            signerInfo.HashAlgorithmName = GetHashAlgorithmName (digestAlgo);
            signerInfo.Certificate = certificate;
            signers[0] = signerInfo;

            if (detach) {
                using (var input = content.AsInputStream ())
                    buffer = CmsDetachedSignature.GenerateSignatureAsync (input, signers, chain).GetResults ();
            } else {
                buffer = CryptographicBuffer.CreateFromByteArray (ReadAllBytes (content));
                buffer = CmsAttachedSignature.GenerateSignatureAsync (buffer, signers, chain).GetResults ();
            }

            CryptographicBuffer.CopyToByteArray (buffer, out signedData);

            return new MemoryStream (signedData, false);
        }

        /// <summary>
        /// Cryptographically signs and encapsulates the content using the specified signer.
        /// </summary>
        /// <remarks>
        /// Cryptographically signs and encapsulates the content using the specified signer.
        /// </remarks>
        /// <returns>A new <see cref="MimeKit.Cryptography.ApplicationPkcs7Mime"/> instance
        /// containing the detached signature data.</returns>
        /// <param name="signer">The signer.</param>
        /// <param name="content">The content.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <para><paramref name="signer"/> is <c>null</c>.</para>
        /// <para>-or-</para>
        /// <para><paramref name="content"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="System.Security.Cryptography.CryptographicException">
        /// An error occurred in the cryptographic message syntax subsystem.
        /// </exception>
        public override ApplicationPkcs7Mime EncapsulatedSign (CmsSigner signer, Stream content)
        {
            if (signer == null)
                throw new ArgumentNullException (nameof (signer));

            if (content == null)
                throw new ArgumentNullException (nameof (content));

            var certificate = GetCertificate (signer.Certificate);
            IList<Certificate> chain = null;

            // FIXME: does the chain need to include the signer's cert as well?
            if (signer.CertificateChain.Count > 1) {
                chain = new List<Certificate> (signer.CertificateChain.Count - 1);

                for (int i = 1; i < signer.CertificateChain.Count; i++)
                    chain.Add (GetCertificate (signer.CertificateChain[i]));
            }

            return new ApplicationPkcs7Mime (SecureMimeType.SignedData, Sign (certificate, chain, signer.DigestAlgorithm, content, false));
        }

        /// <summary>
        /// Sign and encapsulate the content using the specified signer.
        /// </summary>
        /// <remarks>
        /// Sign and encapsulate the content using the specified signer.
        /// </remarks>
        /// <returns>A new <see cref="MimeKit.Cryptography.ApplicationPkcs7Mime"/> instance
        /// containing the detached signature data.</returns>
        /// <param name="signer">The signer.</param>
        /// <param name="digestAlgo">The digest algorithm to use for signing.</param>
        /// <param name="content">The content.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <para><paramref name="signer"/> is <c>null</c>.</para>
        /// <para>-or-</para>
        /// <para><paramref name="content"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// <paramref name="digestAlgo"/> is out of range.
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// The specified <see cref="DigestAlgorithm"/> is not supported by this context.
        /// </exception>
        /// <exception cref="CertificateNotFoundException">
        /// A signing certificate could not be found for <paramref name="signer"/>.
        /// </exception>
        /// <exception cref="System.Security.Cryptography.CryptographicException">
        /// An error occurred in the cryptographic message syntax subsystem.
        /// </exception>
        public override ApplicationPkcs7Mime EncapsulatedSign (MailboxAddress signer, DigestAlgorithm digestAlgo, Stream content)
        {
            if (signer == null)
                throw new ArgumentNullException (nameof (signer));

            if (content == null)
                throw new ArgumentNullException (nameof (content));

            var certificate = GetSignerCertificate (signer);

            return new ApplicationPkcs7Mime (SecureMimeType.SignedData, Sign (certificate, null, digestAlgo, content, false));
        }

        public override ApplicationPkcs7Mime Encrypt (CmsRecipientCollection recipients, Stream content)
        {
            throw new NotImplementedException ();
        }

        public override MimePart Encrypt (IEnumerable<MailboxAddress> recipients, Stream content)
        {
            throw new NotImplementedException ();
        }

        public override MimePart Export (IEnumerable<MailboxAddress> mailboxes)
        {
            throw new NotImplementedException ();
        }

        public override void Import (Stream stream, string password)
        {
            throw new NotImplementedException ();
        }

        public override void Import (X509Certificate certificate)
        {
            throw new NotImplementedException ();
        }

        public override void Import (X509Crl crl)
        {
            throw new NotImplementedException ();
        }

        /// <summary>
        /// Cryptographically signs the content using the specified signer.
        /// </summary>
        /// <remarks>
        /// Cryptographically signs the content using the specified signer.
        /// </remarks>
        /// <returns>A new <see cref="MimeKit.Cryptography.ApplicationPkcs7Signature"/> instance
        /// containing the detached signature data.</returns>
        /// <param name="signer">The signer.</param>
        /// <param name="content">The content.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <para><paramref name="signer"/> is <c>null</c>.</para>
        /// <para>-or-</para>
        /// <para><paramref name="content"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="System.Security.Cryptography.CryptographicException">
        /// An error occurred in the cryptographic message syntax subsystem.
        /// </exception>
        public override ApplicationPkcs7Signature Sign (CmsSigner signer, Stream content)
        {
            if (signer == null)
                throw new ArgumentNullException (nameof (signer));

            if (content == null)
                throw new ArgumentNullException (nameof (content));

            var certificate = GetCertificate (signer.Certificate);
            IList<Certificate> chain = null;

            // FIXME: does the chain need to include the signer's cert as well?
            if (signer.CertificateChain.Count > 1) {
                chain = new List<Certificate> (signer.CertificateChain.Count - 1);

                for (int i = 1; i < signer.CertificateChain.Count; i++)
                    chain.Add (GetCertificate (signer.CertificateChain[i]));
            }

            return new ApplicationPkcs7Signature (Sign (certificate, chain, signer.DigestAlgorithm, content, true));
        }

        /// <summary>
        /// Sign the content using the specified signer.
        /// </summary>
        /// <remarks>
        /// Sign the content using the specified signer.
        /// </remarks>
        /// <returns>A new <see cref="MimeKit.MimePart"/> instance
        /// containing the detached signature data.</returns>
        /// <param name="signer">The signer.</param>
        /// <param name="digestAlgo">The digest algorithm to use for signing.</param>
        /// <param name="content">The content.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <para><paramref name="signer"/> is <c>null</c>.</para>
        /// <para>-or-</para>
        /// <para><paramref name="content"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// <paramref name="digestAlgo"/> is out of range.
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// The specified <see cref="DigestAlgorithm"/> is not supported by this context.
        /// </exception>
        /// <exception cref="CertificateNotFoundException">
        /// A signing certificate could not be found for <paramref name="signer"/>.
        /// </exception>
        /// <exception cref="System.Security.Cryptography.CryptographicException">
        /// An error occurred in the cryptographic message syntax subsystem.
        /// </exception>
        public override MimePart Sign (MailboxAddress signer, DigestAlgorithm digestAlgo, Stream content)
        {
            if (signer == null)
                throw new ArgumentNullException (nameof (signer));

            if (content == null)
                throw new ArgumentNullException (nameof (content));

            var certificate = GetSignerCertificate (signer);

            return new ApplicationPkcs7Signature (Sign (certificate, null, digestAlgo, content, true));
        }

        public override DigitalSignatureCollection Verify (Stream signedData, out MimeEntity entity, CancellationToken cancellationToken = default (CancellationToken))
        {
            throw new NotImplementedException ();
        }

        public override Stream Verify (Stream signedData, out DigitalSignatureCollection signatures, CancellationToken cancellationToken = default (CancellationToken))
        {
            throw new NotImplementedException ();
        }

        public override DigitalSignatureCollection Verify (Stream content, Stream signatureData, CancellationToken cancellationToken = default (CancellationToken))
        {
            throw new NotImplementedException ();
        }

        public override Task<DigitalSignatureCollection> VerifyAsync (Stream content, Stream signatureData, CancellationToken cancellationToken = default (CancellationToken))
        {
            throw new NotImplementedException ();
        }
    }
}
...