<commit_before>//
// OpenPgpContext.cs
//
// Author: <NAME> <<EMAIL>>
//
// Copyright (c) 2013-2015 Xamarin Inc. (www.xamarin.com)
//
// 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.Linq;
using System.Diagnostics;
using System.Collections.Generic;

using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;

using MimeKit.IO;

namespace MimeKit.Cryptography {
	/// <summary>
	/// An abstract OpenPGP cryptography context which can be used for PGP/MIME.
	/// </summary>
	/// <remarks>
	/// Generally speaking, applications should not use a <see cref="OpenPgpContext"/>
	/// directly, but rather via higher level APIs such as <see cref="MultipartSigned"/>
	/// and <see cref="MultipartEncrypted"/>.
	/// </remarks>
	public abstract class OpenPgpContext : CryptographyContext
	{
		EncryptionAlgorithm defaultAlgorithm = EncryptionAlgorithm.Cast5;

		/// <summary>
		/// Gets or sets the default encryption algorithm.
		/// </summary>
		/// <remarks>
		/// Gets or sets the default encryption algorithm.
		/// </remarks>
		/// <value>The encryption algorithm.</value>
		/// <exception cref="System.NotSupportedException">
		/// The specified encryption algorithm is not supported.
		/// </exception>
		public EncryptionAlgorithm DefaultEncryptionAlgorithm {
			get { return defaultAlgorithm; }
			set {
				GetSymmetricKeyAlgorithm (value);
				defaultAlgorithm = value;
			}
		}

#if PORTABLE
		/// <summary>
		/// Gets the public keyring.
		/// </summary>
		/// <remarks>
		/// Gets the public keyring.
		/// </remarks>
		/// <value>The public key ring.</value>
		protected Stream PublicKeyRing {
			get; set;
		}

		/// <summary>
		/// Gets the secret keyring.
		/// </summary>
		/// <remarks>
		/// Gets the secret keyring.
		/// </remarks>
		/// <value>The secret key ring.</value>
		protected Stream SecretKeyRing {
			get; set;
		}
#else
		/// <summary>
		/// Gets the public keyring path.
		/// </summary>
		/// <remarks>
		/// Gets the public keyring path.
		/// </remarks>
		/// <value>The public key ring path.</value>
		protected string PublicKeyRingPath {
			get; set;
		}

		/// <summary>
		/// Gets the secret keyring path.
		/// </summary>
		/// <remarks>
		/// Gets the secret keyring path.
		/// </remarks>
		/// <value>The secret key ring path.</value>
		protected string SecretKeyRingPath {
			get; set;
		}
#endif

		/// <summary>
		/// Gets the public keyring bundle.
		/// </summary>
		/// <remarks>
		/// Gets the public keyring bundle.
		/// </remarks>
		/// <value>The public keyring bundle.</value>
		public PgpPublicKeyRingBundle PublicKeyRingBundle {
			get; protected set;
		}

		/// <summary>
		/// Gets the secret keyring bundle.
		/// </summary>
		/// <remarks>
		/// Gets the secret keyring bundle.
		/// </remarks>
		/// <value>The secret keyring bundle.</value>
		public PgpSecretKeyRingBundle SecretKeyRingBundle {
			get; protected set;
		}

		/// <summary>
		/// Gets the signature protocol.
		/// </summary>
		/// <remarks>
		/// <para>The signature protocol is used by <see cref="MultipartSigned"/>
		/// in order to determine what the protocol parameter of the Content-Type
		/// header should be.</para>
		/// </remarks>
		/// <value>The signature protocol.</value>
		public override string SignatureProtocol {
			get { return "application/pgp-signature"; }
		}

		/// <summary>
		/// Gets the encryption protocol.
		/// </summary>
		/// <remarks>
		/// <para>The encryption protocol is used by <see cref="MultipartEncrypted"/>
		/// in order to determine what the protocol parameter of the Content-Type
		/// header should be.</para>
		/// </remarks>
		/// <value>The encryption protocol.</value>
		public override string EncryptionProtocol {
			get { return "application/pgp-encrypted"; }
		}

		/// <summary>
		/// Gets the key exchange protocol.
		/// </summary>
		/// <remarks>
		/// Gets the key exchange protocol.
		/// </remarks>
		/// <value>The key exchange protocol.</value>
		public override string KeyExchangeProtocol {
			get { return "application/pgp-keys"; }
		}

