当前位置 往事如风 Linux 正文 下一篇:

c语言web服务实现https证书存储在字符串中

一、背景

公司有一个需求需要在windows平台上实现一个web小程序,对该web小程序有如下要求:

  1. 体积小,10M以下
  2. 无需安装,下载下来,鼠标双击即可使用
  3. 实现https加密和浏览器进行通信

二、技术选型

为了实现这个小程序,公司对技术做了如下选型:

  1. 使用C语言编写
  2. 使用mongoose框架
  3. 使用openssl库实现https加密
  4. ssl证书保存在字符串中,不存储在文件中。

注: ssl证书不存储在文件中,而存储在字符串中,是因为该小程序不需要安装,只有一个exe文件,不能有其他额外的文件。

三、技术实现

公司使用mongoose框架做webserver,mongoose非常轻量,github地址是:https://github.com/cesanta/mongoose,目前有5k多个星,比较稳定

实现上比较麻烦的是ssl证书不存储在文件中,而是存储在字符串中。mongoose关于https的实现,都是基于证书为文件实现的,所以需要对相关代码进行修改。

下面直接上干货,修改的函数主要是mongoose.c文件中的mg_use_cert函数。

3.1 mg_user_cert函数默认加载ssl文件代码

static enum mg_ssl_if_result mg_use_cert(SSL_CTX *ctx, const char *cert,
                                         const char *key,
                                         const char **err_msg) {
  if (key == NULL) key = cert;
  if (cert == NULL || cert[0] == '\0' || key == NULL || key[0] == '\0') {
    return MG_SSL_OK;
  } else if (SSL_CTX_use_certificate_file(ctx, cert, 1) == 0) {
    MG_SET_PTRPTR(err_msg, "Invalid SSL cert");
    return MG_SSL_ERROR;
  } else if (SSL_CTX_use_PrivateKey_file(ctx, key, 1) == 0) {
    MG_SET_PTRPTR(err_msg, "Invalid SSL key");
    return MG_SSL_ERROR;
  } else if (SSL_CTX_use_certificate_chain_file(ctx, cert) == 0) {
    MG_SET_PTRPTR(err_msg, "Invalid CA bundle");
    return MG_SSL_ERROR;
  } else {
    SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
#if !MG_DISABLE_PFS && !defined(KR_VERSION)
    BIO *bio = NULL;
    DH *dh = NULL;

    /* Try to read DH parameters from the cert/key file. */
    bio = BIO_new_file(cert, "r");
    if (bio != NULL) {
      dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
      BIO_free(bio);
    }
    /*
     * If there are no DH params in the file, fall back to hard-coded ones.
     * Not ideal, but better than nothing.
     */
    if (dh == NULL) {
      bio = BIO_new_mem_buf((void *) mg_s_default_dh_params, -1);
      dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
      BIO_free(bio);
    }
    if (dh != NULL) {
      SSL_CTX_set_tmp_dh(ctx, dh);
      SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
      DH_free(dh);
    }
#if OPENSSL_VERSION_NUMBER > 0x10002000L
    SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
#endif
  }
  return MG_SSL_OK;
}
C

3.2 修改mg_user_cert函数从字符串中加载ssl证书

直接上修改好的代码,主要思路是:

  1. 使用函数SSL_CTX_use_certificate 替换SSL_CTX_use_certificate_file
  2. 使用PEM_read_bio_RSAPrivateKey 替换SSL_CTX_use_PrivateKey_file.
  3. 使用X509_STORE_add_cert函数替换SSL_CTX_use_certificate_chain_file来添加cert证书

具体代码如下:

