首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基于EAPOL 802.1X的TLS握手

基于EAPOL 802.1X的TLS握手
EN

Stack Overflow用户
提问于 2022-06-01 16:55:06
回答 1查看 345关注 0票数 2

我正在构建一个EAPOL身份验证客户端(802.1XEAPOL)。目前为止的需求只是EAP。我正在使用FreeRadius服务器进行测试,它使用的是TLS1.1,所以这是我正在开发的传输版本。

因为这个请求者使用的是自定义的网络堆栈,而且在一个小型嵌入式设备上,我不能使用OpenSSL库,因为它们将所有握手都作为黑盒套接字级别进行通信。而且,我发现的请求者都包含与AAA和Authenticator紧密交织在一起的代码。我没有太多的空间来添加所有的源代码(除了使支持变得更加困难)。

这是很好的学习,同时滚动自己无论如何。

所以,当我深入研究的时候,我看到了一些与RFC不一致或者根本没有定义的东西。

在询问WPA之前,我想先礼貌地问一句:“这里是问技术问题的好地方,还是还有其他资源”。我被礼貌地忽略了。所以我在这里发帖。

参考RFC 3579、3748、4346、5216等,我对服务器执行了MD5挑战身份验证。成功理解EAP,以太网数据包,片段等。

在TLS上,我成功地接收、组装和解析了TLS Server Hello握手。(RFC 5216只在EAP上定义了TLS头,而RFC 4346解释了整个TLS握手,但EAP使用了其中的一个子集。)由于我可以访问测试服务器证书和密钥,我还验证了用公钥加密预主密钥,并且它用私钥正确地解密。

现在,我正在尝试构建完整的客户端握手,一点一点地添加到消息中。找到我无法解决的问题。

下面,我将参考RFC 4346来获取以下TLS 1.1消息。

在4.3节中,向量是用特定的“表示语言”定义的。对固定的已知长度使用[],对必须包含指示大小的前导值的变量长度使用<..>。

第7.4.7节定义了客户端密钥交换。在我的例子中,它只是一个RSA,因此是一个"EncryptedPreMasterSecret“。第7.4.7.1节定义了RSA的EncryptedPreMasterSecret,即版本和随机数,长度为48个字节。

定义并没有声称这是一个可变向量。但是,如果FreeRadius没有长度的两个字节主机顺序值,则来自它的调试信息将拒绝它。

代码语言:javascript
复制
(27) eap_tls:   TLS-Client-Cert-X509v3-Basic-Constraints += "CA:FALSE"
(27) eap_tls: TLS_accept: SSLv3/TLS read client certificate
(27) eap_tls: <<< recv TLS 1.0 Handshake [length 0104], ClientKeyExchange
(27) eap_tls: >>> send TLS 1.0 Alert [length 0002], fatal decode_error
(27) eap_tls: ERROR: TLS Alert write:fatal:decode error
tls: TLS_accept: Error in error
(27) eap_tls: ERROR: Failed in __FUNCTION__ (SSL_read): error:1419F09F:SSL routines:tls_process_cke_rsa:length mismatch
(27) eap_tls: ERROR: System call (I/O) error (-1)
(27) eap_tls: ERROR: TLS receive handshake failed during operation
(27) eap_tls: ERROR: [eaptls process] = fail

有趣的是,Wireshark似乎并不介意它是否不见了。

通过添加两个字节的长度,我克服了这个失败。但是,我不喜欢它不符合我所读的规范。

这是我错过的地方吗?

因此,我似乎已经通过了PremasterSecret,并转移到证书验证消息。对于它,第7.4.8节定义了包含MD5和SHA散列的证书验证,并引用了7.4.3节。7.4.3中的定义定义了什么是“签名”,并没有声称这是一个可变向量。

事实上,第7.4.3节非常清楚地表明,它是否是一个已知的长度向量(即使用固定长度16和20)。然而,Wireshark在这里也期望有一个两个字节的头,如果它不存在,就报告一个错误。

