国密TLS介绍
握手协议总览
握手协议涉及以下过程
- 交换hello消息来协商密码套件,交换随机数,决定是否会话重用;
- 交换必要的参数,协商预主密钥;
- 交换证书或IBC信息,用于验证对方;
- 使用预主密钥和交换的随机数生成主密钥
- 向记录层提供安全参数
- 验证双方计算的安全参数的一致性、握手过程的真实性和完整性
握手过程如下:
image-20210105142453782.png- 客户端发送客户端hello消息给服务端。
- 服务端应回应服务端hello消息,否则产生一个致命错误并且断开链接。
- 在服务端发送完hello消息后,接着发送自己的证书消息(certificate),服务端密钥交换消息(ServerKeyExchange)。
- 如果服务端需要验证客户端的身份,则向客户端发送证书请求消息(CertificateRequest)。
- 然后发送服务端hello完成消息(ServerHelloDone),标识hello消息阶段已经结束,服务端等待客户端的返回消息。
- 如果服务端发送了一个证书请求消息,客户端必须返回一个证书消息(certificate)。
- 然后客户端发送密钥交换消息,消息内容取决于客户端hello消息和服务端hello消息协商初的密钥交换算法。
- 如果客户端发送了证书消息,那么也应该发送一个带数字签名的证书验证消息供服务端验证客户端的身份。(CertificateVerify)
- 接着客户端发送密码规格变更消息,然后客户端立即使用刚协商的算法和密钥,加密并发送握手结束消息(Finished)。
- 服务端则回应密码规格变更消息,使用刚协商的算法和密钥,加密并发送握手结束消息(Finished)。
- 至此握手过程结束,服务端和客户端可以开始数据安全传输。
如果客户端和服务端决定重用之前的会话,可不必重新协商安全参数。客户端发送客户端hello消息,并且带上要重用的会话标识。如果服务端游匹配的会话存在,服务端则使用相应的会话状态接受连接,发送一个具有相同会话标识的服务端hello消息。然后客户端和服务端各自发送密码规格变成消息和握手结束消息。至此握手过程结束,服务端和客户端可以开始数据安全传输。如果服务端没有匹配的会话标识,服务端会生成一个新的会话标识进行一个完整的握手过程。
image-20210105143953047.png
Hello消息
客户端Hello消息和服务端Hello消息用于确定双方的安全传输能力,包括协议版本,会话标识,密码套件等属性,并且产生和交换随机数。当新会话开始时,记录层连接状态中的密码算法,杂凑算法,压缩算法都初始化为空。
Client Hello
struct{
ProtocolVersion client_version;
Random random;
SessionID session_id;
CipherSuite cipher_suites<2..2^16-1>;
CompressionMethod compression_methods<1..2^8-1>;
}
-
client_version: 客户端在这个会话中使用的协议版本。
-
random:客户端产生的随机信息,其内容包括时钟和随机数;uint32 gmt_unix_time, random_bytes[28].
-
session_id:客户端在连接中使用的会话标识,session_id是一个可变长字段,其值由服务端决定。如果没有可重用的会话标识,或希望协商安全参数,该字段为空,否则表示客户端希望重用该会话。
-
cipher_suites:客户端所支持的密码套件列表。客户端应按照密码套件适用的优先级顺序排列,优先级最高的密码套件应排在首位。每个密码套件包括一个密钥交换算法,一个加密算法和一个校验算法。服务端将在密码套件列表中选择一个与之匹配的密码套件,如果没有可匹配的密码套件,应返回握手失败报警消息handshake_failure并且关闭连接。
序号 名称 值 gmssl中是否有 1 ECDHE_SM1_SM3 {0xe0,0x01} 否 2 ECC_SM1_SM3 {0xe0,0x03} 否 3 IBSDH_SM1_SM3 {0xe0,0x05} 否 4 IBC_SM1_SM3 {0xe0,0x07} 否 5 RSA_SM1_SM3 {0xe0,0x09} 否 6 RSA_SM1_SHA1 {0xe0,0x0a} 否 7 ECDHE_SM4_SM3 {0xe0,0x11} 是 8 ECC_SM4_SM3 {0xe0,0x13} 是 9 IBSDH_SM4_SM3 {0xe0,0x15} 是 10 IBC_SM4_SM3 {0xe0,0x17} 是 11 RSA_SM4_SM3 {0xe0,0x19} 是 12 RSA_SM4_SHA1 {0xe0,0x1a} 是 实现ECC和ECDHE的算法为SM2,实现IBC和IBSDH的算法为SM9
-
compression_methods:客户端所支持的压缩算法列表,客户端应按照压缩算法使用的优先级顺序列表,优先级最高的压缩算法应排在首位。
Server Hello
该消息为服务端Hello消息。
如果能从客户端Hello消息中找到匹配的密码套件,服务端发送这个消息作为对客户端Hello消息的回复。如果找不到匹配的密码套件,服务端将回应handshake failure报警消息。
struct{
ProtocolVersion server_version;
Random random;
SessionID session_id;
CipherSuite cipher_suite;
CompressionMethod compression_method;
}ServerHello;
- server_version:该字段表示服务端支持的协议版本。
- random:服务端产生的随机数。
- session_id:服务端使用的会话标识,如果客户端Hello消息中的会话标识不为空,企鹅服务端存在匹配的会话标识,则服务端重用与该标识对应的会话建立新连接,并在回应的服务端Hello消息中带上与客户端一致的会话标识,否则服务端产生一个新的会话标识,用来建立一个新的会话。
- ciper_suite:服务端从客户端Hello消息中选取一个密码套件。对于重用的会话,本字段存放重用会话使用的密码套件。
- compression_method:服务端从客户端Hello消息中选取的一个压缩算法,对于重用的会话,本字段存放重用会话使用的压缩算法。
ServerCertificate消息
该消息为服务端证书消息。
服务端必须发送一个服务端证书消息给客户端,该消息总是紧跟在服务端Hello消息之后,当选中的密码套件使用RSA或ECC或ECDHE算法时,本消息的内容为服务端的签名证书和加密证书;当选中套件使用IBC或IBSDH算法时,本消息的内容为服务端标识和IBC公共参数,用于客户端与服务端协商IBC公开参数。
证书格式为X.509 v3,证书类型必须能适用于已经确定的密钥交换算法。密钥交换算法与证书密钥类型的关系如下。
密钥交换算法 | 证书密钥类型 |
---|---|
RSA | RSA密钥,必须使用加密证书中的公钥 |
IBC | 服务端标识和IBC公共参数 |
IBSDH | 服务端标识和IBC公共参数 |
ECC | ECC公钥,必须使用加密证书中的公钥 |
ECDHE | ECC公钥,必须使用加密证书中的公钥 |
证书消息结构如下
opaque ASN.1Cert<1..2^24-1>;
struct{
ASN1.Cert cerificate;
}Certificate;
IBC标识及公共参数结构
opaque ASN.1IBCParam<1..2^24-1>;
struct{
opaque ibc_id<1..2^16-1>;
ASN.1IBCParam ibc_parameter;
}Certificate;
Server Key Exchange消息
本消息为服务端密钥交换消息。本消息传送的信息用于客户端计算产生48字节的预主密钥。服务端密钥交换消息结构定义如下:
enum{ECDHE,ECC,IBSDHE,IBC,RSA} KeyExchangeAlgorithm;
struct{
select (KeyExchangeAlgorithm){
case ECDHE:
ServerECDHEParams params;
digitally-signed struct{
opaque client_random[32];
opaque server_random[32];
ServerECDHEParams params;
}signed_params;
case ECC:
digitally-signed struct{
opaque client_random[32];
opaque server_random[32];
opaque ASN.1Cert<1..2^24-1>;
}signed_params;
case IBSDH:
ServerIBSDHParams params;
digitally-signed struct{
opaque client_random[32];
opaque server_random[32];
ServerIBSDHParams params;
}signed_params;
case IBC:
ServerIBCParams params;
digitally-signed struct{
opaque client_random[32];
opaque server_random[32];
ServerIBCParams params;
opaque IBCEncryptionKey[1024];
}signed_params;
case RSA:
digitally-signed struct{
opaque client_random[32];
opaque server_random[32];
opaque ASN.1Cert<1..2^24-1>;
}signed_params;
}
}ServerKeyExchange;
- ServerECDHEParams, 当使用算法是SM2算法时,交换的参数见GM/T0009,其中服务端的公钥不需要交换,客户端直接从服务端的加密证书中获取。statem_gmtls.c(GM/T0009标准里的没太看懂)
struct {
ECParameters curve_params;
ECPoint public;
}ServerECDHParams;
- ServerIBSDHParams:参见SM9算法。
- ServerIBCParams:参见SM9算法。
- IBCEncryptionKey:使用IBC算法时,服务端的加密公钥,长度为1024字节。
- signed_params:当密钥交换方式为ECDHE、IBSDH和IBC时,signed_params是服务端对双方随机数和服务端密钥交换参数的签名;当密钥交换方式为ECC和RSA时,signed_params是服务端对双方随机数和服务端加密证书的签名。
Certificate Request 消息
本消息为证书请求消息。
如果服务端要求认证客户端,则应发送此消息,要求客户端发送自己的证书。
struct {
ClientCertificateType certificate_types<1..2^8-1>;
DistinguishedName certificate_authorities<0..2^16-1>;
}CertificateRequest
- certificate_types:要求客户端提供的证书类型的列表。rsa_sign(1),ecdsa_sign(64),idc_params(80),(255)
- certificate_authorities,如果type是ibc_params,本字段内容是IBC密钥管理中心的信任域名列表。否则是服务端新人的CA的证书DN列表,包括根CA活着二级CA的DN。
Server Hello Done消息
表示握手过程的Hello消息阶段完成。发送完该消息后服务端会等待客户端的响应消息。
客户端接收到服务端的hello完成消息之后,应验证服务端证书是否有效,并检验服务端的hello消息参数是否可以接受。如果可以接受,客户端继续握手过程。否则发送一个Handshake failure致命报警。
struct{}ServerHelloDone;
Client Certificate消息
本消息为客户端证书消息。如果服务端请求客户端证书,客户端要随后发送本消息。消息内容结构同服务端的certificate消息。
Client Key Exchange消息
本消息为客户端端密钥交换消息。
如果服务端请求客户端证书,本消息紧跟于客户端证书消息之后,否则本消息是客户端接受到服务端hello完成消息后所发送的第一条消息。
如果密钥交换算法使用RSA算法、ECC算法和IBC算法,本消息中包含预主密钥,该预主密钥由客户端产生,采用服务端的加密公钥进行加密。当服务端收到加密后的预主密钥后,利用相应的私钥进行解密,获取所述预主密钥的明文。如果是IBC算法,客户端利用获取的服务端标识和IBC公开参数,产生服务端公钥。如果是RSA算法,建议使用PKCS#1版本1.5对加密后的密文进行编码。如果密钥交换算法使用ECDHE算法或IBSDH算法,本消息中包含计算预主命钥的客户端密钥交换参数。
struct{
select(KeyExchangeAlgorithm){
case ECDHE:
Opaque ClientECDHEParams<1..2^16-1>;
case IBSDH:
Opaque ClientIBSDHParams<1..2^16-1>;
case ECC:
Opaque ECCEncryptedPreMasterSecret<0..2^16-1>;
case IBC:
Opaque IBCEncryptedPreMasterSecret<0..2^16-1>;
case RSA:
Opaque RSAEncryptedPreMasterSecret<0..2^16-1>;
}exchange_keys;
}ClientKeyExchange;
-
ClientECDHEParams,见GM/T0009
-
ClientIBSDHParams,客户端的密钥交换参数。
-
ECCEncryptedPreMasterSecret,使用ECC加密算法时,用服务端加密公钥加密的预主密钥。
-
IBCEncryptedPreMasterSecret,使用IBC加密算法时,用服务单加密公钥加密的预主密钥。
-
使用RSA加密算法时,用服务单加密公钥加密的预主密钥。
-
预主密钥的数据结构如下:
struct{ ProtocolVersion client_version; opaque random[46]; }PreMasterSecret;
Certificate Verify消息
本消息为证书校验消息。
该消息用于鉴别客户端是否为证书的合法持有者,只有Client Certificate消息发送时才发送此消息,紧跟于客户端密钥交换消息之后。
证书校验消息的数据结构如下:
struct{
Signature signature;
}CertificateVerify;
struct{
select (SignatureAlgorithm){
case rsa_sha1:
digitally-signed struct{
opaque sha1_hash[20];
};
case rsa_sm3:
digitally-signed struct{
opaque sm3_hash[32];
};
case ecc_sm3://当ECC为SM2算法时,也是用这个套件
digitally-signed struct{
opaque sm3_hash[32];
};
case ibs_sm3:
digitally-signed struct{
opaque sm3_hash[32];
};
}
}Signature;
sm3_hash和sha1_hash是指hash运算的结果,运算的内容是自客户端Hello消息开始直到本消息为止(不包括本消息)的所有与握手有关的消息(加密证书要包在签名计算中),包括握手消息的类型和长度域。当使用SM2算法签名时,使用客户端的签名密钥,见GM/T0009和GM/T0010。
Finished消息
本消息为握手结束消息。
服务端和客户端各自在密码规格变更消息之后发送本消息,用于验证密钥交换过程是否成功,并校验握手过程的完整性。本消息用本次握手过程协商出的算法和密钥保护。本消息的接收方必须校验消息内容的正确性。一旦一方发送了握手结束消息,并且接收到了对方的握手结束消息并通过校验,就可以使用该连接进行数据安全传输。
握手结束消息数据结构如下:
struct{
opaque verify_data[12];
}Finished
其中verify_data为校验数据,该数据产生方法如下:
PRF(master_secret,finished_label,SM3(handshake_messages))
密钥计算
预主密钥计算
ECDHE计算,ServerKeyExchange将ECDHEParams传递过来之后,client就可以计算生成preMasterSecret。server收到ClientKeyExchange也可以计算preMasterSecret.见ECDHE算法原理及实现。
主密钥计算
主密钥由48字节组成,由预主密钥、客户端随机数、服务端随机数、常量字符串,经PRF计算生成。`
master_secret=PRF(pre_master_secret,"master secret",ClientHello.random,ServerHello.random)
// masterFromPreMasterSecret generates the master secret from the pre-master
// secret. See RFC 5246, Section 8.1.
func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, clientRandom, serverRandom []byte) []byte {
seed := make([]byte, 0, len(clientRandom)+len(serverRandom))
seed = append(seed, clientRandom...)
seed = append(seed, serverRandom...)
masterSecret := make([]byte, masterSecretLength)
prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed)
return masterSecret
}
工作密钥
工作密钥包括校验密钥和加密密钥,具体密钥长度由选用的密码算法决定。由主密钥、客户端随机数、服务端随机数、常量字符串,经PRF计算生成。
计算方式如下:
key_block = PRF(SecurityParameters, master_secret, "key expanision", SecurityParameters.server_random, SecurityParameters.client_random)
直到生成所需长度的输出,然后按顺序分割得到所需的密钥:
client_write_MAC_secret[SecurityParameters.hash_size]
server_write_MAC_secret[SecurityParameters.hash_size]
client_write_key[SecurityParameters.key_material_length]
server_write_key[SecurityParameters.key_material_length]
// keysFromMasterSecret generates the connection keys from the master
// secret, given the lengths of the MAC key, cipher key and IV, as defined in
// RFC 2246, Section 6.3.
func keysFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) {
seed := make([]byte, 0, len(serverRandom)+len(clientRandom))
seed = append(seed, serverRandom...)
seed = append(seed, clientRandom...)
n := 2*macLen + 2*keyLen + 2*ivLen
keyMaterial := make([]byte, n)
prfForVersion(version, suite)(keyMaterial, masterSecret, keyExpansionLabel, seed)
clientMAC = keyMaterial[:macLen]
keyMaterial = keyMaterial[macLen:]
serverMAC = keyMaterial[:macLen]
keyMaterial = keyMaterial[macLen:]
clientKey = keyMaterial[:keyLen]
keyMaterial = keyMaterial[keyLen:]
serverKey = keyMaterial[:keyLen]
keyMaterial = keyMaterial[keyLen:]
clientIV = keyMaterial[:ivLen]
keyMaterial = keyMaterial[ivLen:]
serverIV = keyMaterial[:ivLen]
return
}