c++ - 基于 epoll 的服务器失败(相互认证)

标签 c++ sockets ssl openssl

基于 http://simplestcodings.blogspot.com/2010/08/secure-server-client-using-openssl-in-c.html 中的示例我已经编写了以下 epoll 服务器代码来用 SSL 替换常规的 tcp 套接字。自从我添加

以来,我的代码略有修改以进行相互身份验证并验证证书

SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); SSL_CTX_load_verify_locations(ctx,"/home/test/ssl/cert_files/cacert.pem",NULL); .

我的操作系统是 Ubuntu 12.04。

我还生成了服务器证书、客户端证书、服务器 key 、客户端 key ,当然还有来自 IBM 教程的 cacert.pem 文件 http://pic.dhe.ibm.com/infocenter/lnxinfo/v3r0m0/index.jsp?topic=%2Fliaat%2Fliaatseccreatecskeycert.htm

我运行了单个服务器 - 单个客户端代码,它运行得非常好。受此启发,我想看看 epoll 服务器代码的行为方式,但后来我遇到了问题。这是服务器的代码

无效的代码

//(c) 2014 enthusiasticgeek for stackoverflow - epollserver.cc
//============================================================================

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#include <iostream>
#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

#define MAXEVENTS 64


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();  /* 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)
{

    SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL);
    SSL_CTX_load_verify_locations(ctx,"/home/test/ssl/cert_files/cacert.pem",NULL);

    /* set the local certificate from CertFile */
    if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
    {
    ERR_print_errors_fp(stderr);
    abort();
    }
    /* 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();
    }
    /* verify private key */
    if ( !SSL_CTX_check_private_key(ctx) )
    {
    fprintf(stderr, "Private key does not match the public certificate\n");
    abort();
    }
}

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

static int
AibSocketNonBlocking (int sfd)
{
    int flags, s;

    flags = fcntl (sfd, F_GETFL, 0);
    if (flags == -1)
    {
    perror ("fcntl");
    return -1;
    }

    flags |= O_NONBLOCK;
    s = fcntl (sfd, F_SETFL, flags);
    if (s == -1)
    {
    perror ("fcntl");
    return -1;
    }

    return 0;
}

static int
AibCreateAndBind (char *port)
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int s, sfd;

    memset (&hints, 0, sizeof (struct addrinfo));
    hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
    hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
    hints.ai_flags = AI_PASSIVE;     /* All interfaces */

    s = getaddrinfo (NULL, port, &hints, &result);
    if (s != 0)
    {
    fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
    return -1;
    }

    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
    sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    if (sfd == -1)
        continue;

    s = bind (sfd, rp->ai_addr, rp->ai_addrlen);
    if (s == 0)
    {
        /* We managed to bind successfully! */
        break;
    }

    close (sfd);
    }

    if (rp == NULL)
    {
    fprintf (stderr, "Could not bind\n");
    return -1;
    }

    freeaddrinfo (result);

    return sfd;
}

int
main (int argc, char *argv[])
{
    SSL_CTX *ctx;
    SSL_library_init();
    ctx = InitServerCTX();        /* initialize SSL */
    LoadCertificates(ctx, "/home/test/ssl/cert_files/servercert.pem", "/home/test/ssl/cert_files/serverkey.pem"); /* load certs */

    int sfd, s;
    int efd;
    struct epoll_event aibevent;
    struct epoll_event *aibevents;
    if (argc != 2) {
    fprintf (stderr, "Usage: %s [port]\n", argv[0]);
    exit (EXIT_FAILURE);
    }

    char portt[sizeof (unsigned int)];
    snprintf(portt,sizeof portt + 1,"%u",atoi(argv[1]));

    printf("sizeof %s argv[1] = %d\n",argv[1], sizeof(argv[1]));
    printf("sizeof %s portt = %d\n",portt, sizeof(portt));

    sfd = AibCreateAndBind (portt);//argv[1]);
    if (sfd == -1) {
    abort ();
    }
    s = AibSocketNonBlocking (sfd);
    if (s == -1) {
    abort ();
    }
    s = listen (sfd, SOMAXCONN);
    if (s == -1) {
    perror ("listen");
    abort ();
    }
    efd = epoll_create1 (0);
    if (efd == -1) {
    perror ("epoll_create");
    abort ();
    }
    aibevent.data.fd = sfd;
    aibevent.events = EPOLLIN | EPOLLET;
    s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &aibevent);
    if (s == -1) {
    perror ("epoll_ctl");
    abort ();
    }

    // Buffer where events are returned
    //events = static_cast<epoll_event*>(calloc (MAXEVENTS, sizeof event));
    //aibevents = static_cast<epoll_event*>(malloc (MAXEVENTS * sizeof aibevent));
    aibevents = new epoll_event[MAXEVENTS * sizeof aibevent];

    // The event loop
    while (true)
    {
    int n, i;

    n = epoll_wait (efd, aibevents, MAXEVENTS, -1);
    for (i = 0; i < n; i++)
    {
        if ((aibevents[i].events & EPOLLERR) ||
                (aibevents[i].events & EPOLLHUP) ||
                (!(aibevents[i].events & EPOLLIN)))
        {
            // An error has occured on this fd, or the socket is not
            // ready for reading (why were we notified then?)
            fprintf (stderr, "epoll error\n");
            close (aibevents[i].data.fd);
            continue;
        } else if (sfd == aibevents[i].data.fd) {
            // We have a notification on the listening socket, which
            // means one or more incoming connections.
            while (1)
            {

                struct sockaddr in_addr;
                socklen_t in_len;
                int infd;
                char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

                in_len = sizeof in_addr;
                infd = accept (sfd, &in_addr, &in_len);
                if (infd == -1)
                {
                    if ((errno == EAGAIN) ||(errno == EWOULDBLOCK)) {
                        // We have processed all incoming
                        // connections.
                        break;
                    } else {
                        perror ("accept");
                        break;
                    }
                }

                s = getnameinfo (&in_addr, in_len,
                                 hbuf, sizeof hbuf,
                                 sbuf, sizeof sbuf,
                                 NI_NUMERICHOST | NI_NUMERICSERV);
                if (s == 0) {
                    printf("Accepted connection on descriptor %d "
                           "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                }

                // Make the incoming socket non-blocking and add it to the
                // list of fds to monitor.
                s = AibSocketNonBlocking (infd);
                if (s == -1) {
                    abort ();
                }
                aibevent.data.fd = infd;
                aibevent.events = EPOLLIN | EPOLLET;
                s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &aibevent);
                if (s == -1) {
                    perror ("epoll_ctl");
                    abort ();
                }



            }
            continue;
        } else {
            // We have data on the fd waiting to be read. Read and
            // display it. We must read whatever data is available
            // completely, as we are running in edge-triggered mode
            // and won't get a notification again for the same
            // data.
            int done = 0;
            int sd;

            SSL *ssl;

            while (1)
            {
                ssize_t count;
                char buf[1024];
                char reply[1024];

                printf("Performing exchange.\n");

                ssl = SSL_new(ctx);              /* get new SSL state with context */
                SSL_set_fd(ssl, aibevents[i].data.fd);      /* set connection socket to SSL state */

                printf("Performing exchange 1.\n");

                const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";

                if ( SSL_accept(ssl) == FAIL ) {    /* do SSL-protocol accept */
                    ERR_print_errors_fp(stderr);
                    printf("Performing exchange Error 1.\n");
                    done = 1;
                    break;
                } else {
                    ShowCerts(ssl);        /* get any certificates */
                    count = SSL_read(ssl, buf, sizeof(buf)); /* get request */
                    if ( count > 0 )
                    {
                        buf[count] = 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);
                        printf("Performing exchange Error 2.\n");
                        done = 1;
                        break;
                    }
                }
                sd = SSL_get_fd(ssl);       /* get socket connection */
                /*
                                    count = read (aibevents[i].data.fd, buf, sizeof buf);
                                    if (count == -1)
                                    {
                                        // If errno == EAGAIN, that means we have read all
                                        // data. So go back to the main loop.
                                        if (errno != EAGAIN)
                                        {
                                            perror ("read");
                                            done = 1;
                                        }
                                        break;
                                    }
                                    else if (count == 0)
                                    {
                                        // End of file. The remote has closed the
                                        // connection.
                                        done = 1;
                                        break;
                                    }
                                    // Write the buffer to standard output
                                    s = write (1, buf, count);
                                    if (s == -1)
                                    {
                                        perror ("write");
                                        abort ();
                                    }
                                    printf(" read correctly (n > 0) n==%d\n",s);
                                    printf("msg: %s\n", buf);
                                    write(aibevents[i].data.fd, buf,s);
                                    memset(buf,'\0', s);
                */
            }
            if (done)
            {
                printf("Freeing data.\n");
                SSL_free(ssl);         /* release SSL state */
                close(sd);          /* close connection */

                //printf ("Closed connection on descriptor %d\n",
                //        aibevents[i].data.fd);
                // Closing the descriptor will make epoll remove it
                // from the set of descriptors which are monitored.
                //close (aibevents[i].data.fd);
            }
        }
    }
    }
    //free (aibevents);
    delete[] aibevents;
    close (sfd);


    SSL_CTX_free(ctx);         /* release context */

    return EXIT_SUCCESS;
}

下面列出单服务器-单客户端代码,供引用。请注意,我使用相同的客户端代码与 epoll 服务器进行交互。

单一服务器代码。 有效

//SSL-Single Server code that works ! used as a reference for epoll server
#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));
    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();  /* 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)
{

    SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL);
    SSL_CTX_load_verify_locations(ctx,"/home/test/ssl/cert_files/cacert.pem",NULL);

    /* set the local certificate from CertFile */
    if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
    {
    ERR_print_errors_fp(stderr);
    abort();
    }
    /* 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();
    }
    /* verify private key */
    if ( !SSL_CTX_check_private_key(ctx) )
    {
    fprintf(stderr, "Private key does not match the public certificate\n");
    abort();
    }
}

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;
    const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";

    if ( SSL_accept(ssl) == FAIL )     /* do SSL-protocol accept */
    ERR_print_errors_fp(stderr);
    else
    {
    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(int count, char *strings[])
{   SSL_CTX *ctx;
    int server;
    char *portnum;

    if ( count != 2 )
    {
    printf("Usage: %s <portnum>\n", strings[0]);
    exit(0);
    }
    SSL_library_init();
    portnum = strings[1];
    ctx = InitServerCTX();        /* initialize SSL */
    LoadCertificates(ctx, "/home/test/ssl/cert_files/servercert.pem", "/home/test/ssl/cert_files/serverkey.pem"); /* load certs */
    server = OpenListener(atoi(portnum));    /* create server socket */
    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 */
    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 */
} 

单一客户端代码有效

// Single Client Code that works!
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define FAIL    -1


//Added the LoadCertificates how in the server-side makes.
void LoadCertificates(SSL_CTX* ctx, char* CertFile, char* KeyFile)
{

    SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL);
    SSL_CTX_load_verify_locations(ctx,"/home/test/ssl/cert_files/cacert.pem",NULL);
    /* set the local certificate from CertFile */
    if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
    {
    ERR_print_errors_fp(stderr);
    abort();
    }
    /* 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();
    }
    /* verify private key */
    if ( !SSL_CTX_check_private_key(ctx) )
    {
    fprintf(stderr, "Private key does not match the public certificate\n");
    abort();
    }
}

int OpenConnection(const char *hostname, int port)
{   int sd;
    struct hostent *host;
    struct sockaddr_in addr;

    if ( (host = gethostbyname(hostname)) == NULL )
    {
    perror(hostname);
    abort();
    }
    sd = socket(PF_INET, SOCK_STREAM, 0);
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = *(long*)(host->h_addr);
    if ( connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
    {
    close(sd);
    perror(hostname);
    abort();
    }
    return sd;
}

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

    OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */
    SSL_load_error_strings(); /* Bring in and register error messages */
    method = SSLv23_client_method(); /* Create new client-method instance */
    ctx = SSL_CTX_new(method); /* Create new context */
    if ( ctx == NULL )
    {
    ERR_print_errors_fp(stderr);
    abort();
    }
    return ctx;
}

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

    cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */
    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); /* free the malloc'ed string */
    line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
    printf("Issuer: %s\n", line);
    free(line); /* free the malloc'ed string */
    X509_free(cert); /* free the malloc'ed certificate copy */
    }
    else
    printf("No certificates.\n");
}

int main(int count, char *strings[])
{
    SSL_CTX *ctx;
    int server;
    SSL *ssl;
    char buf[1024];
    int bytes;
    if ( count != 3 )
    {
    printf("Usage: %s <host/ip> <portnum>\n", strings[0]);
    exit(0);
    }

    char* hostname=strings[1];//"127.0.0.1";
    char* portnum=strings[2];//"3334";
    char CertFile[] = "/home/test/ssl/cert_files/clientcert.pem";
    char KeyFile[] = "/home/test/ssl/cert_files/clientkey.pem";

    SSL_library_init();

    ctx = InitCTX();
    LoadCertificates(ctx, CertFile, KeyFile);
    server = OpenConnection(hostname, atoi(portnum));
    ssl = SSL_new(ctx); /* create new SSL connection state */
    SSL_set_fd(ssl, server); /* attach the socket descriptor */
    if ( SSL_connect(ssl) == FAIL ) /* perform the connection */
    ERR_print_errors_fp(stderr);
    else
    {   char *msg = "Hello???";

    printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
    ShowCerts(ssl); /* get any certs */
    SSL_write(ssl, msg, strlen(msg)); /* encrypt & send message */
    bytes = SSL_read(ssl, buf, sizeof(buf)); /* get reply & decrypt */
    buf[bytes] = 0;
    printf("Received: \"%s\"\n", buf);
    SSL_free(ssl); /* release connection state */
    }
    close(server); /* close socket */
    SSL_CTX_free(ctx); /* release context */
    return 0;
}

我做了编译使用 g++ -g <src>.cc -o <src> -lssl -lcrypto对于三个文件中的每一个(我有一个 Makefile)

看到的错误是

./tcpserver_epoll 3334 sizeof 3334 argv[1] = 4 sizeof 3334 portt = 4 Accepted connection on descriptor 5 (host=127.0.0.1, port=44716) Performing exchange. Performing exchange 1. Performing exchange Error 1. Freeing data. Performing exchange. Performing exchange 1. 3072792824:error:140760FC:SSL routines:SSL23_GET_CLIENT_HELLO:unknown protocol:s23_srvr.c:628: Performing exchange Error 1. Freeing data.

我想知道是否必须创建 SSL_CTX *ctx; 的多个实例.我有点迷路了。任何帮助表示赞赏。谢谢。

更新:感谢大家的指导。我在这里找到了一个与我想要的非常匹配的例子 http://bendecplusplus.googlecode.com/svn/trunk/ssl_mycode/epoll_ssl/server.c http://bendecplusplus.googlecode.com/svn/trunk/ssl_mycode/epoll_ssl/client.c

最佳答案

您的代码很难阅读(如何正确缩进?)但我认为主要问题是,您认为从 SSL_accept 返回 -1 是 fatal error 。

根据手册页(在这种情况下是正确的)-1 可能发生在 fatal error 或“它也可能发生在需要继续非阻塞 BIO 的操作时。调用 SSL_get_error() 并返回值返回以找出原因。”。所以你必须检查错误,如果它的 SSL_ERROR_WANT_READ 你必须等待套接字可读并且在 SSL_ERROR_WANT_WRITE 直到它可写。

关于c++ - 基于 epoll 的服务器失败(相互认证),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23408184/

相关文章:

sockets - 如何在Nginx和FastCGI中使用套接字

c++ - 使用 C++ 和 Linux 收听 "connection less UDP Multicast"的步骤

java - 在 SSL url 上运行 Karate 时如何修复 'Unexpected end of ZLIB input stream' 错误

c++ - C++ 中的 Doc 字符串工具

C++ 段错误在哪里?

c++ - 为什么类型变量;不调用默认的构造函数?

java - Socket.close() 怎么会失败(没有抛出异常)?

c++ - 为什么 QT 的 QList 方法与 std::list 兼容

tomcat - 在门户网站上使用 SSL 和 Tomcat 时出错

MongoDB SSL 副本设置问题 - 证书不受支持