		/// <summary>
		/// Checks whether or not the specified protocol is supported.
		/// </summary>
		/// <remarks>
		/// Used in order to make sure that the protocol parameter value specified in either a multipart/signed
		/// or multipart/encrypted part is supported by the supplied cryptography context.
		/// </remarks>
		/// <returns><c>true</c> if the protocol is supported; otherwise <c>false</c></returns>
		/// <param name="protocol">The protocol.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="protocol"/> is <c>null</c>.
		/// </exception>
		public override bool Supports (string protocol)
		{
			if (protocol == null)
				throw new ArgumentNullException ("protocol");

			var type = protocol.ToLowerInvariant ().Split ('/');
			if (type.Length != 2 || type[0] != "application")
				return false;

			if (type[1].StartsWith ("x-", StringComparison.Ordinal))
				type[1] = type[1].Substring (2);

			return type[1] == "pgp-signature" || type[1] == "pgp-encrypted" || type[1] == "pgp-keys";
		}

		/// <summary>
		/// Gets the string name of the digest algorithm for use with the micalg parameter of a multipart/signed part.
		/// </summary>
		/// <remarks>
		/// <para>Maps the <see cref="DigestAlgorithm"/> to the appropriate string identifier
		/// as used by the micalg parameter value of a multipart/signed Content-Type
		/// header. For example:</para>
		/// <list type="table">
		/// <listheader><term>Algorithm</term><description>Name</description></listheader>
		/// <item><term><see cref="DigestAlgorithm.MD5"/></term><description>pgp-md5</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha1"/></term><description>pgp-sha1</description></item>
		/// <item><term><see cref="DigestAlgorithm.RipeMD160"/></term><description>pgp-ripemd160</description></item>
		/// <item><term><see cref="DigestAlgorithm.MD2"/></term><description>pgp-md2</description></item>
		/// <item><term><see cref="DigestAlgorithm.Tiger192"/></term><description>pgp-tiger192</description></item>
		/// <item><term><see cref="DigestAlgorithm.Haval5160"/></term><description>pgp-haval-5-160</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha256"/></term><description>pgp-sha256</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha384"/></term><description>pgp-sha384</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha512"/></term><description>pgp-sha512</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha224"/></term><description>pgp-sha224</description></item>
		/// </list>
		/// </remarks>
		/// <returns>The micalg value.</returns>
		/// <param name="micalg">The digest algorithm.</param>
		/// <exception cref="System.ArgumentOutOfRangeException">
		/// <paramref name="micalg"/> is out of range.
		/// </exception>
		public override string GetDigestAlgorithmName (DigestAlgorithm micalg)
		{
			switch (micalg) {
			case DigestAlgorithm.MD5:        return "pgp-md5";
			case DigestAlgorithm.Sha1:       return "pgp-sha1";
			case DigestAlgorithm.RipeMD160:  return "pgp-ripemd160";
			case DigestAlgorithm.MD2:        return "pgp-md2";
			case DigestAlgorithm.Tiger192:   return "pgp-tiger192";
			case DigestAlgorithm.Haval5160:  return "pgp-haval-5-160";
			case DigestAlgorithm.Sha256:     return "pgp-sha256";
			case DigestAlgorithm.Sha384:     return "pgp-sha384";
			case DigestAlgorithm.Sha512:     return "pgp-sha512";
			case DigestAlgorithm.Sha224:     return "pgp-sha224";
			case DigestAlgorithm.MD4:        return "pgp-md4";
			default: throw new ArgumentOutOfRangeException ("micalg");
			}
		}

