基于NetMQ的TLS框架NetMQ.Security的实现分析
基于NetMQ的TLS框架NetMQ.Security的实现分析
前言
介绍
NetMQ是ZeroMQ的C#移植版本,它是对标准socket接口的扩展。它提供了一种异步消息队列,多消息模式,消息过滤(订阅),对多种传输协议的无缝访问。
当前有2个版本正在维护,版本3最新版为3.3.4,版本4最新版本为4.0.0.1。
NetMQ.Security是基于NetMQ的上实现了TLS层。具体描述文档可以看这里
交互过程
TLS连接需要进行4次握手,如下图所示。
打星号是可选项。
支持的协议
NetMQ.Security是在TLS1.2协议上的实现。
TLS协议
想要对TLS有一个了解,可以看该篇文章
在TCP/IP协议之上是Record协议,然后是HandShake协议,HandShake协议包括HandShake协议,Change Spec协议及Alert协议。
NetMQ.Security 实现了TLS中的2层子协议,Handshake协议和Record协议。Alert协议暂时不支持。
- Handshake协议:实现了服务端和客户端之间相互验证,协商加密和MAC算法以及保密密钥,用来保护在SSL记录中发送的数据。
- Record协议: 提供保密性和完整性。使用握手协议定义的密钥和mac值对数据进行加密及校验。
- Alert协议:客户端和服务端之间发生错误时,向对方发送一个警告。如果是致命错误,则算法立即关闭SSL连接,双方还会先删除相关的会话号,秘密和密钥。每个警报消息共2个字节,第1个字节表示错误类型,如果是警报,则值为1,如果是致命错误,则值为2;第2个字节制定实际错误类型。
具体协议格式可以看这篇文章进行了一个归纳。
支持的算法
目前NetMQ.Security只实现了RSA + AES (128/256) + SHA (1/256)
实现
NetMQ.Security是在NetMQ数据交互之间增加了一个SecureChannel层,在创建连接之后进行TLS握手,握手完成及可对数据进行加密和解密。每个连接都有一个SecureChannel,握手完成后会保存加解密的密钥。
public delegate bool VerifyCertificateDelegate(X509Certificate2 certificate2); public interface ISecureChannel : IDisposable { bool SecureChannelReady { get; } X509Certificate2 Certificate { get; set; } CipherSuite[] AllowedCipherSuites { get; set; } void SetVerifyCertificate(VerifyCertificateDelegate verifyCertificate); bool ProcessMessage(NetMQMessage incomingMessage, IList<NetMQMessage> outgoingMesssages); NetMQMessage EncryptApplicationMessage(NetMQMessage plainMessage); NetMQMessage DecryptApplicationMessage(NetMQMessage cipherMessage); } public class SecureChannel : ISecureChannel { private HandshakeLayer m_handshakeLayer; private RecordLayer m_recordLayer; private readonly OutgoingMessageBag m_outgoingMessageBag; private readonly byte[] m_protocolVersion = new byte[] { 3, 3 }; public SecureChannel(ConnectionEnd connectionEnd) { m_handshakeLayer = new HandshakeLayer(this, connectionEnd); m_handshakeLayer.CipherSuiteChange += OnCipherSuiteChangeFromHandshakeLayer; m_recordLayer = new RecordLayer(m_protocolVersion); m_outgoingMessageBag = new OutgoingMessageBag(this); } ... }
- SecureChannelReady: 是否完成握手,完成握手后续数据都进行加解密处理。
- Certificate: X509证书,NetMQ的客户端暂时不支持证书。
- AllowedCipherSuites: 支持的算法簇,客户端和服务端会对其进行商榷来确定最终的算法。
- SetVerifyCertificate: 服务端接收到连接时需要加载私钥。
- ProcessMessage: TLS握手。
- EncryptApplicationMessage: 加密数据。
- DecryptApplicationMessage: 解密数据。
- HandshakeLayer: 握手层。
- RecordLayer: 记录层。
- OutgoingMessageBag: 握手时向对端发送的数据包。
- m_protocolVersion: 协议版本号,源码用的是0,1。我改为了3,3,目前只支持3,3。
代码示例可以看NetMQ的开源开发者之一Doron Somech的博客,NetMQ.Security也是他在NetMQ上实现的,具体代码可以看NetMQ.Securiy,不过我下面的代码进行了一点修改,可以到这里看。
握手
第一次握手
Client Hello
TLS1.2 ClientHello的Record结构如下:
ContentType (1,handshake:22) ProtocolVersion(2) 握手协议长度:(2) 握手协议数据 HandShakeType(ClientHello:1) 内容长度(3) 内容 TLS版本号(2) 随机数(32,4位时间+28位随机数) SessionId长度(1) SessionId(0到32位) Cipher Suites长度(2) Cipher Suites列表 压缩方法长度(1) 压缩方法 扩展长度(2) 扩展内容
括号内的数字表示字节长度,括号内,之后是对其具体值或一些解释。
public bool ProcessMessage(NetMQMessage incomingMessage, IList<NetMQMessage> outgoingMesssages) { ContentType contentType = ContentType.Handshake; if (incomingMessage != null) { ... } bool result = false; if (contentType == ContentType.Handshake) { result = m_handshakeLayer.ProcessMessages(incomingMessage, m_outgoingMessageBag); ... } else { ChangeSuiteChangeArrived = true; } return (SecureChannelReady = result && ChangeSuiteChangeArrived); }
ProcessMessage
方法对即实现了TLS握手,当incomingMessage
参数为null时表示客户端发送的Client Hello。在握手层进行握手处理。
public HandshakeLayer(SecureChannel secureChannel, ConnectionEnd connectionEnd) { // SHA256 is a class that computes the SHA-256 (SHA stands for Standard Hashing Algorithm) of it's input. m_localHash = SHA256.Create(); m_remoteHash = SHA256.Create(); m_secureChannel = secureChannel; SecurityParameters = new SecurityParameters { Entity = connectionEnd, CompressionAlgorithm = CompressionMethod.Null, PRFAlgorithm = PRFAlgorithm.SHA256, CipherType = CipherType.Block }; AllowedCipherSuites = new[] { CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256, CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256, CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA }; VerifyCertificate = c => c.Verify(); }
TLS协议算法簇的枚举值可以看这里
result = m_handshakeLayer.ProcessMessages(incomingMessage, m_outgoingMessageBag); public bool ProcessMessages(NetMQMessage incomingMessage, OutgoingMessageBag outgoingMessages) { if (incomingMessage == null) { if (m_lastReceivedMessage == m_lastSentMessage && m_lastSentMessage == HandshakeType.HelloRequest && SecurityParameters.Entity == ConnectionEnd.Client) { OnHelloRequest(outgoingMessages); return false; } else { throw new ArgumentNullException(nameof(incomingMessage)); } } ... } private void OnHelloRequest(OutgoingMessageBag outgoingMessages) { var clientHelloMessage = new ClientHelloMessage { RandomNumber = new byte[RandomNumberLength] }; m_rng.GetBytes(clientHelloMessage.RandomNumber); SecurityParameters.ClientRandom = clientHelloMessage.RandomNumber; clientHelloMessage.CipherSuites = AllowedCipherSuites; NetMQMessage outgoingMessage = clientHelloMessage.ToNetMQMessage(); HashLocalAndRemote(outgoingMessage); outgoingMessages.AddHandshakeMessage(outgoingMessage); m_lastSentMessage = HandshakeType.ClientHello; }
var clientHelloMessage = new ClientHelloMessage { RandomNumber = new byte[RandomNumberLength] }; internal class ClientHelloMessage : HandshakeMessage { /// <summary> /// Get or set the Random-Number that is a part of the handshake-protocol, as a byte-array. /// </summary> public byte[] RandomNumber { get; set; } /// <summary> /// Get the part of the handshake-protocol that this HandshakeMessage represents /// - in this case a ClientHello. /// </summary> public override HandshakeType HandshakeType => HandshakeType.ClientHello; ... }
生成一个32字节的随机数。
private RandomNumberGenerator m_rng = new RNGCryptoServiceProvider(); m_rng.GetBytes(clientHelloMessage.RandomNumber);
RNGCryptoServiceProvider用于生成随机数。
生成支持的算法簇
NetMQMessage outgoingMessage = clientHelloMessage.ToNetMQMessage(); public override NetMQMessage ToNetMQMessage() { NetMQMessage message = base.ToNetMQMessage(); message.Append(RandomNumber); message.Append(BitConverter.GetBytes(CipherSuites.Length)); byte[] cipherSuitesBytes = new byte[2 * CipherSuites.Length]; int bytesIndex = 0; foreach (CipherSuite cipherSuite in CipherSuites) { cipherSuitesBytes[bytesIndex++] = 0; cipherSuitesBytes[bytesIndex++] = (byte)cipherSuite; } message.Append(cipherSuitesBytes); return message; } internal abstract class HandshakeMessage { ... public virtual NetMQMessage ToNetMQMessage() { NetMQMessage message = new NetMQMessage(); message.Append(new[] { (byte)HandshakeType }); return message; } ... }
和TLS协议进行对比,可以看出NetMQ.Security不支持SessionId。
TLS1.2算法族长度是2字节的16进制值,采用Big-Endian[1],而NetMQ.Security直接使用BitConverter.GetBytes(CipherSuites.Length)获取长度字节,而该方法返回的是四个字节Little-Endian[2]格式的十六进制值。
HashLocalAndRemote(outgoingMessage); private void HashLocalAndRemote(NetMQMessage message) { HashLocal(message); HashRemote(message); } private void HashLocal(NetMQMessage message) { Hash(m_localHash, message); } private static void Hash(HashAlgorithm hash, NetMQMessage message) { foreach (var frame in message) { // Access the byte-array that is the frame's buffer. byte[] bytes = frame.ToByteArray(true); // Compute the hash value for the region of the input byte-array (bytes), starting at index 0, // and copy the resulting hash value back into the same byte-array. hash.TransformBlock(bytes, 0, bytes.Length, bytes, 0); } }
这个方法的目的是计算发送和接收的握手数据的Hash值,最后在finished包会生产一个12位的验签码,对方会校验该验签码是否一致。
将握手协议前增加记录协议的版本号和ContentType。
outgoingMessages.AddHandshakeMessage(outgoingMessage); public void AddHandshakeMessage(NetMQMessage message) { m_messages.Add(m_secureChannel.InternalEncryptAndWrapMessage(ContentType.Handshake, message)); } internal NetMQMessage InternalEncryptAndWrapMessage(ContentType contentType, NetMQMessage plainMessage) { NetMQMessage encryptedMessage = m_recordLayer.EncryptMessage(contentType, plainMessage); //encryptedMessage.Push(m_protocolVersion); encryptedMessage.Push(new[] { (byte)contentType }); encryptedMessage.Push(m_protocolVersion); return encryptedMessage; } public NetMQMessage EncryptMessage(ContentType contentType, NetMQMessage plainMessage) { if (SecurityParameters.BulkCipherAlgorithm == BulkCipherAlgorithm.Null && SecurityParameters.MACAlgorithm == MACAlgorithm.Null) { return plainMessage; } ... }
Client Hello是明文传输,因此无需加密数据。
NetMQ.Security Client Hello Record协议结构如下
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议数据 HandShakeType(ClientHello:1) 内容 随机数(32,4位时间+28位随机数) Cipher Suites长度(2) Cipher Suites列表
由于NetMQ数据包格式已经包含长度,因此NetMQ.Security实现TLS协议的时候把协议中的长度字段都去掉了。
第二次握手
Server Hello
TLS1.2 Server Hello的Record结构如下:
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议长度:(2) 握手协议数据 HandShakeType(Server Hello:) 内容长度(3) 内容 TLS版本号(2) 随机数(32,4位时间+28位随机数) SessionId长度(1) SessionId(0到32位) 选择的Cipher Suite(2) 压缩方法 扩展长度 扩展内容
服务端接收到客户端的连接请求后先会验证Record协议的参数是否合法。
public bool ProcessMessage(NetMQMessage incomingMessage, IList<NetMQMessage> outgoingMesssages) { ContentType contentType = ContentType.Handshake; if (incomingMessage != null) { // Verify that the first two frames are the content-type and the protocol-version, NetMQFrame contentTypeFrame = incomingMessage.Pop(); if (contentTypeFrame.MessageSize != 1) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidFrameLength, "wrong length for message size"); } // Verify that the content-type is either handshake, or change-cipher-suit.. contentType = (ContentType)contentTypeFrame.Buffer[0]; if (contentType != ContentType.ChangeCipherSpec && contentType != ContentType.Handshake) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidContentType, "Unknown content type"); } //标准的没有这个版本 NetMQFrame protocolVersionFrame = incomingMessage.Pop(); byte[] protocolVersionBytes = protocolVersionFrame.ToByteArray(); if (protocolVersionBytes.Length != 2) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidFrameLength, "Wrong length for protocol version frame"); } if (!protocolVersionBytes.SequenceEqual(m_protocolVersion)) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidProtocolVersion, "Wrong protocol version"); } if (ChangeSuiteChangeArrived) { incomingMessage = m_recordLayer.DecryptMessage(contentType, incomingMessage); } } ... }
由于NetMQ.Security发送的record协议第一部分是Protorl Version(0,1),第二部分是Content Type,为了和标准的TLS格式一致,我将他们顺序换了一下,且将版本号改为(3,3)。
验证完毕后就需要对Client发来的握手数据进行处理。解析接受到的数据进行处理并生成ServerHello数据
public bool ProcessMessages(NetMQMessage incomingMessage, OutgoingMessageBag outgoingMessages) { if (incomingMessage == null) { //Client Hello请求 ... } var handshakeType = (HandshakeType)incomingMessage[0].Buffer[0]; switch (handshakeType) { case HandshakeType.ClientHello: OnClientHello(incomingMessage, outgoingMessages); break; ... } m_lastReceivedMessage = handshakeType; return m_done; } private void OnClientHello(NetMQMessage incomingMessage, OutgoingMessageBag outgoingMessages) { if (m_lastReceivedMessage != HandshakeType.HelloRequest || m_lastSentMessage != HandshakeType.HelloRequest) { throw new NetMQSecurityException(NetMQSecurityErrorCode.HandshakeUnexpectedMessage, "Client Hello received when expecting another message"); } HashLocalAndRemote(incomingMessage); var clientHelloMessage = new ClientHelloMessage(); clientHelloMessage.SetFromNetMQMessage(incomingMessage); SecurityParameters.ClientRandom = clientHelloMessage.RandomNumber; AddServerHelloMessage(outgoingMessages, clientHelloMessage.CipherSuites); AddCertificateMessage(outgoingMessages); AddServerHelloDone(outgoingMessages); }
获取客户端传来的随机数和支持的算法簇。
clientHelloMessage.SetFromNetMQMessage(incomingMessage); public virtual void SetFromNetMQMessage(NetMQMessage message) { if (message.FrameCount == 0) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidFramesCount, "Malformed message"); } // remove the handshake type column message.Pop(); } public override void SetFromNetMQMessage(NetMQMessage message) { base.SetFromNetMQMessage(message); if (message.FrameCount != 3) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidFramesCount, "Malformed message"); } // get the random number NetMQFrame randomNumberFrame = message.Pop(); RandomNumber = randomNumberFrame.ToByteArray(); // get the length of the cipher-suites array NetMQFrame ciphersLengthFrame = message.Pop(); int ciphersLength = BitConverter.ToInt32(ciphersLengthFrame.Buffer, 0); // get the cipher-suites NetMQFrame ciphersFrame = message.Pop(); CipherSuites = new CipherSuite[ciphersLength]; for (int i = 0; i < ciphersLength; i++) { CipherSuites[i] = (CipherSuite)ciphersFrame.Buffer[i * 2 + 1]; } }
生成服务端握手数据
AddServerHelloMessage(outgoingMessages, clientHelloMessage.CipherSuites); private void AddServerHelloMessage(OutgoingMessageBag outgoingMessages, CipherSuite[] cipherSuites) { var serverHelloMessage = new ServerHelloMessage { RandomNumber = new byte[RandomNumberLength] }; m_rng.GetBytes(serverHelloMessage.RandomNumber); SecurityParameters.ServerRandom = serverHelloMessage.RandomNumber; // in case there is no match the server will return this default serverHelloMessage.CipherSuite = CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA; foreach (var cipherSuite in cipherSuites) { if (AllowedCipherSuites.Contains(cipherSuite)) { serverHelloMessage.CipherSuite = cipherSuite; SetCipherSuite(cipherSuite); break; } } NetMQMessage outgoingMessage = serverHelloMessage.ToNetMQMessage(); HashLocalAndRemote(outgoingMessage); outgoingMessages.AddHandshakeMessage(outgoingMessage); m_lastSentMessage = HandshakeType.ServerHello; }
服务端默认支持的算法为TLS_RSA_WITH_AES_128_CBC_SHA,若客户端发送的算法服务端支持的话,则会设置双方商榷的最终算法。
生成Server Hello数据
public override NetMQMessage ToNetMQMessage() { NetMQMessage message = base.ToNetMQMessage(); message.Append(RandomNumber); message.Append(new byte[] { 0, (byte)CipherSuite }); return message; } public virtual NetMQMessage ToNetMQMessage() { NetMQMessage message = new NetMQMessage(); message.Append(new[] { (byte)HandshakeType }); return message; }
NetMQ.Security Server Hello Record结构如下
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议数据 HandShakeType(ServerHello:2) 内容 随机数(32,4位时间+28位随机数) 确定的Cipher Suite
Certificate
TLS1.2 Certificate的Record结构如下:
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议长度:(2) 握手协议数据 HandShakeType(Server Hello:11) 长度(3) 内容 服务器公钥列表
将服务器的证书公钥发送给客户端。
AddCertificateMessage(outgoingMessages); private void AddCertificateMessage(OutgoingMessageBag outgoingMessages) { var certificateMessage = new CertificateMessage { Certificate = LocalCertificate }; NetMQMessage outgoingMessage = certificateMessage.ToNetMQMessage(); HashLocalAndRemote(outgoingMessage); outgoingMessages.AddHandshakeMessage(outgoingMessage); m_lastSentMessage = HandshakeType.Certificate; } public override NetMQMessage ToNetMQMessage() { NetMQMessage message = base.ToNetMQMessage(); message.Append(Certificate.Export(X509ContentType.Cert)); return message; }
目前只支持一个证书
NetMQ.Security Certificate结构如下
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议数据 HandShakeType(Certificate:11) 内容 服务器公钥
NetMQ暂时不支持ServerKeyExchange
NetMQ暂不支持客户端证书 ,即不支持发送CertificateRequest包
ChangeCipherSpec
TLS1.2 ChangeCipherSpec结构如下
ContentType (1,handshake:20) ProtocolVersion(2:0303) 握手协议长度:(2) 握手协议数据 ChangeCipherSpec(1)
当服务端接收到ChangeCipherSpec,表示密钥已经生成,后续操作都加密处理。
关于ChangeCipherSpec的具体解析看这里
ServerHelloDone
TLS1.2 ServerHelloDone结构如下
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议长度:(2) 握手协议数据 HandShakeType(ServerHelloDone:14) 长度(3)
AddServerHelloDone(outgoingMessages); private void AddServerHelloDone(OutgoingMessageBag outgoingMessages) { var serverHelloDoneMessage = new ServerHelloDoneMessage(); NetMQMessage outgoingMessage = serverHelloDoneMessage.ToNetMQMessage(); HashLocalAndRemote(outgoingMessage); outgoingMessages.AddHandshakeMessage(outgoingMessage); m_lastSentMessage = HandshakeType.ServerHelloDone; } public virtual NetMQMessage ToNetMQMessage() { NetMQMessage message = new NetMQMessage(); message.Append(new[] { (byte)HandshakeType }); return message; }
NetMQ.Security ServerHelloDone结构如下
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议数据 HandShakeType(ServerHelloDone:14)
客户端处理
接收到数据先对数据格式进行校验,然后解析出ServerHello数据。
public bool ProcessMessages(NetMQMessage incomingMessage, OutgoingMessageBag outgoingMessages) { if (incomingMessage == null) { ... } var handshakeType = (HandshakeType)incomingMessage[0].Buffer[0]; switch (handshakeType) { case HandshakeType.ClientHello: OnClientHello(incomingMessage, outgoingMessages); break; case HandshakeType.ServerHello: OnServerHello(incomingMessage); case HandshakeType.Certificate: OnCertificate(incomingMessage); break; case HandshakeType.ServerHelloDone: OnServerHelloDone(incomingMessage, outgoingMessages); break; ... } ... } private void OnServerHelloDone(NetMQMessage incomingMessage, OutgoingMessageBag outgoingMessages) { if (m_lastReceivedMessage != HandshakeType.Certificate || m_lastSentMessage != HandshakeType.ClientHello) { throw new NetMQSecurityException(NetMQSecurityErrorCode.HandshakeUnexpectedMessage, "Server Hello Done received when expecting another message"); } HashLocalAndRemote(incomingMessage); var serverHelloDoneMessage = new ServerHelloDoneMessage(); serverHelloDoneMessage.SetFromNetMQMessage(incomingMessage); AddClientKeyExchange(outgoingMessages); InvokeChangeCipherSuite(); AddFinished(outgoingMessages); }
从ServerHello获取服务端生成的随机数,然后获取协商好的算法簇。
serverHelloDoneMessage.SetFromNetMQMessage(incomingMessage); public override void SetFromNetMQMessage(NetMQMessage message) { base.SetFromNetMQMessage(message); if (message.FrameCount != 2) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidFramesCount, "Malformed message"); } // Get the random number NetMQFrame randomNumberFrame = message.Pop(); RandomNumber = randomNumberFrame.ToByteArray(); // Get the cipher suite NetMQFrame cipherSuiteFrame = message.Pop(); CipherSuite = (CipherSuite)cipherSuiteFrame.Buffer[1]; }
加载并验证服务端公钥合法性
OnCertificate(incomingMessage); private void OnCertificate(NetMQMessage incomingMessage) { if (m_lastReceivedMessage != HandshakeType.ServerHello || m_lastSentMessage != HandshakeType.ClientHello) { throw new NetMQSecurityException(NetMQSecurityErrorCode.HandshakeUnexpectedMessage, "Certificate received when expecting another message"); } HashLocalAndRemote(incomingMessage); var certificateMessage = new CertificateMessage(); certificateMessage.SetFromNetMQMessage(incomingMessage); if (!VerifyCertificate(certificateMessage.Certificate)) { throw new NetMQSecurityException(NetMQSecurityErrorCode.HandshakeUnexpectedMessage, "Unable to verify certificate"); } RemoteCertificate = certificateMessage.Certificate; }
加载企业传来的公钥
certificateMessage.SetFromNetMQMessage(incomingMessage); public override void SetFromNetMQMessage(NetMQMessage message) { base.SetFromNetMQMessage(message); if (message.FrameCount != 1) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidFramesCount, "Malformed message"); } NetMQFrame certificateFrame = message.Pop(); byte[] certificateBytes = certificateFrame.ToByteArray(); Certificate = new X509Certificate2(); Certificate.Import(certificateBytes); }
对证书进行校验。客户端需要安装服务端的证书文件,由于我们使用的自签名的证书,不知道为什么一直验证失败,网上搜了下别人也有这问题,暂时不管这个问题。
if (!VerifyCertificate(certificateMessage.Certificate)) { throw new NetMQSecurityException(NetMQSecurityErrorCode.HandshakeUnexpectedMessage, "Unable to verify certificate"); } VerifyCertificate = c => c.Verify();
暂时不验证证书。
public void SetVerifyCertificate(VerifyCertificateDelegate verifyCertificate) { m_handshakeLayer.VerifyCertificate = verifyCertificate; } //客户端的SecureChannel对象设置证书校验始终返回true m_clientSecureChannel.SetVerifyCertificate(c => true);
客户端处理ServerDone
OnServerHelloDone(incomingMessage, outgoingMessages); private void OnServerHelloDone(NetMQMessage incomingMessage, OutgoingMessageBag outgoingMessages) { if (m_lastReceivedMessage != HandshakeType.Certificate || m_lastSentMessage != HandshakeType.ClientHello) { throw new NetMQSecurityException(NetMQSecurityErrorCode.HandshakeUnexpectedMessage, "Server Hello Done received when expecting another message"); } HashLocalAndRemote(incomingMessage); var serverHelloDoneMessage = new ServerHelloDoneMessage(); serverHelloDoneMessage.SetFromNetMQMessage(incomingMessage); AddClientKeyExchange(outgoingMessages); InvokeChangeCipherSuite(); AddFinished(outgoingMessages); }
第三次握手
ClientKeyExchange
TLS1.2 ClientKeyExchange的Record结构如下:
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议长度:(2) 握手协议数据 HandShakeType(ClientKeyExchange:16) 长度(3) 内容 密钥长度(3) 密钥
接收到服务端的ServerHelloDone之后,客户端需要继续处理第三次握手。
TLS1.2协议提到,若客户端没有发送证书,那在ServerHelloDone之后必须是ClientKeyExchange。
客户端生成一个48位的随机数称之为pre-master key,该随机数需要使用服务端的公钥进行加密,保证只有服务端才能解密出来。至此三个随机数就产生了,这三个随机数即生成对称加密密钥用于后续数据的加密,具体为什么使用三个随机数这篇文章讲的很清楚了。
AddClientKeyExchange(outgoingMessages); private void AddClientKeyExchange(OutgoingMessageBag outgoingMessages) { var clientKeyExchangeMessage = new ClientKeyExchangeMessage(); var premasterSecret = new byte[ClientKeyExchangeMessage.PreMasterSecretLength]; m_rng.GetBytes(premasterSecret); var rsa = RemoteCertificate.PublicKey.Key as RSACryptoServiceProvider; clientKeyExchangeMessage.EncryptedPreMasterSecret = rsa.Encrypt(premasterSecret, false); GenerateMasterSecret(premasterSecret); NetMQMessage outgoingMessage = clientKeyExchangeMessage.ToNetMQMessage(); HashLocalAndRemote(outgoingMessage); outgoingMessages.AddHandshakeMessage(outgoingMessage); m_lastSentMessage = HandshakeType.ClientKeyExchange; } private void GenerateMasterSecret(byte[] preMasterSecret) { var seed = new byte[RandomNumberLength*2]; Buffer.BlockCopy(SecurityParameters.ClientRandom, 0, seed, 0, RandomNumberLength); Buffer.BlockCopy(SecurityParameters.ServerRandom, 0, seed, RandomNumberLength, RandomNumberLength); SecurityParameters.MasterSecret = PRF.Get(preMasterSecret, MasterSecretLabel, seed, MasterSecretLength); Array.Clear(preMasterSecret, 0, preMasterSecret.Length); } private static byte[] PRF(byte[] secret, string label, byte[] seed, int iterations) { byte[] ls = new byte[label.Length + seed.Length]; Buffer.BlockCopy(Encoding.ASCII.GetBytes(label), 0, ls, 0, label.Length); Buffer.BlockCopy(seed, 0, ls, label.Length, seed.Length); return PHash(secret, ls, iterations); } private static byte[] PHash(byte[] secret, byte[] seed, int iterations) { using (HMACSHA256 hmac = new HMACSHA256(secret)) { byte[][] a = new byte[iterations + 1][]; a[0] = seed; for (int i = 0; i < iterations; i++) { a[i + 1] = hmac.ComputeHash(a[i]); } byte[] prf = new byte[iterations * 32]; byte[] buffer = new byte[32 + seed.Length]; Buffer.BlockCopy(seed, 0, buffer, 32, seed.Length); for (int i = 0; i < iterations; i++) { Buffer.BlockCopy(a[i + 1], 0, buffer, 0, 32); byte[] hash = hmac.ComputeHash(buffer); Buffer.BlockCopy(hash, 0, prf, 32 * i, 32); } return prf; } }
关于HMAC算法可以看这里
NetMQ.Security 使用的是RSA加密算法,还有Diffie-Hellman算法可以看这里
将加密后的PreMasterSecret发送给服务端
NetMQMessage outgoingMessage = clientKeyExchangeMessage.ToNetMQMessage(); public override NetMQMessage ToNetMQMessage() { NetMQMessage message = base.ToNetMQMessage(); message.Append(EncryptedPreMasterSecret); return message; }
NetMQ.Security ClientKeyExchange的Record结构如下:
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议数据 HandShakeType(ClientKeyExchange:16) 内容 密钥
key计算
关于key计算的算法在这里,下面是具体的实现。
InvokeChangeCipherSuite(); private void InvokeChangeCipherSuite() { CipherSuiteChange?.Invoke(this, EventArgs.Empty); } private void OnCipherSuiteChangeFromHandshakeLayer(object sender, EventArgs e) { NetMQMessage changeCipherMessage = new NetMQMessage(); changeCipherMessage.Append(new byte[] { 1 }); m_outgoingMessageBag.AddCipherChangeMessage(changeCipherMessage); m_recordLayer.SecurityParameters = m_handshakeLayer.SecurityParameters; m_recordLayer.InitalizeCipherSuite(); } public void InitalizeCipherSuite() { byte[] clientMAC; byte[] serverMAC; byte[] clientEncryptionKey; byte[] serverEncryptionKey; GenerateKeys(out clientMAC, out serverMAC, out clientEncryptionKey, out serverEncryptionKey); if (SecurityParameters.BulkCipherAlgorithm == BulkCipherAlgorithm.AES) { m_decryptionBulkAlgorithm = new AesCryptoServiceProvider { Padding = PaddingMode.None, KeySize = SecurityParameters.EncKeyLength*8, BlockSize = SecurityParameters.BlockLength*8 }; m_encryptionBulkAlgorithm = new AesCryptoServiceProvider { Padding = PaddingMode.None, KeySize = SecurityParameters.EncKeyLength*8, BlockSize = SecurityParameters.BlockLength*8 }; if (SecurityParameters.Entity == ConnectionEnd.Client) { m_encryptionBulkAlgorithm.Key = clientEncryptionKey; m_decryptionBulkAlgorithm.Key = serverEncryptionKey; } else { m_decryptionBulkAlgorithm.Key = clientEncryptionKey; m_encryptionBulkAlgorithm.Key = serverEncryptionKey; } } else { m_decryptionBulkAlgorithm = m_encryptionBulkAlgorithm = null; } if (SecurityParameters.MACAlgorithm == MACAlgorithm.HMACSha1) { if (SecurityParameters.Entity == ConnectionEnd.Client) { m_encryptionHMAC = new HMACSHA1(clientMAC); m_decryptionHMAC = new HMACSHA1(serverMAC); } else { m_encryptionHMAC = new HMACSHA1(serverMAC); m_decryptionHMAC = new HMACSHA1(clientMAC); } } else if (SecurityParameters.MACAlgorithm == MACAlgorithm.HMACSha256) { if (SecurityParameters.Entity == ConnectionEnd.Client) { m_encryptionHMAC = new HMACSHA256(clientMAC); m_decryptionHMAC = new HMACSHA256(serverMAC); } else { m_encryptionHMAC = new HMACSHA256(serverMAC); m_decryptionHMAC = new HMACSHA256(clientMAC); } } else { m_encryptionHMAC = m_decryptionHMAC = null; } } private void GenerateKeys( out byte[] clientMAC, out byte[] serverMAC, out byte[] clientEncryptionKey, out byte[] serverEncryptionKey) { byte[] seed = new byte[HandshakeLayer.RandomNumberLength * 2]; Buffer.BlockCopy(SecurityParameters.ServerRandom, 0, seed, 0, HandshakeLayer.RandomNumberLength); Buffer.BlockCopy(SecurityParameters.ClientRandom, 0, seed, HandshakeLayer.RandomNumberLength, HandshakeLayer.RandomNumberLength); int length = (SecurityParameters.FixedIVLength + SecurityParameters.EncKeyLength + SecurityParameters.MACKeyLength) * 2; if (length > 0) { byte[] keyBlock = PRF.Get(SecurityParameters.MasterSecret, KeyExpansion, seed, length); clientMAC = new byte[SecurityParameters.MACKeyLength]; Buffer.BlockCopy(keyBlock, 0, clientMAC, 0, SecurityParameters.MACKeyLength); int pos = SecurityParameters.MACKeyLength; serverMAC = new byte[SecurityParameters.MACKeyLength]; Buffer.BlockCopy(keyBlock, pos, serverMAC, 0, SecurityParameters.MACKeyLength); pos += SecurityParameters.MACKeyLength; clientEncryptionKey = new byte[SecurityParameters.EncKeyLength]; Buffer.BlockCopy(keyBlock, pos, clientEncryptionKey, 0, SecurityParameters.EncKeyLength); pos += SecurityParameters.EncKeyLength; serverEncryptionKey = new byte[SecurityParameters.EncKeyLength]; Buffer.BlockCopy(keyBlock, pos, serverEncryptionKey, 0, SecurityParameters.EncKeyLength); } else { clientMAC = serverMAC = clientEncryptionKey = serverEncryptionKey = null; } }
ClientFinish
TLS1.2 ClientFinish的Record结构如下:
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议长度:(2) 握手协议数据 HandShakeType(finished:20) VerifyData
private void AddFinished(OutgoingMessageBag outgoingMessages) { m_localHash.TransformFinalBlock(EmptyArray<byte>.Instance, 0, 0); byte[] seed = m_localHash.Hash; #if NET40 m_localHash.Dispose(); #endif m_localHash = null; var label = SecurityParameters.Entity == ConnectionEnd.Server ? ServerFinishedLabel : ClientFinshedLabel; var finishedMessage = new FinishedMessage { VerifyData = PRF.Get(SecurityParameters.MasterSecret, label, seed, FinishedMessage.VerifyDataLength) }; NetMQMessage outgoingMessage = finishedMessage.ToNetMQMessage(); outgoingMessages.AddHandshakeMessage(outgoingMessage); m_lastSentMessage = HandshakeType.Finished; if (SecurityParameters.Entity == ConnectionEnd.Client) { HashRemote(outgoingMessage); } }
NetMQMessage outgoingMessage = finishedMessage.ToNetMQMessage(); public override NetMQMessage ToNetMQMessage() { NetMQMessage message = base.ToNetMQMessage(); message.Append(VerifyData); return message; }
客户端和服务端会对对方发来的VerifyData进行校验。
关于finished报文的信息可以看这里
NetMQ.Security ClientFinish的Record结构如下:
ContentType (1,handshake:22) ProtocolVersion(2:0303) 握手协议数据 HandShakeType(finished:20) VerifyData
服务端接收客户端finished
服务端和客户端的结构一致。
OnFinished(incomingMessage, outgoingMessages); private void OnFinished(NetMQMessage incomingMessage, OutgoingMessageBag outgoingMessages) { if ( (SecurityParameters.Entity == ConnectionEnd.Client && (!m_secureChannel.ChangeSuiteChangeArrived || m_lastReceivedMessage != HandshakeType.ServerHelloDone || m_lastSentMessage != HandshakeType.Finished)) || (SecurityParameters.Entity == ConnectionEnd.Server && (!m_secureChannel.ChangeSuiteChangeArrived || m_lastReceivedMessage != HandshakeType.ClientKeyExchange || m_lastSentMessage != HandshakeType.ServerHelloDone))) { throw new NetMQSecurityException(NetMQSecurityErrorCode.HandshakeUnexpectedMessage, "Finished received when expecting another message"); } if (SecurityParameters.Entity == ConnectionEnd.Server) { HashLocal(incomingMessage); } var finishedMessage = new FinishedMessage(); finishedMessage.SetFromNetMQMessage(incomingMessage); m_remoteHash.TransformFinalBlock(EmptyArray<byte>.Instance, 0, 0); byte[] seed = m_remoteHash.Hash; #if NET40 m_remoteHash.Dispose(); #else m_remoteHash.Clear(); #endif m_remoteHash = null; var label = SecurityParameters.Entity == ConnectionEnd.Client ? ServerFinishedLabel : ClientFinshedLabel; var verifyData = PRF.Get(SecurityParameters.MasterSecret, label, seed, FinishedMessage.VerifyDataLength); if (!verifyData.SequenceEqual(finishedMessage.VerifyData)) { throw new NetMQSecurityException(NetMQSecurityErrorCode.HandshakeVerifyData, "peer verify data wrong"); } if (SecurityParameters.Entity == ConnectionEnd.Server) { AddFinished(outgoingMessages); } m_done = true; }
m_remoteHash.TransformFinalBlock(EmptyArray<byte>.Instance, 0, 0);
会初始化一个32字节的字节数组当作seed
finishedMessage.SetFromNetMQMessage(incomingMessage); public override void SetFromNetMQMessage(NetMQMessage message) { base.SetFromNetMQMessage(message); if (message.FrameCount != 1) { throw new NetMQSecurityException(NetMQSecurityErrorCode.InvalidFramesCount, "Malformed message"); } NetMQFrame verifyDataFrame = message.Pop(); VerifyData = verifyDataFrame.ToByteArray(); }
第四次握手
ServerFinish
AddFinished(outgoingMessages); private void AddFinished(OutgoingMessageBag outgoingMessages) { m_localHash.TransformFinalBlock(EmptyArray<byte>.Instance, 0, 0); byte[] seed = m_localHash.Hash; #if NET4 m_localHash.Dispose(); #endif m_localHash = null; var label = SecurityParameters.Entity == ConnectionEnd.Server ? ServerFinishedLabel : ClientFinshedLabel; var finishedMessage = new FinishedMessage { VerifyData = PRF.Get(SecurityParameters.MasterSecret, label, seed, FinishedMessage.VerifyDataLength) }; NetMQMessage outgoingMessage = finishedMessage.ToNetMQMessage(); outgoingMessages.AddHandshakeMessage(outgoingMessage); m_lastSentMessage = HandshakeType.Finished; if (SecurityParameters.Entity == ConnectionEnd.Client) { HashRemote(outgoingMessage); } }
客户端接收服务端Finished
处理和上面服务端处理一致。只是不再发送finished包。至此,四次握手完成,后面就是对数据进行加密和解密。
数据传输
ApplicationData
TLS1.2 ApplicationData的Record结构如下:
ContentType (1,ApplicationData:23) ProtocolVersion(2:0303) 长度:(2) 加密数据 向量长度(1) 向量 SeqNum(8) 加密数据列表 加密数据长度(2) 加密数据
数据上层也是Record协议。
NetMQ.Security ApplicationData的Record结构如下:
ContentType (1,ApplicationData:23) ProtocolVersion(2:0303) 加密数据 向量 SeqNum 加密数据列表
数据加密
public NetMQMessage EncryptApplicationMessage([NotNull] NetMQMessage plainMessage) { ... return InternalEncryptAndWrapMessage(ContentType.ApplicationData, plainMessage); } public NetMQMessage EncryptMessage(ContentType contentType, NetMQMessage plainMessage) { ... NetMQMessage cipherMessage = new NetMQMessage(); using (var encryptor = m_encryptionBulkAlgorithm.CreateEncryptor()) { ulong seqNum = GetAndIncreaseSequneceNumber(); byte[] seqNumBytes = BitConverter.GetBytes(seqNum); var iv = GenerateIV(encryptor, seqNumBytes); cipherMessage.Append(iv); // including the frame number in the message to make sure the frames are not reordered int frameIndex = 0; // the first frame is the sequence number and the number of frames to make sure frames was not removed byte[] frameBytes = new byte[12]; Buffer.BlockCopy(seqNumBytes, 0, frameBytes, 0, 8); Buffer.BlockCopy(BitConverter.GetBytes(plainMessage.FrameCount), 0, frameBytes, 8, 4); byte[] cipherSeqNumBytes = EncryptBytes(encryptor, contentType, seqNum, frameIndex, frameBytes); cipherMessage.Append(cipherSeqNumBytes); frameIndex++; foreach (NetMQFrame plainFrame in plainMessage) { byte[] cipherBytes = EncryptBytes(encryptor, contentType, seqNum, frameIndex, plainFrame.ToByteArray()); cipherMessage.Append(cipherBytes); frameIndex++; } return cipherMessage; } }
数据解密
public NetMQMessage DecryptApplicationMessage([NotNull] NetMQMessage cipherMessage) { ... return m_recordLayer.DecryptMessage(ContentType.ApplicationData, cipherMessage); } public NetMQMessage DecryptMessage(ContentType contentType, NetMQMessage cipherMessage) { ... NetMQFrame ivFrame = cipherMessage.Pop(); m_decryptionBulkAlgorithm.IV = ivFrame.ToByteArray(); using (var decryptor = m_decryptionBulkAlgorithm.CreateDecryptor()) { NetMQMessage plainMessage = new NetMQMessage(); NetMQFrame seqNumFrame = cipherMessage.Pop(); byte[] frameBytes; byte[] seqNumMAC; byte[] padding; DecryptBytes(decryptor, seqNumFrame.ToByteArray(), out frameBytes, out seqNumMAC, out padding); ulong seqNum = BitConverter.ToUInt64(frameBytes, 0); int frameCount = BitConverter.ToInt32(frameBytes, 8); int frameIndex = 0; ValidateBytes(contentType, seqNum, frameIndex, frameBytes, seqNumMAC, padding); if (CheckReplayAttack(seqNum)) { throw new NetMQSecurityException(NetMQSecurityErrorCode.ReplayAttack, "Message already handled or very old message, might be under replay attack"); } if (frameCount != cipherMessage.FrameCount) { throw new NetMQSecurityException(NetMQSecurityErrorCode.EncryptedFramesMissing, "Frames was removed from the encrypted message"); } frameIndex++; foreach (NetMQFrame cipherFrame in cipherMessage) { byte[] data; byte[] mac; DecryptBytes(decryptor, cipherFrame.ToByteArray(), out data, out mac, out padding); ValidateBytes(contentType, seqNum, frameIndex, data, mac, padding); frameIndex++; plainMessage.Append(data); } return plainMessage; } }
NetMQ.Security目前只支持AES加密解密。
相关文献
- Overview of SSL/TLS Encryption
- SSL/TLS协议运行机制的概述
- 图解SSL/TLS协议
- The Transport Layer Security (TLS) Protocol Version 1.2
- TLS/SSL报文格式探究
- Traffic Analysis of an ssl/tls session
微信扫一扫二维码关注订阅号杰哥技术分享
本文地址:https://www.cnblogs.com/Jack-Blog/p/9015783.html
作者博客:杰哥很忙
欢迎转载,请在明显位置给出出处及链接
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义