Внедрение C# TLS 1.1

Некоторое время я немного отчаянно пытался реализовать TLS 1.1 в своем приложении. Причиной этого является использование сокетов SocketType.Raw, поэтому мне не доступны ни SslStream, ни другие высокоуровневые классы.

Пока что я застрял в сообщении Готово в протоколе рукопожатия TLS - продолжаю получать bad_record_mac(20) в ответе сервера. Набор шифров: 0x0005 - TLS_RSA_WITH_RC4_128_SHA.

Вот пример кода о том, что происходит:

    byte[] client_random, server_random = new byte[28];
    byte[] pre_master_secret, master_secret;
    byte[] client_write_MAC_secret, server_write_MAC_secret, client_write_key, server_write_key;
    byte[] handshake_messages, verify_data;

    RSACryptoServiceProvider rsa;
    List<X509Certificate2> certificates = new List<X509Certificate2>();

    //certificates are added
    rsa = (RSACryptoServiceProvider)certificates.First().PublicKey.Key;

    private byte[] ClientKeyExchange()
    {
        pre_master_secret = new byte[48];
        (new Random()).NextBytes(pre_master_secret);

        //version 0302 for TLS 1.1
        pre_master_secret[0] = 3;
        pre_master_secret[1] = 2;

        byte[] cryptedData = rsa.Encrypt(pre_master_secret, false);
        //"1603020086"
        string tmp_string = "100000820080" + Utils.BitConverter.ToString(cryptedData).Replace("-", "");
        AddHandShakeData(Utils.BitConverter.StringToByteArray(tmp_string));
        tmp_string =
            "1603020086"
            + tmp_string
            + "140302000101" //Cipher Change Spec
            + "";

        return Utils.BitConverter.StringToByteArray(tmp_string);
    }

    private void ComputeMasterSecret()
    {
        byte[] label = Utils.BitConverter.StringToByteArray(Utils.BitConverter.ConvertStringToHex("master secret", Encoding.ASCII));

        byte[] seed = new byte[client_random.Length + server_random.Length];
        Buffer.BlockCopy(client_random, 0, seed, 0, client_random.Length);
        Buffer.BlockCopy(server_random, 0, seed, client_random.Length, server_random.Length);

        master_secret = PRF(pre_master_secret, label, seed, 48);
    }

    private void ComputeKeys()
    {
        byte[] label = Utils.BitConverter.StringToByteArray(Utils.BitConverter.ConvertStringToHex("key expansion", Encoding.ASCII));

        byte[] seed = new byte[client_random.Length + server_random.Length];
        Buffer.BlockCopy(client_random, 0, seed, 0, client_random.Length);
        Buffer.BlockCopy(server_random, 0, seed, client_random.Length, server_random.Length);

        byte[] key_material = PRF(master_secret, label, seed, 72);

        client_write_MAC_secret = new byte[20];
        Buffer.BlockCopy(key_material, 0, client_write_MAC_secret, 0, 20);
        server_write_MAC_secret = new byte[20];
        Buffer.BlockCopy(key_material, 20, server_write_MAC_secret, 0, 20);
        client_write_key = new byte[16];
        Buffer.BlockCopy(key_material, 40, client_write_key, 0, 16);
        server_write_key = new byte[16];
        Buffer.BlockCopy(key_material, 56, server_write_key, 0, 16);
    }

    private void ComputeVerifyData()
    {
        byte[] label = Utils.BitConverter.StringToByteArray(Utils.BitConverter.ConvertStringToHex("client finished", Encoding.ASCII));

        SHA1 sha1 = SHA1.Create();
        MD5 md5 = MD5.Create();
        md5.ComputeHash(handshake_messages);
        sha1.ComputeHash(handshake_messages);

        byte[] seed = new byte[md5.HashSize / 8 + sha1.HashSize / 8];
        Buffer.BlockCopy(md5.Hash, 0, seed, 0, md5.HashSize / 8);
        Buffer.BlockCopy(sha1.Hash, 0, seed, md5.HashSize / 8, sha1.HashSize / 8);

        verify_data = PRF(master_secret, label, seed, 12);
    }

    private byte[] PRF(byte[] secret, byte[] label, byte[] seed, int output_size)
    {
        int md5_iterations = (int)Math.Ceiling((double)output_size / 16),
            sha1_iterations = (int)Math.Ceiling((double)output_size / 20);

        byte[] md5_data = new byte[output_size],
               sha1_data = new byte[output_size];

        //особое колдунство для нечетного числа
        byte[] secret_1 = new byte[(int)Math.Ceiling((double)secret.Length / 2)],
               secret_2 = new byte[(int)Math.Ceiling((double)secret.Length / 2)];
        Buffer.BlockCopy(secret, 0, secret_1, 0, secret_1.Length);
        Buffer.BlockCopy(secret, secret.Length / 2, secret_2, 0, secret_2.Length);

        byte[] A = new byte[label.Length + seed.Length];
        Buffer.BlockCopy(label, 0, A, 0, label.Length);
        Buffer.BlockCopy(seed, 0, A, label.Length, seed.Length);

        byte[] tmp = new byte[md5_iterations * 16];

        //A(1) ?
        //A = P_MD5(secret_1, A);

        for (int i = 0; i < md5_iterations; i++)
        {
            A = P_MD5(secret_1, A);
            Buffer.BlockCopy(A, 0, tmp, i * A.Length, A.Length);
        }
        Buffer.BlockCopy(tmp, 0, md5_data, 0, md5_data.Length); //output_size = md5_data.Length

        tmp = new byte[sha1_iterations * 20];

        //does it have to start with A(1) ?
        //A = P_SHA1(secret_2, A);


        for (int i = 0; i < sha1_iterations; i++)
        {
            A = P_SHA1(secret_2, A);
            Buffer.BlockCopy(A, 0, tmp, i * A.Length, A.Length);
        }
        Buffer.BlockCopy(tmp, 0, sha1_data, 0, sha1_data.Length); //output_size = sha1_data.Length

        for (int i = 0; i < output_size; i++)
            md5_data[i] = (byte)(md5_data[i] ^ sha1_data[i]);

        return md5_data;
    }

    private byte[] P_MD5(byte[] secret /*это ключ?*/, byte[] seed /*это дата?*/)
    {
        HMACMD5 HMD5 = new HMACMD5(secret);
        HMD5.ComputeHash(seed);
        return HMD5.Hash;
    }

    private byte[] P_SHA1(byte[] secret /*это ключ?*/, byte[] seed /*это дата?*/)
    {
        HMACSHA1 HSHA1 = new HMACSHA1(secret);
        HSHA1.ComputeHash(seed);
        return HSHA1.Hash;
    }

    private byte[] MAC(byte[] secret, byte[] data)
    {
        byte[] secret_64 = new byte[64];
        Buffer.BlockCopy(secret, 0, secret_64, 0, secret.Length);

        for (int i = 0; i < 64; i++)
            secret_64[i] = (byte)(secret_64[i] ^ (byte)54);

        byte[] xor_output_data = new byte[64 + data.Length];
        Buffer.BlockCopy(secret_64, 0, xor_output_data, 0, 64);
        Buffer.BlockCopy(data, 0, xor_output_data, 64, data.Length);

        SHA1 sha1 = SHA1.Create();
        sha1.ComputeHash(xor_output_data);

        secret_64 = new byte[64];
        Buffer.BlockCopy(secret, 0, secret_64, 0, secret.Length);

        for (int i = 0; i < 64; i++)
            secret_64[i] = (byte)(secret_64[i] ^ (byte)92);

        xor_output_data = new byte[64 + sha1.HashSize / 8];
        Buffer.BlockCopy(secret_64, 0, xor_output_data, 0, 64);
        Buffer.BlockCopy(sha1.Hash, 0, xor_output_data, 64, sha1.HashSize / 8);

        sha1.ComputeHash(xor_output_data);

        return sha1.Hash;
    }

    public void RC4(ref Byte[] bytes, Byte[] key)
    {
        Byte[] s = new Byte[128];
        Byte[] k = new Byte[128];
        Byte temp;
        int i, j;

        for (i = 0; i < 128; i++)
        {
            s[i] = (Byte)i;
            k[i] = key[i % key.GetLength(0)];
        }

        j = 0;
        for (i = 0; i < 128; i++)
        {
            j = (j + s[i] + k[i]) % 128;
            temp = s[i];
            s[i] = s[j];
            s[j] = temp;
        }

        i = j = 0;
        for (int x = 0; x < bytes.GetLength(0); x++)
        {
            i = (i + 1) % 128;
            j = (j + s[i]) % 128;
            temp = s[i];
            s[i] = s[j];
            s[j] = temp;
            int t = (s[i] + s[j]) % 128;
            bytes[x] ^= s[t];
        }
    }