所以我把它加进了两个字节头,Wireshark很高兴。

但这仍然不符合规范。已知的最大长度为36字节,相当于一个8位数。因此,需要两个字节违反了规范,该规范在4.3节中说:

代码语言:javascript
复制
The length will be in the form of a number consuming as many bytes as required to hold the vector’s specified maximum (ceiling) length.

然而,即使发生了这种变化,服务器仍然在抱怨。

代码语言:javascript
复制
(13) eap_tls:   TLS-Client-Cert-X509v3-Basic-Constraints += "CA:FALSE"
(13) eap_tls: TLS_accept: SSLv3/TLS read client certificate
(13) eap_tls: <<< recv TLS 1.0 Handshake [length 0106], ClientKeyExchange
(13) eap_tls: TLS_accept: SSLv3/TLS read client key exchange
(13) eap_tls: <<< recv TLS 1.0 Handshake [length 002a], CertificateVerify
(13) eap_tls: >>> send TLS 1.0 Alert [length 0002], fatal decrypt_error
(13) eap_tls: ERROR: TLS Alert write:fatal:decrypt error
tls: TLS_accept: Error in error
(13) eap_tls: ERROR: Failed in __FUNCTION__ (SSL_read)
(13) eap_tls: ERROR: error:04091077:rsa routines:int_rsa_verify:wrong signature length
(13) eap_tls: ERROR: error:1417B07B:SSL routines:tls_process_cert_verify:bad signature

服务器上写着"decrypt_error“。这个验证信息应该是加密的吗?规范没有这么说。在服务器源中,我在任何地方都找不到那条短信。它隐藏得很好,很难找到拒绝它的函数。

如果它应该是加密的,使用的是什么密钥?客户端私钥还是服务器公钥?

再说一遍,这是不是描述了我错过的其他地方?它没有在两个方面遵循规范(使用可变长度和两个字节,其中一个就足够了)。

在第7.4.9节中,完成的消息是使用包含"0..11“的表示语言定义的,在第4节的任何地方都没有定义哪种描述。它是否是一个可变长度向量<0..11>?或者0.11在这里意味着什么?

下一个主要问题:

我是不是弄得太难了?

是否存在OpenSSL调用,只需进行重新组装的TLS握手,并创建客户端握手应答,然后将其填充到提供的缓冲区中?同样,由于嵌入式设备上的请求客户端使用它自己的网络堆栈,所以我不能使用OpenSSL的内部套接字调用进行握手。

OpenSSL文档在许多方面都缺乏,如果存在这样的API,我就不会偶然发现它。

谢谢你的回答和建议。

-Scott

EN

回答 1

Stack Overflow用户

发布于 2022-06-30 15:27:37

我已经解决了这个最初的问题,虽然我没有所有我原来的问题的答案。

具体来说,我会回答“我是不是把这件事搞得太难了”。答案是,是,

原来我正在寻找的OpenSSL api是BIO api,我了解到它实际上是一个允许您直接处理缓冲区的API,而不是使用OpenSSL套接字进行通信。将这看作是“中间的人”,在发送接收电话之间插入您的代码。

显然,BIO也可以用于文件IO之类的其他事情。

在我的例子中,这是EAPOL,它是原始的以太网数据包,但是假设您需要在没有TCP/IP支持的I2C线上执行一些类似TLS握手的加密。在这种情况下,您需要将流读入缓冲区,将其复制到输入BIO中,然后进行OpenSSL调用。然后从输出生物中获取数据并发送。

下面是一些应该解释所有内容的特定代码片段:

首先是标题。我只是全力以赴,因为我正在使用代码中没有显示的许多其他特性(OpenSSL页面给出了这些信息,但是API文档没有告诉您原型在哪里是非常烦人的)。一些标题名称是关于它们的贡献的线索:

代码语言:javascript
复制
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/md5.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include <openssl/rsa.h>
#include <openssl/err.h>

