public void Setup () { var bytes = new byte[9 * 1024]; int position = 0; random = new Random (); random.NextBytes (bytes); // this is our master stream, all operations on the chained stream // should match the results on this stream master = new MemoryStream (bytes); mbuf = new byte[4096]; buf = new byte[4096]; // write the content into the memory block stream in random chunks blocks = new MemoryBlockStream (); Assert.IsTrue (blocks.CanRead, "Expected to be able to read from the memory block stream."); Assert.IsTrue (blocks.CanWrite, "Expected to be able to write to the memory block stream."); Assert.IsTrue (blocks.CanSeek, "Expected to be able to seek in the memory block stream."); Assert.IsFalse (blocks.CanTimeout, "Did not expect to be able to set timeouts in the memory block stream."); while (position < bytes.Length) { int n = Math.Min (bytes.Length - position, random.Next () % 4096); blocks.Write (bytes, position, n); position += n; } blocks.Seek (0, SeekOrigin.Begin); }
public ImapReplayCommand (string command, string resource) { Command = command; using (var stream = GetType ().Assembly.GetManifestResourceStream ("UnitTests.Net.Imap.Resources." + resource)) { var memory = new MemoryBlockStream (); using (var filtered = new FilteredStream (memory)) { filtered.Add (new Unix2DosFilter ()); stream.CopyTo (filtered, 4096); } Response = memory.ToArray (); } }
public void Setup() { var bytes = new byte[9 * 1024]; int position = 0; random = new Random (); random.NextBytes (bytes); // this is our master stream, all operations on the chained stream // should match the results on this stream master = new MemoryStream (bytes); mbuf = new byte[4096]; buf = new byte[4096]; // write the content into the memory block stream in random chunks blocks = new MemoryBlockStream (); while (position < bytes.Length) { int n = Math.Min (bytes.Length - position, random.Next () % 4096); blocks.Write (bytes, position, n); position += n; } blocks.Seek (0, SeekOrigin.Begin); }
/// <summary> /// Decrypts the <see cref="MultipartEncrypted"/> part. /// </summary> /// <remarks> /// Decrypts the <see cref="MultipartEncrypted"/> and extracts any digital signatures in cases /// where the content was also signed. /// </remarks> /// <returns>The decrypted entity.</returns> /// <param name="ctx">The OpenPGP cryptography context to use for decrypting.</param> /// <param name="signatures">A list of digital signatures if the data was both signed and encrypted.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="ctx"/> is <c>null</c>. /// </exception> /// <exception cref="System.FormatException"> /// <para>The <c>protocol</c> parameter was not specified.</para> /// <para>-or-</para> /// <para>The multipart is malformed in some way.</para> /// </exception> /// <exception cref="System.NotSupportedException"> /// The provided <see cref="OpenPgpContext"/> does not support the protocol parameter. /// </exception> /// <exception cref="PrivateKeyNotFoundException"> /// The private key could not be found to decrypt the encrypted data. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The user chose to cancel the password prompt. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// 3 bad attempts were made to unlock the secret key. /// </exception> public MimeEntity Decrypt (OpenPgpContext ctx, out DigitalSignatureCollection signatures) { if (ctx == null) throw new ArgumentNullException (nameof (ctx)); var protocol = ContentType.Parameters["protocol"]; if (string.IsNullOrEmpty (protocol)) throw new FormatException (); protocol = protocol.Trim ().ToLowerInvariant (); if (!ctx.Supports (protocol)) throw new NotSupportedException (); if (Count < 2) throw new FormatException (); var version = this[0] as MimePart; if (version == null) throw new FormatException (); var ctype = version.ContentType; var value = string.Format ("{0}/{1}", ctype.MediaType, ctype.MediaSubtype); if (value.ToLowerInvariant () != protocol) throw new FormatException (); var encrypted = this[1] as MimePart; if (encrypted == null || encrypted.ContentObject == null) throw new FormatException (); if (!encrypted.ContentType.IsMimeType ("application", "octet-stream")) throw new FormatException (); using (var memory = new MemoryBlockStream ()) { encrypted.ContentObject.DecodeTo (memory); memory.Position = 0; return ctx.Decrypt (memory, out signatures); } }
static MimePart GetMimePart (AttachmentBase item) { var mimeType = item.ContentType.ToString (); var part = new MimePart (ContentType.Parse (mimeType)); var attachment = item as Attachment; if (attachment != null) { var disposition = attachment.ContentDisposition.ToString (); part.ContentDisposition = ContentDisposition.Parse (disposition); } switch (item.TransferEncoding) { case System.Net.Mime.TransferEncoding.QuotedPrintable: part.ContentTransferEncoding = ContentEncoding.QuotedPrintable; break; case System.Net.Mime.TransferEncoding.Base64: part.ContentTransferEncoding = ContentEncoding.Base64; break; case System.Net.Mime.TransferEncoding.SevenBit: part.ContentTransferEncoding = ContentEncoding.SevenBit; break; } if (item.ContentId != null) part.ContentId = item.ContentId; var stream = new MemoryBlockStream (); item.ContentStream.CopyTo (stream); stream.Position = 0; part.ContentObject = new ContentObject (stream); return part; }
/// <summary> /// Verifies the multipart/signed part. /// </summary> /// <remarks> /// Verifies the multipart/signed part using the supplied cryptography context. /// </remarks> /// <returns>A signer info collection.</returns> /// <param name="ctx">The cryptography context to use for verifying the signature.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="ctx"/> is <c>null</c>. /// </exception> /// <exception cref="System.FormatException"> /// The multipart is malformed in some way. /// </exception> /// <exception cref="System.NotSupportedException"> /// <paramref name="ctx"/> does not support verifying the signature part. /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public DigitalSignatureCollection Verify (CryptographyContext ctx) { if (ctx == null) throw new ArgumentNullException ("ctx"); var protocol = ContentType.Parameters["protocol"]; if (string.IsNullOrEmpty (protocol)) throw new FormatException ("The multipart/signed part did not specify a protocol."); if (!ctx.Supports (protocol.Trim ())) throw new NotSupportedException ("The specified cryptography context does not support the signature protocol."); if (Count < 2) throw new FormatException ("The multipart/signed part did not contain the expected children."); var signature = this[1] as MimePart; if (signature == null || signature.ContentObject == null) throw new FormatException ("The signature part could not be found."); var ctype = signature.ContentType; var value = string.Format ("{0}/{1}", ctype.MediaType, ctype.MediaSubtype); if (!ctx.Supports (value)) throw new NotSupportedException (string.Format ("The specified cryptography context does not support '{0}'.", value)); using (var signatureData = new MemoryBlockStream ()) { signature.ContentObject.DecodeTo (signatureData); signatureData.Position = 0; using (var cleartext = new MemoryBlockStream ()) { // Note: see rfc2015 or rfc3156, section 5.1 var options = FormatOptions.Default.Clone (); options.NewLineFormat = NewLineFormat.Dos; this[0].WriteTo (options, cleartext); cleartext.Position = 0; return ctx.Verify (cleartext, signatureData); } } }
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 Stream Encrypt (PgpEncryptedDataGenerator encrypter, Stream content) { var memory = new MemoryBlockStream (); using (var armored = new ArmoredOutputStream (memory)) { using (var compressed = Compress (content)) { using (var encrypted = encrypter.Open (armored, compressed.Length)) { compressed.CopyTo (encrypted, 4096); encrypted.Flush (); } } armored.Flush (); } memory.Position = 0; return memory; }
/// <summary> /// Cryptographically signs the content. /// </summary> /// <remarks> /// Cryptographically signs the content using the specified signer and digest algorithm. /// </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.ArgumentException"> /// <paramref name="signer"/> cannot be used for signing. /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// The <paramref name="digestAlgo"/> was out of range. /// </exception> /// <exception cref="System.NotSupportedException"> /// The <paramref name="digestAlgo"/> is not supported. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The user chose to cancel the password prompt. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// 3 bad attempts were made to unlock the secret key. /// </exception> public ApplicationPgpSignature Sign (PgpSecretKey signer, DigestAlgorithm digestAlgo, Stream content) { if (signer == null) throw new ArgumentNullException ("signer"); if (!signer.IsSigningKey) throw new ArgumentException ("The specified secret key cannot be used for signing.", "signer"); if (content == null) throw new ArgumentNullException ("content"); var hashAlgorithm = GetHashAlgorithm (digestAlgo); var memory = new MemoryBlockStream (); using (var armored = new ArmoredOutputStream (memory)) { var compresser = new PgpCompressedDataGenerator (CompressionAlgorithmTag.ZLib); using (var compressed = compresser.Open (armored)) { var signatureGenerator = new PgpSignatureGenerator (signer.PublicKey.Algorithm, hashAlgorithm); var buf = new byte[4096]; int nread; signatureGenerator.InitSign (PgpSignature.CanonicalTextDocument, GetPrivateKey (signer)); while ((nread = content.Read (buf, 0, buf.Length)) > 0) signatureGenerator.Update (buf, 0, nread); var signature = signatureGenerator.Generate (); signature.Encode (compressed); compressed.Flush (); } armored.Flush (); } memory.Position = 0; return new ApplicationPgpSignature (memory); }
void OnGroupsChanged (object sender, EventArgs e) { var stream = new MemoryBlockStream (); var options = FormatOptions.Default; for (int i = 0; i < groups.Count; i++) groups[i].WriteTo (options, stream); stream.Position = 0; ContentObject = new ContentObject (stream); }
/// <summary> /// Cryptographically signs the specified entity. /// </summary> /// <remarks> /// <para>Signs the entity using the supplied signer and <see cref="SecureMimeContext"/>.</para> /// <para>For better interoperability with other mail clients, you should use /// <see cref="MultipartSigned.Create(SecureMimeContext, CmsSigner, MimeEntity)"/> /// instead as the multipart/signed format is supported among a much larger /// subset of mail client software.</para> /// </remarks> /// <returns>The signed entity.</returns> /// <param name="ctx">The S/MIME context to use for signing.</param> /// <param name="signer">The signer.</param> /// <param name="entity">The entity.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="ctx"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="signer"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="entity"/> is <c>null</c>.</para> /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public static ApplicationPkcs7Mime Sign (SecureMimeContext ctx, CmsSigner signer, MimeEntity entity) { if (ctx == null) throw new ArgumentNullException (nameof (ctx)); if (signer == null) throw new ArgumentNullException (nameof (signer)); if (entity == null) throw new ArgumentNullException (nameof (entity)); using (var memory = new MemoryBlockStream ()) { var options = FormatOptions.CloneDefault (); options.NewLineFormat = NewLineFormat.Dos; entity.WriteTo (options, memory); memory.Position = 0; return ctx.EncapsulatedSign (signer, memory); } }
/// <summary> /// Encrypts the specified entity. /// </summary> /// <remarks> /// Encrypts the entity to the specified recipients using the supplied <see cref="SecureMimeContext"/>. /// </remarks> /// <returns>The encrypted entity.</returns> /// <param name="ctx">The S/MIME context to use for encrypting.</param> /// <param name="recipients">The recipients.</param> /// <param name="entity">The entity.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="ctx"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="recipients"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="entity"/> is <c>null</c>.</para> /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public static ApplicationPkcs7Mime Encrypt (SecureMimeContext ctx, CmsRecipientCollection recipients, MimeEntity entity) { if (ctx == null) throw new ArgumentNullException (nameof (ctx)); if (recipients == null) throw new ArgumentNullException (nameof (recipients)); if (entity == null) throw new ArgumentNullException (nameof (entity)); using (var memory = new MemoryBlockStream ()) { var options = FormatOptions.CloneDefault (); options.NewLineFormat = NewLineFormat.Dos; entity.WriteTo (options, memory); memory.Position = 0; return ctx.Encrypt (recipients, memory); } }
static void FetchMessageBody(ImapEngine engine, ImapCommand ic, int index) { var streams = (Dictionary<string, Stream>) ic.UserData; var token = engine.ReadToken (ic.CancellationToken); var labels = new MessageLabelsChangedEventArgs (index); var flags = new MessageFlagsChangedEventArgs (index); bool labelsChanged = false; bool flagsChanged = false; var buf = new byte[4096]; string specifier; Stream stream; int nread; if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (token, false); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen || token.Type == ImapTokenType.Eoln) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (token, false); var atom = (string) token.Value; ulong modseq; uint uid; switch (atom) { case "BODY": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenBracket) throw ImapEngine.UnexpectedToken (token, false); specifier = string.Empty; do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseBracket) break; if (token.Type == ImapTokenType.OpenParen) { do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (token, false); } while (true); } else if (token.Type != ImapTokenType.Atom) { throw ImapEngine.UnexpectedToken (token, false); } else { specifier += (string) token.Value; } } while (true); if (token.Type != ImapTokenType.CloseBracket) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.Atom) { var region = (string) token.Value; if (region[0] != '<' || region[region.Length - 1] != '>') throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); } switch (token.Type) { case ImapTokenType.Literal: stream = new MemoryBlockStream (); while ((nread = engine.Stream.Read (buf, 0, buf.Length, ic.CancellationToken)) > 0) stream.Write (buf, 0, nread); streams[specifier] = stream; stream.Position = 0; break; case ImapTokenType.QString: case ImapTokenType.Atom: stream = new MemoryStream (Encoding.UTF8.GetBytes ((string) token.Value), false); break; default: throw ImapEngine.UnexpectedToken (token, false); } break; case "UID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out uid) || uid == 0) throw ImapEngine.UnexpectedToken (token, false); labels.UniqueId = new UniqueId (uid); flags.UniqueId = new UniqueId (uid); break; case "MODSEQ": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out modseq) || modseq == 0) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); labels.ModSeq = modseq; flags.ModSeq = modseq; break; case "FLAGS": // even though we didn't request this piece of information, the IMAP server // may send it if another client has recently modified the message flags. flags.Flags = ImapUtils.ParseFlagsList (engine, flags.UserFlags, ic.CancellationToken); flagsChanged = true; break; case "X-GM-LABELS": // even though we didn't request this piece of information, the IMAP server // may send it if another client has recently modified the message labels. labels.Labels = ImapUtils.ParseLabelsList (engine, ic.CancellationToken); labelsChanged = true; break; default: throw ImapEngine.UnexpectedToken (token, false); } } while (true); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); if (flagsChanged) ic.Folder.OnMessageFlagsChanged (flags); if (labelsChanged) ic.Folder.OnMessageLabelsChanged (labels); }
/// <summary> /// Create a multipart/encrypted MIME part by signing and encrypting the specified entity. /// </summary> /// <remarks> /// Signs the entity using the supplied signer and digest algorithm and then encrypts to /// the specified recipients, encapsulating the result in a new multipart/encrypted part. /// </remarks> /// <returns>A new <see cref="MultipartEncrypted"/> instance containing /// the signed and encrypted version of the specified entity.</returns> /// <param name="ctx">The OpenPGP cryptography context to use for singing and encrypting.</param> /// <param name="signer">The signer to use to sign the entity.</param> /// <param name="digestAlgo">The digest algorithm to use for signing.</param> /// <param name="recipients">The recipients for the encrypted entity.</param> /// <param name="entity">The entity to sign and encrypt.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="ctx"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="signer"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="recipients"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="entity"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para><paramref name="signer"/> cannot be used for signing.</para> /// <para>-or-</para> /// <para>One or more of the recipient keys cannot be used for encrypting.</para> /// <para>-or-</para> /// <para>No recipients were specified.</para> /// </exception> /// <exception cref="System.ArgumentOutOfRangeException"> /// The <paramref name="digestAlgo"/> was out of range. /// </exception> /// <exception cref="System.NotSupportedException"> /// The <paramref name="digestAlgo"/> is not supported. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The user chose to cancel the password prompt. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// 3 bad attempts were made to unlock the secret key. /// </exception> public static MultipartEncrypted SignAndEncrypt (OpenPgpContext ctx, PgpSecretKey signer, DigestAlgorithm digestAlgo, IEnumerable<PgpPublicKey> recipients, MimeEntity entity) { if (ctx == null) throw new ArgumentNullException (nameof (ctx)); if (signer == null) throw new ArgumentNullException (nameof (signer)); if (recipients == null) throw new ArgumentNullException (nameof (recipients)); if (entity == null) throw new ArgumentNullException (nameof (entity)); using (var memory = new MemoryBlockStream ()) { var options = FormatOptions.CloneDefault (); options.NewLineFormat = NewLineFormat.Dos; entity.WriteTo (options, memory); memory.Position = 0; var encrypted = new MultipartEncrypted (); encrypted.ContentType.Parameters["protocol"] = ctx.EncryptionProtocol; // add the protocol version part encrypted.Add (new ApplicationPgpEncrypted ()); // add the encrypted entity as the second part encrypted.Add (ctx.SignAndEncrypt (signer, digestAlgo, recipients, memory)); return encrypted; } }
/// <summary> /// Decrypts the <see cref="MultipartEncrypted"/> part. /// </summary> /// <remarks> /// Decrypts the <see cref="MultipartEncrypted"/> and extracts any digital signatures in cases /// where the content was also signed. /// </remarks> /// <returns>The decrypted entity.</returns> /// <param name="signatures">A list of digital signatures if the data was both signed and encrypted.</param> /// <exception cref="System.FormatException"> /// <para>The <c>protocol</c> parameter was not specified.</para> /// <para>-or-</para> /// <para>The multipart is malformed in some way.</para> /// </exception> /// <exception cref="System.NotSupportedException"> /// A suitable <see cref="MimeKit.Cryptography.CryptographyContext"/> for /// decrypting could not be found. /// </exception> /// <exception cref="PrivateKeyNotFoundException"> /// The private key could not be found to decrypt the encrypted data. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The user chose to cancel the password prompt. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// 3 bad attempts were made to unlock the secret key. /// </exception> public MimeEntity Decrypt (out DigitalSignatureCollection signatures) { var protocol = ContentType.Parameters["protocol"]; if (string.IsNullOrEmpty (protocol)) throw new FormatException (); protocol = protocol.Trim ().ToLowerInvariant (); if (Count < 2) throw new FormatException (); var version = this[0] as MimePart; if (version == null) throw new FormatException (); var ctype = version.ContentType; var value = string.Format ("{0}/{1}", ctype.MediaType, ctype.MediaSubtype); if (value.ToLowerInvariant () != protocol) throw new FormatException (); var encrypted = this[1] as MimePart; if (encrypted == null || encrypted.ContentObject == null) throw new FormatException (); if (!encrypted.ContentType.IsMimeType ("application", "octet-stream")) throw new FormatException (); using (var ctx = CryptographyContext.Create (protocol)) { using (var memory = new MemoryBlockStream ()) { var pgp = ctx as OpenPgpContext; encrypted.ContentObject.DecodeTo (memory); memory.Position = 0; if (pgp != null) return pgp.Decrypt (memory, out signatures); signatures = null; return ctx.Decrypt (memory); } } }
/// <summary> /// Decrypts the specified encryptedData and extracts the digital signers if the content was also signed. /// </summary> /// <remarks> /// Decrypts the specified encryptedData and extracts the digital signers if the content was also signed. /// </remarks> /// <returns>The decrypted stream.</returns> /// <param name="encryptedData">The encrypted data.</param> /// <param name="signatures">A list of digital signatures if the data was both signed and encrypted.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="encryptedData"/> is <c>null</c>. /// </exception> /// <exception cref="PrivateKeyNotFoundException"> /// The private key could not be found to decrypt the stream. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The user chose to cancel the password prompt. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// 3 bad attempts were made to unlock the secret key. /// </exception> /// <exception cref="Org.BouncyCastle.Bcpg.OpenPgp.PgpException"> /// An OpenPGP error occurred. /// </exception> public Stream GetDecryptedStream (Stream encryptedData, out DigitalSignatureCollection signatures) { if (encryptedData == null) throw new ArgumentNullException ("encryptedData"); using (var armored = new ArmoredInputStream (encryptedData)) { var factory = new PgpObjectFactory (armored); var obj = factory.NextPgpObject (); var list = obj as PgpEncryptedDataList; if (list == null) { // probably a PgpMarker... obj = factory.NextPgpObject (); list = obj as PgpEncryptedDataList; if (list == null) throw new PgpException ("Unexpected OpenPGP packet."); } PgpPublicKeyEncryptedData encrypted = null; PrivateKeyNotFoundException pkex = null; bool hasEncryptedPackets = false; PgpSecretKey secret = null; foreach (PgpEncryptedData data in list.GetEncryptedDataObjects ()) { if ((encrypted = data as PgpPublicKeyEncryptedData) == null) continue; hasEncryptedPackets = true; try { secret = GetSecretKey (encrypted.KeyId); break; } catch (PrivateKeyNotFoundException ex) { pkex = ex; } } if (!hasEncryptedPackets) throw new PgpException ("No encrypted packets found."); if (secret == null) throw pkex; factory = new PgpObjectFactory (encrypted.GetDataStream (GetPrivateKey (secret))); List<IDigitalSignature> onepassList = null; PgpSignatureList signatureList = null; PgpCompressedData compressed = null; var memory = new MemoryBlockStream (); obj = factory.NextPgpObject (); while (obj != null) { if (obj is PgpCompressedData) { if (compressed != null) throw new PgpException ("Recursive compression packets are not supported."); compressed = (PgpCompressedData) obj; factory = new PgpObjectFactory (compressed.GetDataStream ()); } else if (obj is PgpOnePassSignatureList) { if (memory.Length == 0) { var onepasses = (PgpOnePassSignatureList) obj; onepassList = new List<IDigitalSignature> (); for (int i = 0; i < onepasses.Count; i++) { var onepass = onepasses[i]; var pubkey = PublicKeyRingBundle.GetPublicKey (onepass.KeyId); if (pubkey == null) { // too messy, pretend we never found a one-pass signature list onepassList = null; break; } onepass.InitVerify (pubkey); var signature = new OpenPgpDigitalSignature (pubkey, onepass) { PublicKeyAlgorithm = GetPublicKeyAlgorithm (onepass.KeyAlgorithm), DigestAlgorithm = GetDigestAlgorithm (onepass.HashAlgorithm), }; onepassList.Add (signature); } } } else if (obj is PgpSignatureList) { signatureList = (PgpSignatureList) obj; } else if (obj is PgpLiteralData) { var literal = (PgpLiteralData) obj; using (var stream = literal.GetDataStream ()) { var buffer = new byte[4096]; int nread; while ((nread = stream.Read (buffer, 0, buffer.Length)) > 0) { if (onepassList != null) { // update our one-pass signatures... for (int index = 0; index < nread; index++) { byte c = buffer[index]; for (int i = 0; i < onepassList.Count; i++) { var pgp = (OpenPgpDigitalSignature) onepassList[i]; pgp.OnePassSignature.Update (c); } } } memory.Write (buffer, 0, nread); } } } obj = factory.NextPgpObject (); } memory.Position = 0; if (signatureList != null) { if (onepassList != null && signatureList.Count == onepassList.Count) { for (int i = 0; i < onepassList.Count; i++) { var pgp = (OpenPgpDigitalSignature) onepassList[i]; pgp.CreationDate = signatureList[i].CreationTime; pgp.Signature = signatureList[i]; } signatures = new DigitalSignatureCollection (onepassList); } else { signatures = GetDigitalSignatures (signatureList, memory); memory.Position = 0; } } else { signatures = null; } return memory; } }
/// <summary> /// Exports the specified public keys. /// </summary> /// <remarks> /// Exports the specified public keys. /// </remarks> /// <returns>A new <see cref="MimeKit.MimePart"/> instance containing the exported public keys.</returns> /// <param name="keys">The keys.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="keys"/> is <c>null</c>. /// </exception> public MimePart Export (PgpPublicKeyRingBundle keys) { if (keys == null) throw new ArgumentNullException ("keys"); var content = new MemoryBlockStream (); using (var armored = new ArmoredOutputStream (content)) { keys.Encode (armored); armored.Flush (); } content.Position = 0; return new MimePart ("application", "pgp-keys") { ContentDisposition = new ContentDisposition ("attachment"), ContentObject = new ContentObject (content) }; }
/// <summary> /// Decrypts the content. /// </summary> /// <remarks> /// Decrypts the content using the specified <see cref="SecureMimeContext"/>. /// </remarks> /// <returns>The decrypted <see cref="MimeKit.MimeEntity"/>.</returns> /// <param name="ctx">The S/MIME context to use for decrypting.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="ctx"/> is <c>null</c>. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The "smime-type" parameter on the Content-Type header is not "enveloped-data". /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public MimeEntity Decrypt (SecureMimeContext ctx) { if (ctx == null) throw new ArgumentNullException ("ctx"); if (SecureMimeType != SecureMimeType.EnvelopedData) throw new InvalidOperationException (); using (var memory = new MemoryBlockStream ()) { ContentObject.DecodeTo (memory); memory.Position = 0; return ctx.Decrypt (memory); } }
static Stream Compress (Stream content) { var compresser = new PgpCompressedDataGenerator (CompressionAlgorithmTag.ZLib); var memory = new MemoryBlockStream (); using (var compressed = compresser.Open (memory)) { var literalGenerator = new PgpLiteralDataGenerator (); using (var literal = literalGenerator.Open (compressed, 't', "mime.txt", content.Length, DateTime.Now)) { content.CopyTo (literal, 4096); literal.Flush (); } compressed.Flush (); } memory.Position = 0; return memory; }
/// <summary> /// Imports the certificates contained in the content. /// </summary> /// <remarks> /// Imports the certificates contained in the content. /// </remarks> /// <param name="ctx">The S/MIME context to import certificates into.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="ctx"/> is <c>null</c>. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The "smime-type" parameter on the Content-Type header is not "certs-only". /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public void Import (SecureMimeContext ctx) { if (ctx == null) throw new ArgumentNullException ("ctx"); if (SecureMimeType != SecureMimeType.CertsOnly) throw new InvalidOperationException (); using (var memory = new MemoryBlockStream ()) { ContentObject.DecodeTo (memory); memory.Position = 0; ctx.Import (memory); } }
static void FetchMessageBody(ImapEngine engine, ImapCommand ic, int index, ImapToken tok) { var token = engine.ReadToken (ic.CancellationToken); var args = new MessageFlagsChangedEventArgs (index); var type = FetchReturnType.MimeMessage; bool emit = false; if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (token, false); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen || token.Type == ImapTokenType.Eoln) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (token, false); var atom = (string) token.Value; ulong modseq; uint uid; switch (atom) { case "BODY": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenBracket) throw ImapEngine.UnexpectedToken (token, false); do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseBracket) break; if (token.Type == ImapTokenType.OpenParen) { type = FetchReturnType.Stream; do { token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.CloseParen) break; if (token.Type != ImapTokenType.Atom) throw ImapEngine.UnexpectedToken (token, false); } while (true); } else if (token.Type != ImapTokenType.Atom) { throw ImapEngine.UnexpectedToken (token, false); } else { type = FetchReturnType.MimeEntity; } } while (true); if (token.Type != ImapTokenType.CloseBracket) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type == ImapTokenType.Atom) { var region = (string) token.Value; if (region[0] != '<' || region[region.Length - 1] != '>') throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); type = FetchReturnType.Stream; } if (token.Type != ImapTokenType.Literal) throw ImapEngine.UnexpectedToken (token, false); switch (type) { case FetchReturnType.MimeMessage: ic.UserData = MimeMessage.Load (engine.Stream, ic.CancellationToken); break; case FetchReturnType.MimeEntity: ic.UserData = MimeEntity.Load (engine.Stream, ic.CancellationToken); break; default: var stream = new MemoryBlockStream (); var buf = new byte[4096]; int nread; ic.CancellationToken.ThrowIfCancellationRequested (); while ((nread = engine.Stream.Read (buf, 0, buf.Length)) > 0) { ic.CancellationToken.ThrowIfCancellationRequested (); stream.Write (buf, 0, nread); } ic.UserData = stream; break; } break; case "UID": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !uint.TryParse ((string) token.Value, out uid) || uid == 0) throw ImapEngine.UnexpectedToken (token, false); args.Uid = new UniqueId (uid); break; case "MODSEQ": token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.OpenParen) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.Atom || !ulong.TryParse ((string) token.Value, out modseq) || modseq == 0) throw ImapEngine.UnexpectedToken (token, false); token = engine.ReadToken (ic.CancellationToken); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); args.ModSeq = modseq; break; case "FLAGS": // even though we didn't request this piece of information, the IMAP server // may send it if another client has recently modified the message flags. args.Flags = ImapUtils.ParseFlagsList (engine, ic.CancellationToken); emit = true; break; default: throw ImapEngine.UnexpectedToken (token, false); } } while (true); if (token.Type != ImapTokenType.CloseParen) throw ImapEngine.UnexpectedToken (token, false); if (emit) ic.Folder.OnFlagsChanged (args); }
/// <summary> /// Verifies the signed-data and returns the unencapsulated <see cref="MimeKit.MimeEntity"/>. /// </summary> /// <remarks> /// Verifies the signed-data and returns the unencapsulated <see cref="MimeKit.MimeEntity"/>. /// </remarks> /// <returns>The list of digital signatures.</returns> /// <param name="ctx">The S/MIME context to use for verifying the signature.</param> /// <param name="entity">The unencapsulated entity.</param> /// <exception cref="System.ArgumentNullException"> /// <paramref name="ctx"/> is <c>null</c>. /// </exception> /// <exception cref="System.InvalidOperationException"> /// The "smime-type" parameter on the Content-Type header is not "signed-data". /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public DigitalSignatureCollection Verify (SecureMimeContext ctx, out MimeEntity entity) { if (ctx == null) throw new ArgumentNullException ("ctx"); if (SecureMimeType != SecureMimeType.SignedData) throw new InvalidOperationException (); using (var memory = new MemoryBlockStream ()) { ContentObject.DecodeTo (memory); memory.Position = 0; return ctx.Verify (memory, out entity); } }
/// <summary> /// Compresses the specified entity. /// </summary> /// <remarks> /// <para>Compresses the specified entity using the specified <see cref="SecureMimeContext"/>.</para> /// <para>It should be noted that this feature is not supported by most mail clients, /// even among those that support S/MIME.</para> /// </remarks> /// <returns>The compressed entity.</returns> /// <param name="ctx">The S/MIME context to use for compressing.</param> /// <param name="entity">The entity.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="ctx"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="entity"/> is <c>null</c>.</para> /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public static ApplicationPkcs7Mime Compress (SecureMimeContext ctx, MimeEntity entity) { if (ctx == null) throw new ArgumentNullException ("ctx"); if (entity == null) throw new ArgumentNullException ("entity"); using (var memory = new MemoryBlockStream ()) { var options = FormatOptions.CloneDefault (); options.NewLineFormat = NewLineFormat.Dos; entity.WriteTo (options, memory); memory.Position = 0; return ctx.Compress (memory); } }
/// <summary> /// Encrypts the specified entity. /// </summary> /// <remarks> /// Encrypts the entity to the specified recipients using the supplied <see cref="SecureMimeContext"/>. /// </remarks> /// <returns>The encrypted entity.</returns> /// <param name="ctx">The S/MIME context to use for encrypting.</param> /// <param name="recipients">The recipients.</param> /// <param name="entity">The entity.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="ctx"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="recipients"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="entity"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// Valid certificates could not be found for one or more of the <paramref name="recipients"/>. /// </exception> /// <exception cref="CertificateNotFoundException"> /// A certificate could not be found for one or more of the <paramref name="recipients"/>. /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public static ApplicationPkcs7Mime Encrypt (SecureMimeContext ctx, IEnumerable<MailboxAddress> recipients, MimeEntity entity) { if (ctx == null) throw new ArgumentNullException ("ctx"); if (recipients == null) throw new ArgumentNullException ("recipients"); if (entity == null) throw new ArgumentNullException ("entity"); using (var memory = new MemoryBlockStream ()) { var options = FormatOptions.CloneDefault (); options.NewLineFormat = NewLineFormat.Dos; entity.WriteTo (options, memory); memory.Position = 0; return (ApplicationPkcs7Mime) ctx.Encrypt (recipients, memory); } }
/// <summary> /// Creates a new <see cref="MultipartSigned"/>. /// </summary> /// <remarks> /// Cryptographically signs the entity using the supplied signer in order /// to generate a detached signature and then adds the entity along with /// the detached signature data to a new multipart/signed part. /// </remarks> /// <returns>A new <see cref="MultipartSigned"/> instance.</returns> /// <param name="ctx">The S/MIME context to use for signing.</param> /// <param name="signer">The signer.</param> /// <param name="entity">The entity to sign.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="ctx"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="signer"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="entity"/> is <c>null</c>.</para> /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public static MultipartSigned Create (SecureMimeContext ctx, CmsSigner signer, MimeEntity entity) { if (ctx == null) throw new ArgumentNullException ("ctx"); if (signer == null) throw new ArgumentNullException ("signer"); if (entity == null) throw new ArgumentNullException ("entity"); PrepareEntityForSigning (entity); using (var memory = new MemoryBlockStream ()) { using (var filtered = new FilteredStream (memory)) { // Note: see rfc3156, section 3 - second note filtered.Add (new ArmoredFromFilter ()); // Note: see rfc3156, section 5.4 (this is the main difference between rfc2015 and rfc3156) filtered.Add (new TrailingWhitespaceFilter ()); // Note: see rfc2015 or rfc3156, section 5.1 filtered.Add (new Unix2DosFilter ()); entity.WriteTo (filtered); filtered.Flush (); } memory.Position = 0; // Note: we need to parse the modified entity structure to preserve any modifications var parser = new MimeParser (memory, MimeFormat.Entity); var parsed = parser.ParseEntity (); memory.Position = 0; // sign the cleartext content var micalg = ctx.GetDigestAlgorithmName (signer.DigestAlgorithm); var signature = ctx.Sign (signer, memory); var signed = new MultipartSigned (); // set the protocol and micalg Content-Type parameters signed.ContentType.Parameters["protocol"] = ctx.SignatureProtocol; signed.ContentType.Parameters["micalg"] = micalg; // add the modified/parsed entity as our first part signed.Add (parsed); // add the detached signature as the second part signed.Add (signature); return signed; } }
/// <summary> /// Cryptographically signs the specified entity. /// </summary> /// <remarks> /// <para>Signs the entity using the supplied signer, digest algorithm and <see cref="SecureMimeContext"/>.</para> /// <para>For better interoperability with other mail clients, you should use /// <see cref="MultipartSigned.Create(SecureMimeContext, CmsSigner, MimeEntity)"/> /// instead as the multipart/signed format is supported among a much larger /// subset of mail client software.</para> /// </remarks> /// <returns>The signed entity.</returns> /// <param name="ctx">The S/MIME context to use for signing.</param> /// <param name="signer">The signer.</param> /// <param name="digestAlgo">The digest algorithm to use for signing.</param> /// <param name="entity">The entity.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="ctx"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="signer"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="entity"/> is <c>null</c>.</para> /// </exception> /// <exception cref="CertificateNotFoundException"> /// A signing certificate could not be found for <paramref name="signer"/>. /// </exception> /// <exception cref="Org.BouncyCastle.Cms.CmsException"> /// An error occurred in the cryptographic message syntax subsystem. /// </exception> public static ApplicationPkcs7Mime Sign (SecureMimeContext ctx, MailboxAddress signer, DigestAlgorithm digestAlgo, MimeEntity entity) { if (ctx == null) throw new ArgumentNullException ("ctx"); if (signer == null) throw new ArgumentNullException ("signer"); if (entity == null) throw new ArgumentNullException ("entity"); using (var memory = new MemoryBlockStream ()) { var options = FormatOptions.CloneDefault (); options.NewLineFormat = NewLineFormat.Dos; entity.WriteTo (options, memory); memory.Position = 0; return ctx.EncapsulatedSign (signer, digestAlgo, memory); } }
Stream GetResourceStream (string name) { using (var response = GetType ().Assembly.GetManifestResourceStream ("UnitTests.Net.Pop3.Resources." + name)) { var memory = new MemoryBlockStream (); using (var filtered = new FilteredStream (memory)) { if (testUnixFormat) filtered.Add (new Dos2UnixFilter ()); else filtered.Add (new Unix2DosFilter ()); response.CopyTo (filtered, 4096); } memory.Position = 0; return memory; } }
/// <summary> /// Cryptographically signs and encrypts the specified content for the specified recipients. /// </summary> /// <remarks> /// Cryptographically signs and encrypts the specified content for the specified recipients. /// </remarks> /// <returns>A new <see cref="MimeKit.MimePart"/> instance /// containing the encrypted data.</returns> /// <param name="signer">The signer.</param> /// <param name="digestAlgo">The digest algorithm to use for signing.</param> /// <param name="cipherAlgo">The encryption algorithm.</param> /// <param name="recipients">The recipients.</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="recipients"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="content"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// <para><paramref name="signer"/> cannot be used for signing.</para> /// <para>-or-</para> /// <para>One or more of the recipient keys cannot be used for encrypting.</para> /// <para>-or-</para> /// <para>No recipients were specified.</para> /// </exception> /// <exception cref="System.NotSupportedException"> /// The specified encryption algorithm is not supported. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The user chose to cancel the password prompt. /// </exception> /// <exception cref="System.UnauthorizedAccessException"> /// 3 bad attempts were made to unlock the secret key. /// </exception> public MimePart SignAndEncrypt (PgpSecretKey signer, DigestAlgorithm digestAlgo, EncryptionAlgorithm cipherAlgo, IEnumerable<PgpPublicKey> recipients, Stream content) { // TODO: document the exceptions that can be thrown by BouncyCastle if (signer == null) throw new ArgumentNullException ("signer"); if (!signer.IsSigningKey) throw new ArgumentException ("The specified secret key cannot be used for signing.", "signer"); if (recipients == null) throw new ArgumentNullException ("recipients"); if (content == null) throw new ArgumentNullException ("content"); var encrypter = new PgpEncryptedDataGenerator (GetSymmetricKeyAlgorithm (cipherAlgo), true); var hashAlgorithm = GetHashAlgorithm (digestAlgo); int count = 0; foreach (var recipient in recipients) { if (!recipient.IsEncryptionKey) throw new ArgumentException ("One or more of the recipient keys cannot be used for encrypting.", "recipients"); encrypter.AddMethod (recipient); count++; } if (count == 0) throw new ArgumentException ("No recipients specified.", "recipients"); var compresser = new PgpCompressedDataGenerator (CompressionAlgorithmTag.ZLib); using (var compressed = new MemoryBlockStream ()) { using (var signed = compresser.Open (compressed)) { var signatureGenerator = new PgpSignatureGenerator (signer.PublicKey.Algorithm, hashAlgorithm); signatureGenerator.InitSign (PgpSignature.CanonicalTextDocument, GetPrivateKey (signer)); var subpacket = new PgpSignatureSubpacketGenerator (); foreach (string userId in signer.PublicKey.GetUserIds ()) { subpacket.SetSignerUserId (false, userId); break; } signatureGenerator.SetHashedSubpackets (subpacket.Generate ()); var onepass = signatureGenerator.GenerateOnePassVersion (false); onepass.Encode (signed); var literalGenerator = new PgpLiteralDataGenerator (); using (var literal = literalGenerator.Open (signed, 't', "mime.txt", content.Length, DateTime.Now)) { var buf = new byte[4096]; int nread; while ((nread = content.Read (buf, 0, buf.Length)) > 0) { signatureGenerator.Update (buf, 0, nread); literal.Write (buf, 0, nread); } literal.Flush (); } var signature = signatureGenerator.Generate (); signature.Encode (signed); signed.Flush (); } compressed.Position = 0; var memory = new MemoryBlockStream (); using (var armored = new ArmoredOutputStream (memory)) { using (var encrypted = encrypter.Open (armored, compressed.Length)) { compressed.CopyTo (encrypted, 4096); encrypted.Flush (); } armored.Flush (); } memory.Position = 0; return new MimePart ("application", "octet-stream") { ContentDisposition = new ContentDisposition ("attachment"), ContentObject = new ContentObject (memory) }; } }
void QueueCommand(SmtpCommand type, string command) { if (queue == null) queue = new MemoryBlockStream (); var bytes = Encoding.UTF8.GetBytes (command + "\r\n"); queue.Write (bytes, 0, bytes.Length); queued.Add (type); }
/// <summary> /// Create a multipart/encrypted MIME part by encrypting the specified entity. /// </summary> /// <remarks> /// Encrypts the entity to the specified recipients, encapsulating the result in a /// new multipart/encrypted part. /// </remarks> /// <returns>A new <see cref="MultipartEncrypted"/> instance containing /// the encrypted version of the specified entity.</returns> /// <param name="ctx">The OpenPGP cryptography context to use for encrypting.</param> /// <param name="recipients">The recipients for the encrypted entity.</param> /// <param name="entity">The entity to sign and encrypt.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="ctx"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="recipients"/> is <c>null</c>.</para> /// <para>-or-</para> /// <para><paramref name="entity"/> is <c>null</c>.</para> /// </exception> /// <exception cref="System.ArgumentException"> /// One or more of the recipient keys cannot be used for encrypting. /// </exception> public static MultipartEncrypted Encrypt (OpenPgpContext ctx, IEnumerable<PgpPublicKey> recipients, MimeEntity entity) { if (ctx == null) throw new ArgumentNullException (nameof (ctx)); if (recipients == null) throw new ArgumentNullException (nameof (recipients)); if (entity == null) throw new ArgumentNullException (nameof (entity)); using (var memory = new MemoryBlockStream ()) { using (var filtered = new FilteredStream (memory)) { filtered.Add (new Unix2DosFilter ()); entity.WriteTo (filtered); filtered.Flush (); } memory.Position = 0; var encrypted = new MultipartEncrypted (); encrypted.ContentType.Parameters["protocol"] = ctx.EncryptionProtocol; // add the protocol version part encrypted.Add (new ApplicationPgpEncrypted ()); // add the encrypted entity as the second part encrypted.Add (ctx.Encrypt (recipients, memory)); return encrypted; } }