本文介绍一下如何在 TLS/DTLS 中使用 Raw Public Key 替代传统的 X.509 Certificate.
简介
通常情况下, TLS 握手过程中使用 PKIX certificate, 通过校验这些证书是否合法来确保 TLS 通信的安全。
但是,在一些比较小的场景中,服务器客户端都使用自签名的证书,然后这些证书通过其他方式分发给需要通信的各个节点。在这种情况下,证书校验就退化为仅仅比较证书的二进制内容是否相等了,其他的校验都将不再需要。
因此, TLS 提供了一种机制,可以使用 X.509 证书中的一部分内容来表示这整个证书,以便获得以下好处:
- TLS 握手过程中交换的证书信息将会很小,减少握手过程所需要交换的数据量
- 处理证书的代码将会很简单。通常的 TLS 实现中,处理证书将会是很大一个的功能模块。
- 仅需要实现很少的 ASN.1 解析器功能。
- 证书校验模块也将被大幅度简化。
注意, 这些优点对于物联网/嵌入式设备将会是非常重要的。
RawPublicKey VS X.509Certificate
一个完整的 X.509 Certificate 将会携带如下信息:
- Certificate Version (V3)
- Serverial Number
- Siganture Algorithm, Signature
- Issuer
- Subject
- Validate From, Validate To
- Public Key
- Other extenstions(SubjectAltName, Key Usage, Extended Key usage, OCSP, CRL and so on)
对于一个 RawPublicKey, 它携带如下信息:
- Public Key
就像下图这么简单:
编码这些信息仅仅需要 91 字节。
而通常的证书的字节大小在 K 的数量级上。
RawPublicKey 的内容是 X.509 Certificate 的 SubjectPublicKeyInfo
字段。如下:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- If present, version MUST be v2 or v3
extensions [3] EXPLICIT Extensions OPTIONAL
-- If present, version MUST be v3
}
新扩展: client_certificate_type && server_certificate_type
在 TLS 握手过程中, 这两个扩展均可携带在 ClientHello
和 ServerHello
中。
它的值表示客户端或服务器支持的 TLS Certificate 类型(可以是多种类型)。常见的 TLS certificate 类型有: X.509 和 RawPublicKey.
opaque ASN.1Cert<1..2^24-1>;
struct {
select(certificate_type){
// certificate type defined in this document.
case RawPublicKey:
opaque ASN.1_subjectPublicKeyInfo<1..2^24-1>;
// X.509 certificate defined in RFC 5246
case X.509:
ASN.1Cert certificate_list<0..2^24-1>;
// Additional certificate type based on
// "TLS Certificate Types" subregistry
};
} Certificate;
struct {
select(ClientOrServerExtension) {
case client:
CertificateType client_certificate_types<1..2^8-1>;
case server:
CertificateType client_certificate_type;
}
} ClientCertTypeExtension;
struct {
select(ClientOrServerExtension) {
case client:
CertificateType server_certificate_types<1..2^8-1>;
case server:
CertificateType server_certificate_type;
}
} ServerCertTypeExtension;
TLS 握手过程
握手流程大体如下:
client_hello,
client_certificate_type,
server_certificate_type ->
<- server_hello,
client_certificate_type,
server_certificate_type,
certificate,
server_key_exchange,
certificate_request,
server_hello_done
certificate,
client_key_exchange,
certificate_verify,
change_cipher_spec,
finished ->
<- change_cipher_spec,
finished
Application Data <-------> Application Data
影响到的 TLS 握手消息包括:
- ClientHello
- ServerHello
- Certificate
ClientHello 信息
为了表明自己支持 RawPublicKey
模式, 客户端需要在 ClientHello
消息中附带 client_certificate type
和 server_certificate type
扩展。
ClientHello.client_certificate_type
表示客户端支持的 TLS certificate type. 当服务器要求客户端发送自己的证书的时候, 客户端需要发送对应格式的证书给服务器端。
如果客户端不支持除了 X.509 证书之外的证书类型, 那么客户端不要包含这个扩展在 ClientHello 中。
ClientHello.server_certificate_type
表示客户端可以处理的 TLS certificate type.
ServerHello 消息
当客户端收到携带 client_certificate_type
和/或 server_certificate type
扩展的 ClientHello
消息, 它按照以下规则发送响应:
- 如果服务器端不支持这两个扩展, 那么它返回一个不携带这两个扩展的
ServerHello
给客户端。 - 如果服务器端支持这两个扩展,但是没有和客户端支持的证书类型不匹配, 那么它中止当前 tls 会话,并返回一个携带 “unsupported_certificate” 的 Alert 消息。
- 如果服务器端支持这两个扩展, 并且和客户端有至少一个匹配的证书类型,它按照如下规则发送
ServerHello
:- 当 ClientHello 中包含client_certificate type, 并且服务器端想要客户端发送证书给它, 那么响应的 ServerHello 消息中必须包括 client_certificate_type. client_certificate_type 的值是从客户端的 client_certificate 的列表中选择出来的,选中的类型是客户端和服务器端都支持的类型。
- 当 ClientHello 中包含server_certificate type,并且它的值中包含 RawPublicKey, 服务器端返回一个携带 server_certificate_type 扩展,并且它的值也是 RawPublicKey 的 ServerHello 给客户端。
Certificate 消息
当 客户端和服务端协商好启用 RawPublicKey 之后, 服务器端发送给客户端的 Certificate 消息中将仅仅包含 SubjectPublicKeyInfo, 而不是完整的 X.509 Certificate.
同样的,如果服务器端要求客户端发送自己的证书给服务器,并要求使用 RawPublicKey, 那么客户端发送的 Certificate 消息中也将仅仅包含 SubjectPublicKeyInfo, 而不是完整的 X.509 Certificate.
其他 TLS 握手消息
对于其他握手消息没有影响, 按照标准的 TLS/DTLS 握手流程进行即可。
Client/Server Authentication
因为启用了 RawPublicKey 之后, TLS 握手过程中就不再发送完整的证书了,就的证书校验逻辑也不再适用。
RFC 对于如何校验没有做任何要求, 完全取决于应用层的设计。
实例
普通 TLS 握手
ClientHello
注意, 这里 ClientHello 中没有包含 client_certificate_type 扩展, 因为客户端没有配置证书,并且服务器端不要求客户端发送自己的证书。
客户端发送的 server_certificate_type 表示自己支持 Raw public Key 和 X.509 两种类型。
ServerHello
服务器端也支持Raw public Key, 返回 server_certificate_type 中包含 Raw public Key, 表明对当前 DTLS 连接启用 Raw Public Key 模式。
Certificate
因为启用了 RawPublicKey 模式,Certificate消息中包含的仅仅是 SubjectPublicKeyInfo 信息, 而不是完整的证书信息。
验证客户端身份的 TLS 握手
ClientHello
ServerHello
后边 Certificate 消息内容与上面相似, 不再赘述。