static int mg_use_cert(SSL_CTX *ctx, const char *cert, const char *key) {
  if (ctx == NULL) {
    return -1;
  } else {
    int ret = 0;
#if 0
    if (SSL_CTX_use_certificate_file(ctx, cert, 1) == 0) {
        return -2;
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, key, 1) == 0) {
        return -2;
    }
#endif
    /* 使用函数SSL_CTX_use_certificate 替换SSL_CTX_use_certificate_file
     * 创建BIO结构,存储pem字符串内容,pem字符串内容存储在fangstarHelperPem字符数组中
     * fangstarHelperPem的存储见第四节:字符串存储ssl证书
     */
      BIO *cbio = BIO_new(BIO_s_mem());
      BIO_puts(cbio, fangstarHelperPem);
      X509* xCert = NULL;
      xCert = PEM_read_bio_X509(cbio, NULL, 0, NULL);
      ret = SSL_CTX_use_certificate(ctx, xCert);
      BIO_free(cbio);
      if (ret != 1) {
          printf("Failed to use cretificate\n");
          return -2;
      }
    /* 使用PEM_read_bio_RSAPrivateKey 替换SSL_CTX_use_PrivateKey_file.
     * 创建BIO结构,存储key字符串内容,key字符串内容存储在fangstarHelperKey字符数组中
     * fangstarHelperKey的存储见第下一节:字符串存储ssl证书
     */
      BIO *kbio = BIO_new(BIO_s_mem());
      BIO_puts(kbio, fangstarHelperKey);
      RSA *rsa = PEM_read_bio_RSAPrivateKey(kbio, NULL, 0, NULL);

      BIO_free(kbio);
      ret =  SSL_CTX_use_RSAPrivateKey(ctx, rsa);
      if (ret != 1) {
          printf("Failed to use RSA Private Key\n");
        return -2;
      }

      if (!SSL_CTX_check_private_key(ctx)) {
          printf("Failed to check RSA Private Key\n");
          return -2;
      }

#ifndef MG_DISABLE_PFS
    BIO *bio = NULL;
    DH *dh = NULL;
    /* Try to read DH parameters from the cert/key file. */
    //bio = BIO_new_file(cert, "r");
    bio = BIO_new(BIO_s_mem());
    BIO_puts(bio, fangstarHelperPem);
    //bio = BIO_new_mem_buf(fangstarHelperPem, sizeof(fangstarHelperPem));
    if (bio != NULL) {
      dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
      BIO_free(bio);
    }
    /*
     * If there are no DH params in the file, fall back to hard-coded ones.
     * Not ideal, but better than nothing.
     */
    if (dh == NULL) {
      bio = BIO_new_mem_buf((void *) mg_s_default_dh_params, -1);
      dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
      BIO_free(bio);
    }
    if (dh != NULL) {
      SSL_CTX_set_tmp_dh(ctx, dh);
      SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
      DH_free(dh);
    }
#endif
    SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
    //SSL_CTX_use_certificate_chain_file(ctx, cert);
    /* 使用X509_STORE_add_cert函数替换SSL_CTX_use_certificate_chain_file来添加cert证书 */
    BIO *chainBio = BIO_new(BIO_s_mem());
    BIO_puts(chainBio, fangstarHelperPem);
    X509* chainCert = NULL;
    while (chainCert = PEM_read_bio_X509(chainBio, NULL, 0, NULL)) {
        X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), chainCert);
    }
    BIO_free(chainBio);
    return 0;
  }
}
C

3.3 字符串存储ssl证书

本文只给出了ssl证书的一部分,详情如下

3.3.1 字符存储key

const char fangstarHelperKey[] = 
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEowIBAAKCAQEAnNRp0Ii1Rarc+0BIi5LPzVYbDd0lLoYLgjveTOfFq+0mH2/I\n"
"kLfqDG2rzEZ1NiVXvmtwsiqzejvmeWwraWpNDJVzyytCwevGiUZAlsZ4gXvUsdEO\n"
"2fL7QrOi+ve/AVaqEL7UuudVzh+pyVhZ1+fRRn4Ep2jLiEfJTyXwc6XWgSR5iqHg\n"
"gKHNNeYwirUPdCbIzcyOWEMghVe6O5Vyw6K8TlCuSd4YrxTcIJ6i4xO8jbly3y6N\n"
"XrRlp128ZmF2SO8PFDvF5y64k12031Zf+qhbJisdU7TJfqz+15CjAxBAK9ve33ff\n"
"PMMg/ple/Yxt5YdPl1cGp7G3k3DwfkvZPW+jlwIDAQABAoIBAEr5lZi3Mooa1Ehd\n"
"hBEN38BXsNlg5tymAqyBOJZlm2/FERuoUic1dXP5nk8rQ+/dzGPhc2AJ4AHIzzcv\n"
"SlW8Fxez/0aa6PBSanjIGCq8uXGVpKpZupLhgdNyk7ENbcgTCXy17ndoDvJa2s3C\n"
"-----END RSA PRIVATE KEY-----\n";
C

3.3.2 字符存储key

const char fangstarHelperPem[] = 
"-----BEGIN CERTIFICATE-----\n"
"MIIFjzCCBHegAwIBAgIQAQQoucU55ET7eK62KAW67DANBgkqhkiG9w0BAQsFADBu\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg\n"
"RFYgVExTIENBIC0gRzEwHhcNMTgwOTA2MDAwMDAwWhcNMTkwOTA2MTIwMDAwWjAe\n"
"MRwwGgYDVQQDExNyZmlkLmUuZmFuZ3N0YXIubmV0MIIBIjANBgkqhkiG9w0BAQEF\n"
"AAOCAQ8AMIIBCgKCAQEAnNRp0Ii1Rarc+0BIi5LPzVYbDd0lLoYLgjveTOfFq+0m\n"
"H2/IkLfqDG2rzEZ1NiVXvmtwsiqzejvmeWwraWpNDJVzyytCwevGiUZAlsZ4gXvU\n"
"sdEO2fL7QrOi+ve/AVaqEL7UuudVzh+pyVhZ1+fRRn4Ep2jLiEfJTyXwc6XWgSR5\n"
"iqHggKHNNeYwirUPdCbIzcyOWEMghVe6O5Vyw6K8TlCuSd4YrxTcIJ6i4xO8jbly\n"
"QSr4yaGavtbuS2Ezievk0jBZ7v24VNtyiOIeFxoU7WDjM2g=\n"
"-----END CERTIFICATE-----\n"
"-----BEGIN CERTIFICATE-----\n"
"MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n"
"QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT\n"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
"b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH\n"
"MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc\n"
"-----END CERTIFICATE-----\n";
C

四、参考文件

curlx.c
gistfile1.txt
OpenSSL API

本文来自网络,不代表往事如风立场,转载请注明出处:https://www.pastlikewind.com/2019/07/20/375/

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

返回顶部