您当前的位置:首页 > 计算机 > 安全防护

花式获取ssl证书有效期

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

前言

在网络通信过程中,为了数据在传输过程中保持私密,就要用到了数据加密认证的过程,加密证书就诞生了,今天主要分析有关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文件的有效期,如果内容帮助到了您,手留余香,点个赞👍吧

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