具有ECDHE密钥和证书的服务器不起作用

5
我使用下面的server.c源代码,生成了以下两个文件:
sinful-host-cert.pem
sinful-host.key
具体步骤在这里描述:椭圆曲线CA指南 运行程序后出现以下错误:

140722397161136:error:10071065:elliptic curve routines:func(113):reason(101):ec_lib.c:995: 140722397161136:error:0B080075:x509 certificate routines:func(128):reason(117):x509_cmp.c:346:

我使用以下命令进行编译:

gcc server.c -ldl -lcrypto -lssl -o Server

我认为错误发生在以下代码行:

if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0)

server.c

#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>
#include "openssl/ssl.h"
#include "openssl/err.h"

#define FAIL    -1

int OpenListener(int port)
{   int sd;
    struct sockaddr_in addr;


    sd = socket(PF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));

    inet_aton("10.8.0.26", &addr.sin_addr);

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    //addr.sin_addr.s_addr = INADDR_ANY;

    if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
    {
        perror("can't bind port");
        abort();
    }
    if ( listen(sd, 10) != 0 )
    {
        perror("Can't configure listening port");
        abort();
    }
    return sd;
}

SSL_CTX* InitServerCTX(void)
{   const SSL_METHOD *method;
    SSL_CTX *ctx;

    OpenSSL_add_all_algorithms();  /* load & register all cryptos, etc. */
    SSL_load_error_strings();   /* load all error messages */

    //method = SSLv23_server_method();
    method = TLSv1_2_server_method();  /* create new server-method instance */
    ctx = SSL_CTX_new(method);   /* create new context from method */
    if ( ctx == NULL )
    {
        ERR_print_errors_fp(stderr);
        abort();
    }
    return ctx;
}

void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile)
{
    //New lines 
    if (SSL_CTX_set_cipher_list(ctx, "ECDHE-ECDSA-AES128-GCM-SHA256") != 1)
         ERR_print_errors_fp(stderr);

    if (SSL_CTX_load_verify_locations(ctx, CertFile, KeyFile) != 1)
        ERR_print_errors_fp(stderr);

    if (SSL_CTX_set_default_verify_paths(ctx) != 1)
        ERR_print_errors_fp(stderr);
    //End new lines

    /* set the local certificate from CertFile */
    if (SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0)
    {
        ERR_print_errors_fp(stderr);
        abort();
    }

    printf("FFFF\n");
    /* set the private key from KeyFile (may be the same as CertFile) */
    if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0)
    {
        ERR_print_errors_fp(stderr);
        abort();
    }
    printf("GGGG\n");
    /* verify private key */
    if (!SSL_CTX_check_private_key(ctx))
    {
        fprintf(stderr, "Private key does not match the public certificate\n");
        abort();
    }

    //New lines - Force the client-side have a certificate
    //SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
    //SSL_CTX_set_verify_depth(ctx, 4);
    //End new lines
}

