在网络通信过程中,为了数据在传输过程中保持私密,就要用到了数据加密认证的过程,加密证书就诞生了,今天主要分析有关pem类型加密证书的解析,读取证书里的有效期,。
加密证书有两种格式,pem和key 这两种格式分别存储的是证书base64加密和私钥base64加密还有格式分割符,也就是说pem存的是证书,key存的是私钥。
代码如下(示例):
- openssl x509 -in cert.pem -noout -dates
-
输出打印:
- notBefore=Jan 4 04:18:30 2021 GMT
- notAfter=Dec 30 04:18:30 2036 GMT
-
代码如下(示例):
- #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;
- }
-
使用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文件的有效期,如果内容帮助到了您,手留余香,点个赞👍吧