下面的这些代码片段包装在一个类中,并使用引号"m_“的匈牙利符号来表示它们是类的成员。首先,您需要两个BIO句柄,一个用于接收并传递到OpenSSL,另一个用于OpenSSL的回复。似乎没有任何要求您提前知道缓冲区大小。

声明BIOS

代码语言:javascript
复制
BIO  *m_sslIn, *m_sslOut;

并初始化它们

代码语言:javascript
复制
m_sslIn = NULL;
m_sslOut = NULL;

现在假设SSL被配置,Cert,CA,Keys等等.都已加载并创建了上下文(此答案并不是对SSL调用的解释)。

代码语言:javascript
复制
m_sslCtx = SSL_CTX_new(SSLv23_method());
m_ssl = SSL_new(m_sslCtx);

创造生物。输入和输出都是相同的。

代码语言:javascript
复制
m_sslIn = BIO_new(BIO_s_mem());
BIO_reset(m_sslIn);
m_sslOut = BIO_new(BIO_s_mem());
BIO_reset(m_sslOut);

现在最酷的部分。告诉OpenSSL,它将使用这些BIO作为缓冲区,这样您就可以将数据放入并取出。

代码语言:javascript
复制
SSL_set_bio(m_ssl, m_sslIn, m_sslOut);

这一点很重要:我发现当您完成时,不需要调用BIO_free()。因为SSL已经连接了它们,这意味着它将为您释放它们。这是一个恼人的特性,不告诉任何人就在里面。但直到我弄明白了这一点,我才发现“多个缓冲区被释放了”。

下面假设您已经读取了类似原始数据包或I2C线路之类的数据。注意,当您初始化TLS握手时,第一个消息还不存在(TLS从向另一侧发送ClientHello消息开始)。因此,在第一个调用时将输入BIO保持为空,然后发出一个调用SSL_connect(m_ssl)来开始。

代码语言:javascript
复制
if (msg) {      //    is there a message?
    result = BIO_write(m_sslIn, msg, len);
}

result = SSL_connect(m_ssl);
if (result < 0) {
    int err = SSL_get_error(m_ssl, result);
    printf("Error code: %d\n", err);
    if (err == SSL_ERROR_WANT_READ) {
        printf(" Want More read\n");
    } else if (err == SSL_ERROR_WANT_WRITE) {
        printf(" Want More write\n");
    } else {
        printf(" ERROR\n");
    }
}

SSL_connect()调用将导致失败。但失败的是错误: SSL_ERROR_WANT_READ,这意味着它正在等待更多的读取。因此,在调用之后,复制OpenSSL在输出BIO中所放的内容:

代码语言:javascript
复制
result = BIO_ctrl_pending(m_sslOut);            //  Any data there to read? 
if (result == 0) return 0;
m_msgOut = (unsigned char *) malloc(result);   //  Of course YOU own this memory, and need to clean it up   
length = BIO_read(m_sslOut,m_msgOut,result);
m_msgLength = length;
FlushErrors(m_ssl);        //  See below

在第一次传递时,您将看到开头的"CLIENT_HELLO“消息。

因此,现在在缓冲区中有消息,将其发送到服务器,并从服务器读取回复。获取该答复并将其再次存储在输入BIO中,再次调用SSL_connect(),然后从BIO中读取响应。一直往前走,直到握手结束。

此外,如上面的代码片段所示,每次调用SSL_connect时,都必须清除SSL句柄中堆积的所有错误:

代码语言:javascript
复制
int FlushErrors(void *ssl_ctx) {
    int count = 0;
    int result;
    while ((result = ERR_get_error())) {
        printf("TLS - SSL error: %s",
           ERR_error_string(result, NULL));
        count++;
    }
    return count;
}

对于我的EAPOL客户端来说,这个过程非常简单。复杂的部分是处理往返消息的碎片,因为一些TLS握手可能有许多证书,而且相当大。重要的是,在将其放入BIO并调用下一个SSL_connect之前,重新组装来自服务器的整个消息。

我希望这能帮助下一个人做同样的事情。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72465522

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档