2025年4月14日 星期一 乙巳(蛇)年 正月十五 夜 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 安全防护

花式获取ssl证书有效期

时间:03-14来源:作者:点击数:51

前言

在网络通信过程中,为了数据在传输过程中保持私密,就要用到了数据加密认证的过程,加密证书就诞生了,今天主要分析有关pem类型加密证书的解析,读取证书里的有效期,。

一、.pem是什么?

加密证书有两种格式,pem和key 这两种格式分别存储的是证书base64加密和私钥base64加密还有格式分割符,也就是说pem存的是证书,key存的是私钥。

二、读取pem证书有效期

1. 命令读取

代码如下(示例):

  • openssl x509 -in cert.pem -noout -dates

输出打印:

  • notBefore=Jan 4 04:18:30 2021 GMT
  • notAfter=Dec 30 04:18:30 2036 GMT

2. C/C++读取

代码如下(示例):

  • #include <openssl/pem.h>
  • #include <openssl/asn1.h>
  • static int ossl_ascii_isdigit(const char inchar) {
  • if (inchar > 0x2F && inchar < 0x3A)
  • return 1;
  • return 0;
  • }
  • static int leap_year(const int year)
  • {
  • if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0))
  • return 1;
  • return 0;
  • }
  • static void determine_days(struct tm *tm)
  • {
  • static const int ydays[12] = {
  • 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
  • };
  • int y = tm->tm_year + 1900;
  • int m = tm->tm_mon;
  • int d = tm->tm_mday;
  • int c;
  • tm->tm_yday = ydays[m] + d - 1;
  • if (m >= 2) {
  • /* March and onwards can be one day further into the year */
  • tm->tm_yday += leap_year(y);
  • m += 2;
  • } else {
  • /* Treat January and February as part of the previous year */
  • m += 14;
  • y--;
  • }
  • c = y / 100;
  • y %= 100;
  • /* Zeller's congruence */
  • tm->tm_wday = (d + (13 * m) / 5 + y + y / 4 + c / 4 + 5 * c + 6) % 7;
  • }
  • int ASN1_time_to_tm(struct tm *tm, const ASN1_TIME *d)
  • {
  • static const int min[9] = { 0, 0, 1, 1, 0, 0, 0, 0, 0 };
  • static const int max[9] = { 99, 99, 12, 31, 23, 59, 59, 12, 59 };
  • static const int mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  • char *a;
  • int n, i, i2, l, o, min_l = 11, strict = 0, end = 6, btz = 5, md;
  • struct tm tmp;
  • #if defined(CHARSET_EBCDIC)
  • const char upper_z = 0x5A, num_zero = 0x30, period = 0x2E, minus = 0x2D, plus = 0x2B;
  • #else
  • const char upper_z = 'Z', num_zero = '0', period = '.', minus = '-', plus = '+';
  • #endif
  • /*
  • * ASN1_STRING_FLAG_X509_TIME is used to enforce RFC 5280
  • * time string format, in which:
  • *
  • * 1. "seconds" is a 'MUST'
  • * 2. "Zulu" timezone is a 'MUST'
  • * 3. "+|-" is not allowed to indicate a time zone
  • */
  • if (d->type == V_ASN1_UTCTIME) {
  • if (d->flags & ASN1_STRING_FLAG_X509_TIME) {
  • min_l = 13;
  • strict = 1;
  • }
  • } else if (d->type == V_ASN1_GENERALIZEDTIME) {
  • end = 7;
  • btz = 6;
  • if (d->flags & ASN1_STRING_FLAG_X509_TIME) {
  • min_l = 15;
  • strict = 1;
  • } else {
  • min_l = 13;
  • }
  • } else {
  • return 0;
  • }
  • l = d->length;
  • a = (char *)d->data;
  • o = 0;
  • memset(&tmp, 0, sizeof(tmp));
  • /*
  • * GENERALIZEDTIME is similar to UTCTIME except the year is represented
  • * as YYYY. This stuff treats everything as a two digit field so make
  • * first two fields 00 to 99
  • */
  • if (l < min_l)
  • goto err;
  • for (i = 0; i < end; i++) {
  • if (!strict && (i == btz) && ((a[o] == upper_z) || (a[o] == plus) || (a[o] == minus))) {
  • i++;
  • break;
  • }
  • if (!ossl_ascii_isdigit(a[o]))
  • goto err;
  • n = a[o] - num_zero;
  • /* incomplete 2-digital number */
  • if (++o == l)
  • goto err;
  • if (!ossl_ascii_isdigit(a[o]))
  • goto err;
  • n = (n * 10) + a[o] - num_zero;
  • /* no more bytes to read, but we haven't seen time-zone yet */
  • if (++o == l)
  • goto err;
  • i2 = (d->type == V_ASN1_UTCTIME) ? i + 1 : i;
  • if ((n < min[i2]) || (n > max[i2]))
  • goto err;
  • switch (i2) {
  • case 0:
  • /* UTC will never be here */
  • tmp.tm_year = n * 100 - 1900;
  • break;
  • case 1:
  • if (d->type == V_ASN1_UTCTIME)
  • tmp.tm_year = n < 50 ? n + 100 : n;
  • else
  • tmp.tm_year += n;
  • break;
  • case 2:
  • tmp.tm_mon = n - 1;
  • break;
  • case 3:
  • /* check if tm_mday is valid in tm_mon */
  • if (tmp.tm_mon == 1) {
  • /* it's February */
  • md = mdays[1] + leap_year(tmp.tm_year + 1900);
  • } else {
  • md = mdays[tmp.tm_mon];
  • }
  • if (n > md)
  • goto err;
  • tmp.tm_mday = n;
  • determine_days(&tmp);
  • break;
  • case 4:
  • tmp.tm_hour = n;
  • break;
  • case 5:
  • tmp.tm_min = n;
  • break;
  • case 6:
  • tmp.tm_sec = n;
  • break;
  • }
  • }
  • /*
  • * Optional fractional seconds: decimal point followed by one or more
  • * digits.
  • */
  • if (d->type == V_ASN1_GENERALIZEDTIME && a[o] == period) {
  • if (strict)
  • /* RFC 5280 forbids fractional seconds */
  • goto err;
  • if (++o == l)
  • goto err;
  • i = o;
  • while ((o < l) && ossl_ascii_isdigit(a[o]))
  • o++;
  • /* Must have at least one digit after decimal point */
  • if (i == o)
  • goto err;
  • /* no more bytes to read, but we haven't seen time-zone yet */
  • if (o == l)
  • goto err;
  • }
  • /*
  • * 'o' will never point to '\0' at this point, the only chance
  • * 'o' can point to '\0' is either the subsequent if or the first
  • * else if is true.
  • */
  • if (a[o] == upper_z) {
  • o++;
  • } else if (!strict && ((a[o] == plus) || (a[o] == minus))) {
  • int offsign = a[o] == minus ? 1 : -1;
  • int offset = 0;
  • o++;
  • /*
  • * if not equal, no need to do subsequent checks
  • * since the following for-loop will add 'o' by 4
  • * and the final return statement will check if 'l'
  • * and 'o' are equal.
  • */
  • if (o + 4 != l)
  • goto err;
  • for (i = end; i < end + 2; i++) {
  • if (!ossl_ascii_isdigit(a[o]))
  • goto err;
  • n = a[o] - num_zero;
  • o++;
  • if (!ossl_ascii_isdigit(a[o]))
  • goto err;
  • n = (n * 10) + a[o] - num_zero;
  • i2 = (d->type == V_ASN1_UTCTIME) ? i + 1 : i;
  • if ((n < min[i2]) || (n > max[i2]))
  • goto err;
  • /* if tm is NULL, no need to adjust */
  • if (tm != NULL) {
  • if (i == end)
  • offset = n * 3600;
  • else if (i == end + 1)
  • offset += n * 60;
  • }
  • o++;
  • }
  • if (offset && !OPENSSL_gmtime_adj(&tmp, 0, offset * offsign))
  • goto err;
  • } else {
  • /* not Z, or not +/- in non-strict mode */
  • goto err;
  • }
  • if (o == l) {
  • /* success, check if tm should be filled */
  • if (tm != NULL)
  • *tm = tmp;
  • return 1;
  • }
  • err:
  • return 0;
  • }
  • static int get_pem_effective_time(const char *filename, cert_times_t *pcert_times)
  • {
  • if(pcert_times == NULL)
  • {
  • printf("pcert_times is null\n");
  • return -1;
  • }
  • if(access(filename, F_OK) != 0)
  • {
  • printf("filename:%s not exist!\n", filename);
  • return -1;
  • }
  • FILE *fp = NULL;
  • fp = fopen(filename,"r");
  • if(fp == NULL)
  • {
  • printf("open file:%s failed\n", filename);
  • return -1;
  • }
  • X509 *cert = PEM_read_X509(fp, NULL, NULL, NULL);
  • if(cert)
  • {
  • struct tm stm;
  • //get before time
  • const ASN1_TIME *tm_before = X509_get0_notBefore(cert);
  • ASN1_time_to_tm(&stm, tm_before);
  • pcert_times->sec_before = mktime(&stm);
  • //get after time
  • const ASN1_TIME *tm_after = X509_get0_notAfter(cert);
  • ASN1_time_to_tm(&stm, tm_after);
  • pcert_times->sec_after = mktime(&stm);
  • X509_free(cert);
  • }
  • else
  • {
  • printf("Certificate information not read!\n");
  • return -1;
  • }
  • return 0;
  • }

3. 具体分析

使用openssl自带接口获取:

  • (1) X509 *PEM_read_X509(FILE *out, X509 **x, pem_password_cb *cb, void *u)

该函数是根据传入证书文件的句柄读取证书内容,并返回解析后的X509结构体。

  • (2) const ASN1_TIME *X509_get0_notBefore(const X509 *x)
  • (3) const ASN1_TIME *X509_get0_notAfter(const X509 *x)

该函数是获取证书有效的起始时间和结束时间。

  • (4) int ASN1_time_to_tm(struct tm *tm, const ASN1_TIME *d)

该函数是转换获取到的有效时间格式,这个接口openssl也有自带,但实际测试转换的时间有问题。

总结

可以结合《使用 OpenSSL 和 C 解析 X.509 证书》这篇文章了解更多内容。

以上两种方式都能够获取pem文件的有效期,如果内容帮助到了您,手留余香,点个赞👍吧

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门