SSL - это протокол, позволяющий устанавливать защищенные соединения. На основе SSL 3.0 был разработан TLS. Сейчас, когда говорят об SSL имеют ввиду TLS. TLS - основа безопасности в Интернете.

Как только клиент и сервер решили общаться по протоколу SSL, первое что они делают - процедура установления соединения (рукопожатие - handshake).

Общие понятия

Число

Все данные, представляемые в протоколе записываются в прямом порядке байт. То есть

value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) | ... | byte[n-1];

Вектор

Запись вида

T T'[n];

означает определение нового типа T', который является вектором значений типа T фиксированной длины n. При этом n - количество байт, занимаемых новым типом T'.

note:Ещё раз повторюсь - КОЛИЧЕСТВО БАЙТ! Причем логично, что n должно быть кратно размеру T.

Пример:

opaque Datum[3]; /* Три байта, не интерпретируемые протоколом */
Datum Data[9]; /* 9-байтный вектор значений типа Datum.*/

/*
На C эта запись выглядела бы так:

typedef opaque[3] Datum;
Datum Data[3];
*/

Существует такое понятие как вектор переменной длины. Определение нового типа T' - вектор переменной длины от K до N байт, содержащий значения типа T.

T T'<K..N>

При кодировании таких векторов дополнительно приписывается его текущая длина. Записывается она в виде числа из M байт, где M - число байт, достаточных для хранения числа, отражающего максимальную длину вектора в байтах. Например:

opaque mandatory<300..400>;
 /* вектор не менее 300, но не более 400 байт. Длина записывается числом в 2 байта (достаточно для
 хранения числа 400.*/
uint16 longer<0..800>
 /* вектор чисел типа uint16 с максимальной длиной в 800 байт (то есть может содержать не более
 400 элементов типа uint16. Может быть пустым. Длина записывается числом в 2 байта.*/
note:Как и с вектором фиксированной длины, реальная длина должна быть делителем размера T. То есть не может существовать 17-байтного вектора чисел uint16.

Базовый тип данных - uint8. Определены также

uint8 uint16[2];
uint8 uint24[3];
uint8 uint32[4];
uint8 uint64[8];

В случае представления целого числа в виде вектора байт он представляется как без знаковое целое без лидирующего ноля.

Перечисление

Аналогично языку си:

enum { e1(v1), e2(v2), ..., en(vn) [[, (k)]] } Te;

Соответственно объем, занимаемый одним значением типа "перечисление", равен числу байт, необходимых для хранения максимального значения перечисления.

note:Для задания конкретного числа байт, необходимого для хранения перечисления и служит необязательный параметр n в определении.

Пример:

enum { red(3), green(23), blue(1) } Color;

Color color = Color.blue;
Color color = blue;

Для перечислений, которые никогда не интерпретируются своими значениями последние можно опустить. Например:

enum{ low, medium, high } Amount;

Структура

Аналогично языку си:

struct {
        T1 t1;
        T2 t2;
        ...
        Tn tn;
} [[T]];

Вариант

Поля структуры могут варьироваться в соответствии с каким-либо перечислимым типом.

enum { e1, e2, ..., ek } E;
struct P
        T1 t1;
        T2 t2;
        ...
        Tn tn;
        select (E) {
                case e1: Te1;
                case e2: Te2;
                case e3: case e4: Te3;
                ...
                case en: Ten;
        } [[fv]];
} [[Tv]];

Например, мы можем определить структуру транспорт с полями "скорость движения" и "количество мест". Если это автомобиль, то структура будет содержать поле "объем двигателя", а для велосипеда определим "количество скоростей".

enum { auto, bicycle } TransportType;

struct Transport {
        int16 speed;
        int8  seats;
        select (TransportType) {
                case auto:
                        int8 engine_capacity;
                case bicycle:
                        int8 gears_count;
        }
}
note:в отличии от си перечисления срабатывают одновременно только если они пустые (нет необходимости в конструкции подобной break)

Кодирование криптографических опреаций

Цифровая подпись

struct {
        SignatureAndHashAlgorithm algorithm;
        opaque signature<0..2^16-1>;
} DigitallySigned;

Протокол TLS записи (TLS Record)

Протокол TLS записи является многоуровневым. На каждом уровне сообщения могут иметь поля длины, описания и содержимого (контента).