		/// <summary>
		/// Gets the digest algorithm from the micalg parameter value in a multipart/signed part.
		/// </summary>
		/// <remarks>
		/// Maps the micalg parameter value string back to the appropriate <see cref="DigestAlgorithm"/>.
		/// </remarks>
		/// <returns>The digest algorithm.</returns>
		/// <param name="micalg">The micalg parameter value.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="micalg"/> is <c>null</c>.
		/// </exception>
		public override DigestAlgorithm GetDigestAlgorithm (string micalg)
		{
			if (micalg == null)
				throw new ArgumentNullException ("micalg");

			switch (micalg.ToLowerInvariant ()) {
			case "pgp-md5":         return DigestAlgorithm.MD5;
			case "pgp-sha1":        return DigestAlgorithm.Sha1;
			case "pgp-ripemd160":   return DigestAlgorithm.RipeMD160;
			case "pgp-md2":         return DigestAlgorithm.MD2;
			case "pgp-tiger192":    return DigestAlgorithm.Tiger192;
			case "pgp-haval-5-160": return DigestAlgorithm.Haval5160;
			case "pgp-sha256":      return DigestAlgorithm.Sha256;
			case "pgp-sha384":      return DigestAlgorithm.Sha384;
			case "pgp-sha512":      return DigestAlgorithm.Sha512;
			case "pgp-sha224":      return DigestAlgorithm.Sha224;
			case "pgp-md4":         return DigestAlgorithm.MD4;
			default:                return DigestAlgorithm.None;
			}
		}

#if PORTABLE
		/// <summary>
		/// Initializes a new instance of the <see cref="MimeKit.Cryptography.OpenPgpContext"/> class.
		/// </summary>
		/// <remarks>
		/// Creates a new <see cref="OpenPgpContext"/> using the specified public and private keyrings.
		/// </remarks>
		/// <param name="pubring">The public keyring.</param>
		/// <param name="secring">The secret keyring.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="pubring"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="secring"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.IO.IOException">
		/// An error occurred while reading one of the keyring files.
		/// </exception>
		/// <exception cref="Org.BouncyCastle.Bcpg.OpenPgp.PgpException">
		/// An error occurred while parsing one of the keyring files.
		/// </exception>
		protected OpenPgpContext (Stream pubring, Stream secring)
		{
			if (pubring == null)
				throw new ArgumentNullException ("pubring");

			if (secring == null)
				throw new ArgumentNullException ("secring");

			PublicKeyRing = pubring;
			SecretKeyRing = secring;

			PublicKeyRingBundle = new PgpPublicKeyRingBundle (pubring);
			SecretKeyRingBundle = new PgpSecretKeyRingBundle (secring);

			if (pubring.CanSeek)
				pubring.Seek (0, SeekOrigin.Begin);

			if (secring.CanSeek)
				secring.Seek (0, SeekOrigin.Begin);
		}
#else
		/// <summary>
		/// Initializes a new instance of the <see cref="MimeKit.Cryptography.OpenPgpContext"/> class.
		/// </summary>
		/// <remarks>
		/// Creates a new <see cref="OpenPgpContext"/> using the specified public and private keyring paths.
		/// </remarks>
		/// <param name="pubring">The public keyring file path.</param>
		/// <param name="secring">The secret keyring file path.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="pubring"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="secring"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.IO.IOException">
		/// An error occurred while reading one of the keyring files.
		/// </exception>
		/// <exception cref="Org.BouncyCastle.Bcpg.OpenPgp.PgpException">
		/// An error occurred while parsing one of the keyring files.
		/// </exception>
		protected OpenPgpContext (string pubring, string secring)
		{
			if (pubring == null)
				throw new ArgumentNullException ("pubring");

			if (secring == null)
				throw new ArgumentNullException ("secring");

			PublicKeyRingPath = pubring;
			SecretKeyRingPath = secring;

			if (File.Exists (pubring)) {
				using (var file = File.OpenRead (pubring)) {
					PublicKeyRingBundle = new PgpPublicKeyRingBundle (file);
				}
			} else {
				PublicKeyRingBundle = new PgpPublicKeyRingBundle (new byte[0]);
			}

			if (File.Exists (secring)) {
				using (var file = File.OpenRead (secring)) {
					SecretKeyRingBundle = new PgpSecretKeyRingBundle (file);
				}
			} else {
				SecretKeyRingBundle = new PgpSecretKeyRingBundle (new byte[0]);
			}
		}
#endif

		/// <summary>
		/// Initializes a new instance of the <see cref="MimeKit.Cryptography.OpenPgpContext"/> class.
		/// </summary>
		/// <remarks>
		/// Subclasses choosing to use this constructor MUST set the <see cref="PublicKeyRingPath"/>,
		/// <see cref="SecretKeyRingPath"/>, <see cref="PublicKeyRingBundle"/>, and the
		/// <see cref="SecretKeyRingBundle"/> properties themselves.
		/// </remarks>
		protected OpenPgpContext ()
		{
		}

		static string GetFingerprint (long keyId, int length)
		{
			if (length < 16)
				return ((int) keyId).ToString ("X2");

			return keyId.ToString ("X2");
		}

		static bool PgpPublicKeyMatches (PgpPublicKey key, MailboxAddress mailbox)
		{
			var secure = mailbox as SecureMailboxAddress;

			if (secure != null && !string.IsNullOrEmpty (secure.Fingerprint)) {
				var fingerprint = GetFingerprint (key.KeyId, secure.Fingerprint.Length);

				return secure.Fingerprint.EndsWith (fingerprint, StringComparison.OrdinalIgnoreCase);
			}

			foreach (string userId in key.GetUserIds ()) {
				MailboxAddress email;

				if (!MailboxAddress.TryParse (userId, out email))
					continue;

				if (mailbox.Address.Equals (email.Address, StringComparison.OrdinalIgnoreCase))
					return true;
			}

			return false;
		}

