提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
在网络通信过程中,为了数据在传输过程中保持私密,就要用到了数据加密认证的过程,加密证书就诞生了,今天主要分析有关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文件的有效期,如果内容帮助到了您,手留余香,点个赞👍吧