带有 ECDHE 密钥和证书的服务器不工作

Server with ECDHE key and cert not working

我使用下面的 server.c 来源,我生成了

sinful-host-cert.pem
sinful-host.key

如此处所述:Elliptic Curve CA Guide

当运行程序出现以下错误:

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 */
}

as described here: Elliptic Curve CA Guide...

此页面有太多错误和遗漏,我会舍弃它。第一个红旗是白色文字和黑色背景。这告诉我经验不足的人正在提供页面...

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 等域参数

"lots of problems later" 记录在 OpenSSL wiki Elliptic Curve Cryptography, Named Curves 中。以下是您将遇到的一些问题:

  • 客户: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命令中使用of/lack或-*form将在下面讨论。


SSL_CTX* InitServerCTX(void) { ... }

这个代码块有不少问题。最值得注意的是缺少 ECDH 回调。您在哪里设置 SSL_CTX_set_tmp_ecdh 回调(OpenSSL 1.0.1 及以下版本),或者对 SSL_CTX_set_ecdh_auto 的调用在哪里(OpenSSL 1.0.2 及以上版本)?

其他包括默认协议、默认密码列表、弱密码和受伤密码、匿名协议的包含以及压缩。有关提供服务器上下文的部分代码示例,请参阅 .


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)

一般来说,无论是 -keyform-certform-inform-outform 等命令,总是使用 *form 选项。 OpenSSL并不总是正确的(即使默认情况下它应该使用 PEM)。


The error occurs at this line I think

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

如果私钥有密码,那么您需要提供密码回调或从文件中删除密码。

剥离密码是可以的,因为存储明文私钥没有区别;或加密的私钥,在密钥旁边的配置文件中带有密码。在这两种情况下,您唯一有效的安全措施是文件系统 ACL。

相关,这被称为无人值守的密钥存储问题。 Guttman 在他的书中对此进行了讨论 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) 清除下层库问题(见下文)。清除这些问题后,再看看这个问题是否仍然存在。


It looks like you are using an old version of OpenSSL, and that does not provide the newer error codes...

确保您是 运行 OpenSSL 1.0.0 或更高版本。 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 上的 Library Initialization


 if (SSL_CTX_set_cipher_list(ctx, "ECDHE-ECDSA-AES128-GCM-SHA256")

由于 SecureTransport 库中的错误,这会让您在某些版本的 OS X 和 iOS 上遇到麻烦。 Apple 仅在其操作系统的某些版本上修复了它。

如果您计划维修 Apple hardwarez,那么您将需要一个额外的 non-ECDHE-ECDSA 密码。并且您需要使用服务器端上下文选项 SSL_OP_SAFARI_ECDHE_ECDSA_BUG.

相关,Apple 在不修复其安全漏洞方面相当大胆。你有损坏的 ECDHE-ECDSA 密码套件;和像 CVE-2015-1130 (Hidden Backdoor with Root).

这样的宝石

这是我的 ECDH 回调在 OpenSSL 1.0.1 及更低版本中的样子。 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 默认使用 secp256

SSL_get_certificate 未记录。但是在<openssl src>/apps/s_cb.c中使用。这就是 OpenSSL 著名的 "self documenting" 代码。

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;
}