ASN1有很多實現版本,OpenSSL主要采用DER格式。
本文假設妳已經安裝好了OpenSSL,並且持有壹份1.1.1的源碼。
ASN1相關的頭文件為asn1.h、asn1t.h、源文件在crypto/asn1目錄中。
這個結構負責管理ANS1大多數數據類型的內存組織方式,字段含義:
length —— 管理的數據長度。
type —— 管理的數據類型。
data —— 數據指針。
flags —— 標誌位,跟具體數據類型有關。
管理的數據類型:
主要數據類型的取值:
其它壹些類型的定義:
在OpenSSL的實現中,每個數據類型都有壹個ITEM結構,它負責定義這個數據類型的編解碼規則。
相關字段含義如下:
itype —— 定義ITEM自身的類型,取值:
utype —— 定義管理的數據類型,取值前面有說明,例如V_ASN1_INTEGER。
templates —— 數據處理方法模板數組指針。
tcount —— 模板數組的個數。
funcs —— 回調方法指針。
size —— 負責數據結構的大小。
sname —— 指向數據結構的名稱。
前面講到,對每個數據類型來說,OpenSSL需要壹個對應的ITEM結構,下面這幾個宏為每個數據類型聲明ITEM結構,同時也聲明了相應的助記函數。如下:
拿 DECLARE_ASN1_FUNCTIONS(ASN1_INTEGER) 來說,宏定義展開形式為:
還有幾個宏負責為數據類型實現相應的助記函數。如下:
拿 IMPLEMENT_ASN1_ITEM(ASN1_INTEGER) 來說,其展開形式為:
可以看到,展開形式所實現的助記函數實際上是對ASN1內部幾個函數的封裝調用,對提供上層數據類型與內部類型的轉換,其中ASN1_VALUE是類型上下轉換的紐帶,它是個萬能指針:
typedef struct ASN1_VALUE_st ASN1_VALUE;
在asn1.h中,對基本數據類型聲明了助記函數:
可是我暫時還沒有找到對基本數據類型助記函數的宏實現。
OpenSSL的ASN1編碼處理依賴ASN1_ITEM對象,對基本類型來說,OpenSSL本身已經做了聲明和初使化,所以可以直接以相關函數中使用。然而對於自定義結構體,需要開發者來定義ASN1_ITEM對象和規則,有壹些宏可以提供幫助,摘錄部分如下:
在ASN1_ITEM結構中,ASN1_TEMPLATE的結構定義如下:
主要字段含義:
offset —— 字段在父結構中的偏移。
field_name —— 字段名稱。
item —— 指向字段對應結構類型的ASN1_ITEM對象。
舉例定義如下的自定義結構:
將宏展開,為:
ASN1_VALUE *ASN1_item_new(const ASN1_ITEM *it);
void ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it);
根據ITEM對象創建和釋放對應的數據類型。
ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **val, const unsigned char **in, long len, const ASN1_ITEM *it);
根據ITEM對象將DER編碼轉換為二進制結構。
成功返回有效指針,成功返回NULL。
如果val為NULL,將內部分配內存,但必須由外部使用者釋放。
其內部實現為:
可以看到內部做了為val傳入NULL的兼容性處理。
int ASN1_item_i2d(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it);
根據ITEM對象將二進制結構轉換為DER編碼。
成功返回DER編碼長度,失敗返回負數。
如果*out為NULL,將內部分配內存,但內存必須由外部使用者釋放。若**out為NULL,將僅返回長度,這用於試探長度的場景。
其內部實現為:
可以看到內部做了為out和*out傳入NULL的兼容性處理。在out傳入NULL的情況下,無法接收緩沖區,只能返回長度。
void *ASN1_item_d2i_bio(const ASN1_ITEM *it, BIO *in, void *x);
根據ITEM對象將DER編碼轉換為二進制結構,輸入的DER編碼來源於BIO對象。
成功返回有效指針,成功返回NULL。
事實上,本函數是對ASN1_item_d2i()的封裝調用。
其內部實現為:
void *ASN1_item_d2i_fp(const ASN1_ITEM *it, FILE *in, void *x);
ASN1_item_d2i_bio()的FILE版本。
ASN1_item_d2i_bio()和ASN1_item_d2i_fp() 這兩個函數關於輸出參數x的類型定義是不恰當的,後面講的 ASN1_d2i_bio()和ASN1_d2i_bio() 有修復這個問題。
int ASN1_item_i2d_bio(const ASN1_ITEM *it, BIO *out, void *x);
根據ITEM對象將二進制結構轉換為DER編碼,輸出到BIO對象中。
成功返回DER編碼長度,失敗返回負數。
如果*out為NULL,將內部分配內存,但內存必須由外部使用者釋放。事實上,本函數是對ASN1_item_i2d()的封裝調用。
其內部實現為:
int ASN1_item_i2d_fp(const ASN1_ITEM *it, FILE *out, void *x);
ASN1_item_i2d_bio()的FILE版本。
其實上,以上函數由於直接需要ASN1_ITEM做為參數,很少在程序中直接使用,而是由DECLARE_ASN1_FUNCTIONS宏聲明的變體,或者下面介紹的函數代替。
void *ASN1_d2i_bio(void *(*xnew) (void), d2i_of_void *d2i, BIO *in, void **x);
從BIO對象讀取DER編碼數據,轉換為對應的二進制結構,具體的轉換依賴d2i函數指針。
成功返回有效指針,失改返回NULL。
其內部實現為:
void *ASN1_d2i_fp(void *(*xnew) (void), d2i_of_void *d2i, FILE *in, void **x);
ASN1_d2i_bio()的FILE版本。
int ASN1_i2d_bio(i2d_of_void *i2d, BIO *out, unsigned char *x);
將二進制結構轉換為DER編碼,輸出到BIO中,具體的轉換依賴i2d函數指針。
成功返回1,失敗返回0。
其內部實現為:
int ASN1_i2d_fp(i2d_of_void *i2d, FILE *out, void *x);
ASN1_i2d_bio() 的FILE版本。
上面d2i_of_void 和 i2d_of_void 函數的簽名為:
通常為數據類型的轉換助記函數。
int ASN1_INTEGER_set(ASN1_INTEGER *a, long v);
將v值設置到a中。成功返回1,失敗返回0。
long ASN1_INTEGER_get(const ASN1_INTEGER *a);
獲取a中存儲的整數值。失敗返回-1。
int ASN1_ENUMERATED_set(ASN1_ENUMERATED *a, long v);
將v值設置到a中。成功返回1,失敗返回0。
long ASN1_ENUMERATED_get(const ASN1_ENUMERATED *a);
獲取a中存儲的枚舉值。失敗返回-1。
int ASN1_STRING_set(ASN1_STRING *str, const void *data, int len);
將data和len指向的串值設置到str中。成功返回1,失敗返回0。
unsigned char *ASN1_STRING_data(ASN1_STRING *x);
獲取x的字符串首指針。成功返回有效指針,失敗返回NULL。
int ASN1_STRING_length(const ASN1_STRING *x);
獲取x的字符串長度。
int i2a_ASN1_INTEGER(BIO *bp, const ASN1_INTEGER *a);
將ASN1_INTEGER轉換為ASC碼,輸出到bp中。成功返回1,失敗返回0。
int a2i_ASN1_INTEGER(BIO *bp, ASN1_INTEGER *bs, char *buf, int size);
將bp中的ASC碼轉換為ASN1_INTEGER,buf存放BIO的ASC碼。成功返回1,失敗返回0。
int i2a_ASN1_ENUMERATED(BIO *bp, const ASN1_ENUMERATED *a);
將ASN1_ENUMERATED轉換為ASC碼,輸出到bp中。成功返回1,失敗返回0。
int a2i_ASN1_ENUMERATED(BIO *bp, ASN1_ENUMERATED *bs, char *buf, int size);
將bp中的ASC碼轉換為ASN1_ENUMERATED,buf存放BIO的ASC碼。成功返回1,失敗返回0。
int i2a_ASN1_STRING(BIO *bp, const ASN1_STRING *a, int type);
將a中的字符串轉換為ASC碼輸出到bp中,type不起作用。返回轉換的ASC字符串長度。
int a2i_ASN1_STRING(BIO *bp, ASN1_STRING *bs, char *buf, int size);
將bp中的ASC碼轉換為ASN1_STRING,buf存放BIO的ASC碼。成功返回1,失敗返回0。
ASN1_OBJECT *ASN1_OBJECT_new(void);
分配OID對象。
void ASN1_OBJECT_free(ASN1_OBJECT *a);
釋放OID對象。
OID的編碼規為:第壹個八位組采用公式:first_arc* 40+second_arc。如果壹個數大於127,就采用多個8位表示,最高位用1表示後續還有octet,用0表示後續沒有。成功返回OID編碼的字節數。
int a2d_ASN1_OBJECT(unsigned char *out, int olen, const char *buf, int num);
計算由ASC字符串指定的OID的DER編碼。返回編碼的字節數。所果事先需要知道編碼的長度來分配內存,可以設置out為NULL,olen為0,獲取編碼字節長度,根據該長度再去分配內存。
ASN1_OBJECT *d2i_ASN1_OBJECT(ASN1_OBJECT **a, const unsigned char **pp, long length);
將DER編碼轉換為OID對象。成功返回有效指針。
int i2d_ASN1_OBJECT(const ASN1_OBJECT *a, unsigned char **pp);
將OID對象換轉為DER編碼。返回DER編碼的長度。
int i2a_ASN1_OBJECT(BIO *bp, const ASN1_OBJECT *a);
將DER編碼轉換為ASC字符串,結果輸出到bp中。返回ASC字符串長度。
下面這個例子測試了很多函數的用法,請自行放開註釋並測試。