X.509證書的讀取操作與分析(Python版)
X.509
1. 內容解析
1.1 定義
X.509 是密碼學裡公鑰證書的格式標準。
X.509 證書己應用在包括 TLS/SSL
在內的眾多 Intenet 協議裡.同時它也用在很多非線上應用場景裡,比如電子簽名服務。
X.509證書裡含有公鑰、身份資訊(比如網路主機名,組織的名稱或個體名稱等)和簽名信息(可以是證書籤發機構CA的簽名,也可以是自簽名)。對於一份經由可信的證書籤發機構簽名或者可以通過其它方式驗證的證書,證書的擁有者就可以用證書及相應的私鑰來建立安全的通訊,對文件進行數字簽名.
另外除了證書本身功能,X.509還附帶了證書吊銷列表和用於從最終對證書進行簽名的證書籤發機構直到最終可信點為止的證書合法性驗證演算法。
1.2 組成結構
-
證書
-
版本號
- 作用:【標識證書的版本(版本1、版本2、或是版本3)】
-
序列號
- 作用:【標識證書的唯一整數,由證書頒發者分配的本證書的唯一識別符號】
-
簽名演算法
- 作用:【由於簽名書的演算法標識,由物件識別符號加上相關的引數組成,用於說明本證書所用的數字簽名演算法。例如,SHA-1 和 RSA 的物件識別符號就用來說明該數字簽名是利用 RSA 對SHA-1 雜湊加密】
-
頒發者
- 作用:【證書頒發者的可識別名】
-
證書有效期
- 作用:【證書的有效期時間段】
- “Not Before” 此日期前無效
- “Not After” 此日期後有效
- 以上二者分別由
UTC
時間或一般的時間表示
- 作用:【證書的有效期時間段】
-
主體
- 作用:【證書擁有者的可識別名,這個欄位必須是非空的,除非你在證書擴充套件中有別名】
-
主體公鑰資訊
- 作用:【標識主題的公鑰以及演算法識別符號】
- 公鑰演算法
- 主題公鑰
- 作用:【標識主題的公鑰以及演算法識別符號】
-
頒發者唯一身份資訊(可選項)
- 作用:證書頒發者的唯一識別符號,僅在版本 2 與版本 3 中有要求,屬於可選項
-
主題唯一身份資訊
- 作用:證書擁有者的唯一識別符號,僅在版本 2 和版本 3 中有要求,屬於可選項
-
擴充套件資訊 (次重點部分)
-
發行者祕鑰識別符號
- 作用:【證書所含金鑰的唯一識別符號,用來區分同一證書擁有者的多對金鑰】
-
祕鑰使用
-
作用:【一個位元串,指明(限定)證書的公鑰可以完成的功能或服務,如:證書籤名、資料加密等。
如果某一證書將 KeyUsage 擴充套件標記為“極重要”,而且設定為“keyCertSign”,則在 SSL 通訊期間該證書出現時將被拒絕,因為該證書擴充套件表示相關私鑰應只用於簽寫證書,而不應該用於 SSL。】
-
-
CRL 分佈點
- 作用:【指明 CRL 的分佈地點】
-
私鑰的使用期
- 作用:【指明證書中與公鑰相聯絡的私鑰的使用期限,它也由 “ Not Before “ 和" Not After "組成。若此項不存在時,公私鑰的使用期是一樣的】
-
證書策略
- 作用:【由物件識別符號和限定符組成,這些物件識別符號說明證書的頒發和使用策略有關】
-
策略對映
- 作用:【表明兩個 CA 域之間的一個或多個策略物件識別符號的等價關係,僅在 CA 證書裡存在】
-
主體別名
- 作用:【指出證書擁有者的別名,如電子郵件地址、IP 地址等,別名是和 DN 繫結在一起的】
-
頒發者別名
- 作用:【指出證書頒發者的別名,如電子郵件地址、IP地址等,但頒發者的 DN 必須出現在證書的頒發者欄位】
-
主體目錄屬性
- 作用:【指出證書擁有者的一系列屬性。可以使用這一項來傳遞訪問控制資訊】
-
-
-
證書籤名演算法
-
數字簽名
1.3 安全性
- 採用黑名單方式的證書吊銷列表 CRL 和線上證書狀態協議( OCSP )
- 如果客戶端僅信任在CRL可用的時候信任證書,那就失去離線信任的需求。因此通常客戶端會在CRL不可用的情況下信任證書,因而給了那些可以控制通道的攻擊者可乘之機。如谷歌的Adam Langley所說,對CRL的檢查有如你期望安全帶在出事故事一定能正常使用的
- 在大範圍及複雜的分佈模式下選用CRL並不明智
- OCSP由於沒有吊銷狀態的歷史記錄也會出現歧義
- 聚合問題
- 代表問題: 證書頒發機構事沒辦法限制其下屬頒發的證書作出名字及屬性方面的限制。而且在Internet上存在著相當多的證書頒發機構,想對他們進行分類和策略上的限制是一項不可能完成的任務。
- 分佈問題: 證書鏈引的下屬頒發機構,橋接頒發機構以及交叉認證使得證書驗證變得非常複雜,需要付出很大的代價。層次式的第三方信任模型作為一種唯一的模型的話,路徑驗證也可能出現含糊不明的情況岐義,這對於已經建立雙邊信任也很不方便。
- 釋出一個對主機名的擴充套件驗證並不能防止再發佈一個驗證要求低一些的適用於同一個主機名的證書。這就造成了不能對中間人攻擊的有效保護
1.4 證書檔名擴充套件型別
X.509有多種常用的副檔名。不過其中的一些還用於其它用途,就是說具有這個副檔名的檔案可能並不是證書,比如說可能只是儲存了私鑰。
.pem
– (隱私增強型電子郵件) DER編碼的證書再進行 Base64 編碼的資料存放在"-----BEGIN CERTIFICATE-----“和”-----END CERTIFICATE-----"之中.cer, .crt, .der
– 通常是DER二進位制格式的,但 Base64 編碼後也很常見。.p7b, .p7c
– PKCS#7- 注:
PKCS#7
是簽名或加密資料的格式標準,官方稱之為容器。由於證書是可驗真的簽名資料,所以可以用 SignedData 結構表述。 - 注:
P7C
檔案是退化的 SignedData 結構,沒有包括簽名的資料。
- 注:
.p12
– PKCS#12格式,包含證書的同時可能還有帶密碼保護的私鑰- 注:
PKCS#12
由 PFX 進化而來的用於交換公共的和私有的物件的標準格式。
- 注:
.pfx
– PFX,PKCS#12之前的格式(通常用 PKCS#12 格式,比如那些由 IIS 產生的 PFX 檔案)
2. 資料結構
2.1 編碼
X.509
證書的結構使用ASN1
進行描述資料結構,並進行編碼;
ASN1
採用一個個的資料塊來描述整個資料結構,整個資料塊都由四部分組成:
-
資料塊資料型別標識(一個位元組)
-
資料型別包括簡單型別和結構型別
- 簡單型別:整型、位元串、日期型等
- 結構型別:順序型別、選擇型別、集合型別
-
資料塊數型別標識的一個位元組結構如下:
bit8
-bit7
標識TAG
型別 ,共四種類型,universal(00)、application(01)、context-specific(10)、private(11)
bit6
表示是否為結構型別bit5
-bit1
是型別的 TAG 值,根據bit8 - bit7
值為universal(00)
時,bit5
-bit1
表示不同的universal
值,如圖:bit8
-bit7
為context-specific
時,bit5-bit1
的值表示- [0] -> 證書的版本
- [1] -> issuerUniqueID,表示證書發行者的唯一 id
- [2] -> subjectUniqueID,表示證書主體的唯一 id
- [3] -> 表示證書的擴充套件欄位
-
舉例,如:
DER二進位制檔案的前4個位元組構成具有剩餘位元組的ASN.1序列。
如
30 82 06 E1
根據Type-Length-Value
表示,第一個位元組30(00110000)表示是一個universal class type,後面接著一個結構化型別的SEQUENCE。
-
-
資料塊長度( 1 - 128 個位元組),有三種編碼格式
- 長度小於等於 127 ,用一個位元組表示
- 長度大於 127 ,用多個位元組表示
- 位元組為
0x80
,表示資料塊長度不定,由資料塊結束標識結束資料塊
-
資料塊的值
- 存放資料塊的值,具體編碼隨資料塊型別的不同而不同
-
資料塊結束標識(可選)
- 結束標示欄位,兩個位元組(0x000),只有在長度不定時才會出現
2.2 主體結構
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate, -- 證書主體
signatureAlgorithm AlgorithmIdentifier, -- 證書籤名演算法標識
signatureValue BIT STRING --證書籤名值,是使用signatureAlgorithm部分指定的簽名演算法對tbsCertificate證書主題部分簽名後的值.
}
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1, -- 證書版本號
serialNumber CertificateSerialNumber, -- 證書序列號,對同一CA所頒發的證書,序列號唯一標識證書
signature AlgorithmIdentifier, --證書籤名演算法標識
issuer Name, --證書發行者名稱
validity Validity, --證書有效期
subject Name, --證書主體名稱
subjectPublicKeyInfo SubjectPublicKeyInfo,--證書公鑰
issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
-- 證書發行者ID(可選),只在證書版本2、3中才有
subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
-- 證書主體ID(可選),只在證書版本2、3中才有
extensions [3] EXPLICIT Extensions OPTIONAL
-- 證書擴充套件段(可選),只在證書版本3中才有
}
2.3 Version
證書的版本號:0,1,2
Version ::= INTEGER {
v1(0), -- 對應值為 0 ,證書只有基本項時為V1
v2(1), -- 對應值為 1 ,證書除了基本項之外,還包含了簽發者 ID 與使用者 ID
v3(2) -- 證書中包含擴充套件項
}
2.4 CertificateSerialNumber
證書的序列號:一串整數,對於每一張證書,證書序列號均唯一
CertificateSerialNumber ::= INTEGER
2.5 AlgorithmIdentifier
演算法結構標識,用來給定具體的演算法型別。
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
2.6 Validity
有效期:起始與終止時間
Validity ::= SEQUENCE {
notBefore Time, -- 證書有效期起始時間
notAfter Time -- 證書有效期終止時間
}
2.7 PublicKey
公鑰資訊
RSAPublicKey ::= SEQUENCE { -- RSA演算法時的公鑰值
modulus INTEGER, -- n
publicExponent INTEGER -- e --
}
2.8 UniqueIdentifier
由於簽發者和使用者的型別都是一樣的,是一些DN項(Distinguish Name)的集合
AttributeTypeAndValue ::= SEQUENCE {
type OBJECT IDENTIFIER,
value ANY DEFINED BY type
}
常見的DN項有:
屬性型別名稱 | 含義 | 簡寫 |
---|---|---|
Common Name | 通用名稱 | CN |
Organizational Unit name | 機構單元名稱 | OU |
Organization name | 機構名 | O |
Locality | 地理位置 | L |
State or province name | 州/省名 | S |
Country | 國名 | C |
2.9 SubjectPublicKeyInfo
定義公鑰型別資訊:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier, -- 公鑰演算法
subjectPublicKey BIT STRING -- 公鑰值
}
2.10 Extension
可省略的擴充套件型別:
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
Extension ::= SEQUENCE {
extnID OBJECT IDENTIFIER,
critical BOOLEAN DEFAULT FALSE,
extnValue OCTET STRING
}
3. 讀取操作程式
3.1 語言選擇
語言:Python
選擇原因:
- 第一次在寫的時候,由於直接使用了
Java
的基本類庫對證書進行操作,雖然能夠正常輸出所需要的結果,但是並沒有達到完成這次作業應有的目的,也沒有對X.509
證書的資料結構有一個直觀的認識。 - 在查詢了
X.509
證書的資料結構之後,從新使用python
進行程式設計,不依附現有的類庫,讀入證書並進行檔案解析。 - 使用
python
的好處在於可以方便進行字串的操作,同時使用Spyder
編譯器可以顯示出程式的函式呼叫過程,方便debug
3.2 編譯環境
作業系統: windows 10
條件:
- 安裝 Anaconda(一個大型的
python
整合環境,可以根據需要構建python 2
與python 3
獨立的編譯環境) - Spyder 編譯器
- 生成
der
型別的X.509
證書
3.3 證書讀取
使用python
的open()
函式,以可讀方式開啟證書:
filename = 'test.der'
f = open(filename,'rb')
3.4 型別判斷
X.509證書在儲存時候是按照位元組儲存的,同時我們需要根據2.1
中的資料標識型別來對具體的某個段進行操作,所以方法具體如下:
data = f.read(1)
type = ord(date);#將位元組轉為對應的數字
if type < 0x80: #那麼則證明滿足 universal 型別,可採用對應操作方法
elif type >= 0x80 and type < 0xa0:#隱式 Tag 操作型別
elif type >= 0x00: #顯式 Tag 操作型別
3.5 待處理資料長度判斷
這步主要是對X.509
中type > 127
的資料進行操作,比如:當我們解析公鑰時候,就需要進行多位元組操作
if type > 0x80:
type -= 0x80
for i in range(type):
length *= 256
length += ord(f.read(1))
else:
length = type
3.6 處理各種型別資料
我們需要根據資料型別的長度,來選定我們在2.3-2.10
中使用的資料型別,進而完成對資料的輸出:
比如,Interger
型別:
res_string = ""
nextByte = ord(f.read(1))
length = parse_Length(nexttype)
for i in range(0, length):
res_string += hex(ord(f.read(1)))[2:]
其他型別的轉換類似。
3.7 程式輸出
在上一步對所有的資料進行處理之後,我們可以將處理過的資料依次輸出了,比如對序列號的輸出:
choice = INTEGER_CHIOCE.get()
if choice == "Serial Number":
temp = ""
for i in range(0,len(parse_string)-3):
temp += parse_string[i:i+2] + ":"
temp += parse_string[len(parse_string)-2:len(parse_string)]
print(choice,":\n",temp,"\n")
elif choice != "Default Version":
print(choice,": ",Version[parse_string],"\n")
4. 編譯執行
4.1 Spyder下程式執行過程
上圖為程式中的函式呼叫過程;
4.2 執行結果
4.2.1 證書生成
- 生成一個2048位的金鑰
- 指令:
openssl genrsa -des3 -out privkey.pem 2048
- 指令:
- 生成一個證書請求
- 指令:
openssl req -new -key privkey.pem -out cert.csr
- 指令:
- 生成
cert.der
證書- 指令:
openssl x509 -outform der -in cert.pem -out cert.der
- 指令:
4.2.2 執行結果
對上一步生成的證書進行解析,結果如圖:
注:
- Version : 3
- Serial number : 9d:dd:d1:14:4c:c7:7a:a8:85:55:5c:c0:0d:d6:69:97:7f:f9:92:22:22:26:64:48:84:47:7f:8f
- Signature Algorithm : sha1WithRSAEncryption
- Validity:
- Not Before : 130514000000Z
- Not After : 160518120000Z
- Issuer:
- Country Name:US
- Organization Name:DigiCert Inc
- Organizational Unit Name:www.digicert.com
- Common Name:DigiCert High Assurance CA-3
- Subject Public Key Info RSAEncryption:1.2.840.113549.1.1.1
- Subject Public Key Encryption : 略
- Signature Algorithm:sha1WithRSAEncryption
- SIGNATURE Encryption : 略