На вход протокола подаются сообщения, он формирует из них блоки, упаковывает (опционально), добавляет имитовставку, шифрует и передает результат. Полученные данные расшифровываются, проверяются, распаковываются, собираются и доставляются клиенту на более высокий уровень.

Протокол TLS записи используют более сложные протоколы, в зависимости от типа содержимого различают следующие: * протокол установления соединения (handshake protocol) * протокол тревоги (alert protocol) * протокол изменения параметров шифрования (change cipher spec protocol) * протокол данных приложения (application data protocol)

Существует процедура описания новых типов содержимого и регистрации их (посредством IANA) в реестре типов содержимого, в результате чего TLS является расширяемым. Если тип содержимого неизвестен, в обязательном порядке отправляется сигнал unexpected_message.

При проектировании протокола, основывающегося на TLS разработчик должен четко понимать от чего TLS защищает, а от чего нет. К примеру, тип и длина записи не защищены шифрованием. Если эта информация должна быть спрятана, об этом должен позаботиться разработчик.

Хеш-код идентификации сообщений и псевдослучайная функция

TODO: стр. 14

Как упоминалось выше, на уровне TLS записи используется имитовставка для защиты целостности сообщений. В качестве имитовставки в алгоритмах, описанных RFC-5246 используется HMAC (хеш-имитовставка). Дополнительно, необходима конструкция, которая дополняла бы ключи до блоков, достаточных для генерации ключа или валидации?!

Состояния соединения

Состояние TLS соединения определяет алгоритм сжатия, шифрования, имитовставки, а также параметры алгоритмов - ключ имитовставки и ключи шифрования как на запись, так и на чтение. Выделяют 4 состояния соединения:

  1. current_write
  2. current_read
  3. pending_write
  4. pending_read

Обработка всех сообщений производится в текущих состояниях (первые два). Параметры ожидающих состояний (последние два) могут быть установлены с помощью протокола установления соединения (handshake protcol), а затем по протоколу изменения параметров шифрования ожидающее состояние может заместить текущее, которое в свою очередь очищается.

note:Не позволяется делать замещение текущего состояния ожидаемым, если ожидаемое не было инициализировано соответствующими параметрами.

Параметры TLS соединения:

  • connection end (оконечная точка соединения) Является ли сущность клиентом или сервером в данном соединении.

    enum { server, client } ConnectionEnd;
    
  • PRF algorithm (псевдослучайная функция) Алгоритм, используемый для генерации ключей из главного ключа (master key).

    enum{ tls_prf_sha256 } PRFAlgorithm;
    
  • bulk encryption algorithm (алгоритм шифрования) Спецификация включает в себя размер ключа, является ли шифрование блочными или AEAD, размер блока шифра (если имеется), и длины явного и неявного инициализирующих векторов.

    enum { null, rc4, 3des, aes } BulkCipherAlgorithm;
    
  • MAC algorithm (алгоритм имитовставки) Спецификация включает размер значения, возвращаемого алгоритмом.

    enum { stream, block, aead } CipherType;
    
  • compression algorithm (алгоритм сжатия) Спецификация включает всю информацию, необходимую для сжатия

    enum { null(0), (255) } CompressionMethod;
    
  • master secret 48-байтный ключ, вырабатываемый членами соединения

  • client random 32-байтное значение, вырабатываемое клиентом

  • server random 32-байтное значение, вырабатываемое сервером

struct {
        ConnectionEnd       entity;
        PRFAlgorithm        prf_algorithm;
        BulkCipherAlgorithm bulk_cipher_algorithm;
        CipherType          cipher_type;
        uint8               enc_key_length;
        uint8               block_length;
        uint8               fixed_iv_length;
        uint8               record_iv_length;
        MACAlgorithm        mac_algorithm;
        uint8               mac_length;
        uint8               mac_key_length;
        CompressionMethod   compression_algorithm;
        opaque              master_secret[48];
        opaque              client_random[32];
        opaque              server_random[32];
} SecurityParameters;

На уровне записи, используя параметры, генерируются (алгоритм генерации описан далее)

  • ключ имитовставки клиента на запись
  • ключ имитовставки сервера на запись
  • ключ шифрования клиента на запись
  • ключ шифрования сервера на запись
  • инициализирующий вектор клиента на запись
  • инициализирующий вектор сервера на запись

Клиентские параметры на запись используются сервером при получении и обработке сообщений и наоборот, соответственно.