void ShowCerts(SSL* ssl)
{   X509 *cert;
    char *line;

    cert = SSL_get_peer_certificate(ssl); /* Get certificates (if available) */
    if ( cert != NULL )
    {
        printf("Server certificates:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("Subject: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("Issuer: %s\n", line);
        free(line);
        X509_free(cert);
    }
    else
        printf("No certificates.\n");
}

void Servlet(SSL* ssl) /* Serve the connection -- threadable */
{   char buf[1024];
    char reply[1024];
    int sd, bytes, err;
    const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";

    printf("huhupre\n");

    err = SSL_accept(ssl);

    if ( err <= 0 ) {    /* do SSL-protocol accept */
        printf("%d\n",err);
        ERR_print_errors_fp(stderr);
    }
    else
    {
        printf("XXXXXX\n");
        //SSL_write(ssl, "huhu\n\r", 8);
        ShowCerts(ssl);        /* get any certificates */
        bytes = SSL_read(ssl, buf, sizeof(buf)); /* get request */
        if ( bytes > 0 )
        {
            buf[bytes] = 0;
            printf("Client msg: \"%s\"\n", buf);
            sprintf(reply, HTMLecho, buf);   /* construct reply */
            SSL_write(ssl, reply, strlen(reply)); /* send reply */
        }
        else
            ERR_print_errors_fp(stderr);
    }
    sd = SSL_get_fd(ssl);       /* get socket connection */
    SSL_free(ssl);         /* release SSL state */
    close(sd);          /* close connection */
}

int main()
{   SSL_CTX *ctx;
    int server;
    char portnum[]="5000";

        char CertFile[] = "sinful-host-cert.pem";
        char KeyFile[] = "sinful-host.key";

    SSL_library_init();

    ctx = InitServerCTX();        /* initialize SSL */
    LoadCertificates(ctx, CertFile, KeyFile); /* load certs */
    server = OpenListener(atoi(portnum));    /* create server socket */

    printf("%d while\n", server);
    while (1)
    {   struct sockaddr_in addr;
        socklen_t len = sizeof(addr);
        SSL *ssl;

        int client = accept(server, (struct sockaddr*)&addr, &len);  /* accept connection as usual */
        printf("Connection: %s:%d\n",inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        ssl = SSL_new(ctx);              /* get new SSL state with context */
        if (ssl == NULL) {

            ERR_print_errors_fp(stderr);
            return 0;

        }
        SSL_set_fd(ssl, client);      /* set connection socket to SSL state */
        Servlet(ssl);         /* service connection */
    }
    close(server);          /* close server socket */
    SSL_CTX_free(ctx);         /* release context */
}

当我运行以下命令时:openssl s_server -cert sinful-host-cert.pem -key sinful-host.key -accept 8888 -cipher ECDHE-ECDSA-AES256-GCM-SHA384,并使用以下命令连接到它:openssl s_client -host localhost -port 8888,它可以正常工作,因此密钥和证书似乎是好的,不是吗? - CutiePie666
请确认以下事项(1)证书和密钥是否为PEM格式;(2)密钥是否未设置密码;(3)您是否使用了命名曲线(而不是域参数)?也请说明您使用的OpenSSL版本。 - jww
1个回答

14

如此描述:椭圆曲线CA指南...

这个页面有很多错误和遗漏,我会抛弃它。第一个红旗是白色文本和黑色背景。这告诉我,提供该页面的人缺乏经验...

From the page:

openssl ecparam -list-curves

这应该是 -list_curves,而不是 -list-curves

From the page:

openssl ecparam -out sinful.key -name sect283k1 -genkey

这应该是:

openssl ecparam -param_enc named_curve -out sinful.key -name sect283k1 -genkey

如果您不使用命名曲线,那么以后会遇到很多问题,例如当客户端尝试连接服务器时。这里,命名曲线是像secp256k1这样的曲线的OID,而不是像pabG等域参数。
"以后会遇到很多问题"在 OpenSSL wiki 椭圆曲线密码学,命名曲线中有记录。以下是您将遇到的一些问题:
  • 客户端:139925962778272:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure:s3_pkt.c:1256:SSL alert number 40
  • 客户端:139925962778272:error:1409E0E5:SSL routines:SSL3_WRITE_BYTES:ssl handshake failure:s3_pkt.c:596
  • 服务器:140339533272744:error:1408A0C1:SSL routines:SSL3_GET_CLIENT_HELLO:no shared cipher:s3_srvr.c:1353

此外,为了最大的互操作性,您应该使用secp256k1。其次是secp521r1

此外,在openssl ecparamopenssl req命令中使用/不使用-*form将在下面讨论。


SSL_CTX* InitServerCTX(void) { ... }
这段代码有很多问题。其中最显著的问题是缺乏ECDH回调函数。你在哪里设置SSL_CTX_set_tmp_ecdh回调函数(OpenSSL 1.0.1及以下版本),或者在哪里调用SSL_CTX_set_ecdh_auto(OpenSSL 1.0.2及以上版本)?
其他问题包括默认协议、默认密码列表、弱密码和受损密码、匿名协议的包含和压缩。有关提供服务器上下文的部分示例代码,请参见'No Shared Cipher' Error with EDH-RSA-DES-CBC3-SHA

The error occurs at this line I think

if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0)
我认为这可以追溯到你提到的有问题的页面。具体来说,就是这个页面:
openssl req -x509 -new -key sinful.key -out sinful-ca.pem -outform PEM -days 3650

应该加上 -keyform (注意添加)。

openssl req -x509 -new -key sinful.key -keyform PEM -out sinful-ca.pem -outform PEM -days 3650

或者

if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_ASN1) <= 0)

通常,在命令中始终使用*form选项,无论是-keyform-certform-inform-outform等。即使 OpenSSL 默认使用 PEM,但它并不总是正确的。请注意保留 HTML 标签。

The error occurs at this line I think

if (SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0)
如果私钥有密码,那么您需要提供密码回调或从文件中删除密码。
删除密码是可以接受的,因为存储明文私钥或在密钥旁边的配置文件中存储带有口令的加密私钥没有区别。在这两种情况下,您拥有的唯一有效安全性是文件系统 ACL。
此问题被称为“无人值守密钥存储”问题。古特曼在他的书《Engineering Security》中讨论了这个问题,但它是一个没有解决方案的问题。

以下是更完整的错误信息...看起来您正在使用旧版本的OpenSSL,该版本不提供较新的错误代码。

When running the program get the following errors:

 140722397161136:error:10071065:elliptic curve routines:func(113):reason(101):ec_lib.c:995
 140722397161136:error:0B080075:x509 certificate routines:func(128):reason(117):x509_cmp.c:346

首先,是 0x10071065 错误:

$ /usr/local/ssl/macosx-x64/bin/openssl errstr 0x10071065
error:10071065:elliptic curve routines:EC_POINT_cmp:incompatible objects

"

0x10071065通常表示客户端和服务器使用不兼容的EC字段。在这种情况下,您应该使用secp256k1secp521r1中的任何一种。

其次,0x0B080075错误:

"
$ /usr/local/ssl/macosx-x64/bin/openssl errstr 0x0B080075
error:0B080075:x509 certificate routines:X509_check_private_key:unknown key type

我猜测证书和私钥不匹配。但这只是一个猜测。我建议 (1) 解决命名曲线问题,(2) 解决 sect283k1 问题,(3) 解决低版本库问题(见下文)。解决这些问题后,再看看是否仍存在此问题。
似乎你正在使用旧版本的OpenSSL,它不提供新的错误代码......请确保你正在运行1.0.0或更高版本的OpenSSL。0.9.8只支持有限的EC,但真正发挥作用直到1.0.0。最好使用OpenSSL 1.0.2。
 OpenSSL_add_all_algorithms();  /* load & register all cryptos, etc. */
 SSL_load_error_strings();   /* load all error messages */
此外,请参阅 OpenSSL wiki 上的 库初始化
 if (SSL_CTX_set_cipher_list(ctx, "ECDHE-ECDSA-AES128-GCM-SHA256")
这会在某些版本的OS X和iOS上给你带来麻烦,因为SecureTransport库中存在一个错误。苹果只在其操作系统的某些版本上修复了它。
如果您计划维修苹果硬件,则需要一个附加的non-ECDHE-ECDSA密码。并且您需要使用服务器端上下文选项SSL_OP_SAFARI_ECDHE_ECDSA_BUG
相关的是,苹果对不修复他们的安全漏洞非常大胆。您有损坏的ECDHE-ECDSA密码套件;以及像CVE-2015-1130(带根后门的隐藏后门)这样的宝石。
以下是OpenSSL 1.0.1及以下版本中ECDH回调的代码示例。OpenSSL 1.0.2应该使用SSL_CTX_set_ecdh_auto。这是C++代码,但很容易转换为C代码。另请参见OpenSSL邮件列表上的SL_CTX_set_tmp_ecdh_callback semantics in 1.0.1
下面的代码可能更加健壮。回调应该使用SSL_get_certificate(而不是SSL_get_peer_certificate)获取证书,查询证书的EC字段,然后在适当的字段中提供临时密钥,例如secp256k1secp571k1。(它有效是因为我的证书使用secp256,并且EcdhCallback将其用作默认值)。

SSL_get_certificate没有文档说明。但它在<openssl src>/apps/s_cb.c中使用。这就是OpenSSL著名的“自我记录”代码。

using SSL_ptr = std::shared_ptr<SSL>;
using SSL_CTX_ptr = std::shared_ptr<SSL_CTX>;

using EC_KEY_ptr = std::unique_ptr<EC_KEY, decltype(&::EC_KEY_free)>;
using EC_GROUP_ptr = std::unique_ptr<EC_GROUP, decltype(&::EC_GROUP_free)>;
using EC_POINT_ptr = std::unique_ptr<EC_POINT, decltype(&::EC_POINT_free)>;
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BIO_MEM_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using BIO_FILE_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
...

SSL_CTX* CreateServerContext(const string & domain)
{
    const SSL_METHOD* method = SSLv23_server_method();
    ASSERT(method != NULL);

    SSL_CTX_ptr t(SSL_CTX_new(method), ::SSL_CTX_free);
    ASSERT(t.get() != NULL);

    long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
            flags |= SSL_OP_NO_COMPRESSION;
            flags |= SSL_OP_SAFARI_ECDHE_ECDSA_BUG;
            flags |= SSL_OP_CIPHER_SERVER_PREFERENCE;

    SSL_CTX_set_options(t.get(), flags);

    string ciphers = "HIGH:!aNULL:!RC4:!MD5";
    rc = SSL_CTX_set_cipher_list(t.get(), ciphers.c_str());    
    ...

    LogDebug("GetServerContext: setting ECDH callback");
    SSL_CTX_set_tmp_ecdh_callback(t.get(), EcdhCallback);
    ...

    return t.release();
}

EC_KEY* EcdhCallback(SSL *ssl, int is_export, int keylength)
{
    UNUSED(ssl);
    UNUSED(is_export);
    UNUSED(keylength);

    /* This callback is OK, but OpenSSL calls it in a broken fashion. */
    /* With 1.0.1e and 1.0.1f, the value is 1024-bits. That is more   */
    /* appropriate for RSA.... We'll try and rewrite it here.         */
    if (keylength >= 1024)
    {
        keylength = 256;
        LogRelevant("EcdhCallback: field size is wrong, using 256-bit group");
    }

#if defined(ALLOW_ECDH_192_PARAMS)
    if (keylength <= 192 + 4)
        return ECDH192();
#endif

    if (keylength <= 224 + 4)
        return ECDH224();
    else if (keylength <= 256 + 4)
        return ECDH256();
    else if (keylength <= 384 + 4)
        return ECDH384();
    else if (keylength <= 521 + 4)
        return ECDH521();

    return ECDH521();
}

#if defined(ALLOW_ECDH_192_PARAMS)
static EC_KEY* ECDH192()
{
    static EC_KEY_ptr key(NULL, NULL);
    static once_flag flag;

    call_once(flag, []()
    {
        key = EC_KEY_ptr(InitEcdhkey(192), ::EC_KEY_free);
        ASSERT(key.get());

        if(!key.get())
            LogError("ECDH192: InitEcdhkey failed");
    });

    return key.get();
}
#endif

static EC_KEY* ECDH224()
{
    static EC_KEY_ptr key(NULL, NULL);
    static once_flag flag;

    call_once(flag, []()
    {
        key = EC_KEY_ptr(InitEcdhkey(224), ::EC_KEY_free);
        ASSERT(key.get());

        if(!key.get())
            LogError("ECDH224: InitEcdhkey failed");
    });

    return key.get();
}

static EC_KEY* ECDH256()
{
    static EC_KEY_ptr key(NULL, NULL);
    static once_flag flag;

    call_once(flag, []()
    {
        key = EC_KEY_ptr(InitEcdhkey(256), ::EC_KEY_free);
        ASSERT(key.get());

        if(!key.get())
            LogError("ECDH256: InitEcdhkey failed");
    });

    return key.get();
}

static EC_KEY* ECDH384()
{
    static EC_KEY_ptr key(NULL, NULL);
    static once_flag flag;

    call_once(flag, []()
    {
        key = EC_KEY_ptr(InitEcdhkey(384), ::EC_KEY_free);
        ASSERT(key.get());

        if(!key.get())
            LogError("ECDH384: InitEcdhkey failed");
    });

    return key.get();
}

static EC_KEY* ECDH521()
{
    static EC_KEY_ptr key(NULL, NULL);
    static once_flag flag;

    call_once(flag, []()
    {
        key = EC_KEY_ptr(InitEcdhkey(521), ::EC_KEY_free);
        ASSERT(key.get());

        if(!key.get())
            LogError("ECDH521: InitEcdhkey failed");
    });

    return key.get();
}

static EC_KEY* InitEcdhkey(int bits)
{
    if (bits <= 160 + 4)
        bits = 160;
    else if (bits <= 192 + 4)
        bits = 192;
    else if (bits <= 224 + 4)
        bits = 224;
    else if (bits <= 256 + 4)
        bits = 256;
    else if (bits <= 384 + 4)
        bits = 384;
    else if (bits <= 521 + 4)
        bits = 521;
    else
        bits = 521;

    EC_KEY* key = EC_KEY_new_by_curve_name(CurveToNidByBits(bits));
    unsigned long err = ERR_get_error();

    ASSERT(key != NULL);
    if (key == NULL)
    {
        ostringstream oss;
        oss << "InitEcdhkey: EC_KEY_new_by_curve_name failed for ";
        oss << bits << "-bit key, error " << err << ", 0x" << err;
        LogError(oss);
    }

    return key;
}

哇,非常感谢您提供如此丰富的信息!我需要一些时间来加以消化和学习,还请耐心等待。另外,我使用的是OpenSSL 1.02a版本,并且我至少对这一事实非常确定,您为什么认为我使用的是旧版本呢? - CutiePie666
@CutiePie666 - 从错误信息来看是这样的。你能提供你的链接命令吗?如果你只是使用了-lcrypto-lssl,那么你可能正在加载库的下一级版本(尽管你需要1.0.2)。另请参见使用静态和动态库编译OpenSSL。静态链接避免了加载错误的库。 - jww
我遵循了你非常详细的回答,但仍然得到相同的错误。错误信息为:140201599391408:error:10071065:elliptic curve routines:func(113):reason(101):ec_lib.c:995: 140201599391408:error:0B080075:x509 certificate routines:func(128):reason(117):x509_cmp.c:346: 当我执行 OpenSSL_add_all_algorithms();时,会得到这些错误;但当我使用 OpenSSL_add_ssl_algorithms(); 时,就不会出现这些错误。我不明白为什么在加载私钥文件时不会出错?现在我只能协商ECDH而不能使用ECDHE。 - CutiePie666
s_server在源文件中没有使用SSL_CTX_set_tmp_ecdh_callback(),但是当我使用它们与s_server连接并使用s_client连接时,我的证书和密钥都可以正常工作,并且它使用tlsv1_2 ECDHE-ECDSA-AES256-GCM-SHA384,就像应该的那样。但是当我使用我的源代码时,我要么得到上述错误,要么有时它会工作(?),但然后我只得到ECDH-ECDSA等。 - CutiePie666
1
截至2019年,prime256v1(又名secp256r1)和secp384r1似乎比secp256k1更好。后者似乎不被浏览器支持。 - Erwan Legrand
注意:在OpenSSL 1.1.0及更高版本中,SSL_CTX_set_ecdh_auto()是无操作。 - Jari Turkia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接