private byte[] DecodeSignature(Message encryptedMessage, EncryptedMessageHeaders encryptedHeaders)
        {
            if (string.IsNullOrWhiteSpace(encryptedHeaders.Signature))
            {
                _diagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.SignatureVerificationFailure)
                {
                    Detail  = "Missing signature",
                    Message = encryptedMessage
                }.Build());

                throw new MessageEncryptionException("Missing signature");
            }

            byte[] signature;
            try
            {
                signature = Convert.FromBase64String(encryptedHeaders.Signature);
            }
            catch (Exception ex)
            {
                _diagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.SignatureVerificationFailure)
                {
                    Detail    = "Error decoding signature",
                    Message   = encryptedMessage,
                    Exception = ex
                }.Build());

                throw new MessageEncryptionException("Error decoding signature", ex);
            }

            return(signature);
        }
        public async Task <Message> Encrypt(Message message)
        {
            byte[] iv;
            using (var csp = new AesCryptoServiceProvider())
            {
                csp.GenerateIV();
                iv = csp.IV;
            }

            var headerCleartext = await MarshalHeaders(message.Headers);

            var contentCleartext = await MarshalContent(message.Content);

            var headerCiphertext = await Encrypt(iv, headerCleartext);

            var contentCiphertext = await Encrypt(iv, contentCleartext);

            var headerSignature = Sign(headerCleartext);

            var encryptedHeaders = new EncryptedMessageHeaders
            {
                // Message ID must be available in cleartext
                MessageId          = message.Headers.MessageId,
                IV                 = Convert.ToBase64String(iv),
                Headers            = Convert.ToBase64String(headerCiphertext),
                Signature          = Convert.ToBase64String(headerSignature),
                SignatureAlgorithm = "HMACSHA256"
            };
            var encryptedContent = Convert.ToBase64String(contentCiphertext);

            return(new Message(encryptedHeaders, encryptedContent));
        }
        public async Task <Message> Decrypt(Message encryptedMessage)
        {
            var encryptedHeaders = new EncryptedMessageHeaders(encryptedMessage.Headers);
            var iv = Convert.FromBase64String(encryptedHeaders.IV);
            var headerCiphertext  = Convert.FromBase64String(encryptedHeaders.Headers);
            var contentCiphertext = Convert.FromBase64String(encryptedMessage.Content);

            var signature       = DecodeSignature(encryptedMessage, encryptedHeaders);
            var keyNumber       = 0;
            var keyCount        = _decryptionKeys.Count;
            var innerExceptions = new List <Exception>();

            foreach (var key in _decryptionKeys)
            {
                keyNumber++;
                byte[] headerCleartext;
                byte[] contentCleartext;
                try
                {
                    headerCleartext = await Decrypt(headerCiphertext, key, iv);
                }
                catch (Exception ex)
                {
                    innerExceptions.Add(ex);
                    _diagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.DecryptionError)
                    {
                        Detail    = $"Error decrypting message headers using key {keyNumber} of {keyCount}",
                        Message   = encryptedMessage,
                        Exception = ex
                    }.Build());
                    continue;
                }

                var signatureVerified = false;
                try
                {
                    signatureVerified = Verify(key, headerCleartext, signature);
                    if (!signatureVerified)
                    {
                        _diagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.SignatureVerificationFailure)
                        {
                            Detail  = $"Signature verification failed using key {keyNumber} of {keyCount}",
                            Message = encryptedMessage
                        }.Build());
                    }
                }
                catch (Exception ex)
                {
                    innerExceptions.Add(ex);
                    _diagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.SignatureVerificationFailure)
                    {
                        Detail    = $"Unexpected error verifying message signature using key {keyNumber} of {keyCount}",
                        Message   = encryptedMessage,
                        Exception = ex
                    }.Build());
                }

                if (!signatureVerified)
                {
                    continue;
                }

                try
                {
                    contentCleartext = await Decrypt(contentCiphertext, key, iv);
                }
                catch (Exception ex)
                {
                    innerExceptions.Add(ex);
                    _diagnosticService.Emit(new DiagnosticEventBuilder(this, DiagnosticEventType.DecryptionError)
                    {
                        Detail    = $"Error decrypting message content using key {keyNumber} of {keyCount}",
                        Message   = encryptedMessage,
                        Exception = ex
                    }.Build());
                    continue;
                }

                var headers = await UnmarshalHeaders(headerCleartext);

                var content = await UnmarshalContent(contentCleartext);

                return(new Message(headers, content));
            }

            throw new MessageEncryptionException($"Unable to decrypt and verify message using any of {keyCount} available decryption key(s)", innerExceptions);
        }