		/// <summary>
		/// Gets the public key associated with the mailbox address.
		/// </summary>
		/// <remarks>
		/// Gets the public key associated with the mailbox address.
		/// </remarks>
		/// <returns>The encryption key.</returns>
		/// <param name="mailbox">The mailbox.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="mailbox"/> is <c>null</c>.
		/// </exception>
		/// <exception cref="PublicKeyNotFoundException">
		/// The public key for the specified <paramref name="mailbox"/> could not be found.
		/// </exception>
		protected virtual PgpPublicKey GetPublicKey (MailboxAddress mailbox)
		{
			if (mailbox == null)
				throw new ArgumentNullException ("mailbox");

			foreach (PgpPublicKeyRing keyring in PublicKeyRingBundle.GetKeyRings ()) {
				foreach (PgpPublicKey key in keyring.GetPublicKeys ()) {
					if (!PgpPublicKeyMatches (keyring.GetPublicKey (), mailbox))
						continue;

					if (!key.IsEncryptionKey || key.IsRevoked ())
						continue;

					long seconds = key.GetValidSeconds ();
					if (seconds != 0) {
						var expires = key.CreationTime.AddSeconds ((double) seconds);
						if (expires >= DateTime.Now)
							continue;
					}

					return key;
				}
			}

			throw new PublicKeyNotFoundException (mailbox, "The public key could not be found.");
		}

		/// <summary>
		/// Gets the public keys for the specified mailbox addresses.
		/// </summary>
		/// <remarks>
		/// Gets the public keys for the specified mailbox addresses.
		/// </remarks>
		/// <returns>The encryption keys.</returns>
		/// <param name="mailboxes">The mailboxes.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="mailboxes"/> is <c>null</c>.
		/// </exception>
		/// <exception cref="PublicKeyNotFoundException">
		/// A public key for one or more of the <paramref name="mailboxes"/> could not be found.
		/// </exception>
		protected virtual IList<PgpPublicKey> GetPublicKeys (IEnumerable<MailboxAddress> mailboxes)
		{
			if (mailboxes == null)
				throw new ArgumentNullException ("mailboxes");

			var recipients = new List<PgpPublicKey> ();

			foreach (var mailbox in mailboxes)
				recipients.Add (GetPublicKey (mailbox));

			return recipients;
		}

		static bool PgpSecretKeyMatches (PgpSecretKey key, MailboxAddress mailbox)
		{
			var secure = mailbox as SecureMailboxAddress;

			if (secure != null && !string.IsNullOrEmpty (secure.Fingerprint)) {
				var fingerprint = GetFingerprint (key.KeyId, secure.Fingerprint.Length);

				return secure.Fingerprint.EndsWith (fingerprint, StringComparison.OrdinalIgnoreCase);
			}

			foreach (string userId in key.UserIds) {
				MailboxAddress email;

				if (!MailboxAddress.TryParse (userId, out email))
					continue;

				if (mailbox.Address.Equals (email.Address, StringComparison.OrdinalIgnoreCase))
					return true;
			}

			return false;
		}

		/// <summary>
		/// Gets the signing key associated with the mailbox address.
		/// </summary>
		/// <remarks>
		/// Gets the signing key associated with the mailbox address.
		/// </remarks>
		/// <returns>The signing key.</returns>
		/// <param name="mailbox">The mailbox.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="mailbox"/> is <c>null</c>.
		/// </exception>
		/// <exception cref="PrivateKeyNotFoundException">
		/// A private key for the specified <paramref name="mailbox"/> could not be found.
		/// </exception>
		protected virtual PgpSecretKey GetSigningKey (MailboxAddress mailbox)
		{
			if (mailbox == null)
				throw new ArgumentNullException ("mailbox");

			foreach (PgpSecretKeyRing keyring in SecretKeyRingBundle.GetKeyRings ()) {
				foreach (PgpSecretKey key in keyring.GetSecretKeys ()) {
					if (!PgpSecretKeyMatches (keyring.GetSecretKey (), mailbox))
						continue;

					if (!key.IsSigningKey)
						continue;

					var pubkey = key.PublicKey;
					if (pubkey.IsRevoked ())
						continue;

					long seconds = pubkey.GetValidSeconds ();
					if (seconds != 0) {
						var expires = pubkey.CreationTime.AddSeconds ((double) seconds);
						if (DateTime.Now >= expires)
							continue;
					}

					return key;
				}
			}

			throw new PrivateKeyNotFoundException (mailbox, "The private key could not be found.");
		}

