<commit_before>﻿using System.Security.Cryptography;
using System.Text.Json;

using Amazon.Kms;
using Amazon.S3;

namespace Amazon.Ses;

public sealed class EmailMessageDecryptor
{
    private readonly KmsClient _kms;

    public EmailMessageDecryptor(KmsClient kms)
    {
        _kms = kms;
    }

    public async Task<byte[]> DecryptAsync(S3Object encryptedBlob)
    {
        ArgumentNullException.ThrowIfNull(encryptedBlob);

        var cekAlg = encryptedBlob.Properties["x-amz-meta-x-amz-cek-alg"];

        if (cekAlg is not "AES/GCM/NoPadding")
        {
            throw new Exception($"Expected AES/GCM/NoPadding. Was '{cekAlg}'");
        }

        var encryptionContext = JsonSerializer.Deserialize<Dictionary<string, string>>(encryptedBlob.Properties["x-amz-meta-x-amz-matdesc"])!;

        byte[] envelopeKey = Convert.FromBase64String(encryptedBlob.Properties["x-amz-meta-x-amz-key-v2"]);
        byte[] envelopeIV  = Convert.FromBase64String(encryptedBlob.Properties["x-amz-meta-x-amz-iv"]);
        long contentLength = long.Parse(encryptedBlob.Properties["x-amz-meta-x-amz-unencrypted-content-length"]);
        int tagLength      = int.Parse(encryptedBlob.Properties["x-amz-meta-x-amz-tag-len"]);

        if (tagLength != 128)
        {
            throw new Exception($"x-amz-tag-len must be 128 bits. Was {tagLength}");
        }

        if (contentLength > 10_000_000)
        {
            throw new Exception("> 10MB");
        }

        string kmsCmkId = encryptionContext["kms_cmk_id"];

        byte[] blobBytes = await encryptedBlob.ReadAsByteArrayAsync().ConfigureAwait(false);

        var tag        = blobBytes[^16..];
        var ciphertext = blobBytes[0..^16];

        var decryptResult = await _kms.DecryptAsync(new DecryptRequest(kmsCmkId, envelopeKey, encryptionContext));
       
        using var aes = new AesGcm(decryptResult.Plaintext);

        var message = new byte[contentLength];

        aes.Decrypt(envelopeIV, ciphertext: ciphertext, tag: tag, plaintext: message);

        return message;
    }
}<commit_msg>Use InvariantCulture to parse integers<commit_after>﻿using System.Globalization;
using System.Security.Cryptography;
using System.Text.Json;

using Amazon.Kms;
using Amazon.S3;

namespace Amazon.Ses;

public sealed class EmailMessageDecryptor
{
    private readonly KmsClient _kms;

    public EmailMessageDecryptor(KmsClient kms)
    {
        _kms = kms;
    }

    public async Task<byte[]> DecryptAsync(S3Object encryptedBlob)
    {
        ArgumentNullException.ThrowIfNull(encryptedBlob);

        var cekAlg = encryptedBlob.Properties["x-amz-meta-x-amz-cek-alg"];

        if (cekAlg is not "AES/GCM/NoPadding")
        {
            throw new Exception($"Expected AES/GCM/NoPadding. Was '{cekAlg}'");
        }

        var encryptionContext = JsonSerializer.Deserialize<Dictionary<string, string>>(encryptedBlob.Properties["x-amz-meta-x-amz-matdesc"])!;

        byte[] envelopeKey = Convert.FromBase64String(encryptedBlob.Properties["x-amz-meta-x-amz-key-v2"]);
        byte[] envelopeIV  = Convert.FromBase64String(encryptedBlob.Properties["x-amz-meta-x-amz-iv"]);
        long contentLength = long.Parse(encryptedBlob.Properties["x-amz-meta-x-amz-unencrypted-content-length"], CultureInfo.InvariantCulture);
        int tagLength      = int.Parse(encryptedBlob.Properties["x-amz-meta-x-amz-tag-len"], CultureInfo.InvariantCulture);

        if (tagLength != 128)
        {
            throw new Exception($"x-amz-tag-len must be 128 bits. Was {tagLength}");
        }

        if (contentLength > 10_000_000)
        {
            throw new Exception("> 10MB");
        }

        string kmsCmkId = encryptionContext["kms_cmk_id"];

        byte[] blobBytes = await encryptedBlob.ReadAsByteArrayAsync().ConfigureAwait(false);

        var tag        = blobBytes[^16..];
        var ciphertext = blobBytes[0..^16];

        var decryptResult = await _kms.DecryptAsync(new DecryptRequest(kmsCmkId, envelopeKey, encryptionContext));
       
        using var aes = new AesGcm(decryptResult.Plaintext);

        var message = new byte[contentLength];

        aes.Decrypt(envelopeIV, ciphertext: ciphertext, tag: tag, plaintext: message);

        return message;
    }
}
{'fraction_non_alphanumeric': 0.1182567726737338, 'fraction_numerical': 0.013191990577149587, 'mean_word_length': 2.9906015037593985, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '3308351', 'n_tokens_mistral': 1407, 'n_tokens_neox': 1316, 'n_words': 240}