После генерации ключей и установления параметров, состояния соединения могут стать текущими состояниями. Эти текущие состояния ОБЯЗАНЫ обновляться для каждой обработанной записи.

Элементы состояния соединения:

  • compression state (состояние сжатия)
  • cipher state (состояние шифрования)
  • MAC key (ключ имитовставки)
  • sequence number (номер) Состояния соединения нумеруются (чтение и запись отдельно). Номер устанавливается нулевым, когда состояние соединения становится активным. Номера имеют тип uint16. С каждой записью номер увеличивается на 1.

Фрагментация

Уровень записи получает данные от более высоких уровней в непустых блоках заданного размера.

Уровень записи фрагментирует блоки в TLSPlaintext записи (см. ниже). Размер записи не может превышать 2^14 байт. Границы сообщения клиента не сохраняются. То есть в одной записи может содержаться несколько сообщений или наоборот в нескольких записях может быть одно сообщение.

struct {
        uint8 major;
        uint8 minor;
} ProtocolVersion;

enum {
        change_cipher_spec(20),
        alert(21),
        handshake(22),
        application_data(23),
        (255)
} ContentType;

struct {
        ContentType type;
        ProtocolVersion version; /*** TLS 1.2 uses {3,3} version ***/
        uint16 length;
        opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

Для содержимого типа установление соединения (handshake), тревоги (alert), изменение параметров шифрования (change cipher spec) TLS записи не могут быть нулевой длины, а для данных (application data) могут (например для усложнения анализа трафика).

note:Записи разных типов могут перемешиваться. Данные (application data), как правило, имеют меньший приоритет. Тем не менее записи обязаны отправляться по сети в том же порядке, в котором они проходят уровень TLS record. Получатель должен получить и обработать перемешанные записи с данными только после завершения установления соединения.

Сжатие и распаковка данных

Все записи сжимаются и распаковываются в соответствии с алгоритмом, определенным в текущем состоянии. Алгоритм сжатия и распаковки используется всегда, просто иногда он определен как CompressionMethod.null. Алгоритм сжатия переводит TLSPlaintext запись в TLSCompressed структуру. Алгоритм сжатия TLS описан в RFC-2749.

Должно использоваться сжатие без потерь, не увеличивающее длину содержимого более чем на 1024 байт. Если при распаковке получился фрагмент, длина которого больше 2^14 байт (больше максимально возможного), то функция распаковки должна сообщить об ошибке.

struct {
        ContentType type; /*** как и TLSPlaintext ***/
        ProtocolVersion version; /*** как и в TLSPlaintext ***/
        uint16 length; /*** не больше 2^14 + 1024 ***/
        opaque fragment[TLSCompressed.length];
} TLSCompressed;
note:В случае с CompressionMethod.null структуры TLSPlaintext и TLSCompressed совпадают.

Защита записей

Функции шифрования и имитовставки преобразуют TLSCompressed структуру в TLSCiphertext. Функции расшифрования осуществляют обратную операцию. Имитовставка имеет номер для обнаружения пропущенных или повторяющихся сообщений.

struct {
        ContentType type;
        ProtocolVersion version;
        uint16 length; /*** не превышет 2^14 + 2048 ***/
        select (SecurityParameters.cipher_type) {
                case stream: GenericStreamCipher;
                case block:  GenericBlockCipher;
                case aead:   GenericAEADCipher;
        } fragment; /*** зашифрованный TLSCompressed.fragment ***/
} TLSCiphertext;

Пустой или стандартный потоковый шифр

stream-ciphered struct {
        opaque Content[TLSCompressed.length];
        opaque MAC[SecurityParameters.mac_length];
} GenericStreamCipher;

Имитовставка генерируется следующим образом:

  MAC(MAC_write_key, seq_num
          + TLSCompressed.type
          + TLSCompressed.version
          + TLSCompressed.length
          + TLSCompressed.fragment
  );

"+" - это конкатенация
note:Имитовставка вычисляется до шифрования. Потоковый шифр шифрует блок, включая MAC. TODO:

Блочное шифрование

struct {
        /*** вектор инициализации ***/
        opaque IV(SecurityParameters.record_iv_length];
        block-ciphered struct {
                opaque content[TLSCompressed.length];
                opaque MAC[SecurityParameters.mac_length];
                uint8 padding[GenericBlockCipher.padding_length];
                uint8 padding_length;
        };
} GenericBlockCipher;

padding

Дополнение plaintext до длины, кратной длине блока блочного шифра. Дополнение не может быть длинее 255 байт. Можно делать дополнение более длинным, чем это необходимо (но не длинее 255) для предотвращение атак на протокол, основанных на анализе длин сообщений.

padding_length

Длина блока padding. Составляет ровно 1 байт.

Пусть, например, размер блока равен 8, длина содержимого 61 байт, а имитовставка имеет размер 20 байт, тогда то, что должно шифроваться имеет длину 81 байт и 1 байт отводится на длину дополнения. Таким образом дополнение должно состоять минимум из 8*11 - (81 + 1) = 6 байт. Оно может быть и больше: 6 + 8 байт, 6 + 8 + 8 байт, ..., 254 байта. Тогда последние 8 байт (для минимального дополнения) GenericBlockCipher будут

0x## 0x06 0x06 0x06 0x06 0x06 0x06 0x06

0x## - последний байт имитовставки

AEAD шифры

Аутентифицирующее шифрование (см. RFC-5116 Authenticated Encryption with Associated Data) - форма шифрования при которой в дополнение к обеспечению конфиденциальности открытого текста, предоставляется возможность проверки целостности и подлинности. Authenticated Encryption with Associated Data (AEAD) шифрование предоставляет возможность проверки целостности и подлинности некоторых дополнительных данных (также называемых дополнительные подлинные данные), которые не зашифрованы.

Многие приложения для обеспечения целостности используют имитовставку, а для обеспечения конфиденциальности какой-то алгоритм шифрования. При этом используется два независимых ключа. Идея AEAD состоит в том, чтобы заменить этот подход единым алгоритмом.

struct {
        opaque nonce_explicit[SecurityParameters.record_iv_length];
        aead-ciphered struct {
                opaque content[TLSCompressed.length];
        };
} GenericAEADCipher;

дополнительные подлинные данные определяются следующим образом:

additional_data = seq_num + TLSCompressed.type + TLSCompressed.version +
                TLSCompressed.length;

"+" - это конкатенация

Генерация ключа

Главный ключ раскрывается в последовательность байт, которая разделяется на клиентский ключ на запись, серверный ключ на запись а также клиентский и серверный ключи имитовставки на запись. Некоторые AEAD шифры могут дополнительно нуждаться в клиентском и серверном инициализирующих векторах.

раскрытие в последовательность байт:

key_block = PRF(SecurityParameters.master_secret,
        "key expansion",
        SecurityParameters.server_random +
        SecurityParameters.client_random);

разделение:

client_write_MAC_key[SecurityParameters.mac_key_length];
server_write_MAC_key[SecurityParameters.mac_key_length];
client_write_key[SecurityParameters.enc_key_length];
server_write_key[SecurityParameters.enc_key_length];
client_write_IV[SecurityParameters.fixed_iv_length];
server_write_IV[SecurityParameters.enc_key_length];

Протоколы установления соединения (handshaking protocols)

Существует 3 подпротокола по которым стороны вырабатывают соглашения относительно параметров (security parameters) соединения на уровне записи для аутентификации себя, вырабатывания начальных параметров и сообщений об ошибках.

В процессе установления соединения узлы вырабатывают сессию, состоящую из следующих элементов:

  • идентификатор сессии
  • сертификаты сторон
  • метод сжатия
  • параметры алгоритма шифрования
  • главный ключ
  • восстанавливаемость

Эти элементы далее могут быть использованы для составления параметров протокола TLS записи. С помощью данной сессии можно устанавливать множество TLS соединений.

Протокол изменения параметров шифрования

Протокол состоит из одного сообщения, которое шифруется и сжимается по текущему (не ожидаемому, а текущему) состоянию. Сообщение состоит из одного байта 0x01.

struct {
        enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;

Сообщение ChangeCipherSpec посылается как клиентом, так и сервером. Получатель данного сообщения должен заменить текущее состояние чтения ожидаемым состоянием чтения, а отправитель заменяет ожидаемое состояние записи текущим состоянием записи.

Сообщение ChangeCipherSpec посылается в процессе установления соединения после того как параметры безопасности согласованы, но до посылки подтверждающего сообщения Finished.

Протокол тревоги

Одним из типов содержимого является тип тревоги. Сообщение тревоги содержит важность сообщения и описание. Сообщение важности fatal незамедлительно прерывает соединение. В данном случае может происходить какое-то общение между узлами, но идентификатор сессии становится недействительным. Таким образом, сессия не может использоваться для установления новых соединений. Сообщение тревоги шифруется и сжимается, как и остальные.

enum { warning(1), fatal(2), (255) } AlertLevel;

enum {
        close_notify(0),
        unexpected_message(10),
        bad_record_mac(20),
        decryption_failed_RESERVED(21),
        record_overflow(22),
        decompression_failure(30),
        handshake_failure(40),
        no_certificate_RESERVED(41),
        bad_certificate(42),
        unsupported_certificate(43),
        certificate_revoked(44),
        certificate_expired(45),
        certificate_unknown(46),
        illegal_parameter(47),
        unknown_ca(48),
        access_denied(49),
        decode_error(50),
        decrypt_error(51),
        export_restriction_RESERVED(60),
        protocol_version(70),
        insufficient_security(71),
        internal_error(80),
        user_canceled(90),
        no_renegotiation(100),
        unsupported_extension(110),
        (255)
} AlertDescription;

struct {
        AlertLevel level;
        AlertDescription description;
} Alert;

Закрывающие сообщения тревоги

Любая сторона может завершить соединение. Для этого она должна послать close_notify. Сообщение close_notify говорит о том, что отправитель больше не будет посылать сообщений. Перед закрытием соединения (если не происходило каких-то критических ошибок) закрывающая сторона в обязательном порядке должна послать close_notify, получив close_notify необходимо послать его в ответ, хотя закрывающая сторона вовсе не должна его дожидаться.

При получении сообщения тревоги с критической ошибкой соединение закрывается. При получении сообщений типа warning соединение не должно закрываться. Если всё-таки его необходимо по какой-лдибо причине закрыть, необходимо послать сообщение о критической ошибке. Таким образом сообщения warning - не очень-то полезная штука и редко посылаются.

Определены следующие сообщения об ошибках:

TODO:

  • unexpected_message

    An inappropriate message was received. This alert is always fatal and should never be observed in communication between proper implementations.

  • bad_record_mac

    This alert is returned if a record is received with an incorrect MAC. This alert also MUST be returned if an alert is sent because a TLSCiphertext decrypted in an invalid way: either it wasn’t an even multiple of the block length, or its padding values, when checked, weren’t correct. This message is always fatal and should never be observed in communication between proper implementations (except when messages were corrupted in the network).

  • decryption_failed_RESERVED

    This alert was used in some earlier versions of TLS, and may have permitted certain attacks against the CBC mode [CBCATT]. It MUST NOT be sent by compliant implementations.

  • record_overflow

    A TLSCiphertext record was received that had a length more than 2^14+2048 bytes, or a record decrypted to a TLSCompressed record with more than 2^14+1024 bytes. This message is always fatal and should never be observed in communication between proper implementations (except when messages were corrupted in the network).

  • decompression_failure

    The decompression function received improper input (e.g., data that would expand to excessive length). This message is always fatal and should never be observed in communication between proper implementations.

  • handshake_failure

    Reception of a handshake_failure alert message indicates that the sender was unable to negotiate an acceptable set of security parameters given the options available. This is a

  • no_certificate_RESERVED

    This alert was used in SSLv3 but not any version of TLS. NOT be sent by compliant implementations. It MUST NOT be sent by compliant implementations.

  • bad_certificate

    A certificate was corrupt, contained signatures that did not verify correctly, etc.

  • unsupported_certificate

    A certificate was of an unsupported type.

  • certificate_revoked

    A certificate was revoked by its signer.

  • certificate_expired

    A certificate has expired or is not currently valid.

  • certificate_unknown

    Some other (unspecified) issue arose in processing the certificate, rendering it unacceptable. illegal_parameter A field in the handshake was out of range or inconsistent with other fields. This message is always fatal.

  • unknown_ca

    A valid certificate chain or partial chain was received, but the certificate was not accepted because the CA certificate could not be located or couldn’t be matched with a known, trusted CA. This message is always fatal.

  • access_denied

    A valid certificate was received, but when access control was applied, the sender decided not to proceed with negotiation. This message is always fatal.

  • decode_error

    A message could not be decoded because some field was out of the specified range or the length of the message was incorrect. This message is always fatal and should never be observed in communication between proper implementations (except when messages were corrupted in the network).

  • decrypt_error