		/// <summary>
		/// Gets the password for key.
		/// </summary>
		/// <remarks>
		/// Gets the password for key.
		/// </remarks>
		/// <returns>The password for key.</returns>
		/// <param name="key">The key.</param>
		/// <exception cref="System.OperationCanceledException">
		/// The user chose to cancel the password request.
		/// </exception>
		protected abstract string GetPasswordForKey (PgpSecretKey key);<commit_msg>Fixed PGP key expiration logic when encrypting<commit_after>//
// OpenPgpContext.cs
//
// Author: <NAME> <<EMAIL>>
//
// Copyright (c) 2013-2015 Xamarin Inc. (www.xamarin.com)
//
// 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.Linq;
using System.Diagnostics;
using System.Collections.Generic;

using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Bcpg.OpenPgp;

using MimeKit.IO;

namespace MimeKit.Cryptography {
	/// <summary>
	/// An abstract OpenPGP cryptography context which can be used for PGP/MIME.
	/// </summary>
	/// <remarks>
	/// Generally speaking, applications should not use a <see cref="OpenPgpContext"/>
	/// directly, but rather via higher level APIs such as <see cref="MultipartSigned"/>
	/// and <see cref="MultipartEncrypted"/>.
	/// </remarks>
	public abstract class OpenPgpContext : CryptographyContext
	{
		EncryptionAlgorithm defaultAlgorithm = EncryptionAlgorithm.Cast5;

		/// <summary>
		/// Gets or sets the default encryption algorithm.
		/// </summary>
		/// <remarks>
		/// Gets or sets the default encryption algorithm.
		/// </remarks>
		/// <value>The encryption algorithm.</value>
		/// <exception cref="System.NotSupportedException">
		/// The specified encryption algorithm is not supported.
		/// </exception>
		public EncryptionAlgorithm DefaultEncryptionAlgorithm {
			get { return defaultAlgorithm; }
			set {
				GetSymmetricKeyAlgorithm (value);
				defaultAlgorithm = value;
			}
		}

#if PORTABLE
		/// <summary>
		/// Gets the public keyring.
		/// </summary>
		/// <remarks>
		/// Gets the public keyring.
		/// </remarks>
		/// <value>The public key ring.</value>
		protected Stream PublicKeyRing {
			get; set;
		}

		/// <summary>
		/// Gets the secret keyring.
		/// </summary>
		/// <remarks>
		/// Gets the secret keyring.
		/// </remarks>
		/// <value>The secret key ring.</value>
		protected Stream SecretKeyRing {
			get; set;
		}
#else
		/// <summary>
		/// Gets the public keyring path.
		/// </summary>
		/// <remarks>
		/// Gets the public keyring path.
		/// </remarks>
		/// <value>The public key ring path.</value>
		protected string PublicKeyRingPath {
			get; set;
		}

		/// <summary>
		/// Gets the secret keyring path.
		/// </summary>
		/// <remarks>
		/// Gets the secret keyring path.
		/// </remarks>
		/// <value>The secret key ring path.</value>
		protected string SecretKeyRingPath {
			get; set;
		}
#endif

		/// <summary>
		/// Gets the public keyring bundle.
		/// </summary>
		/// <remarks>
		/// Gets the public keyring bundle.
		/// </remarks>
		/// <value>The public keyring bundle.</value>
		public PgpPublicKeyRingBundle PublicKeyRingBundle {
			get; protected set;
		}

		/// <summary>
		/// Gets the secret keyring bundle.
		/// </summary>
		/// <remarks>
		/// Gets the secret keyring bundle.
		/// </remarks>
		/// <value>The secret keyring bundle.</value>
		public PgpSecretKeyRingBundle SecretKeyRingBundle {
			get; protected set;
		}

		/// <summary>
		/// Gets the signature protocol.
		/// </summary>
		/// <remarks>
		/// <para>The signature protocol is used by <see cref="MultipartSigned"/>
		/// in order to determine what the protocol parameter of the Content-Type
		/// header should be.</para>
		/// </remarks>
		/// <value>The signature protocol.</value>
		public override string SignatureProtocol {
			get { return "application/pgp-signature"; }
		}

		/// <summary>
		/// Gets the encryption protocol.
		/// </summary>
		/// <remarks>
		/// <para>The encryption protocol is used by <see cref="MultipartEncrypted"/>
		/// in order to determine what the protocol parameter of the Content-Type
		/// header should be.</para>
		/// </remarks>
		/// <value>The encryption protocol.</value>
		public override string EncryptionProtocol {
			get { return "application/pgp-encrypted"; }
		}

		/// <summary>
		/// Gets the key exchange protocol.
		/// </summary>
		/// <remarks>
		/// Gets the key exchange protocol.
		/// </remarks>
		/// <value>The key exchange protocol.</value>
		public override string KeyExchangeProtocol {
			get { return "application/pgp-keys"; }
		}

		/// <summary>
		/// Checks whether or not the specified protocol is supported.
		/// </summary>
		/// <remarks>
		/// Used in order to make sure that the protocol parameter value specified in either a multipart/signed
		/// or multipart/encrypted part is supported by the supplied cryptography context.
		/// </remarks>
		/// <returns><c>true</c> if the protocol is supported; otherwise <c>false</c></returns>
		/// <param name="protocol">The protocol.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="protocol"/> is <c>null</c>.
		/// </exception>
		public override bool Supports (string protocol)
		{
			if (protocol == null)
				throw new ArgumentNullException ("protocol");

			var type = protocol.ToLowerInvariant ().Split ('/');
			if (type.Length != 2 || type[0] != "application")
				return false;

			if (type[1].StartsWith ("x-", StringComparison.Ordinal))
				type[1] = type[1].Substring (2);

			return type[1] == "pgp-signature" || type[1] == "pgp-encrypted" || type[1] == "pgp-keys";
		}

