DTLS
简介
DTLS:Datagram Transport Layer Security:数据报传输层安全。
背景
SSL:Secure Sockets Layer:安全套接层。为网络通信提供安全及数据完整性保障。SSL 3.0作为历史文献IETF通过RFC 6101发表。
TLS:Transport Layer Security:传输层安全。IETF将SSL标准化,即RFC 2246,并将其称为TLS。
SSL/TLS协议并不能用于UDP协议,但UDP同样有安全传输的需求,于是有了DTLS协议。DTLS基于TLS扩展,使之支持UDP协议。DTLS 1.0 基于 TLS 1.1,DTLS 1.2 基于 TLS 1.2。
概述
DTLS是一个保证传输安全的协议,DTLS采用加密保证数据机密性。
在《密码学简述》一文中提到:非对称加密的计算开销非常大,因此在一个会话通信中,采用对称加密的方式保证数据机密性更为合适,而非对称加密可以应用在对称加密的密钥分发、数字签名等方面。
DTLS使用非对称加密分发对称加密的密钥,即DTLS握手。DTLS握手完成后,即可使用对称加密的密钥进行加密通信。
DTLS的安全性在以下几个方面得到了保障:
- DTLS在握手时进行cookie校验,防止dos攻击(放重放)。
- DTLS在握手时具有身份证书和数据签名,防止篡改和冒充。
- DTLS在握手时使用公钥密码进行加密,防止秘密密钥被泄露。
- DTLS在通信时使用秘密密钥进行加密,防止通信内容泄露(机密性)。
- DTLS在传输方面使用HMAC对数据完整性进行校验,防止数据被篡改(完整性)。
DTLS由两部分组成:Record Layer 和 Handshake Protocol
- Record Layer:描述了DTLS中 DTLSPlaintext,DTLSCompressed,DTLSCiphertext 三种数据的格式。包含:change_cipher_spec,alert,handshake,application_data 等多种ContentType。
- Handshake Layer:使用Record Layer协商对称加密的密钥。
DTLS Record Layer
Record Struct
enum {
change_cipher_spec(20), alert(21), handshake(22),
application_data(23), (255)
} ContentType;
struct {
ContentType type;
ProtocolVersion version;
uint16 epoch; // New field
uint48 sequence_number; // New field
uint16 length;
opaque fragment[DTLSPlaintext.length];
} DTLSPlaintext;
struct {
ContentType type;
ProtocolVersion version;
uint16 epoch; // New field
uint48 sequence_number; // New field
uint16 length;
opaque fragment[DTLSCompressed.length];
} DTLSCompressed;
struct {
ContentType type;
ProtocolVersion version;
uint16 epoch; // New field
uint48 sequence_number; // New field
uint16 length;
select (CipherSpec.cipher_type) {
case block: GenericBlockCipher;
case aead: GenericAEADCipher; // New field
} fragment;
} DTLSCiphertext;
- type:ContentType:用来标识当前record是4种协议中的哪一种(change_cipher_spec, alert, handshake, application_data)。
- version:定义当前协商的DTLS协议版本(如:DTLS 1.2)。
- epoch:译为纪元,时期;在 Handshake 之后进入 next epoch,即 epoch + 1
- sequence_number:序列号。
- length:fragment 长度。
- fragment:当前 record 的 payload。
Wireshark Capture
Figure 1: DTLS CaptureFigure 2: Handshake Message
Figure 3: Application Data
由此可见:
- 一个 Record Layer 封装4种协议中的某一种的一个 Fragment。
- 例如:
- Handshake Protocol: Client Hello 的一个 Fragment。
- 通常,一个 Fragment 就是一个完整的 Client Hello Message。
- Handshake Protocol: Certifycate 的一个 Fragment。
- 有时,一个 Fragment 装不下一个 Certificate (会超出MTU的大小),因此,Certificate 通常会被划分成多个 Fragment 进行传输。
- Handshake Protocol: Client Hello 的一个 Fragment。
- 例如:
- 多个 Record Layer 封装在一个 UDP Packet 中进行传输。
- 例如:
- DTLSv1.2 Record Layer: Handshake Protocol: Server Hello
- DTLSv1.2 Record Layer: Handshake Protocol: Certificate
- DTLSv1.2 Record Layer: Handshake Protocol: Server Key Exchange
- DTLSv1.2 Record Layer: Handshake Protocol: Certificate Request
- DTLSv1.2 Record Layer: Handshake Protocol: Server Hello Done
- 例如:
注:
- 一个 Fragment 不一定是一个完整的 message,正如其名:Fragment(分段),Fragment 可能只是 message 的一部分。
- 例如:当 Handshake 的 Certificate比较大时,我们需要将 Certificate分段,Certificate 将由多个 Handshake Protocol 组成,被封装在多个 Record Layer 中进行传输。
DTLS Handshake
握手是为了校验通信双方身份,并安全地协商密钥,因此在握手的过程会通过 Message 验证证书、协商加密算法和密钥参数、使用非对称加密交换对称密钥、并使用数字签名(MAC鉴权摘要)保证数据不被篡改。
DTLS 使用多种安全机制提供安全服务。
DTLS提供:序号(顺序),分段,重传 等机制以应对可能存在的需求。
DTLS定义了基于cookie验证的机制来预防Dos攻击。
Handshake Process
Client Server
------ ------
ClientHello --------> Flight 1
<------- HelloVerifyRequest Flight 2
(contains cookie)
ClientHello --------> Flight 3
(with cookie)
ServerHello \
Certificate* \
ServerKeyExchange* Flight 4
[CertificateRequest] /
<-------- ServerHelloDone /
[Certificate] \
ClientKeyExchange \
[CertificateVerify] Flight 5
[ChangeCipherSpec] /
Finished --------> /
[ChangeCipherSpec] \ Flight 6
<-------- Finished /
- Flight:表示连续的一组 Message,可以理解为:一方的一个 Flight 发完后才轮到 对方 的回合。
- [CertificateRequest]、[Certificate]、[CertificateVerify] 和[ChangeCipherSpec] 是可选的,且 [CertificateRequest]、[Certificate]、[CertificateVerify] 是逐一向前依赖的。
-
特别说明:如果可以(小于MTU),一个 Flight 可以用同一个UDP包传输。
Figure 5: one Flight in one UDP Packet
Handshake Message Format
struct {
HandshakeType msg_type;
uint24 length;
uint16 message_seq; // New field
uint24 fragment_offset; // New field
uint24 fragment_length; // New field
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case hello_verify_request: HelloVerifyRequest; // New type
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;
- msg_type:handshake message 有多种类型。
- length:数据的总长度
- message_seq:message layer 的序列号,有别于 record layer 的序列号
- fragment_offset:分段的偏移,当message未被分段时,fragment_offset = 0
- fragment_length:分段的总长度,当message未被分段时,fragment_length = length
- body:根据 length、fragment_offset、fragment_length 重组,根据 msg_type 解析
Handshake Message Type
enum {
hello_request(0), client_hello(1), server_hello(2),
hello_verify_request(3), // New field
certificate(11), server_key_exchange (12),
certificate_request(13), server_hello_done(14),
certificate_verify(15), client_key_exchange(16),
finished(20), (255) } HandshakeType;
在 DTLS Handshake 中,通信的双方有 client 和 server 之分,因此在 Handshake Message Type 中定义了 client_hello、server_hello、server_key_exchange 、server_hello_done 和 client_key_exchange 共5个以 client/server 为前缀的枚举值。
Handshake Message
基于TLS,扩展了HelloVerifyRequest,TLS Handshake详见: RFC 5246
HelloRequest
struct { } HelloRequest;
- server 可以在任何时候向 client 发送此消息,当 client 收到 HelloRequest,client 应当发送 ClientHello 作为答复。
- 在实际的大多数场景,server 不会向 client 发送 HelloRequest,而是由 client 主动向 server 发起 ClientHello。
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;
- client_version:client 希望使用的DTLS版本。
- random:client 随机生成的随机结构。
- session_id:client 希望用于此连接的会话 ID。如果 session_id 为空,则意味着无可用的session_id 或是client希望生成新的安全参数。
- cipher_suites:client 支持的加密套件列表,按 client 偏好排序。如果 session_id 字段不为空(session_id不会空:暗示会话恢复请求),它必须包括该会话中的cipher_suites(用于会话恢复)。
- compression_methods:client 支持的压缩方法列表,按client 偏好排序。如果 session_id 字段不为空(session_id不会空:暗示会话恢复请求),它必须包括该会话中的compression_method(用于会话恢复)。
-
extensions:扩展列表 。
Figure 6: Client Hello
HelloVerifyRequest
- Server 为 ClientHello 的 Client 生成 cookie,并在 HelloVerifyRequest 中携带cookie发送至 Client 。
- Client 需要重发 ClientHello 并携带 cookie 至 Server 。
- 这一机制用于防 Dos 攻击。
ServerHello
struct {
ProtocolVersion server_version;
Random random;
SessionID session_id;
CipherSuite cipher_suite;
CompressionMethod compression_method;
select (extensions_present) {
case false:
struct {};
case true:
Extension extensions<0..2^16-1>;
};
} ServerHello;
- server_version:该字段将包含 client 建议的最低版本,以及 server 支持的最高版本。
- random:server 依据 client 的 random 随机生成的 random。
- session_id:
- cipher_suite:server 从 client 提供的 cipher_suites 选择一个支持的 cipher_suite。
- compression_method:server 从 client 提供的 compression_methods 选择一个支持的 compression_method。
-
extensions:只有 client 提供的 extension 才能出现在 server 的 extensions 中。
Figure 7: Server Hello
Hello Extensions
struct {
ExtensionType extension_type;
opaque extension_data<0..2^16-1>;
} Extension;
enum {
signature_algorithms(13), (65535)
} ExtensionType;
- extension_type:标识特定的扩展类型。
- extension_data:该扩展类型指向的扩展信息。
Certificate
opaque ASN.1Cert<1..2^24-1>;
struct {
ASN.1Cert certificate_list<0..2^24-1>;
} Certificate;
-
certificate_list:证书序列(链)。 sender的证书必须首先出现在列表中,且 之后的每个证书都必须直接证明它前面的那个,因为证书验证要求分发根密钥。独立地,指定根证书颁发机构的自签名证书可以从链中省略,假设远程端必须已经拥有它以便在任何情况下对其进行验证。
Figure 8: Certificate
ServerKeyExchange
struct {
select (KeyExchangeAlgorithm) {
case dh_anon:
ServerDHParams params;
case dhe_dss:
case dhe_rsa:
ServerDHParams params;
digitally-signed struct {
opaque client_random[32];
opaque server_random[32];
ServerDHParams params;
} signed_params;
case rsa:
case dh_dss:
case dh_rsa:
struct {} ;
/* message is omitted for rsa, dh_dss, and dh_rsa */
/* may be extended, e.g., for ECDH -- see [TLSECC] */
};
} ServerKeyExchange;
- params:server 的密钥交换参数。
-
signed_params:对于非匿名密钥交换,server 的签名的密钥交换参数
Figure 9: Server Key Exchange with DH algorithm
CertificateRequest
struct {
ClientCertificateType certificate_types<1..2^8-1>;
SignatureAndHashAlgorithm
supported_signature_algorithms<2^16-1>;
DistinguishedName certificate_authorities<0..2^16-1>;
} CertificateRequest;
- certificate_types:client 可以使用的证书类型列表。
- supported_signature_algorithms:server 所能够验证的 signature 算法对的列表,按 server 的偏好降序排列。
-
certificate_authorities:server 可接受的证书机构者的专有名称列表,以 DER 编码格式表示。如果 certificate_authorities 列表为空,那么客户端可以发送适当的 ClientCertificateType 的任何证书。
Figure 10: Certificate Request
ServerHelloDone
struct { } ServerHelloDone;
-
ServerHelloDone:由server发送给client作为ServerHello的结尾,发送此消息后,server将等待client的响应。
Figure 11: Server Hello Done
ClientKeyExchange
struct {
select (KeyExchangeAlgorithm) {
case rsa:
EncryptedPreMasterSecret;
case dhe_dss:
case dhe_rsa:
case dh_dss:
case dh_rsa:
case dh_anon:
ClientDiffieHellmanPublic;
} exchange_keys;
} ClientKeyExchange;
EncryptedPreMasterSecret
struct {
ProtocolVersion client_version;
opaque random[46];
} PreMasterSecret;
struct {
public-key-encrypted PreMasterSecret pre_master_secret;
} EncryptedPreMasterSecret;
- 如果使用 RSA 进行密钥协商和身份验证,则client生成一个 48 字节的PreMasterSecret,并使用来自服务器证书的公钥对PreMasterSecret进行加密,并将密文添加到EncryptedPreMasterSecret中,然后以ClientKeyExchange Message将PreMasterSecret发送给server,server 使用私钥解密得到密钥。
ClientDiffieHellmanPublic
enum { implicit, explicit } PublicValueEncoding;
struct {
select (PublicValueEncoding) {
case implicit: struct { };
case explicit: opaque dh_Yc<1..2^16-1>;
} dh_public;
} ClientDiffieHellmanPublic;
-
如果客户端的证书中尚未包含客户端的 Diffie-Hellman 公共值 (Yc),则此结构会传送客户端的 Diffie-Hellman 公共值 (Yc)。用于 Yc 的编码由枚举的 PublicValueEncoding 确定。
-
implicit:如果客户端发送了包含合适的证书 Diffie-Hellman 密钥(用于 fixed_dh 客户端身份验证),然后 yc 是隐式的,不需要再次发送。 在这种情况下,客户端的ClientKeyExchange将被发送,但它必须是空的。
-
explicit:明确需要发送 Yc。
-
dh_Yc:客户端的 Diffie-Hellman 公共价值 (Yc)。
-
理解:
- 如果使用 DH 算法,且未在客户端的证书中描述(explicit),则客户端使用 ClientDiffieHellmanPublic 将 客户端 的公钥发送给 服务端。
- 由服务端决定使用的对称加密的密钥,并使用客户端的公钥对对称加密的密钥进行加密。
- 服务端通过Change Cipher Spec Protocol将加密后的对称加密密钥发送给客户端。
-
客户端再使用私钥解密得到对称加密密钥
Figure12: Client Key Exchange by Diffie-Hellman Algorithm
CertificateVerify
Structure of this message:
struct {
digitally-signed struct {
opaque handshake_messages[handshake_messages_length];
}
} CertificateVerify;
- 消息用于提供对客户端证书的显式验证。 此消息仅在具有签名功能的客户端的Certificate Message发送之后发送(即,除包含固定 Diffie-Hellman 参数的证书之外的所有证书)。 发送时,它必须立即遵循客户端的ClientKeyExchange Message。
-
handshake_messages:指发送或接收的所有握手消息,从client hello开始直到但不包括这个消息,包括握手消息的类型和长度字段。这是迄今为止交换的所有握手结构的串联。请注意,这需要双方缓冲消息或计算所有潜在哈希算法的运行哈希。server 可以通过在 CertificateRequest 消息中提供一组受限的摘要算法来最小化CertificateVerify的计算成本。签名中使用的散列和签名算法必须是出现在 CertificateRequest 消息的 supported_signature_algorithms 字段中的算法之一。此外,散列和签名算法必须与客户端的最终实体证书中的密钥兼容。
Figure 13: Ceritificate Verify
Change Cipher Spec Message
struct {
enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;
- ChangeCipherSpec 消息由客户端和服务器互相通知对方后续记录将被受新协商的 CipherSpec 和 keys 保护。
-
ChangeCipherSpec 消息是在安全参数达成一致后握手,但在发送 Finished 之前。
Figure 14: ChangeCipherSpec
Finished
struct {
opaque verify_data[verify_data_length];
} Finished;
verify_data
PRF(master_secret, finished_label, Hash(handshake_messages))
[0..verify_data_length-1];
- verify_data:PRF(master_secret, finished_label, Hash(handshake_messages))
- handshake_messages: 包括从 ClientHello 开始直到但不包括此 Finished 消息的所有握手消息(不包括 HelloRequest )。 这可能与CertificateVerify中提到的 handshake_messages 不同,因为它会包含 CertificateVerify 消息(如果已发送)。 另外,客户端发送的Finished消息的handshake_messages将与服务器发送的Finished消息handshake_messages不同,因为第二个发送的将包含前一个。
- 注意:ChangeCipherSpec Message、Alert和任何其他的不是 handshake type 的 message 也不包含在哈希中计算。 此外, HelloRequest 也不参与计算。
DTLS 超时重传机制
可以采用 DTLS_set_timer_cb
进行超时重传。
DTLS Record 支持 Message Seq,因此对端可以正确识别重传包(重传的Seq不变)。
DTLS in OpenSSL
SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
创建一个新的 SSL_CTX 对象作为 framework 来建立 TLS/SSL 或 DTLS 连接。
OpenSSL提供了DTLS_method, DTLS_server_method, DTLS_client_method, DTLSv1_method, DTLSv1_server_method, DTLSv1_client_method, DTLSv1_2_method, DTLSv1_2_server_method, DTLSv1_2_client_method
用于创建DTLS的SSL_CTX 。
SSL *SSL_new(SSL_CTX *ctx);
为 connection 创建 SSL 结构。
我们可以使用 OpenSSL 提供的 API 管理 CTX 和 SSL。
常用的有:
// set list of available SSL_CIPHERs
int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str);
// load certificate
int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x);
// load key data
int SSL_CTX_use_PrivateKey(SSL_CTX *ctx, EVP_PKEY *pkey);
// set peer certificate verification parameters
void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, SSL_verify_cb verify_callback);
// set peer certificate verification parameters
// 深度?正如前文所述,证书存在依赖,类似树?
void SSL_CTX_set_verify_depth(SSL_CTX *ctx, int depth);
// manage whether to read as many input bytes as possible
SSL_CTX_set_read_ahead(SSL_CTX *ctx, int yes);
// Configure and query SRTP support
int SSL_CTX_set_tlsext_use_srtp(SSL_CTX *ctx, const char *profiles);
// Store and retrieve extra data from the SSL_CTX, SSL or SSL_SESSION
int SSL_set_ex_data(SSL *s, int idx, void *arg);
// handle information callback for SSL connections
void SSL_set_info_callback(SSL *ssl, void (*callback)());
// manipulate SSL options
long SSL_set_options(SSL *ssl, long options);
// perform a TLS/SSL handshake
int SSL_do_handshake(SSL *ssl);
// Set callback for controlling DTLS timer duration
void DTLS_set_timer_cb(SSL *s, DTLS_timer_cb cb);
OpenSSL对X509证书的支持:
// X509 certificate ASN1 allocation functions
X509 *X509_new(void); // void X509_free(X509 *a);
// X509_NAME_new : ASN1 object utilities
// example : X509_NAME *X509_NAME_new(void);
TYPE *TYPE_new(void); // void TYPE_free(TYPE *a);
// X509_NAME modification functions
int X509_NAME_add_entry_by_txt(X509_NAME *name, const char *field, int type,
const unsigned char *bytes, int len, int loc, int set);
// get and set issuer names
int X509_set_issuer_name(X509 *x, X509_NAME *name);
// get and set subject names
int X509_set_subject_name(X509 *x, X509_NAME *name);
// get or set certificate, certificate request or CRL version
int X509_set_version(X509 *x, long version);
// get or set certificate or certificate request public key
int X509_set_pubkey(X509 *x, EVP_PKEY *pkey);
// sign or verify certificate, certificate request or CRL signature
int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md);
// get digest of various objects
int X509_digest(const X509 *data, const EVP_MD *type, unsigned char *md,
unsigned int *len);
更多详见:
OpenSSL Man
0RTT Handshake in TLS1.3
在《密码学简述》一文中的对称密钥分配一节中提到一种方案:如果A和B各自有一个到达第三方C的加密链路,C能够在加密链路上传递密钥给A和B。
0RTT Handshake似乎便是源于这个想法,RFC8446在section-2.3写道:When clients and servers share a PSK (either obtained externally or via a previous handshake), TLS 1.3 allows clients to send data on the first flight ("early data"). The client uses the PSK to authenticate the server and to encrypt the early data.
我们可以将A和B视为clients and servers,将C视为PSK。
Client Server
ClientHello
+ early_data
+ key_share*
+ psk_key_exchange_modes
+ pre_shared_key
(Application Data*) -------->
ServerHello
+ pre_shared_key
+ key_share*
{EncryptedExtensions}
+ early_data*
{Finished}
<-------- [Application Data*]
(EndOfEarlyData)
{Finished} -------->
[Application Data] <-------> [Application Data]