    A handshake cryptographic operation failed, including being unable to correctly verify a signature or validate a Finished message. This message is always fatal.

  • export_restriction_RESERVED

    This alert was used in some earlier versions of TLS. be sent by compliant implementations. It MUST NOT be sent by compliant implementations.

  • protocol_version

    The protocol version the client has attempted to negotiate is recognized but not supported. (For example, old protocol versions might be avoided for security reasons.) This message is always fatal.

  • insufficient_security

    Returned instead of handshake_failure when a negotiation has failed specifically because the server requires ciphers more secure than those supported by the client. This message is always fatal.

  • internal_error

    An internal error unrelated to the peer or the correctness of the protocol (such as a memory allocation failure) makes it impossible to continue. This message is always fatal.

  • user_canceled

    This handshake is being canceled for some reason unrelated to a protocol failure. If the user cancels an operation after the handshake is complete, just closing the connection by sending a close_notify is more appropriate. This alert should be followed by a close_notify. This message is generally a warning.

  • no_renegotiation

    Sent by the client in response to a hello request or by the server in response to a client hello after initial handshaking. Either of these would normally lead to renegotiation; when that is not appropriate, the recipient should respond with this alert. At that point, the original requester can decide whether to proceed with the connection. One case where this would be appropriate is where a server has spawned a process to satisfy a request; the process might receive security parameters (key length, authentication, etc.) at startup, and it might be difficult to communicate changes to these parameters after that point. This message is always a warning.

  • unsupported_extension

    sent by clients that receive an extended server hello containing an extension that they did not put in the corresponding client hello. This message is always fatal.

Добавление новых ошибок производится через IANA.

Протокол установления соединения (детально)

Что происходит во время установления соединения