		/// <summary>
		/// Gets the string name of the digest algorithm for use with the micalg parameter of a multipart/signed part.
		/// </summary>
		/// <remarks>
		/// <para>Maps the <see cref="DigestAlgorithm"/> to the appropriate string identifier
		/// as used by the micalg parameter value of a multipart/signed Content-Type
		/// header. For example:</para>
		/// <list type="table">
		/// <listheader><term>Algorithm</term><description>Name</description></listheader>
		/// <item><term><see cref="DigestAlgorithm.MD5"/></term><description>pgp-md5</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha1"/></term><description>pgp-sha1</description></item>
		/// <item><term><see cref="DigestAlgorithm.RipeMD160"/></term><description>pgp-ripemd160</description></item>
		/// <item><term><see cref="DigestAlgorithm.MD2"/></term><description>pgp-md2</description></item>
		/// <item><term><see cref="DigestAlgorithm.Tiger192"/></term><description>pgp-tiger192</description></item>
		/// <item><term><see cref="DigestAlgorithm.Haval5160"/></term><description>pgp-haval-5-160</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha256"/></term><description>pgp-sha256</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha384"/></term><description>pgp-sha384</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha512"/></term><description>pgp-sha512</description></item>
		/// <item><term><see cref="DigestAlgorithm.Sha224"/></term><description>pgp-sha224</description></item>
		/// </list>
		/// </remarks>
		/// <returns>The micalg value.</returns>
		/// <param name="micalg">The digest algorithm.</param>
		/// <exception cref="System.ArgumentOutOfRangeException">
		/// <paramref name="micalg"/> is out of range.
		/// </exception>
		public override string GetDigestAlgorithmName (DigestAlgorithm micalg)
		{
			switch (micalg) {
			case DigestAlgorithm.MD5:        return "pgp-md5";
			case DigestAlgorithm.Sha1:       return "pgp-sha1";
			case DigestAlgorithm.RipeMD160:  return "pgp-ripemd160";
			case DigestAlgorithm.MD2:        return "pgp-md2";
			case DigestAlgorithm.Tiger192:   return "pgp-tiger192";
			case DigestAlgorithm.Haval5160:  return "pgp-haval-5-160";
			case DigestAlgorithm.Sha256:     return "pgp-sha256";
			case DigestAlgorithm.Sha384:     return "pgp-sha384";
			case DigestAlgorithm.Sha512:     return "pgp-sha512";
			case DigestAlgorithm.Sha224:     return "pgp-sha224";
			case DigestAlgorithm.MD4:        return "pgp-md4";
			default: throw new ArgumentOutOfRangeException ("micalg");
			}
		}

