X.509 数字证书解析

X.509 数字证书结构

ASN.1 描述的 X.509 证书结构如下。整体的 SEQUENCE 包含三个部分:tbsCertificate 表示数字证书的信息,signatureAlgorithm 表示签名证书所用的算法,而 signatureValue 表示具体的签名值。

Certificate ::= SEQUENCE {
    tbsCertificate       TBSCertificate,
    signatureAlgorithm   AlgorithmIdentifier,
    signatureValue       BIT STRING
}

数字证书的信息结构如下,它包括标识证书的版本、序列号、签名算法、证书颁发者信息、有效期、证书主体、主体公钥信息等内容。

TBSCertificate ::= SEQUENCE {
    version         [0]  EXPLICIT Version DEFAULT v1,
    serialNumber         CertificateSerialNumber,
    signature            AlgorithmIdentifier,
    issuer               Name,
    validity             Validity,
    subject              Name,
    subjectPublicKeyInfo SubjectPublicKeyInfo,
    issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
    -- If present, version MUST be v2 or v3
    subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
    -- If present, version MUST be v2 or v3
    extensions      [3]  EXPLICIT Extensions OPTIONAL
    -- If present, version MUST be v3
}

解析器的实现

.cer 等数字证书文件中,ASN.1 元素是按照 TLV 格式编码的,其中 TLV 表示 type-length-value 或 tag-length-value。数字证书文件的处理以字节为单位进行,每次处理一个 TLV 结构:

  • 首先读入一个字节,表示当前元素的类型。

  • 然后,再读入一个字节(记为 K),如果 K 小于 0x80,则将 K 作为当前元素值的长度;否则,读入接下来的 (K - 0x80) 个字节,作为当前元素值的长度。

  • 最后,依据元素的类型,读入相应的值,其长度已在上一步求出。

常用的 ASN.1 元素类型如下:

;; types
(define BOOLEAN #x01)           ;; boolean
(define INTEGER #x02)           ;; integer
(define BIT-STRING #x03)        ;; bit string
(define OCTET-STRING #x04)      ;; octet string
(define NULL #x05)              ;; null
(define OBJECT-IDENTIFIER #x06) ;; object identifier
(define UTF8-STRING #x0C)       ;; utf-8 string
(define PRINTABLE-STRING #x13)  ;; printable string
(define UTC-TIME #x17)          ;; utc time
(define SEQUENCE #x30)          ;; sequence
(define SET #x31)               ;; set

每一个 TLV 结构的处理过程如下。read-tlv 函数的返回值为 TLV 结构的总长度以及元素值。返回 TLV 结构总长度的原因是 SEQUENCE、SET 和标签的处理是递归进行的,而这些元素的值同样有长度限制,被调用方返回TLV 结构总长度可让调用方知道目前已经读取了多少个字节的值,并以此作为是否继续递归的依据。

;; read type-length-value
;; return (the length of tlv, value)
(define (read-tlv in)
  ;; value type
  (define type (read-byte in))
  ;; init length of value
  (define len0 (read-byte in))
  ;; real length of value
  (define len len0)
  ;; length of tlv
  (define tlv-len 2)
  ;; get the real length of value
  (when (> len0 #x80)
    (set! len
          (for/fold ([len 0])
                    ([i (in-range (- len0 #x80))])
            (begin (set! tlv-len (add1 tlv-len))
                   (+ (* len 256) (read-byte in))))))
  ;; set the length of tlv
  (set! tlv-len (+ tlv-len len))

  ;; check type
  (cond
    ;; [boolean]
    [(= type BOOLEAN)
     (let ([val (if (= (read-byte in) #xFF)
                    "True" "False")])
       ;; return result
       (values tlv-len val))]
    ;; [integer]
    [(= type INTEGER)
     (let ([val (for/fold ([hex-str ""])
                          ([i (in-range len)])
                  (string-append hex-str
                                 (byte->hex-str (read-byte in))))])
       ;; return result
       (values tlv-len val))]
    ;; [bit string]
    [(= type BIT-STRING)
     (let* ([padding-len (read-byte in)]
            [bin-int (for/fold ([int 0])
                               ([i (in-range (sub1 len))])
                       (+ (* int 256) (read-byte in)))]
            [val (arithmetic-shift bin-int (- padding-len))])
       ;; return result
       (values tlv-len val))]
    ;; [octet string]
    [(= type OCTET-STRING)
     (let ([val (for/fold ([hex-str ""])
                          ([i (in-range len)])
                  (string-append hex-str
                                 (byte->hex-str (read-byte in))))])
       ;; return result
       (values tlv-len val))]
    ;; [null]
    [(= type NULL)
     ;; return result
     (values tlv-len "NULL")]
    ;; [object identifier]
    [(= type OBJECT-IDENTIFIER)
     (let ([val ""])
       ;; special byte
       (let-values ([(spec1 spec2) (quotient/remainder (read-byte in) 40)])
         (set! val (string-append val
                                  (~a spec1) "." (~a spec2))))
       ;; the rest
       (let ([term 0])
         (for ([i (in-range (sub1 len))])
           (let* ([int-8 (read-byte in)]
                  [int-7 (bitwise-and int-8 #x7f)])
             ;; update term
             (set! term (+ (* term 128) int-7))
             ;; check if current byte has 0 as the most significant bit
             (when (= (bitwise-and int-8 #x80) 0)
               ;; append current term to current value
               (set! val (string-append val "." (~a term)))
               ;; reset term
               (set! term 0)))))
       ;; return result
       (values tlv-len (hash-ref oid-map val)))]
    ;; [printable string]
    [(or (= type UTF8-STRING)
         (= type PRINTABLE-STRING))
     (let ([val (read-string len in)])
       ;; return result
       (values tlv-len val))]
    ;; [utc time]
    [(= type UTC-TIME)
     (let ([val (read-string len in)])
       (define (str->utc-time str)
         (string-append
          ;; YY
          "20" (substring str 0 2) "-"
          ;; MM
          (substring str 2 4) "-"
          ;; DD
          (substring str 4 6) " "
          ;; hh
          (substring str 6 8) ":"
          ;; mm
          (substring str 8 10) ":"
          ;; ss
          (substring str 10 12)))
       ;; return result
       (values tlv-len (str->utc-time val)))]
    ;; [sequence] or [set] or [tag]
    [(or (= type SEQUENCE)
         (= type SET)
         (>= type #xa0))
     (let ([val-list '()])
       (let loop ([len-to-read len])
         (when (> len-to-read 0)
           (let-values ([(tlv-len val) (read-tlv in)])
             (set! val-list (append val-list (list val)))
             (loop (- len-to-read tlv-len)))))
       ;; return result
       (values tlv-len val-list))]
    ;; [error]
    [else
     (error "No Such Type")]))

正如前文所说,X.509 数字证书的整体结构是一个 SEQUENCE,该结构可由 Racket 的列表表示(类似于 brag 生成的 AST)。以下是一个证书结构的例子:

'((("02")
   "01e3b49aa18d8aa981256950b8"
   ("SHA-256 with RSA" "NULL")
   ((("organizationalUnitName" "GlobalSign Root CA - R2"))
    (("organizationName" "GlobalSign"))
    (("commonName" "GlobalSign")))
   ("2017-06-15 00:00:42" "2021-12-15 00:00:42")
   ((("countryName" "US"))
    (("organizationName" "Google Trust Services"))
    (("commonName" "GTS CA 1O1")))
   (("RSA" "NULL")
    31795268810366627125472379554728404010951627368207957505401768592454400303224548834341015151703028354596681900193558440687144942914300751672140195645755871282018249670557794798264455490202461016292667447495415244515309114542332479763781176797708680344357419033542595524503349322305048909437710098429599041734609247453878624554102927779072146778297535925602538896805003907131596637813072619981366075126916314225516752120977468075452024219340781652424284108610936973351854567407580757256177316933009587420470230765084832666529024403649118799126369121227061435599355610308332860344978154584138117481338130075677390942537789977749981813782721121853964289)
   ((("keyUsage" "True" "03020186")
     ("extKeyUsage" "301406082b0601050507030106082b06010505070302")
     ("basicConstraints" "True" "30060101ff020100")
     ("subjectKeyIdentifier" "041498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b")
     ("authorityKeyIdentifier"
      "301680149be20757671c1ec06a06de59b49a2ddfdc19862e")
     ("authorityInfoAccess"
      "3027302506082b060105050730018619687474703a2f2f6f6373702e706b692e676f6f672f67737232")
     ("cRLDistributionPoints"
      "30293027a025a0238621687474703a2f2f63726c2e706b692e676f6f672f677372322f677372322e63726c")
     ("certificatePolicies"
      "30363034060667810c010202302a302806082b06010505070201161c68747470733a2f2f706b692e676f6f672f7265706f7369746f72792f"))))
  ("SHA-256 with RSA" "NULL")
  3345434918610313049729486550840793910093460890897243357053074002893534760920000680323557091577387194140208872464238356169095501631346396732293932531632908931521996513321657568320271817999787810106314738445221079665910895646553070509384459037069343785450015303460251318834479619492145061542527912325273711552761667538267287114616260169098541076413176085893931205750539766126373456822337590801838411536482259834178620183942680302090570740234952680125676239622787256708788355405864346917641592951199536655183540639825650775730296452844997780321196540595246976487411275180761536504304202476712722313813899755849221207498)

针对用列表结构表示的证书,定义以下输出函数:

;; display indentation
(define (indent num)
  (display (make-string num #\space)))

;; number of spaces
(define 1-TAB 4)
(define 2-TAB 8)
(define 3-TAB 12)

;; display X.509 certificate
(define (display-X.509 cer-struct)
  (displayln "Certificate:")
  ;; ================== [Part 1] to be signed certificate ==================
  (let ([tbs-certificate (first cer-struct)])
    (indent 1-TAB)
    (displayln "Data:")
    ;; Version
    (let* [(version-str (first (first tbs-certificate)))
           (version (cond
                      [(string=? version-str "00") 1]
                      [(string=? version-str "01") 2]
                      [(string=? version-str "02") 3]
                      [else (error "Not Such Version")]))]
      (indent 2-TAB)
      (displayln (format "Version: ~a" version)))
    ;; Serial Number
    (let [(serial-number (second tbs-certificate))]
      (indent 2-TAB)
      (displayln (format "Serial Number: ~a" serial-number)))
    ;; Signature Algorithm Identifier
    (let [(signature-algorithm (first (third tbs-certificate)))]
      (indent 2-TAB)
      (displayln (format "Signature Algorithm: ~a" signature-algorithm)))
    ;; Issuer
    (let [(name-list (fourth tbs-certificate))]
      (indent 2-TAB)
      (displayln "Issuer")
      (for ([item name-list])
        (let ([kv (first item)])
          (indent 3-TAB)
          (displayln (format "~a: ~a" (first kv) (second kv))))))
    ;; Validity Period
    (let* ([validity-period (fifth tbs-certificate)]
           [not-before (first validity-period)]
           [not-after (second validity-period)])
      (indent 2-TAB)
      (displayln "Validity Period")
      (indent 3-TAB)
      (displayln (format "Not Before: ~a" not-before))
      (indent 3-TAB)
      (displayln (format "Not After: ~a" not-after)))
    ;; Subject
    (let [(name-list (sixth tbs-certificate))]
      (indent 2-TAB)
      (displayln "Subject")
      (for ([item name-list])
        (let ([kv (first item)])
          (indent 3-TAB)
          (displayln (format "~a: ~a" (first kv) (second kv))))))
    ;; Subject Public Key Information
    (let* ([info (seventh tbs-certificate)]
           [algorithm (first (first info))])
      (indent 2-TAB)
      (displayln "Subject Public Key Information")
      (indent 3-TAB)
      (displayln (format "Public Key Algorithm: ~a" algorithm))))
  ;; ===================== [Part 2] type of signature ======================
  (let* ([signature-type (second cer-struct)]
         [algorithm (first signature-type)])
    (indent 1-TAB)
    (displayln (format "Signature Algorithm: ~a" algorithm)))
  ;; ========================= [Part 3] signature ==========================
  (let ([signature (third cer-struct)])
    (indent 1-TAB)
    (displayln (format "Signature: ~X" signature))))

注意 read-tlv 中求得的 OID 为原始字符串(比如 1.2.840.113549.1.1.1),可定义哈希表将这些 OID 转换为具体的描述名称(比如 RSA):

;; http://www.oid-info.com
(define oid-map
  (make-hash
   (list
    (cons "1.2.840.113549.1.1.1" "RSA")
    (cons "1.2.840.113549.1.1.11" "SHA-256 with RSA")
    (cons "1.3.6.1.4.1.311.60.2.1.2" "jurisdictionOfIncorporationStateOrProvinceName")
    (cons "1.3.6.1.4.1.311.60.2.1.3" "jurisdictionOfIncorporationCountryName")
    (cons "1.3.6.1.4.1.11129.2.4.2" "ctEnabled")
    (cons "1.3.6.1.5.5.7.1.1" "authorityInfoAccess")
    (cons "2.5.4.3" "commonName")
    (cons "2.5.4.5" "serialNumber")
    (cons "2.5.4.6" "countryName")
    (cons "2.5.4.7" "localityName")
    (cons "2.5.4.8" "stateOrProvinceName")
    (cons "2.5.4.10" "organizationName")
    (cons "2.5.4.11" "organizationalUnitName")
    (cons "2.5.4.15" "businessCategory")
    (cons "2.5.29.14" "subjectKeyIdentifier")
    (cons "2.5.29.15" "keyUsage")
    (cons "2.5.29.17" "subjectAltName")
    (cons "2.5.29.19" "basicConstraints")
    (cons "2.5.29.31" "cRLDistributionPoints")
    (cons "2.5.29.32" "certificatePolicies")
    (cons "2.5.29.35" "authorityKeyIdentifier")
    (cons "2.5.29.37" "extKeyUsage"))))

完整代码可见 GitHub

运行结果

访问 google.com 并下载证书 google.com.crt,对其进行解析:

参考链接

X.509 Certificate Analysis

What are x509 certificates? RFC? ASN.1? DER?

Updated: