在 Chrome 和 Firefox 中使用带有 OpenSSL 的 SAN 自签名

Self sign with SAN with OpenSSL in Chrome and Firefox

我创建了一个最小代码,用作 HTTPS 服务器,创建的 CA 如下:

$ openssl req -config openssl.conf -x509 -sha256 -nodes -extensions v3_ca -days 3650 -subj '/CN=OpenSSL CA/O=Example Company/C=SE' -newkey rsa:4096 -keyout ca.key -out ca.pem

我在 Chrome 和 Firefox 中将 CA 添加到受信任的证书中,并在主机文件中添加了一个新行以将 test.com 解析为 127.0.0.1。

在 Firefox 中它按预期工作,但是 Chrome returns a 'NET::ERR_CERT_COMMON_NAME_INVALID ' 错误。 Chrome 开始版本 58 不再支持通用名称,而是需要使用 subjectAltName 是有道理的。

所以我添加了一段代码(目前注释掉)来支持这个,然后 Firefox returns 错误 SEC_ERROR_BAD_SIGNATURE,Chrome returns NET::ERR_CERT_AUTHORITY_INVALID.

#include <openssl/err.h>
#include <openssl/conf.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <openssl/x509v3.h>


#define RSA_KEY_BITS (4096)

#define REQ_DN_C "SE"
#define REQ_DN_ST ""
#define REQ_DN_L ""
#define REQ_DN_O "Example Company"
#define REQ_DN_OU ""
#define REQ_DN_CN "test.com"

static void crt_to_pem(X509 *crt, uint8_t **crt_bytes, size_t *crt_size);
static int generate_key_csr(EVP_PKEY **key, X509_REQ **req);
static int generate_set_random_serial(X509 *crt);
static int generate_signed_key_pair(EVP_PKEY *ca_key, X509 *ca_crt, EVP_PKEY **key, X509 **crt);
static void key_to_pem(EVP_PKEY *key, uint8_t **key_bytes, size_t *key_size);
static int load_ca(const char *ca_key_path, EVP_PKEY **ca_key, const char *ca_crt_path, X509 **ca_crt);
static void print_bytes(uint8_t *data, size_t size);


int create_socket() {
    int sockfd, portno;
    char buffer[256];
    struct sockaddr_in serv_addr{}, cli_addr{};
    int  n;

    /* First call to socket() function */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if (sockfd < 0) {
        perror("ERROR opening socket");
        exit(1);
    }

    portno = 8000;

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    /* Now bind the host address using bind() call.*/
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        perror("ERROR on binding");
        exit(1);
    }

    listen(sockfd,5);

    return sockfd;
}

SSL_CTX *create_context()
{
    const SSL_METHOD *method;
    SSL_CTX *ctx;

    method = TLS_server_method();

    ctx = SSL_CTX_new(method);
    if (!ctx) {
        exit(EXIT_FAILURE);
    }

    return ctx;
}