		/// <summary>
		/// Gets the digest algorithm from the micalg parameter value in a multipart/signed part.
		/// </summary>
		/// <remarks>
		/// Maps the micalg parameter value string back to the appropriate <see cref="DigestAlgorithm"/>.
		/// </remarks>
		/// <returns>The digest algorithm.</returns>
		/// <param name="micalg">The micalg parameter value.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="micalg"/> is <c>null</c>.
		/// </exception>
		public override DigestAlgorithm GetDigestAlgorithm (string micalg)
		{
			if (micalg == null)
				throw new ArgumentNullException ("micalg");

			switch (micalg.ToLowerInvariant ()) {
			case "pgp-md5":         return DigestAlgorithm.MD5;
			case "pgp-sha1":        return DigestAlgorithm.Sha1;
			case "pgp-ripemd160":   return DigestAlgorithm.RipeMD160;
			case "pgp-md2":         return DigestAlgorithm.MD2;
			case "pgp-tiger192":    return DigestAlgorithm.Tiger192;
			case "pgp-haval-5-160": return DigestAlgorithm.Haval5160;
			case "pgp-sha256":      return DigestAlgorithm.Sha256;
			case "pgp-sha384":      return DigestAlgorithm.Sha384;
			case "pgp-sha512":      return DigestAlgorithm.Sha512;
			case "pgp-sha224":      return DigestAlgorithm.Sha224;
			case "pgp-md4":         return DigestAlgorithm.MD4;
			default:                return DigestAlgorithm.None;
			}
		}

#if PORTABLE
		/// <summary>
		/// Initializes a new instance of the <see cref="MimeKit.Cryptography.OpenPgpContext"/> class.
		/// </summary>
		/// <remarks>
		/// Creates a new <see cref="OpenPgpContext"/> using the specified public and private keyrings.
		/// </remarks>
		/// <param name="pubring">The public keyring.</param>
		/// <param name="secring">The secret keyring.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="pubring"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="secring"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.IO.IOException">
		/// An error occurred while reading one of the keyring files.
		/// </exception>
		/// <exception cref="Org.BouncyCastle.Bcpg.OpenPgp.PgpException">
		/// An error occurred while parsing one of the keyring files.
		/// </exception>
		protected OpenPgpContext (Stream pubring, Stream secring)
		{
			if (pubring == null)
				throw new ArgumentNullException ("pubring");

			if (secring == null)
				throw new ArgumentNullException ("secring");

			PublicKeyRing = pubring;
			SecretKeyRing = secring;

			PublicKeyRingBundle = new PgpPublicKeyRingBundle (pubring);
			SecretKeyRingBundle = new PgpSecretKeyRingBundle (secring);

			if (pubring.CanSeek)
				pubring.Seek (0, SeekOrigin.Begin);

			if (secring.CanSeek)
				secring.Seek (0, SeekOrigin.Begin);
		}
#else
		/// <summary>
		/// Initializes a new instance of the <see cref="MimeKit.Cryptography.OpenPgpContext"/> class.
		/// </summary>
		/// <remarks>
		/// Creates a new <see cref="OpenPgpContext"/> using the specified public and private keyring paths.
		/// </remarks>
		/// <param name="pubring">The public keyring file path.</param>
		/// <param name="secring">The secret keyring file path.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="pubring"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="secring"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="System.IO.IOException">
		/// An error occurred while reading one of the keyring files.
		/// </exception>
		/// <exception cref="Org.BouncyCastle.Bcpg.OpenPgp.PgpException">
		/// An error occurred while parsing one of the keyring files.
		/// </exception>
		protected OpenPgpContext (string pubring, string secring)
		{
			if (pubring == null)
				throw new ArgumentNullException ("pubring");

			if (secring == null)
				throw new ArgumentNullException ("secring");

			PublicKeyRingPath = pubring;
			SecretKeyRingPath = secring;

			if (File.Exists (pubring)) {
				using (var file = File.OpenRead (pubring)) {
					PublicKeyRingBundle = new PgpPublicKeyRingBundle (file);
				}
			} else {
				PublicKeyRingBundle = new PgpPublicKeyRingBundle (new byte[0]);
			}

			if (File.Exists (secring)) {
				using (var file = File.OpenRead (secring)) {
					SecretKeyRingBundle = new PgpSecretKeyRingBundle (file);
				}
			} else {
				SecretKeyRingBundle = new PgpSecretKeyRingBundle (new byte[0]);
			}
		}
#endif

		/// <summary>
		/// Initializes a new instance of the <see cref="MimeKit.Cryptography.OpenPgpContext"/> class.
		/// </summary>
		/// <remarks>
		/// Subclasses choosing to use this constructor MUST set the <see cref="PublicKeyRingPath"/>,
		/// <see cref="SecretKeyRingPath"/>, <see cref="PublicKeyRingBundle"/>, and the
		/// <see cref="SecretKeyRingBundle"/> properties themselves.
		/// </remarks>
		protected OpenPgpContext ()
		{
		}

		static string GetFingerprint (long keyId, int length)
		{
			if (length < 16)
				return ((int) keyId).ToString ("X2");

			return keyId.ToString ("X2");
		}

		static bool PgpPublicKeyMatches (PgpPublicKey key, MailboxAddress mailbox)
		{
			var secure = mailbox as SecureMailboxAddress;

			if (secure != null && !string.IsNullOrEmpty (secure.Fingerprint)) {
				var fingerprint = GetFingerprint (key.KeyId, secure.Fingerprint.Length);

				return secure.Fingerprint.EndsWith (fingerprint, StringComparison.OrdinalIgnoreCase);
			}

			foreach (string userId in key.GetUserIds ()) {
				MailboxAddress email;

				if (!MailboxAddress.TryParse (userId, out email))
					continue;

				if (mailbox.Address.Equals (email.Address, StringComparison.OrdinalIgnoreCase))
					return true;
			}

			return false;
		}

