OpenSSL Library - 讀取 X509 certificate 的資訊
前陣子完成一個案子,這案子要驗證 RFID 卡的文件內容是否可信。 本文內容是工作過程中寫出來的一個練習程式與小工具。 此工具用途為查看本地的 X509 證書資訊,並回報其是否有效。 工具名稱為 x509-cert-info。
程式內容使用了 OpenSSL 函數庫。OpenSSL 函數庫內容很多,但公式文件的內容很少,就是些草稿。 我將陸續整理工作過程中得到的相關經驗,記錄在部落格上。
x509-cert-info
show-x509-info.h 只是一些自己定義的代號。與 OpenSSL 無關,不多解釋。
typedef enum {
OK,
ERROR_UNKNOWN,
ERROR_ALLOCATE_MEMORY_IS_FAILED,
ERROR_X509_IS_INVALID,
ERROR_X509_IS_EXPIRED,
ERROR_CA_FILE_IS_NOT_FOUND
} State;
typedef enum {
DER,
PEM
} Inform;
#define TRUE 1
#define FALSE 0
typedef unsigned char boolean;
boolean show_X509_info(
const char *cert_filepath,
Inform inform,
X509 **out_x509,
State *state);
show-x509-info.c 定義了函數 show_x509_info()。 此函數要求指定證書(certificate)的文件路徑(cert_filepath)與文件格式(inform)。 讀入的X509資訊放入指標 out_x509 傳回,另將詳細的函數執行狀態存入 state。 函數回傳值若為 TRUE,則此證書的格式正確且日期尚在有效期間內。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/x509.h>
#include "show-x509-info.h"
boolean show_X509_info(
const char *cert_filepath,
Inform inform,
X509 **out_x509,
State *state)
{
boolean rc = FALSE;
FILE *fp = NULL;
X509 *x509data = NULL;
X509_free(*out_x509);
fp = fopen(cert_filepath, "r");
if (fp == NULL) {
*state = ERROR_CA_FILE_IS_NOT_FOUND;
return FALSE;
}
if (inform == DER) {
d2i_X509_fp(fp, &x509data);
}
else {
PEM_read_X509(fp, &x509data, NULL, NULL);
}
fclose(fp);
if (x509data == NULL) {
*state = ERROR_X509_IS_INVALID;
return FALSE;
}
char issuer_name[1024];
char subject_name[1024];
char now_timez[16]; // YYYYMMDDhhmmss: 15, yyMMDDhhmmss: 13
time_t t;
struct tm tm;
X509_NAME_oneline(X509_get_issuer_name(x509data), issuer_name,
sizeof(issuer_name));
X509_NAME_oneline(X509_get_subject_name(x509data), subject_name,
sizeof(subject_name));
printf("Issuer name: %s\n", issuer_name);
printf("Subject name: %s\n", subject_name);
printf("Validity from: %s\n", x509data->cert_info->validity->notBefore->data);
printf("Validity till: %s\n", x509data->cert_info->validity->notAfter->data);
// x509data->cert_info->validity->notBefore->length
if (strcmp(issuer_name, subject_name) != 0) {
*state = ERROR_X509_IS_INVALID;
goto end_func;
}
t = time(NULL);
if (localtime_r(&t, &tm) == NULL) {
*state = ERROR_ALLOCATE_MEMORY_IS_FAILED;
goto end_func;
}
strftime(now_timez, sizeof(now_timez), "%y%m%d%H%M%SZ", &tm);
// notBefore < now_timez < notAfter
// 0 < n < 0
if ((0 < strcmp(now_timez, x509data->cert_info->validity->notBefore->data))
&& (strcmp(now_timez, x509data->cert_info->validity->notAfter->data) <0) )
{
rc = TRUE;
}
else
{
*state = ERROR_X509_IS_EXPIRED;
rc = FALSE;
}
end_func:
*out_x509 = x509data;
return rc;
}
在 OpenSSL 函數庫中,主要的資料輸出入行為是透過它定義的 BIO 類別進行。 但也提供其他的方式,例如透過 C 標準庫的 FILE。 本文還不會用到 BIO ,只用 FILE 讀入指定的文件內容。
處理 DER 格式的X509證書需要使用 d2i_X509, i2d_X509 家族函數,細節宣告於 x509.h (預設位於/usr/include/openssl目錄)。 處理 PEM 格式的X509證書則需要使用 PEM_read_X509, PEM_write_X509 家族函數,細節宣告於 pem.h。 請自行閱讀其標頭檔了解更多內容。
讀入的 X509 結構細節,查看 x509.h 的 struct x509_st。有些內容可以直接取出,例如有效日期。 有些內容則要配合指定的方法,例如 issuer_name 與 subject_name。 至於有哪些方法可以用,請查看 x509.h ,善用你的聯想力。
x509-cert-info 是主程式,它會根據使用者指定的證書路徑與格式,調用函數 show_X509_info() 顯示內容。 並根據 state 回報執行狀態。外部工具 – 例如 shell – 就可以根據程式狀態碼得知證書是否有效。
// gcc -lssl -o x509-cert-info x509-cert-info.c show-x509-info.c
#include <stdio.h>
#include <stdlib.h>
#include <openssl/x509.h>
#include "show-x509-info.h"
int main(int argc, char *args[])
{
char *cert_filepath = NULL;
State state = ERROR_UNKNOWN;
X509 *x509 = NULL;
int inform = DER;
if (argc < 2) {
printf("Show X509 certificate's information.\n");
printf("Usage: %s <cert_filepath> [format]\n", args[0]);
printf("Format: DER or PEM, Default format is DER.\n");
printf("Return status:\n");
printf("\t0: ok.\n");
printf("\t1: UNKNOWN.\n");
printf("\t2: ALLOCATE_MEMORY_IS_FAILED.\n");
printf("\t3: X509_IS_INVALID.\n");
printf("\t4: X509_IS_EXPIRED.\n");
printf("\t5: CA_FILE_IS_NOT_FOUND.\n");
return 255;
}
cert_filepath = args[1];
if (argc >= 3) {
if (args[2][0] == 'P' || args[2][0] == 'p')
inform = PEM;
}
printf("Information of %s:\n", cert_filepath);
if (!show_X509_info(cert_filepath, inform, &x509, &state)) {
switch (state)
{
case ERROR_ALLOCATE_MEMORY_IS_FAILED:
puts("ERROR: ALLOCATE_MEMORY_IS_FAILED\n");
break;
case ERROR_X509_IS_INVALID:
puts("ERROR: X509_IS_INVALID\n");
break;
case ERROR_X509_IS_EXPIRED:
puts("ERROR: X509_IS_EXPIRED\n");
break;
case ERROR_CA_FILE_IS_NOT_FOUND:
puts("ERROR: CA_FILE_IS_NOT_FOUND\n");
break;
default:
puts("ERROR: UNKNOWN\n");
break;
}
return state;
}
return 0;
}
編譯參數如下:
gcc -lssl -o x509-cert-info x509-cert-info.c show-x509-info.c
測試
建立測試用的證書
使用 openssl 工具的 req 選項可以建立一份證書。 在此例中,我們只是要一份測試程式功能的證書,不必考慮公信力,所以建立一份我們自己背書的證書即可。 用例如下:
# create PEM format certificate
openssl req \
-x509 -days 30 -newkey rsa:1024 \
-subj "/CN=rocksaying/O=Rock's blog./C=TW" \
-out mycert.pem
# create DER format certificate
openssl req \
-x509 -days 30 -newkey rsa:1024 \
-subj "/CN=rocksaying/O=Rock's blog./C=TW" \
-out mycert.der -outform DER
days 是有效天數。subj 是證書名稱,在此同時也會是發照者名稱,文字格式是 X.500。
證書格式通常使用 DER 或 PEM。
DER 是內容為二進位碼的證書,而 PEM 則是 Base64 編碼加上特定檔頭的證書。
這兩者可以互轉。例如我有一份 DER 格式的X509證書,要轉成 PEM 格式的話,只需要以 openssl 工具操作:
openssl x509 -in x509.crt -inform DER -out x509.pem -outform PEM
,即可轉得。
由於兩種格式可以互轉,所以建立證書時,通常只需要選擇其中一種格式發布。
測試結果
建立一份 PEM 格式的X509證書,檔名為 mycert.pem。測試的輸出過程如下:
$ openssl req \
> -x509 -days 30 -newkey rsa:1024 \
> -subj "/CN=rocksaying/O=Rock's blog./C=TW" \
> -out mycert.pem
Generating a 1024 bit RSA private key
................++++++
...........++++++
writing new private key to 'privkey.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
$ ./x509-cert-info mycert.pem
Information of mycert.pem:
ERROR: X509_IS_INVALID
$ ./x509-cert-info mycert.pem PEM
Information of mycert.pem:
Issuer name: /CN=rocksaying/O=Rock's blog./C=TW
Subject name: /CN=rocksaying/O=Rock's blog./C=TW
Validity from: 110727071525Z
Validity till: 110826071525Z
我先不帶參數地交給 x509-cert-info 查看內容,由於 x509-cert-info 預設格式為 DER ,故判定證書格式不符。 接著指定選項 PEM,就可成功查看證書內容。
openssl 工具的 x509 選項也可以查看證書內容。用例如: openssl x509 -text -in mycert.pem
。
可與本文程式所輸出的結果對照,確認程式無誤。
References
- PEM_read_X509()
- d2i_X509_fp()
- HOWTO certificates
- OpenSSL Command-Line HOWTO - Paul Heinlein.
OpenSSL 文件至今仍在草稿階段,有許多函數與結構的內容並未提及。 程序員需要自己查看 OpenSSL 的標頭文件(預設位於/usr/include/openssl)找尋有用的內容。 此外,OpenSSL 的源碼可說是必要的參考文件。 我建議將閱讀重點放在 apps 目錄內的源碼,其內容對應 openssl 命令項目,較容易理解其工作內容。
相關文章
- OpenSSL Library - BIO 概論
- OpenSSL Library - EVP, Digest and Cipher
- OpenSSL - SOD 安全文件概論
- OpenSSL - SOD 與 ASN1 解讀
樂多舊回應