  1. Обмениваемся hello сообщениями для согласования алгоритмов, обмена случайными значениями и проверки возобновляемости сессии.
  2. Обмениваемся необходимыми параметрами шифрования, вырабатываем premaster ключ
  3. Обмениваемся сертификатами и информацией аутентификации
  4. Генерируем общий ключ из premaster и обмениваемся случайными значениями
  5. Применяем параметры к уровню записи (TLS Record)
  6. Проверяем, что клиент и сервер получили одинаковые параметры безопасности и установление соединения произошло успешно.

Как происходит установление соединения

Клиент шлёт сообщение ClientHello, на который сервер должен ответить сообщением ServerHello (либо на стороне сервера возникает критическая ошибка и соединение обрывается). При этом устанавливаются следующие атрибуты: версия протокола, идентификатор сессии, набор шифров и методы сжатия. Дополнительно генерируются два случайных значения ClientHello.random и ServerHello.random.

Следом за сообщениями hello сервер шлёт сертификат (сообщение Crtificate), если необходимо подтвердить подлинность сервера. Дополнительно может быть послано ServerKeyExchange сообщение (если у сервера нет сертификата или его сертификат может быть использован только для подписи). Если сервер прошёл проверку подлинности, он может запросить сертификат клиента (если этого требуется в выбранной системе шифрования). Далее сервер посылает сообщение ServerHelloDone. Это сигнализирует о том, что этап обмена hello-сообщениями завершён. Если у клиента был запрошен сертификат, он должен его послать. Затем клиент посылает сообщение ClientKeyExchange (содержимое сообщения зависит от выбранного алгоритма шифрования). Если клиент послал сертификат с возможностью цифровой подписи, то он также плсылает подписанной сообщение CertificateVerify, чтобы показать, что у него имеется закрытый ключ для подписи.

Далее клиент шлет ChangeCipherSpec сообщение и подменяет текущий шифр ожидаемым, после чего незамедлительно шлёт Finished сообщение зашифрованное новым шифром. В ответ сервер пошлёт своё сообщение ChangeCipherSpec, меняет шифр и шлёт Finished. С этого момента установление соединения закончено и можно обмениваться данными.

Client                        Server

ClientHello        -------->
                              ServerHello
                              Certificate*
                              ServerKeyExchange*
                              CertificateRequest*
                   <--------  ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished           -------->
                              [ChangeCipherSpec]
                   <--------  Finished
Application Data   <------->  Application Data

* - опционально

Если клиент с сервером решают продолжить предыдущую сессию или клонировать существующую (чобы не согласовывать новые параметры безопасности), то обмен сообщениями будет строится следующим образом:

Клиент шлет ClientHello, используя идентификатор сессии, которую хотел бы продолжить. Сервер проверяет свой кэш сессий в поисках нужной. Если сессия найдена, то сервер пытается установить соединение с помощью данной сессии, он пошлёт ServerHello с тем же идентификатором сессии, они меняют шифры посредством ChangeCipherSpec сообщений и обмениваются Finished. Если же сессия отсутствует в кэше сервера, то производится полная процедура установления соединения.

Client                        Server

ClientHello        -------->
                              ServerHello
                              [ChangeCipherSpec]
                   <--------  Finished
[ChangeCipherSpec]
Finished           -------->
Application Data   <------->  Application Data

Протокол установления соединения на более низком уровне использует протокол TLS записи для передачи сообщений.

enum {
        hello_request(0),
        client_hello(1),
        server_hello(2),
        certificate(11),
        server_key_exchange (12),
        certificate_request(13),
        server_hello_done(14),
        certificate_verify(15),
        client_key_exchange(16),
        finished(20), (255)
} HandshakeType;

struct {
        HandshakeType msg_type;
        uint24 length;
        select (HandshakeType) {
                case hello_request:       HelloRequest;
                case client_hello:        ClientHello;
                case server_hello:        ServerHello;
                case certificate:         Certificate;
                case server_key_exchange: ServerKeyExchange;
                case certificate_request: CertificateRequest;
                case server_hello_done:   ServerHelloDone;
                case certificate_verify:  CertificateVerify;
                case client_key_exchange: ClientKeyExchange;
                case finished:            Finished;
        } body;
} Handshake;

HelloRequest

Сообщение HelloRequest может быть послано сервером в любой момент. Клиент не обязан отвечать, (а может ответить и no_renegatiation сообщением тревоги), но сервер в таком случае в праве разорвать соединение. Послав HelloRequest сервер должен дождаться какого-то результата процедуры установления соединения.

Структура сообщения:

struct {} HelloRequest;

ClientHello

Структура сообщения:

struct {
        ProtocolVersion client_version;
        Random random;
        SessionID session_id;
        CipherSuite cipher_suites<2..2^16-2>;
        CompressionMethod compression_methods<1..2^8-1>;
        select (extensions_present) {
                case false:
                        struct {};
                case true:
                        Extension extensions<0..2^16-1>;
        };
} ClientHello;

Random

struct {
        uint32 gmt_unix_time;
        opaque random_bytes[28];
} Random;
  • gmt_unix_time - дата и время в стандартном UNIX-времени (секунды с начала эпохи). TLS не определяет, что часы должны идти правильно. Хотя приложение на более высоком уровне может учитывать этот параметр.
  • random_bytes - 28 байт, сгенерированных генератором случайных чисел.

SessionID

Если не пусто, то продолжить сессию. Если пусто - начать новую.

opaque SessionID<0..32>;

Набор шифрования

Комбинация криптоалгоритмов, поддерживаемых клиентом в порядке желательности для клиента. Каждый "набор шифрования" содержит алгоритм распределения ключей, алгоритм шифрования (включая длину закрытого ключа), алгоритм имитовставки и псевдослучайную функцию. Сервер выберет подходящий набор, либо вернет критическую ошибку.

uint8 CipherSuite[2];

Возможные варианты:

Без шифрования (может использоваться только как инициализирующее значение).

CipherSuite TLS_NULL_WITH_NULL_NULL = { 0x00,0x00 };

Для серверов, поддерживающих RSA (должен быть сертификат):

CipherSuite   TLS_RSA_WITH_NULL_MD5                ={  0x00, 0x01  };
CipherSuite   TLS_RSA_WITH_NULL_SHA                ={  0x00, 0x02  };
CipherSuite   TLS_RSA_WITH_NULL_SHA256             ={  0x00, 0x3B  };
CipherSuite   TLS_RSA_WITH_RC4_128_MD5             ={  0x00, 0x04  };
CipherSuite   TLS_RSA_WITH_RC4_128_SHA             ={  0x00, 0x05  };
CipherSuite   TLS_RSA_WITH_3DES_EDE_CBC_SHA        ={  0x00, 0x0A  };
CipherSuite   TLS_RSA_WITH_AES_128_CBC_SHA         ={  0x00, 0x2F  };
CipherSuite   TLS_RSA_WITH_AES_256_CBC_SHA         ={  0x00, 0x35  };
CipherSuite   TLS_RSA_WITH_AES_128_CBC_SHA256      ={  0x00, 0x3C  };
CipherSuite   TLS_RSA_WITH_AES_256_CBC_SHA256      ={  0x00, 0x3D  };

Диффи-Хэллман (у сервера должен быть серктификат, у клиента он тоже может быть запрошен).

CipherSuite   TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA     ={   0x00, 0x0D   };
CipherSuite   TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA     ={   0x00, 0x10   };
CipherSuite   TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA    ={   0x00, 0x13   };
CipherSuite   TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA    ={   0x00, 0x16   };
CipherSuite   TLS_DH_DSS_WITH_AES_128_CBC_SHA      ={   0x00, 0x30   };
CipherSuite   TLS_DH_RSA_WITH_AES_128_CBC_SHA      ={   0x00, 0x31   };
CipherSuite   TLS_DHE_DSS_WITH_AES_128_CBC_SHA     ={   0x00, 0x32   };
CipherSuite   TLS_DHE_RSA_WITH_AES_128_CBC_SHA     ={   0x00, 0x33   };
CipherSuite   TLS_DH_DSS_WITH_AES_256_CBC_SHA      ={   0x00, 0x36   };
CipherSuite   TLS_DH_RSA_WITH_AES_256_CBC_SHA      ={   0x00, 0x37   };
CipherSuite   TLS_DHE_DSS_WITH_AES_256_CBC_SHA     ={   0x00, 0x38   };
CipherSuite   TLS_DHE_RSA_WITH_AES_256_CBC_SHA     ={   0x00, 0x39   };
CipherSuite   TLS_DH_DSS_WITH_AES_128_CBC_SHA256   ={   0x00, 0x3E   };
CipherSuite   TLS_DH_RSA_WITH_AES_128_CBC_SHA256   ={   0x00, 0x3F   };
CipherSuite   TLS_DHE_DSS_WITH_AES_128_CBC_SHA256  ={   0x00, 0x40   };
CipherSuite   TLS_DHE_RSA_WITH_AES_128_CBC_SHA256  ={   0x00, 0x67   };
CipherSuite   TLS_DH_DSS_WITH_AES_256_CBC_SHA256   ={   0x00, 0x68   };
CipherSuite   TLS_DH_RSA_WITH_AES_256_CBC_SHA256   ={   0x00, 0x69   };
CipherSuite   TLS_DHE_DSS_WITH_AES_256_CBC_SHA256  ={   0x00, 0x6A   };
CipherSuite   TLS_DHE_RSA_WITH_AES_256_CBC_SHA256  ={   0x00, 0x6B   };

Диффи-Хэллман (анонимно)

CipherSuite  TLS_DH_anon_WITH_RC4_128_MD5         =  {  0x00, 0x18  };
CipherSuite  TLS_DH_anon_WITH_3DES_EDE_CBC_SHA    =  {  0x00, 0x1B  };
CipherSuite  TLS_DH_anon_WITH_AES_128_CBC_SHA     =  {  0x00, 0x34  };
CipherSuite  TLS_DH_anon_WITH_AES_256_CBC_SHA     =  {  0x00, 0x3A  };
CipherSuite  TLS_DH_anon_WITH_AES_128_CBC_SHA256  =  {  0x00, 0x6C  };
CipherSuite  TLS_DH_anon_WITH_AES_256_CBC_SHA256  =  {  0x00, 0x6D  };

{ 0x00, 0x1C } и { 0x00, 0x1D } заезервированы, во избежание коллизий с SSL.

Метод сжатия

Обязательно должен поддерживаться режим без сжатия.

enum { null(0), (255) } CompressionMethod;

ServerHello

struct {
        ProtocolVersion server_version;
        Random random;
        SessionID session_id;
        CipherSuite cipher_suite;
        CompressionMethod compression_mehtod;
        select (extensions_present ) {
                case false:
                        struct {};
                case true:
                        Extension extemsions<0..2^16-1>;
        };
} ServerHello;

В принципе, понятно как заполняется указанная структура. Некоторые замечания:

  • session_id

    Если сервер вернёт нулевой session_id, это будет означать, что сессия не может быть продолжена.

  • cipher_suite, compression_mehtod

    Сервер должен выбрать один из предоставленных клиентом.

note:Дочитав до этого момента можно уже что-то "пощупать" - см. SSL-практика.

Comments

comments powered by Disqus