Очевидно, что чтение такого количества кода не лучший способ тратить ваше время, поэтому я хотел бы добавить несколько вопросов, которые, надеюсь, могут сильно помочь:

HMAC_MD5 и HMAC_SHA1 - в реализации.Net они берут ключ и берут входной байт [] для вычисления хеша. Согласно RFC4346: во-первых, мы определяем функцию расширения данных, P_hash(секрет, данные), которая использует одну хеш-функцию для раскрытия секрета и заполнения произвольного количества вывода:

   P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
                          HMAC_hash(secret, A(2) + seed) +
                          HMAC_hash(secret, A(3) + seed) + ...

   Where + indicates concatenation.

   A() is defined as:

   A(0) = seed
   A(i) = HMAC_hash(secret, A(i-1))

Что такое семя? Это данные, из которых мы вычисляем хеш? Секрет = ключ, насколько я понимаю. Кроме того, мы начинаем P_hash с A(1)?

Заранее спасибо!

1 ответ

Seed - это Client_random + Server_random. Вам нужно сохранить их от предыдущих шагов рукопожатия, client_hello и server_hello.

A(0) = seed 
A(i) = HMAC_hash(secret,A(i-1)) for i>0

Выход функции A() состоит из A(1), A(2), A(3)...

Другие вопросы по тегам