/// <summary>
        ///     Peel the onion until we can return the original plain data. Remember that the calling environment must contain all
        ///     necessary private keys.
        /// </summary>
        public byte[] PeelAll()
        {
            if (AsymmetricEncryptedKey == null)
            {
                throw new Exception(
                          "AsymmetricEncryptedKey not found. Are you sure this onion doesn't have more layers?");
            }

            byte[] plainData;
            var    onion = this;

            while (!onion.IsRoot)
            {
                onion = onion.PeelOne();
            }

            using (var aes = new AESCryptoAgent())
                using (var rsa = new RSACryptoAgent(onion.AsymmetricThumbprint))
                {
                    var decryptedSymmetricKey = rsa.Decrypt(onion.AsymmetricEncryptedKey);
                    plainData = aes.Decrypt(onion.SymmetricEncryptedData, decryptedSymmetricKey,
                                            onion.SymmetricAlgorithmIV);
                }

            return(plainData);
        }
        /// <summary>
        ///     N-level encryption of existing onion ctor
        /// </summary>
        public PublicKeyEncryptionOnion(PublicKeyEncryptionOnion innerOnion, string asymmetricThumbprint)
        {
            Mandate.That(innerOnion, nameof(innerOnion)).IsNotNull();
            Mandate.That(asymmetricThumbprint, nameof(asymmetricThumbprint)).IsNotNullOrEmpty();


            AsymmetricThumbprint = asymmetricThumbprint;
            InnerOnion           = innerOnion.Clone();
            // allocate a new object so we're self contained and the calling code can do whatever it wants with the parameter object
            InnerOnion.AsymmetricEncryptedKey = null;

            using (var aes = new AESCryptoAgent())
                using (var rsa = new RSACryptoAgent(asymmetricThumbprint))
                {
                    // generate the aes key and use it to re-encrypt the original key
                    var symmetricKey = aes.GenerateKey();
                    SymmetricAlgorithmIV   = aes.GenerateIV();
                    SymmetricEncryptedData = aes.Encrypt(innerOnion.AsymmetricEncryptedKey, symmetricKey,
                                                         SymmetricAlgorithmIV);

                    // encrypt the aes key with the public key
                    AsymmetricEncryptedKey = rsa.Encrypt(symmetricKey);
                }

            // even though we're not using the param onion, we don't want to let it retain the data that's at a lower level of encryption
            innerOnion.AsymmetricEncryptedKey = null;
        }
        /// <summary>
        ///     Return an onion with the top layer removed
        /// </summary>
        public PublicKeyEncryptionOnion PeelOne()
        {
            if (IsRoot)
            {
                throw new Exception(
                          "This object is the root of the onion so there are no more layers to peel. Call 'PeelAll()' instead.");
            }

            if (AsymmetricEncryptedKey == null)
            {
                throw new Exception(
                          "AsymmetricEncryptedKey not found. Are you sure this onion doesn't have more layers?");
            }

            var rv = InnerOnion.Clone();

            // here we want to generate a new object to return since we don't want to decrease the security of the current object
            using (var aes = new AESCryptoAgent())
                using (var rsa = new RSACryptoAgent(AsymmetricThumbprint))
                {
                    // decrypt the previously encrypted key
                    var decryptedSymmetricKey = rsa.Decrypt(AsymmetricEncryptedKey);
                    rv.AsymmetricEncryptedKey = aes.Decrypt(SymmetricEncryptedData, decryptedSymmetricKey,
                                                            SymmetricAlgorithmIV);
                }

            return(rv);
        }
        /// <summary>
        ///     Root-level encryption of plain text ctor
        /// </summary>
        public PublicKeyEncryptionOnion(byte[] plainData, string asymmetricThumbprint)
        {
            if (null == plainData || !plainData.Any())
            {
                throw new ArgumentNullException(nameof(plainData));
            }

            Mandate.That(asymmetricThumbprint, nameof(asymmetricThumbprint)).IsNotNullOrEmpty();


            AsymmetricThumbprint = asymmetricThumbprint;
            InnerOnion           = null;

            using (var aes = new AESCryptoAgent())
                using (var rsa = new RSACryptoAgent(asymmetricThumbprint))
                {
                    // generate the aes key and use it to encrypt the original plaintext
                    var symmetricKey = aes.GenerateKey();
                    SymmetricAlgorithmIV   = aes.GenerateIV();
                    SymmetricEncryptedData = aes.Encrypt(plainData, symmetricKey, SymmetricAlgorithmIV);

                    // encrypt the aes key with the public key
                    AsymmetricEncryptedKey = rsa.Encrypt(symmetricKey);
                }
        }