int main(int argc, char **argv)
{
    char *ca_key_path = "ca.key";
    char *ca_crt_path = "ca.pem";

    /* Load CA key and cert. */
    EVP_PKEY *ca_key = NULL;
    X509 *ca_crt = NULL;
    if (!load_ca(ca_key_path, &ca_key, ca_crt_path, &ca_crt)) {
        fprintf(stderr, "Failed to load CA certificate and/or key!\n");
        return 1;
    }

    /* Generate keypair and then print it byte-by-byte for demo purposes. */
    EVP_PKEY *key = NULL;
    X509 *crt = NULL;

    int ret = generate_signed_key_pair(ca_key, ca_crt, &key, &crt);
    if (!ret) {
        fprintf(stderr, "Failed to generate key pair!\n");
        return 1;
    }

    /*
    X509_EXTENSION *cert_ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_subject_alt_name, "DNS:test.com");
    X509_add_ext(crt, cert_ex, -1);
    */
    

    /* Convert key and certificate to PEM format. */
    uint8_t *key_bytes = NULL;
    uint8_t *crt_bytes = NULL;
    size_t key_size = 0;
    size_t crt_size = 0;

    key_to_pem(key, &key_bytes, &key_size);
    crt_to_pem(crt, &crt_bytes, &crt_size);

    /* Print key and certificate. */
    print_bytes(key_bytes, key_size);
    print_bytes(crt_bytes, crt_size);


    auto ctx = create_context();
    auto lfd = create_socket();

    while (true) {
        int clilen = 0;
        struct sockaddr_in serv_addr{}, cli_addr{};
        auto s = accept(lfd, (struct sockaddr *)&cli_addr, reinterpret_cast<socklen_t *>(&clilen));

        auto ssl = SSL_new(ctx);
        SSL_set_fd(ssl, s);

        SSL_use_certificate(ssl, crt);
        SSL_use_PrivateKey(ssl, key);

        SSL_accept(ssl);

        char msg_200ok[] = "HTTP/1.1 404 \n"
                           "Content-Length: 0\n\n";
        SSL_write(ssl, msg_200ok, sizeof msg_200ok - 1);
    }



    /* Free stuff. */
    EVP_PKEY_free(ca_key);
    EVP_PKEY_free(key);
    X509_free(ca_crt);
    X509_free(crt);
    free(key_bytes);
    free(crt_bytes);

    return 0;
}

void crt_to_pem(X509 *crt, uint8_t **crt_bytes, size_t *crt_size)
{
    /* Convert signed certificate to PEM format. */
    BIO *bio = BIO_new(BIO_s_mem());
    PEM_write_bio_X509(bio, crt);
    *crt_size = BIO_pending(bio);
    *crt_bytes = (uint8_t *)malloc(*crt_size + 1);
    BIO_read(bio, *crt_bytes, *crt_size);
    BIO_free_all(bio);
}

int generate_signed_key_pair(EVP_PKEY *ca_key, X509 *ca_crt, EVP_PKEY **key, X509 **crt)
{
    /* Generate the private key and corresponding CSR. */
    X509_REQ *req = NULL;
    if (!generate_key_csr(key, &req)) {
        fprintf(stderr, "Failed to generate key and/or CSR!\n");
        return 0;
    }

    /* Sign with the CA. */
    *crt = X509_new();

    X509_set_version(*crt, 2); /* Set version to X509v3 */

    /* Generate random 20 byte serial. */
    generate_set_random_serial(*crt);

    /* Set issuer to CA's subject. */
    X509_set_issuer_name(*crt, X509_get_subject_name(ca_crt));

    /* Set validity of certificate to 2 years. */
    X509_gmtime_adj(X509_get_notBefore(*crt), 0);
    X509_gmtime_adj(X509_get_notAfter(*crt), (long)2*365*24*3600);

    /* Get the request's subject and just use it (we don't bother checking it since we generated
     * it ourself). Also take the request's public key. */
    X509_set_subject_name(*crt, X509_REQ_get_subject_name(req));
    EVP_PKEY *req_pubkey = X509_REQ_get_pubkey(req);
    X509_set_pubkey(*crt, req_pubkey);
    EVP_PKEY_free(req_pubkey);

    /* Now perform the actual signing with the CA. */
    if (X509_sign(*crt, ca_key, EVP_sha256()) == 0) goto err;

    X509_REQ_free(req);
    return 1;
    err:
    EVP_PKEY_free(*key);
    X509_REQ_free(req);
    X509_free(*crt);
    return 0;
}