		/// <summary>
		/// Gets the public key associated with the mailbox address.
		/// </summary>
		/// <remarks>
		/// Gets the public key associated with the mailbox address.
		/// </remarks>
		/// <returns>The encryption key.</returns>
		/// <param name="mailbox">The mailbox.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="mailbox"/> is <c>null</c>.
		/// </exception>
		/// <exception cref="PublicKeyNotFoundException">
		/// The public key for the specified <paramref name="mailbox"/> could not be found.
		/// </exception>
		protected virtual PgpPublicKey GetPublicKey (MailboxAddress mailbox)
		{
			if (mailbox == null)
				throw new ArgumentNullException ("mailbox");

			foreach (PgpPublicKeyRing keyring in PublicKeyRingBundle.GetKeyRings ()) {
				foreach (PgpPublicKey key in keyring.GetPublicKeys ()) {
					if (!PgpPublicKeyMatches (keyring.GetPublicKey (), mailbox))
						continue;

					if (!key.IsEncryptionKey || key.IsRevoked ())
						continue;

					long seconds = key.GetValidSeconds ();
					if (seconds != 0) {
						var expires = key.CreationTime.AddSeconds ((double) seconds);
						if (expires <= DateTime.Now)
							continue;
					}

					return key;
				}
			}

			throw new PublicKeyNotFoundException (mailbox, "The public key could not be found.");
		}

		/// <summary>
		/// Gets the public keys for the specified mailbox addresses.
		/// </summary>
		/// <remarks>
		/// Gets the public keys for the specified mailbox addresses.
		/// </remarks>
		/// <returns>The encryption keys.</returns>
		/// <param name="mailboxes">The mailboxes.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="mailboxes"/> is <c>null</c>.
		/// </exception>
		/// <exception cref="PublicKeyNotFoundException">
		/// A public key for one or more of the <paramref name="mailboxes"/> could not be found.
		/// </exception>
		protected virtual IList<PgpPublicKey> GetPublicKeys (IEnumerable<MailboxAddress> mailboxes)
		{
			if (mailboxes == null)
				throw new ArgumentNullException ("mailboxes");

			var recipients = new List<PgpPublicKey> ();

			foreach (var mailbox in mailboxes)
				recipients.Add (GetPublicKey (mailbox));

			return recipients;
		}

		static bool PgpSecretKeyMatches (PgpSecretKey key, MailboxAddress mailbox)
		{
			var secure = mailbox as SecureMailboxAddress;

			if (secure != null && !string.IsNullOrEmpty (secure.Fingerprint)) {
				var fingerprint = GetFingerprint (key.KeyId, secure.Fingerprint.Length);

				return secure.Fingerprint.EndsWith (fingerprint, StringComparison.OrdinalIgnoreCase);
			}

			foreach (string userId in key.UserIds) {
				MailboxAddress email;

				if (!MailboxAddress.TryParse (userId, out email))
					continue;

				if (mailbox.Address.Equals (email.Address, StringComparison.OrdinalIgnoreCase))
					return true;
			}

			return false;
		}

		/// <summary>
		/// Gets the signing key associated with the mailbox address.
		/// </summary>
		/// <remarks>
		/// Gets the signing key associated with the mailbox address.
		/// </remarks>
		/// <returns>The signing key.</returns>
		/// <param name="mailbox">The mailbox.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="mailbox"/> is <c>null</c>.
		/// </exception>
		/// <exception cref="PrivateKeyNotFoundException">
		/// A private key for the specified <paramref name="mailbox"/> could not be found.
		/// </exception>
		protected virtual PgpSecretKey GetSigningKey (MailboxAddress mailbox)
		{
			if (mailbox == null)
				throw new ArgumentNullException ("mailbox");

			foreach (PgpSecretKeyRing keyring in SecretKeyRingBundle.GetKeyRings ()) {
				foreach (PgpSecretKey key in keyring.GetSecretKeys ()) {
					if (!PgpSecretKeyMatches (keyring.GetSecretKey (), mailbox))
						continue;

					if (!key.IsSigningKey)
						continue;

					var pubkey = key.PublicKey;
					if (pubkey.IsRevoked ())
						continue;

					long seconds = pubkey.GetValidSeconds ();
					if (seconds != 0) {
						var expires = pubkey.CreationTime.AddSeconds ((double) seconds);
						if (expires <= DateTime.Now)
							continue;
					}

					return key;
				}
			}

			throw new PrivateKeyNotFoundException (mailbox, "The private key could not be found.");
		}

		/// <summary>
		/// Gets the password for key.
		/// </summary>
		/// <remarks>
		/// Gets the password for key.
		/// </remarks>
		/// <returns>The password for key.</returns>
		/// <param name="key">The key.</param>
		/// <exception cref="System.OperationCanceledException">
		/// The user chose to cancel the password request.
		/// </exception>
		protected abstract string GetPasswordForKey (PgpSecretKey key);
{'fraction_non_alphanumeric': 0.14721461187214613, 'fraction_numerical': 0.009497716894977169, 'mean_word_length': 3.1581859607247478, 'pattern_counts': {'":': 22, '<': 661, '<?xml version=': 0, '>': 659, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4317104', 'n_tokens_mistral': 13289, 'n_tokens_neox': 11505, 'n_words': 3229}