int generate_key_csr(EVP_PKEY **key, X509_REQ **req)
{
    *key = NULL;
    *req = NULL;
    RSA *rsa = NULL;
    BIGNUM *e = NULL;

    *key = EVP_PKEY_new();

    *req = X509_REQ_new();

    rsa = RSA_new();

    e = BN_new();


    BN_set_word(e, 65537);
    RSA_generate_key_ex(rsa, RSA_KEY_BITS, e, NULL);
    EVP_PKEY_assign_RSA(*key, rsa);
    X509_REQ_set_pubkey(*req, *key);

    /* Set the DN of the request. */
    X509_NAME *name = X509_REQ_get_subject_name(*req);
    X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char*)REQ_DN_C, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (const unsigned char*)REQ_DN_ST, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (const unsigned char*)REQ_DN_L, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (const unsigned char*)REQ_DN_O, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (const unsigned char*)REQ_DN_OU, -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char*)REQ_DN_CN, -1, -1, 0);

    /* Self-sign the request to prove that we posses the key. */
    if (!X509_REQ_sign(*req, *key, EVP_sha256())) goto err;

    BN_free(e);

    return 1;
    err:
    EVP_PKEY_free(*key);
    X509_REQ_free(*req);
    RSA_free(rsa);
    BN_free(e);
    return 0;
}

int generate_set_random_serial(X509 *crt)
{
    /* Generates a 20 byte random serial number and sets in certificate. */
    unsigned char serial_bytes[20];
    if (RAND_bytes(serial_bytes, sizeof(serial_bytes)) != 1) return 0;
    serial_bytes[0] &= 0x7f; /* Ensure positive serial! */
    BIGNUM *bn = BN_new();
    BN_bin2bn(serial_bytes, sizeof(serial_bytes), bn);
    ASN1_INTEGER *serial = ASN1_INTEGER_new();
    BN_to_ASN1_INTEGER(bn, serial);

    X509_set_serialNumber(crt, serial); // Set serial.

    ASN1_INTEGER_free(serial);
    BN_free(bn);
    return 1;
}

void key_to_pem(EVP_PKEY *key, uint8_t **key_bytes, size_t *key_size)
{
    /* Convert private key to PEM format. */
    BIO *bio = BIO_new(BIO_s_mem());
    PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL);
    *key_size = BIO_pending(bio);
    *key_bytes = (uint8_t *)malloc(*key_size + 1);
    BIO_read(bio, *key_bytes, *key_size);
    BIO_free_all(bio);
}

int load_ca(const char *ca_key_path, EVP_PKEY **ca_key, const char *ca_crt_path, X509 **ca_crt)
{
    BIO *bio = NULL;
    *ca_crt = NULL;
    *ca_key = NULL;

    /* Load CA public key. */
    bio = BIO_new(BIO_s_file());
    if (!BIO_read_filename(bio, ca_crt_path)) goto err;
    *ca_crt = PEM_read_bio_X509(bio, NULL, NULL, NULL);
    if (!*ca_crt) goto err;
    BIO_free_all(bio);

    /* Load CA private key. */
    bio = BIO_new(BIO_s_file());
    if (!BIO_read_filename(bio, ca_key_path)) goto err;
    *ca_key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
    if (!ca_key) goto err;
    BIO_free_all(bio);
    return 1;
    err:
    BIO_free_all(bio);
    X509_free(*ca_crt);
    EVP_PKEY_free(*ca_key);
    return 0;
}

void print_bytes(uint8_t *data, size_t size)
{
    for (size_t i = 0; i < size; i++) {
        printf("%c", data[i]);
    }
}

问题是什么?如何调试? 谢谢

您正试图在 签名后将扩展添加到证书中。这使得 CA 的签名无效。您必须在签名前这样做,例如:

    X509_EXTENSION *cert_ex = X509V3_EXT_conf_nid(nullptr, nullptr, NID_subject_alt_name, "DNS:test.com");
    X509_add_ext(*crt, cert_ex, -1);

    /* Now perform the actual signing with the CA. */
    if (X509_sign(*crt, ca_key, EVP_sha256()) == 0) goto err;

因此它在 Firefox 和 Chrome 上对我都适用。