[轉帖][譯] 寫給工程師:關於證書(certificate)和公鑰基礎設施(PKI)的一切(SmallStep, 2018)

http://arthurchiao.art/blog/everything-about-pki-zh/

 

本文翻譯自 2018 年的一篇英文博客: Everything you should know about certificates and PKI but are too afraid to ask, 作者 MIKE MALONE。

這篇長文並不是枯燥、零碎地介紹 PKI、X.509、OID 等概念,而是從前因後果、歷史沿革 的角度把這些東西串聯起來,邏輯非常清晰,讓讀者知其然,更知其所以然。


證書和 PKI 的目標其實很簡單:將名字關聯到公鑰(bind names to public keys)。

加密方式的演進:

 MAC         最早的驗證消息是否被篡改的方式,發送消息時附帶一段驗證碼
  |          雙方共享同一密碼,做哈希;最常用的哈希算法:HMAC
  |
  \/
 Signature   解決 MAC 存在的一些問題;雙方不再共享同一密碼,而是使用密鑰對
  |
  |
  \/
 PKC         公鑰加密,或稱非對稱加密,最常用的一種 Signature 方式
  |          公鑰給別人,私鑰自己留着;
  |          發送給我的消息:別人用 *我的公鑰* 加密;我用我的私鑰解密
  \/
 Certificate   公鑰加密的基礎,概念:CA/issuer/subject/relying-party/...
    |          按功能來說,分爲兩種
    |
    |---用於 *簽名*(簽發其他證書) 的證書
    |---用於 *加解密* 的證書

證書(certificate)相關格式及其關係(沉重的歷史負擔):

  最常用的格式   |      信息比 X.509 更豐富的格式       |       其他格式

  mTLS 等常用        Java 常用            微軟常用
                     .p7b .p7c          .pfx .p12

  X.509 v3            PKCS#7               PKCS#12        SSH 證書    PGP 證書     =====>  證書格式
      \                 |                    /                                           (封裝格式,證書結構體)
       \                |                   /
        \               |                  /
         \              |                 /
          \-------------+----------------/
                        |
                       ASN.1 (類似於 JSON、ProtoBuf 等)                          =====>  描述格式
                        |
          /-------------+----------------\
         /              |                 \
        /               |                  \
       /                |                   \
      /                 |                    \
   DER                 PEM                                                         =====>  編碼格式
二進制格式           文本格式                                                             (序列化)
  .der            .pem .crt .cer

一些解釋:

  1. X.509 從結構上定義證書中應該包含的信息,例如簽發者、祕鑰等等; 但使用哪個格式(例如 JSON 還是 YAML 還是 ASN.1)來描述,並不屬於 X.509 的內容;

  2. ASN.1 是 X.509 的描述格式(或者說用 ASN.1 格式來定義 X.509),類似於現在的 protobuf;

    • ASN 中有很多數據類型,除了常見的整形、字符串等類型,還有一個稱爲 OID 的特殊類型,用點分整數表示,例如 2.5.4.3,有點像 URI 或 IP 地址,在設計上是全球唯一標識符,
    • ASN.1 只是一種描述格式,並未定義如何序列化爲比特流,因此又引出了 ASN.1 的編碼格式; ASN.1 與其編碼格式的關係,類似 unicode 與 utf8 的關係。
  3. ASN.1 的常見編碼格式:

    • DER:一種二進制編碼格式
    • PEM:一種文本編碼格式,通常以 .pem.crt 或 .cer 爲後綴。
  4. 某些場景下,X.509 信息不夠豐富,因此又設計了一些信息更豐富(例如可以包含證書 鏈、祕鑰)的證書封裝格式,包括 PKCS #7 和 #12。

    • 仍然用 ASN.1 格式描述
    • 基本都是用 DER 編碼

以上提到的東西,再加上 CA、信任倉庫、信任鏈、certificate path validation、CSR、證書生命週期管理、 SPIFFE 等還沒有提到但也與加密相關的東西,統稱爲公鑰基礎設施(PKI)。


翻譯時調整了一些配圖,也加了幾張新圖,以方便展示和理解。

由於譯者水平有限,本文不免存在遺漏或錯誤之處。如有疑問,請查閱原文。

以下是譯文。



1 前言

證書(certificates)與 PKI(public key infrastructure,公鑰基礎設施)很難。我認識的很多非常聰明的人也會繞過這一主題。 我個人也很長時間沒去碰這些內容,但說起來很諷刺,我沒去碰的原因是不懂: 因爲不懂,所以不好意思問 —— 然後更不懂,自然更不好意思問 —— 如此形成惡性循環。

但最終,我還是硬着頭皮學習了這些東西。

1.1 爲什麼要學習 PKI

我覺得 PKI 能使一個人在加解密層面(乃至更大的安全層面)去思考如何定義一個系統。 具體來說,PKI 技術,

  • 都是通用的、廠商無關的(universal and vendor neutral);
  • 適用於任何地方,因此即使系統可分佈在世界各地,彼此之間也能安全地通信;
  • 在概念上很簡單,並且非常靈活;如果使用我們的 TLS everywhere 模型, 那甚至連 VPN 都不需要了。

總之一句話:非常強大!

1.2 本文目的

在深入理解了 PKI 之後,我很後悔沒有早點學這些東西。

  1. PKI 非常強大且有趣,雖然它背後的數學原理很複雜,一些相關標準也設計地非常愚蠢 (巴洛克式的複雜),但其 核心概念其實非常簡單;
  2. 證書是識別(identify)代碼和設備的最佳方式, 而 identity(身份)對安全、監控、指標等很多東西都非常有用;
  3. 使用證書並不是太難,不會難於學習一門新語言或一種新數據庫。

那爲什麼大家對這些內容望而卻步呢?我認爲主要是缺少很好的文檔,所以經常看地雲裏霧裏,半途而棄。

本文試圖彌補這一缺失。我認爲大部分工程師花一個小時讀完本文後,都將瞭解到 關於加解密的那些最重要概念和使用場景 —— 這正是本文的目的 —— 一小時只是很小的一個投資,而且這些內容是無法通過其他途徑學到的。

本文將用到以下兩個開源工具:

1.3 極簡 TL; DR(太長不讀)

證書和 PKI 的目的:將名字關聯到公鑰(bind names to public keys)。

這是關於證書和 PKI 的最高抽象,其他都屬於實現細節。

2 術語

本文將用到以下術語。

2.1 Entity(實體)

Entity 是任何存在的東西(anything that exists) —— 即使 只在邏輯或概念上存在(even if only exists logically or conceptually)。 例如,

  • 你用的計算機是一個 entity,
  • 你寫的代碼也是一個 entity,
  • 你自己也是一個 entity,
  • 你早餐喫的雜糧餅也是一個 entity,
  • 你六歲時見過的鬼也是一個 entity —— 即使你媽媽告訴你世界上並沒有鬼,這只是你的臆想。

2.2 Identity(身份)

每個 entity(實體)都有一個 identity(身份)。 要精確定義這個概念比較困難,這麼來說吧:identity 是使你之所以爲你 (what makes you you)的東西,懂嗎?

具體到計算機領域,identity 通常用一系列屬性來表示,描述某個具體的 entity, 這裏的屬性包括 group、age、location、favorite color、shoe size 等等。

2.3 Identifier(身份標識符)

Identifier 跟 identity 還不是一個東西:每個 identifier 都是一個唯一標識符, 也唯一地關聯到某個有 identity 的 entity。

例如,我是 Mike,但 Mike 並不是我的 identity,而只是個 name —— 雖然二者在我們 小範圍的討論中是同義的。

2.4 Claim(聲明) & Authentication(認證)

  • 一個 entity 能 claim(聲明)說,它擁有某個或某些 name。
  • 其他 entity 能夠對這個 claim 進行認證(authenticate),以確認這份聲明的真假。

    一般來說,認證的目的是確認某些 claim 的合法性。

  • Claim 不是隻能關聯到 name,還可以關聯到別的東西。例如,我能 claim 任何東西: my age, your age, access rights, the meaning of life 等等。

2.5 Subscriber & CA & relying party (RP)

  • 能作爲一個證書的 subject 的 entity,稱爲 subscriber(證書 owner)或 end entity。

    對應地,subscriber 的證書有時也稱爲 end entity certificates 或 leaf certificates, 原因在後面討論 certificate chains 時會介紹。

  • CA(certificate authority,證書權威)是給 subscriber 頒發證書的 entity,是一種 certificate issuer(證書頒發者)。

    CA 的證書,通常稱爲 root certificate 或 intermediate certificate,具體取決於 CA 類型。

  • Relying party 是 使用證書的用戶(certificate user),它驗證由 CA 頒發(給 subscriber)的證書是否合法。

    一個 entity 可以同時是一個 subscriber 和一個 relying party。 也就是說,單個 entity 既有自己的證書,又使用其他證書來認證 remote peers, 例如雙向 TLS(mutual TLS,mTLS)場景。

2.6 小結

對於我們接下來的討論,這些術語就夠了。下面將進入正題,看如何在實際中實現 證書的聲明和認證。

想了解更多相關術語,可參考 RFC 4949

3 MAC(消息認證碼)和 signature(簽名)

3.1 MAC(message authentication code)和 HMAC(hash-based MAC)

MAC(消息認證碼)是一小段數據,用於驗證某個 entity 發送的消息未被篡改。 其基本原理如下圖所示:

MAC/HMAC 原理。圖片來自:okta.com

  1. 對消息(message)和雙方都知道的一個密碼 (shared secret,a password)做哈希,得到的哈希值就是 MAC;
  2. 發送方將消息連帶 MAC 一起發給接收方;
  3. 接收方收到消息之後,用同一個密碼來計算 MAC,然後跟消息中提供的 MAC 對比。如果相同,就證明未被篡改。

關於哈希:

  • 哈希是單向的,因此無法從輸出反推輸入;這一點至關重要,否則截獲消息的人就可以根據 MAC 和哈希函數反推 secrets。
  • 生成 MAC 的哈希算法選擇也至關重要,本文不會展開,但提醒一點:不要試圖用自己設計的 MAC 算法。
  • 最常用的 MAC 算法是 HMAC(hash-based message authentication code)。

3.2 Signature(簽名)與不可否認性

討論 MAC 其實是爲了引出 signature(簽名)這一主題。

簽名在概念上與 MAC 類似,但不是用共享 secret 的方式, 而是使用一對祕鑰(key pair):

  • MAC 方式中,至少有兩個 entity 需要知道共享的 secret,也就是消息的發送方和接 收方。雙方都可以生成 MAC,因此給定一個合法的 MAC,我們是 無法知道是誰生成的。

  • 簽名就不同了:簽名能用公鑰(public key)驗證,但只能用相應的 私鑰(private key)生成。 因此對於接收方來說,它只能驗證簽名是否合法,而無法生成同樣的簽名。

如果只有一個 entity 知道祕鑰,那這種特性稱爲 non-repudiation (不可否認性):持有私鑰的人無法否認(repudiate)數據是由他簽名的這一事實。

3.3 小結

MAC 與 signature 都叫做簽名,是因爲它們和現實世界中的簽名是很像的。例如,如果想 讓某人同意某事,並且事後還能證明他們當時的確同意了,就把問題寫下來,然後讓他們 手寫簽字(簽名)。

4 Public key cryptography(公鑰加密,或稱非對稱加密)

證書和 PKI 的基礎是公鑰加密(public key cryptography), 也叫非對稱加密(asymmetric cryptography)。

4.1 祕鑰對

公鑰加密系統使用祕鑰對(key pair)加解密。一個祕鑰對包含:

  1. 一個私鑰(private key):owner 持有,解密用,不要分享給任何人;

    這一點非常重要,值得重複一遍:公鑰加密系統的安全性取決於私鑰(private key)的機密性。

  2. 一個公鑰(public key):加密用,可分發和共享給別人;

祕鑰可以做的事情:

  1. 加解密:公鑰(public key)加密,私鑰(private key)解密。
  2. 簽名:私鑰(private key)對數據進行簽名(sign some data); 任何有公鑰的人都可以對簽名進行驗證,證明這個簽名確實是私鑰生成的。

4.2 公鑰加密系統使計算機能“看到”對方

公鑰加密是數學給計算機科學的神祕禮物, 其數學基礎 顯然很複雜,但如果只是使用,那並不理解它的每一步數學原理。 公鑰加密使計算機能做一些之前無法做的事情:它們現在能看到對方是誰了。

這句話的意思是說,公鑰加密使一臺計算(或代碼)能向其他計算機或程序證明, 不用直接分享某些信息,它也能知道該信息。更具體來說,

  • 以前要證明你有密碼,就必須向別人展示這個密碼。但展示之後,任何有這個密碼的人就都能使用它了。
  • 私鑰卻與此不同。你能通過私鑰對我的身份進行認證(authenticate my identity),但卻無法假冒我。

    例如,你發給我一個大隨機數,我對這個隨機數進行簽名,然後將再發送給你。 你能用公鑰對這個簽名進行認證,確認這個簽名(消息)確實來自我。 這就是一種證明你在和我(而不是別的其他的人)通信的很好證據。這使得網絡上的 計算機能有效地知道它們在和誰通信。

    這聽起來是一件如此理所當然的事情,但仔細地想一下,網絡上只有流動的 0 和 1, 你怎麼知道消息來自誰,在和誰通信?因此公鑰加密系統是一個非常偉大的發明。

5 證書(certificate):計算機和代碼的駕駛證

前面說道,公鑰加密系統使我們能知道和誰在通信,但這個的前提是: 要知道(有)對方的公鑰。

那麼,如果對方不知道我的公鑰怎麼辦? 這就輪到證書出場了。

想一下,我們需求其實非常簡單:

  • 首先要將公鑰和它的 owner 信息發給對方;
  • 但光有這個信息還不行,還要讓對方相信這些信息;

    證書就是用來解決這個問題的,解決方式是請一個雙方都信任的權威機構 對以上信息作出證明(簽名)。

5.1 證書的內容:(subscriber 的)公鑰+名字

  • 證書是一個數據結構,其中包含一個 public key 和一個 name;
  • 權威機構對證書進行簽名,簽名的大概意思是:public key xxx 關聯到了 name xx;

    對證書進行簽名的 entity 稱爲 issuer(或 certificate authority, CA), 證書中的 entity 稱爲 subject。

舉個例子,如果某個 Issuer 爲 Bob 簽發了一張證書,其中的內容就可以解讀如下:

Some Issuer says Bob’s public key is 01:23:42…

證書是權威機構頒發的身份證明,並沒有什麼神奇之處

其中 Some Issuer 是證書的簽發者(證書權威),證書是爲了證明這是 Bob 的公鑰, Some Issuer 也是這個聲明的簽字方。

5.2 證書的本質:基於對 issuer 公鑰的信任來學習其他公鑰

由上可知,如果知道 Some Issuer 的公鑰,就可以通過驗證簽名的方式來 對它(用私鑰)簽發的證書進行認證(authenticate)。 如果如果你信任 Some Issuer,那你就可以信任這個聲明。

因此,證書使大家能基於對 issuer 公鑰的信任和知識,來學習到其他 entity 的公鑰 (上面的例子中就是 Bob)。這就是證書的本質。

5.3 與駕照的類比

證書就像是計算機/代碼的駕照或護照。如果你之前從未見過我,但信任車管局,那你可以 用我的駕照做認證:

  • 首先驗證駕照是真的(檢查 hologram 等),
  • 然後人臉和照片上對的上,
  • 然後看名字是我,等等。

計算機用證書做類似的事情:如果之前從未和其他電腦通信,但信任 一些證書權威,那可以用證書來認證:

  • 首先驗證證書是合法的(用證書籤發者的公鑰檢查簽名等),
  • 然後提取證書中的(subscriber 的)公鑰和名字,
  • 然後用 subscriber 的公鑰,通過網絡驗證該 subscriber 的簽名;
  • 查看名字是否正確等等。

5.4 證書內容解析舉例

下面是個真實的證書:

還是與駕照類比:

  • 駕照:描述了你是否有資格開車;
  • 證書:描述你是否是一個 CA,你的公鑰能否用來簽名或加密。
  • 二者都有有效期。

上圖中有大量的細節,很多東西將在下面討論到。但歸根結底還是本文最開始總結的那句話 :證書不過是一個將名字關聯到公鑰(bind names to public keys)的東西。 其他都是實現細節。

6 證書編碼格式及歷史演進

接下來看一看證書在底層的表示(represented as bits and bytes)。

這部分內容複雜且相當令人沮喪。事實上,我懷疑證書和祕鑰詭異的編碼方式 是導致 PKI 如此混亂和令人沮喪的根源。

6.1 X.509 證書

一般來說,人們提到“證書”而沒有加額外限定詞時,指的都是 X.509 v3 證書。

  • 更準確地說,他們指的是 RFC 5280 中描述、 CA/Browser Forum Baseline Requirements中進一步完善的 PKIX 變種。
  • 換句話說,指的是瀏覽器理解並用來做 HTTPS(HTTP over TLS)的那些證書。

也有其他的證書格式,例如著名的 SSH 和 PGP 都有它們各自的格式。

本文主要關注 X.509,理解了 X.509,其他格式都是類似的。 由於這些證書使用廣泛,因此有很好的函數庫,而且也用在瀏覽器之外的場景。毫無疑問,它們是 internal PKI 頒發的最常見證書格式。重要的是,這些證書在很多 TLS/HTTPS 客戶端/服 務端程序中都是開箱即用的。

X.509 起源:電信領域

瞭解一點 X.509 的歷史對理解它會有很大幫助。

X.509 在 1988 年作爲國際電信聯盟(ITU)X.500 項目的一部分首次標準化。 這是通信(telecom)領域的標準,想通過它構建一個全球電話簿(global telephone book)。 雖然這個項目沒有成功,但卻留下了一些遺產,X.509 就是其中之一。

如果查看 X.509 的證書,會看到其中包含了 locality、state、country 等信息, 之前可能會有疑問爲什麼爲 web 設計的證書會有這些東西,現在應該明白了,因爲 X.509 並不是爲 web 設計的。

6.2 ASN.1:數據抽象格式

X.509 構建在 ASN.1 (Abstract Syntax Notation,抽象語法標註)之上,後者是另一個 ITU-T 標準 (X.208 and X.680)。

ASN.1 定義數據類型,

  • 可以將 ASN.1 理解成 X.509 的 JSON,
  • 但實際上更像 protobuf、thrift 或 SQL DDL。

RFC 5280 用 ASN.1 來定義 X.509 證書,其中包括名字、祕鑰、簽名等信息。

6.3 OID (object identitfier)

ASN.1 除了有常見的數據類型,如整形、字符串、集合、列表等, 還有一個不常見但很重要的類型:OID(object identifier,對象標識符)。

  • OID 與 URI 有些像,但比 URI 要怪。
  • OID (在設計上)是全球唯一標識符。
  • 在結構上,OID 是在一個 hierarchical namespace 中的一個整數序列(例如 2.5.4.3)。

可以用 OID 來 tag 一段數據的類型。例如,一個 string 本來只是一個 string,但可 以 tag 一個 OID 2.5.4.3,然後就變成了一個特殊 string:這是 X.509 的通用名字(common name) 字段。

6.4 ASN.1 編碼格式

ASN.1 只是抽象(abstract),因爲這個標準並未定義在數據層應該如何表示(represented as bits and bytes)。 ASN.1 與其編碼格式的關係,就像 unicode 與 utf8 的區別。 因此,有很多種編碼規則(encoding rules),描述具體如何表示 ASN.1 數據。 原以爲增加這層額外的抽象會有所幫助,但實際證明大部分情況下反而徒增煩惱。

DER (distinguished encoding rules):二進制格式

ASN.1 有很多種編碼規則, 但用於 X.509 和其他加密相關的,只有一種常見格式:DER —— 雖然有時也會用到 non-canonical 的 basic encoding rules (BER,基礎編碼規則) 。

DER 是非常簡單的 TLV(type-length-value)編碼,但實際上用戶無需 關心這些,因爲函數庫封裝好了。但不要高興得太早 —— 雖然我們不必關心 DER 的編解碼, 但要能判斷給定的某個 X.509 證書是 DER 還是其他類型編碼的。這裏的其他類型包括:

  1. 一些比 DER 更友好的格式,
  2. 封裝了證書及其他額外信息的格式(something more than just a certificate)。

DER 編碼的證書通常以 .der 爲後綴。

PEM (privacy enhanced email):文本格式

DER 是二進制格式,不便複製粘貼。因此大部分證書都是以 PEM 格式打包的,這是 另一個歷史怪胎。

如果你熟悉 MIME 的話,二者是比較類似的: 由 header、base64 編碼的 payload、footer 三部分組成。 header 中有標籤(label)來描述 payload。例如下面是一個 PEM 編碼的 X.509 證書:

-----BEGIN CERTIFICATE-----
MIIBwzCCAWqgAwIBAgIRAIi5QRl9kz1wb+SUP20gB1kwCgYIKoZIzj0EAwIwGzEZ
MBcGA1UEAxMQTDVkIFRlc3QgUm9vdCBDQTAeFw0xODExMDYyMjA0MDNaFw0yODEx
BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRc+LHppFk8sflIpm/XKpbNMwx3
SDAfBgNVHSMEGDAWgBTirEpzC7/gexnnz7ozjWKd71lz5DAKBggqhkjOPQQDAgNH
ADBEAiAejDEfua7dud78lxWe9eYxYcM93mlUMFIzbWlOJzg+rgIgcdtU9wIKmn5q
FU3iOiRP5VyLNmrsQD3/ItjUN1f1ouY=
-----END CERTIFICATE-----

但令人震驚的時,即便如此簡單的功能,在實現上也已經出現混亂:PEM labels 在不同工具之間是不一致的。 RFC 7468 試圖標準化 PEM 的使用規範, 但也並不完整,不是所有工具都遵循這個規範。

PEM 編碼的證書通常以 .pem.crt 或 .cer 爲後綴。 再次提醒,這只是“通常”情況,實際上某些工具可能並不遵循這些慣例。

下面介紹幾個前面提到的“其他類型的打包格式”。

6.5 比 X.509 信息更豐富的證書打包(封裝)格式

X.509 只是一種常用的證書格式,但有人覺得這種格式能裝的信息不夠多,因此 又定義了一些比 X.509 更大的數據結構(但仍然用 ASN.1), 能將證書、祕鑰以及其他東西封裝(打包)到一起。因此,有時說我需要“一個證書”時,其 實真正說的是包(package)中包含的那個“證書”(a certificate in one of these envelopes),而不是這個包本身。

PKCS #7:Java 中常用

你可能會遇到的是一個稱爲 PKCS(Public Key Cryptography Standards,公鑰加密標準)的標準的一部分, 它由 RSA labs 發佈(真實歷史要 更加複雜一些,本文不展開)。

其中的第一個標準是 PKCS#7,後面被 IETF 重新冠名爲 Cryptographic Message Syntax (CMS) ,其中可以包含多個證書(以 full certificate chain 方式編碼,後面會看到)。

PKCS#7 在 Java 中使用廣泛。常見擴展名是 .p7b and .p7c

PKCS #12:微軟常用

另一個常見的打包格式 <a href=https://tools.ietf.org/html/rfc7292>PKCS#12</a>, 它能將一個證書鏈(這一點與 PKCS#7 類似)連同一個(加密之後的)私鑰打包到一起。

微軟的產品多用這種格式,常見後綴.pfx and .p12

再次說明,PKCS#7 和 PKCS#12 envelopes 仍然使用 ASN.1,這意味着 它們都能以原始 DER、BER 或 PEM 的格式編碼。 從我個人的經驗來看,二者幾乎都是 DER 編碼的。

6.6 祕鑰編解碼

祕鑰編碼(Key encoding)的過程與以上描述的類似(複雜):

  • 用某種 ASN.1 數據結構描述祕鑰(key);
  • 用 DER 做二進制編碼,或用 PEM (hopefully with a useful header) 做一些稍微友好一些的表示。

祕鑰的解密過程(deciphering),一半是是科學,一半是藝術。

如果足夠幸運,根據 RFC 7468 就能找到其中的 PEM payload;

  1. 橢圓曲線祕鑰通常符合 RFC 7468 規範,雖然 這裏看起來似乎也並沒有什麼標準

    下面是一個 PEM 編碼的橢圓曲線祕鑰(PEM-encoded elliptic curve key):

     $ step crypto keypair --kty EC --no-password --insecure ec.pub ec.prv
    
     $ cat ec.pub ec.prv
     -----BEGIN PUBLIC KEY-----
     MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc73/+JOESKlqWlhf0UzcRjEe7inF
     uu2z1DWxr+2YRLfTaJOm9huerJCh71z5lugg+QVLZBedKGEff5jgTssXHg==
     -----END PUBLIC KEY-----
     -----BEGIN EC PRIVATE KEY-----
     MHcCAQEEICjpa3i7ICHSIqZPZfkJpcRim/EAmUtMFGJg6QjkMqDMoAoGCCqGSM49
     AwEHoUQDQgAEc73/+JOESKlqWlhf0UzcRjEe7inFuu2z1DWxr+2YRLfTaJOm9hue
     rJCh71z5lugg+QVLZBedKGEff5jgTssXHg==
     -----END EC PRIVATE KEY-----
    
  2. 其他祕鑰,通常用 PEM label “PRIVATE KEY” 描述

PEM 編碼的 PKCS#8 格式私鑰

PEM label “PRIVATE KEY” 描述的祕鑰,通常暗示這是一個 PKCS#8 payload, 這是一種私鑰(private key)封裝格式,其中包含祕鑰類型和其他 metadata。

密碼加密的私鑰

用密碼來加密私鑰也很常見(private keys encrypted using a password),這裏的密碼可以是 a shared secret or symmetric key。 看起來大致如下(Proc-Type and DEK-Info 是 PEM 的一部分,表示這個 PEM 的 payload 是用 AES-256-CBC 加密的):

-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,b3fd6578bf18d12a76c98bda947c4ac9

qdV5u+wrywkbO0Ai8VUuwZO1cqhwsNaDQwTiYUwohvot7Vw851rW/43poPhH07So
sdLFVCKPd9v6F9n2dkdWCeeFlI4hfx+EwzXLuaRWg6aoYOj7ucJdkofyRyd4pEt+
Mj60xqLkaRtphh9HWKgaHsdBki68LQbObLOz4c6SyxI=
-----END EC PRIVATE KEY-----

PKCS#8 對象也能被加密,這種情況下 header label 應該是 "ENCRYPTED PRIVATE KEY" per RFC 7468。 這種情況下不會看到 Proc-Type 和 Dek-Info headers,因爲這些信息此時編碼到了 payload 中。

公鑰、私鑰常見擴展名

  • 公鑰:.pub or .pem
  • 私鑰:.prv, .key, or .pem

但再次說明,有些工具或組織可能並不遵循業界慣例。

6.7 小結

  • ASN.1 用於定義數據類型,例如證書(certificate)和祕鑰(key)—— 就像用 JSON 定義一個 request body —— X.509 用 ASN.1 定義。
  • DER 是一組將 ASN.1 編碼成二進制(比特和字節)的編碼規則(encoding rules)。
  • PKCS#7 and PKCS#12 是比 X.509 更大的數據結構(封裝格式),也用 ASN.1 定義,其 中能包含除了證書之外的其他東西。二者分別在 Java 和 Microsoft 產品中使用較多。
  • DER 編碼之後是二進制數據,不方便複製粘貼,因此大部分證書都是用 PEM 編碼的,它 用 base64 對 DER 進行編碼,然後再加上自己的 label。
  • 私鑰通常用是 PEM 編碼的 PKCS#8 對象,但有時也會用密碼來加密。

如果覺得以上內容理解起來很雜亂,那並不是你的問題,而是加密領域的現狀就是如此。我已經盡力了。

7 PKI (Public Key Infrastructure)

至此我們已經知道了證書的來歷和樣子,但這僅僅是本文的一半。 下面看證書是如何創建和使用的。

Public key infrastructure (PKI) 是一個統稱,包括了我們在 如下與證書和祕鑰管理及交互操作時需要用到的所有東西:簽發、分發、存放、使用、驗證、撤回等等。 就像“數據庫基礎設施” 一樣,這個名詞是有意取的這樣模糊的。

  • 證書是大部分 PKI 的構建模塊,而證書權威是其基礎。
  • PKI 包括了 libraries, cron jobs, protocols, conventions, clients, servers, people, processes, names, discovery mechanisms, and all the other stuff you’ll need to use public key cryptography effectively。

自己從頭開始構建一個 PKI 是一件極其龐大的工作, 但實際上 一些簡單的 PKI 甚至並不使用證書。例如,

  • 編輯 ~/.ssh/authorized_keys 文件時,就是在配置 一個簡單的無證書形式的(certificate-less)PKI,SSH 通過這種方式在扁平文件內 實現 public key 和 name 的綁定;
  • PGP 用證書,但不用 CA,而是用一個 web-of-trust model;
  • 甚至可以 用區塊鏈 來 assign name 並將它們 bind 到 public key。

如果從頭開始構建一個 PKI,唯一確定的事情是:你需要用到公鑰(public keys), 其他東西都隨設計而異。

下文將主要關注 web 領域使用的 PKI,以及基於 Web PKI 技術、遵循現有標準的 internal PKI。

證書和 PKI 的目標其實很簡單:將名字關聯到公鑰(bind names to public keys)。 在下面的內容中,不要忘了這一點。

7.1 Web PKI vs Internal PKI

瀏覽器訪問 HTTPS 鏈接時會用到 Web PKI。雖然也有一些問題,但它大大提升了 web 的安全性,而且基本上對用戶透明。在訪問互聯網 web 服務時,應該在所有可能的情 況下都啓用它。

  • Web PKI 由 RFC 5280 定義, CA/Browser Forum (a.k.a., CA/B or CAB Forum) 對其進行了進一步完善。
  • 有時也稱爲 “Internet PKI” 或 PKIX (after the working group that created it).

PKIX 和 CAB Forum 文檔涵蓋了很大內容。 它們定義了前面討論的各種證書、還定義什麼是 “name” 以及位於證書中什麼位置、能使用什麼簽名算法、 RP 如何判斷 issuer 的證書、如何指定證書的 validity period (issue and expiry dates)、 撤回、certificate path validation、CA 判斷某人是否擁有一個域名等等。

Web PKI 很重要,是因爲瀏覽器默認使用 Web PKI 證書。

Internal PKI 是用戶爲自己的產品基礎設施使用的 PKI,這些產品包括

  • 服務、容器、虛擬機等;
  • 企業 IT 應用;
  • 公司終端設備,例如筆記本電腦、手機等;
  • 其他需要識別的代碼或設備。

Internal PKI 使你能認證和建立加密通道,這樣你的服務就可以安全地在公網上的任意位置互相通信了。

7.2 有了 Web PKI,爲什麼還要使用自己的 internal PKI?

首先,簡單來說:Web PKI 設計中並沒有考慮內部使用場景。 即使有了 Let’s Encrypt 這樣的提供免費證書和自動化交付的 CA, 用戶還是需要自己處理 rate limits 和 availability 之類的事情。 如果有很多 service,部署很頻繁,就非常不方便。

另外,Web PKI 中,用戶對證書生命週期、撤回機制、續約過程、祕鑰類型、算法等等很 多重要的細節都沒有控制權,或只有很少控制權。而下文將會看到,這些都是非常重要的東西。

最後,CA/Browser Forum Baseline Requirements 實際上禁止將 Web PKI CA 關聯到 internal IPs (e.g., 10.0.0.0/8) 及 internal DNS names that aren’t fully-qualified and resolvable in public global DNS (e.g., you can’t bind a kubernetes cluster DNS name like foo.ns.svc.cluster.local)。 如果需要在證書中綁定到這些 name,或者簽發大量證書,或者控制證書細節,就需要自己的 internal PKI.

下面一節將看到,信任(或缺乏信任)是避免將 Web PKI 用於內部場景的另一個原因。

總結起來,建議:

  • 面向公網的服務或 API,使用 Web PKI;
  • 其他所有場景,都使用 internal PKI。

8 Trust & Trustworthiness

8.1 Trust Stores(信任倉庫)

前面介紹到,證書可解讀爲一個 statement 或 claim,例如:

Issuer(簽發者)說,該 subject 的公鑰是 xxx。

Issuer 會對這份聲明進行簽名,relying party 能(通過 issuer 的公鑰)驗證(authenticate)簽名是否合法。 但這裏其實跳過了一個重要問題:relying party 是如何知道 issuer 的公鑰的?

預配置信任的根證書

答案其實很簡單:relying parties 在自己的 trust store(信任庫)預先配置了一個它 信任的根證書(trusted root certificates,也稱爲 trust anchors)列表,

預配置的具體方式(the manner in which this pre-configuration occurs), 是 PKI 非常重要的一面:

  • 一種方式是從另一個 PKI 來 bootstrap:可以用一些自動化工具,通過 SSH 將 root 證 書拷貝到 relying party。這裏用到裏前面提到的 SSH PKI。
  • 如果是在 cloud 上,那 PKI 依賴層次(信任鏈)又深了一步:SSH PKI 是由 Web PKI 加上認證方式 來 bootstrap 的,這裏的認證是你創建 cloud 賬戶時選擇的認證方式。

信任鏈

如果沿着這個信任鏈(chain of trust)回溯足夠遠,最後總能找到人(people):每個 信任鏈都終結在現實世界(meatspace)。

下面這個圖畫地更清楚一些,

Image credit: Cilium TLS inspection

根證書自簽名

信任倉庫中的根證書是自簽名的(self-signed):issuer 和 subject 相同。邏輯上,這種 statement 表示的是:

Mike 說:Mike 的公鑰是 xxx。

自簽名的證書保證了該證書的 subject/issuer 知道對應的私鑰, 但任何人都可以生成一個自簽名的證書,這個證書中可以寫任何他們想寫的名字(name)。

因此證書的起源(provenance)就非常關鍵:一個自簽名的證書,只有 當它進入信任倉庫的過程是可信任時,才應該信任這個根證書。

  • 在 macOS 上,信任倉庫是由 Keychain 管理的。
  • 在一些 Linux 發行版上,可能只是 /etc 或其他路徑下面的一些文件。
  • 如果你的用戶能修改這些文件,那最好先確認是你信任這些用戶的。

信任倉庫的來源

所以,信任倉庫又從哪裏來?對於 Web PKI 來說,最重要的 relying parties 就是瀏覽器。主流瀏覽器默認使用的信任倉庫 —— 及其他任何使用 TLS 的東西 —— 都是由四個組織維護的:

  1. Apple’s root certificate:iOS/macOS 程序
  2. Microsoft’s root certificate program:Windows 使用
  3. Mozilla’s root certificate program: Mozilla 產品使用,由於其開放和透明,也作爲其他一些信任倉庫從基礎 (e.g., for many Linux distributions)
  4. Google 未維護 root certificate program (Chrome 通常使用所在計算的操作系統的信任倉庫),但 維護了自己的黑名單, 列出了自己不信任的根證書或特定證書。 (ChromeOS builds off of Mozilla’s certificate program)

操作系統的信任倉庫

操作系統中的信任倉庫通常都是系統自帶的。

  • Firefox 自帶了自己的信任倉庫(通過 TLS 從 mozilla.org 分發 —— bootstrapping off of Web PKI using some other trust store)。
  • 編程語言和其他非瀏覽器的東西例如 curl,通過默認用操作系統的信任倉庫。

因此,這個信任倉庫通常情況下,會被該系統上預裝的很多東西默認使用;通過軟件更新( 通常使用另一個 PKI 來簽名)而更新。

信任倉庫中通常包含了超過 100 個由這些程序維護的常見證書權威(certificate authorities)。 其中一些著名的:

  • Let’s Encrypt
  • Symantec
  • DigiCert
  • Entrust

如果想編程控制:

  • Cloudflare’s cfssl project maintains a github repository that includes the trusted certificates from various trust stores to assist with certificate bundling (which we’ll discuss momentarily).
  • For a more human-friendly experience you can query Censys to see which certificates are trusted by MozillaApple, and Microsoft.

8.2 Trustworthiness(可靠性)

這 100 多個證書權威在理論上是可信的(trusted) —— 瀏覽器和其他 一些軟件默認情況下信任由這些權威頒發的證書。

但是,這並不意味着它們是可靠的(trustworthy)。 已經出現過 Web PKI 證書權威向政府機構提供假證書的事故,以便 窺探流量(snoop on traffic)或仿冒某些網站。 這類“受信任的” CA 中,其中在司法管轄權之外的地方運營 —— 包括民主國家和專制國家。

  • NSA 利用每個可能的機會來削弱 Web PKI。2011 年,兩個“受信任的”證書權威 DigiNotar and Comodo  被攻陷了。 DigiNotar 證書泄露可能與 NSA 相關。
  • 此外,還有大量 CA 簽發格式不對或不兼容的證書。因此,雖然按業界規範來說 這些 CA 是受信的,但按照經驗來說它們是不可靠(不靠譜)的。

我們很快就會看到,Web PKI 的安全性取決於安全性最弱的權威(the least secure CA)的安全性。 這顯然不是我們希望的。

瀏覽器社區已經在採取行動來解決這些問題。 CA/Browser Forum Baseline Requirements 規定了這些受信的證書權威在簽發證書時應該遵守的規則。 作爲 WebTrust audit 項目的一部分,在將 CA 加入到某些信任倉庫(例如 Mozilla 的)之前,會對 CA 合規性進行審計。

如果內部場景(internal stuff)已經在使用 TLS,你可能大部分情況下 並不需要信任這些 public CA。 如果信任了,就爲 NSA 和其他組織打開了一扇地獄之門:你的系統安全性將取決於 100 多 個組織中安全性最弱的那一個。

8.3 Federation

證書欺騙的風險

令事情更糟糕的是,Web PKI relying parties (RPs) 信任它們的信任倉庫中任何 CA 簽發給任何 subscriber 的證書。結果是 Web PKI 整體的安全性取決於所有 Web PKI CA 中最弱的那個。 2011 DigiNotar 攻擊就說明了這個問題:

  • 作爲攻擊的一部分,給 google.com 簽發了一個假證書, 這個證書被大部分瀏覽器和操作系統信任,而它們不管 google 和 DigiNotar 沒有任何關係這一事實。
  • 還有類似的欺騙證書頒發給了 Yahoo!, Mozilla, The Tor Project。
  • 最終的解決方式是將 DigiNotar 的根證書從主流信任倉庫中移除,但顯然在此期間已經造成了大量破壞。

最近,森海塞爾(Sennheiser)因爲在它們的 HeadSetup APP 信任倉庫中 安裝了一個自簽名的根證書 引起了一次重大安全事故,

  • 他們將相應的私鑰(private key)嵌入在了 app 的配置中,
  • 任何人都能從中提取這個私鑰,然後頒發證書給任何 domain,
  • 因此,任何在自己的信任倉庫中添加了 Sennheiser 證書的,都將會信任這些欺騙證書。

這完全摧毀了 TLS 帶來的好處,太糟糕了!

改進措施

已經有一些機制來減少此類風險:

  1. Certificate Authority Authorization (CAA) allows you to restrict which CAs can issue certificates for your domain using a special DNS record.
  2. Certificate Transparency (CT) (RFC 6962) mandates that CAs submit every certificate they issue to an impartial observer that maintains a public certificate log to detect fraudulently issued certificates. Cryptographic proof of CT submission is included in issued certificate
  3. HTTP Public Key Pinning (HPKP or just “pinning”) lets a subscriber (a website) tell an RP (a browser) to only accept certain public keys in certificates for a particular domain.

這裏存在的問題是:缺少 RP 端的支持。CAB Forum now mandates CAA checks in browsers. Some browsers also have some support for CT and HPKP. 但對於 其他 RPs (e.g., most TLS standard library implementations) 這些東西幾乎都是沒有 貫徹執行的。This issue will come up repeatedly: a lot of certificate policy must be enforced by RPs, and RPs can rarely be bothered. If RPs don’t check CAA records and don’t require proof of CT submission this stuff doesn’t do much good.

Internal PKI 使用單獨的信任倉庫

在任何情況下,如果使用自己的 internal PKI,都應該爲 internal 服務維護一個單獨的信任倉庫。 即,

  • 不要將你的根證書直接加到系統已有的信任倉庫,
  • 而應該配置 internal TLS 只使用你自己的根證書。

Internal PKI 細粒度控制:CAA & SPIFFE

如果想在內部實現更好的聯邦(federation) —— 例如限制 internal CA 能簽發哪些證書,

  • 可以試試 CAA records 然後對 RPs 進行恰當配置。
  • 還可以看看 SPIFFE,這是一個還在不斷髮展的項目, 目標是對一些 internal PKI 相關的問題進行標準化。

9 什麼是證書權威(Certificate Authority)?

前面已經討論了很多 CA 相關的東西,但我們還沒定義什麼是 CA。

  • 一個證書權威(CA)就是一個受信任的證書頒發者。
  • CA 通過對一個證書進行簽名,對一個公鑰和名字之間的綁定關係(binding)做擔保。
  • 本質上來說,一個 CA 只不過是另一個證書加上用來籤其他證書的相應私鑰。

顯然需要一些邏輯和過程來將這些東西串聯起來。CA 需要將它的證書分發到信任倉庫,接受和處理 證書請求,頒發證書給 subscriber。

  • 一個暴露此類 API 給外部調用、自動化這些過程的 CA 稱爲在線證書權威(online CA)。
  • 在信任倉庫中那些自簽名的根證書 稱爲根證書權威(root CA)。

9.1 Web PKI 不能自動化簽發證書

CAB Forum Baseline Requirements 4.3.1 明確規定:一個 Web PKI CA 的 root private key 只能通過 issue a direct command 來簽發證書。

  • 換句話說,Web PKI root CA 不能自動化證書籤名(certificate signing)過程。
  • 對於任何大的 CA operation 來說,無法在線完成都是一個問題。 不可能每次簽發一個證書時,都人工敲一個命令。

這樣規定是出於安全考慮。

  • Web PKI root certificates 廣泛存在於信任倉庫中,很難被撤回。截獲一個 root CA private key 理論上將影響幾十億的人和設備。
  • 因此,最佳實踐就是,確保 root private keys 是離線的(offline),理想情況下在一些 專用硬件 上,連接到某些物理空間隔離的設備上,有很好的物理安全性,有嚴格的使用流程。

一些 internal PKI 也遵循類似的實踐,但實際上並沒有這個必要。

  • 如果能自動化 root certificate rotation (例如,通過配置管理或編排工具,更新信任倉庫), 你就能輕鬆地 rotate 一個 compromised root key。
  • 由於人們如此沉迷於 internal PKI 的根祕鑰管理,導致 internal PKI 的部署效率大大 降低。你的 AWS root account credentials 至少也是機密信息,你又是如何管理它的呢?

9.2 Intermediates, Chains, and Bundling

在 root CA offline 的前提下,爲使證書 issuance 可擴展(例如,使自動化成爲可能), root private key 只在很少情況下使用,

  1. 用來簽發幾個intermediate certificates
  2. 然後 intermediate CA(也稱爲 subordinate CAs)用相應的 intermediate private keys 來簽發 leaf certificates to subscribers。

如下圖所示:

Image credit: Cilium TLS inspection

下面這張圖把簽發關係展示地更清楚,

Image credit: Cilium TLS inspection

Intermediates 通常並不包含在信任倉庫中,所以撤回或 roate 比較容易, 因此通過 intermediate CA,就實現了 certificate issuance 的在線和自動化(online and automated)。

這種 leaf、intermediate、root 組成的證書捆綁(bundle)機制, 形成了一個證書鏈(certificate chain)。

  • leaf 由 intermediate 簽發,
  • intermediate 又由 root 簽發,
  • root 自簽名(signs itself)。

技術上來說,上面都是簡化的例子,你可以創建更長的 chain 和更復雜的圖(例如, cross-certification)。 但不推薦這麼做,因爲複雜性很快會失控。在任何情況下, end entity certificates 都是葉子節點,這也是稱爲葉子證書(leaf certificate)的原因。

當配置一個 subscriber 時(例如,Apache、Nginx、Linkderd、Envoy), 通常不僅需要葉子證書,還需要一個包含了 intermediates 的 certificate bundle。

  • 有時會用 PKCS#7 和 PKCS#12,因爲它們能包含一個完整的證書鏈(certificate chain)。
  • 更多情況下,證書鏈編碼成一個簡單的空行隔開的 PEM 對象(sequence of line-separated PEM objects)。

    Some stuff expects the certs to be ordered from leaf to root, other stuff expects root to leaf, and some stuff doesn’t care. More annoying inconsistency. Google and Stack Overflow help here. Or trial and error.

    下面是一個例子:

      $ cat server.crt
      -----BEGIN CERTIFICATE-----
      MIICFDCCAbmgAwIBAgIRANE187UXf5fn5TgXSq65CMQwCgYIKoZIzj0EAwIwHzEd
      ...
      MBsGA1UEAxMUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwHhcNMTgxMjA1MTc0OTQ0WhcN
      HO3iTsozZsCuqA34HMaqXveiEie4AiEAhUjjb7vCGuPpTmn8HenA5hJplr+Ql8s1
      d+SmYsT0jDU=
      -----END CERTIFICATE-----
      -----BEGIN CERTIFICATE-----
      MIIBuzCCAWKgAwIBAgIRAKBv/7Xs6GPAK4Y8z4udSbswCgYIKoZIzj0EAwIwFzEV
      ...
      BRvPAJZb+soYP0tnObqWdplmO+krWmHqCWtK8hcCIHS/es7GBEj3bmGMus+8n4Q1
      x8YmK7ASLmSCffCTct9Y
      -----END CERTIFICATE-----
    

    Again, annoying and baroque, but not rocket science.

9.3 RP:Certificate path validation

由於 intermediate certificates 並未包含在信任倉庫中,因此需要與 leaf certificates 一樣分發和驗證。

  • 前面已經介紹,配置 subscriber 時需要提供這些 intermediates,subscribers 隨後再將它們傳給 RP。
  • 如果使用 TLS,那這個過程發生在 TLS 握手時。
  • 當一個 subscriber 將它的證書發給 relying party 時,其中會包含所有能證明來自信任的根證書的 intermediates。
  • relying party 通過一個稱爲 certificate path validation 的過程來驗證 leaf 和 intermediate certificates 。

完整的 certificate path validation 算法比較複雜。包括了

  • checking certificate expirations
  • revocation status
  • various certificate policies
  • key use restrictions
  • a bunch of other stuff

顯然,PKI RP 準確實現這個算法是非常關鍵的。

  • 如果關閉 certificate path validation (例如,curl -k),用戶將面臨重大風險,所以不要關閉。
  • 完成正確的 TLS 並沒有那麼難,certificate path validation 是 TLS 中完成認證(authentication)的部分。

可能有人會說,channel 已經是加密的了,因此關閉沒關係 —— 錯,有關係。 沒有認證(authentication)的加密是毫無價值的 —— 這就像在教堂懺悔: 你說的話都是私密的,但卻並不知道簾幕後面的人是誰 —— 只不過這裏不是教堂,而是互聯網。

10 祕鑰和證書的生命週期

在能通過 TLS 等協議使用證書之前,要先配置如何從 CA 獲取一個證書。 邏輯上來說這是一個相當簡單的過程:

  1. 需要證書的 subscriber 自己先生成一個 key pair,然後通過請求發送給 CA,
  2. CA 檢查其中關聯的 name 是否正確,如果正確就簽名並返回一個證書。

證書會過期,過期的證書就不會被 RP 信任了。如果證書快過期了而還想繼續用它,就需要 續期(renew )並輪轉(rotate)它。如果想在一個證書過期之前就讓 RP 停止信任它,就需要執行撤銷(revoke)。

與 PKI 相關的大部分東西一樣,這些看似簡單的過程實際上都充滿坑。 其中也隱藏了計算機科學中最難的兩個問題:緩存一致性和命名(naming)。 但另一方面,一旦理解了背後的原理,再反過來看實際在用的一些東西就簡單多了。

10.1 Naming things(命名相關)

DN (distinguished names)

歷史上,X.509 使用 X.500 distinguished names (DN) 來命名證書的使用者(name the subject of a certificate),即 subscriber。 一個 DN 包含了一個 common name (對作者我來說,就是 “Mike Malone”),此外還可以包含 locality、country、organization、organizational unit 及其他一些東西(數字電話簿相關)。

  • 沒人理解 DN,它在互聯網上也沒什麼意義。
  • 應該避免使用 DN。如果真的要用,也要儘量保持簡單。
  • 無需使用全部字段,實際上,也不應該使用全部字段。
  • common name 可能就是需要用到的全部字段了,如果你是一個 thrill seeker ,可以在用上一個 organization name。

PKIX 規定一個網站的 DNS hostname 應該關聯到 DN common name。最近,CAB Forum 已 經廢棄了這個規定,使整個 DN 字段變成可選的(Baseline Requirements, sections 7.1.4.2)。

SAN (subject alternative name)

現代最佳實踐使用 subject alternative name (SAN) X.509 extension 來 bind 證書中的 name。

常用的 SAN 有四種類型,綁定的都是廣泛使用的名字:

  1. domain names (DNS)
  2. email addresse
  3. IP addresse
  4. URI

在我們討論的上下文中,這些都是唯一的,而且它們能很好地映射到我們想識別的東西:

  • email addresses for people
  • domain names and IP addresses for machines and code,
  • URIs if you want to get fancy

應該使用 SAN。

注意,Web PKI 允許在一個證書內 bind 多個 name,name 也也允許通配符。也就是說,

  • 一個證書可以有多個 SAN,也可以有類似 *.smallstep.com 這樣的 SAN。
  • 這對有多個域名的的網站來說很有用。

10.2 生成 key pairs

有了 name 之後,需要先生成一個密鑰對,然後才能創建證書。前面提到:PKI 的安全性 在根本上取決於一個簡單的事實:只有與證書中的 subscriber name 對應的 entity,才應該擁有與該證書對應的私鑰。 爲確保這個條件成立,

  • 最佳實踐是讓 subscriber 生成它自己的密鑰對,這樣就只有它自己知道私鑰。
  • 絕對應該避免通過網絡發送私鑰。

生成證書時使用什麼類型的祕鑰?這一主題值得單獨寫一篇文章,這裏 只提供一點快速指導(截止 2018.12)。

  • 如今有一個緩慢但清晰的從 RSA 轉向橢圓曲線祕鑰的趨勢( ECDSA 或 EdDSA)。
  • 如果決定使用 RSA 祕鑰,確保它們至少是 2048 比特長,但也不要超過 4096 比特。
  • 如果使用 ECDSA,那 P-256 曲線可能是最好選擇(secp256k1 or prime256v1 in openssl), 除非你擔心 NSA,這種情況下你可以選擇更 fancier 一些的東西,例如 EdDSA with Curve25519(但對這些祕鑰的支持還不是太好)。

下面是用 openssl 生成一個橢圓曲線 P-256 key pair 的例子:

$ openssl ecparam -name prime256v1 -genkey -out k.prv
$ openssl ec -in k.prv -pubout -out k.pub

# 也可以用 step 生成
$ step crypto keypair --kty EC --curve P-256 k.pub k.prv

還可以通過編程來生成這些證書,這樣能做到證書不落磁盤。

10.3 Issuance(確保證書中的信息都是對的)

subscriber 有了一個 name 和一對 key 之後,下一步就是從 CA 獲取一個 leaf certificate。 對 CA 來說,它需要認證(證明)兩件事情:

  1. subscriber 證書中的公鑰,確實是該 subscriber 的公鑰(例如,驗證該 subscriber 知道對應的私鑰);

    這一步通常通過一個簡單的技術機制實現:證書籤名請求(certificate signing request, CSR)。

  2. 證書中將要綁定的 name,確實是該 subscriber 的 name。

    這一步要難很多。抽象來說,這個過程稱爲 identity proofing(身份證明)或 registration(註冊).

10.3.1 Certificate signing requests(證書籤名請求,PKCS#10)

Subscriber 請求一個證書時,會向 CA 會提交一個 certificate signing request (CSR)。

  • CSR 也是一個 ASN.1 結構,定義在 PKCS#10
  • 與證書類似,CSR 數據結構包括一個公鑰、一個名字和一個簽名。
  • CSR 是自簽名的,用與 CRS 中公鑰對應的私鑰自簽名。

    • 這個簽名用於證明該 subscriber 有對應的私鑰,能對任何用其公鑰加密的東西進行解密。
    • 還使即使 CSR 被複制或轉發,都沒有能篡改其中的內容(篡改無效)。
  • CSR 中包括了很多證書細節配置項。但在實際中,大部分配置項都會被 CA 忽略。大部分 CA 都使用自己的固定模板, 或提供一個 administrative 接口來收集這些信息。

用 step 命令創建一個密鑰對和 CSR 的例子:

$ step certificate create -csr test.smallstep.com test.csr test.key

OpenSSL 功能也非常強大,但 用起來不夠方便

10.3.2 Identity proofing(身份證明過程)

CA 收到一個 CSR 並驗證簽名之後,接下來需要確認證書中綁定的 name 是否真的 是這個 subscriber 的 name。這項工作很棘手。 證書的核心功能是能讓 RP 對 subscriber 進行認證。因此, 如果一個證書都還沒有頒發,CA 如何對這個 subscriber 進行認證呢?

答案是:分情況。

Web PKI 證明身份過程

Web PKI 有三種類型的證書,它們最大的區別就是如何識別 subscriber, 以及它們所用到的 identity proofing 機制。

這三種證書是:

  1. domain validation (DV,域驗證)

    DV 證書綁定的是 DNS name,CA 在頒發時需要驗證的這個 domain name 確實是由該 subscriber 控制的。

    證明過程通常是通過一個簡單的流程,例如

    1. 給 WHOIS 記錄中該 domain name 的管理員發送一封確認郵件。
    2. ACME protocol (最初由 Let’s Encrypt 開發和使用)改進了這種方式,更加自動化:不再用郵件驗證 ,而是由 ACME CA 提出一個 challenge,該 subscriber 通過完成這個問題來證明它擁有 這個域名。challenge 部分屬於 ACME 規範的擴展部門,常見的包括:

      • 在指定的 URL 上提供一個隨機數(HTTP challenge)
      • 在 DNS TXT 記錄中放置一個隨機數(DNS challenge)
  2. organization validation (OV,組織驗證)

    • OV 和下面將介紹的 EV 證書構建在 DV 證書之上,它們包括了 name 和域名 所屬組織的位置信息(location)。
    • OV 和 EV 證書不僅僅將證書關聯到域名,還關聯到控制這個域名的法律實體(legal entity)。
    • OV 證書的驗證過程,不同的 CA 並不統一。爲解決這個問題,CAB Forum 引入了 EV 證書。
  3. extended validation (EV,擴展驗證)

    • EV 證書包含的基本信息與 OV 是一樣的,但強制要求嚴格驗證(identity proofing)。
    • EV 過程需要幾天或幾個星期,其中可能包括公網記錄搜索(public records searches)和公司人員(用筆)簽署的(紙質)證詞。

    這些完成之後,當相應網站時,某些瀏覽器會在 URL 欄中顯示該組織的名稱。例如:

    但除了這個場景之外,EV certificates 並未得到廣泛使用,Web PKI RP 也未強依賴它。

本質上來說,每個 Web PKI RP 只需要 DV 級別的 assurance 就行了, 也就是確保域名是被該 subscriber 控制的。重要的是能理解一個 DV 證書在設計上的意思和在實際上做了什麼:

  • 在設計上,希望通過它證明:請求這個證書的 entity 擁有對應的域名;
  • 在實際上,真正完成的操作是:在某個時間,請求這個證書的 entity 能讀一封郵件,或配置一條 DNS 記錄,或能通過 HTTP serve 一個指定隨機數等等。

但話說回來,DNS、電子郵件和 BGP 這些底層基礎設施本身的安全性也並沒有做到足夠好, 針對這些基礎設施的攻擊還是 時有發生, 目的之一就是獲取證書。

Internal PKI 證明身份過程

上面是 Web PKI 的身份證明過程,再來看 internal PKI 的身份證明過程。

實際上,用戶可以使用任何方式來做 internal PKI 的 identity proofing, 並且效果可能比 Web PKI 依賴 DNS 或郵件方式的效果更好。

乍聽起來好像很難,但其實不難,因爲可以利用已有的受信基礎設施: 用來搭建基礎設施的工具,也能用來爲這些基礎設施之上的服務創建和證明安全身份。

  • 如果用戶已經信任 Chef/Puppet/Ansible/Kubernetes,允許它們將代碼放到服務器上, 那也應該信任它們能完成 identity attestations
  • 如果在 AWS 上,可以用 instance identity documents
  • 如果在 GCP:GCP
  • Azure

provisioning infrastructure 必須理解 identity 的概念,這樣才能將正確的代碼放到正確的位置。 此外,用戶必須信任這套機制。基於這些知識和信任,才能配置 RP 信任倉庫、將 subscribers 納入你的 internal PKI 管理範圍。 而完成這些功能全部所需做的就是:設計和實現某種方式,能讓 provisioning infrastructure 在每個服務啓動時,能將它們的 identity 告訴你的 CA。 順便說一句,這正是 step certificates 解決的事情。

10.4 Expiration(過期)

證書通常都會過期。雖然這不是強制規定,但一般都這麼做。設置一個過期時間非常重要,

  • 證書都是分散在各處的:通常 RP 在驗證一個證書時,並沒有某個中心式權威能感知到(這個操作)。
  • 如果沒有過期時間,證書將永久有效。
  • 安全領域的一條經驗就是:時間過的越久,憑證被泄露的概率就越接近 100%。

因此,設置過期時間非常重要。具體來說,X.509 證書中包含一個有效時間範圍:

  1. issued at
  2. not before
  3. not after:過了這個時間,證書就過期了。

這個機制看起來設計良好,但實際上也是有一些不足的:

  • 首先,沒有什麼能阻止 RP 錯誤地(或因爲糟糕的設計)接受一個過期證書;
  • 其次,證書是分散的。驗證證書是否過期是每個 RP 的責任,而有時它們會出亂子。例如,RP 依賴的系統時鐘不對時。 最壞的情況就是系統時鐘被重置爲了 unix epoch(1970.1.1),此時它無法信任任何證書。

在 subscriber 側,證書過期後,私鑰要處理得當:

  • 如果一個密鑰對之前是用來簽名/認證的(例如,基於 TLS),

    • 應該在不需要這個密鑰對之後,立即刪除私鑰。
    • 保留已經失效的簽名祕鑰(signing key)會導致不必要的風險:對誰都已經沒有用處,反而會被拿去仿冒簽名。
  • 如果密鑰對是用來加密的,情況就不同了。

    • 只要還有數據是用這個加密過的,就需要留着這個私鑰。

這就是爲什麼很多人會說,不要用同一組祕鑰來同時做簽名和加密(signing and encryption)。 因爲當一個用於簽名的私鑰過期時,無法實現祕鑰生命週期的最佳管理: 最終不得不保留着這個私鑰,因爲解密還要用它。

10.5 Renewal(續期)

證書快過期時,如果還想繼續使用,就需要續期。

10.5.1 Web PKI 證書續期

Web PKI 實際上並沒有標準的續期過期:

  • 沒有一個標準方式來延長證書的合法時間,
  • 一般是直接用一個新證書替換過期的。
  • 因此續期過程和 issuance 過程是一樣的:生成並提交一個 CSR,然後完成 identity proofing。

10.5.2 Internal PKI 證書續期

對於 internal PKI 我們能做的更好。

最簡單的方式是:

  • 用 mTLS 之類的協議對老證書續期。
  • CA 能對 subscriber 提供的客戶端證書進行認證(authenticate),重籤一個更長的時間,然後返回這個證書。
  • 這使得續期過程很容易自動化,而且強制 subscriber 定期與中心權威保持溝通。
  • 基於這種機制能輕鬆構建一個證書的監控和撤銷基礎設施。

10.5.3 小結

證書的續期過程其實並不是太難,最難的是記得續期這件事。

幾乎每個管理過公網證書的人,都經歷過證書過期導致的生產事故,例如這個。 我的建議是:

  1. 發現問題之後,一定要全面排查,解決能發現的所有此類問題。
  2. 另外,使用生命週期比較短的證書。這會反過來逼迫你們優化和自動化整個流程。

Let’s Encrypt 使自動化非常容易,它簽發 90 天有效期的證書,因此對 Web PKI 來說非常合適。 對於 internal PKI,建議有效期籤的更短:24 小時或更短。有一些實現上的挑戰 —— hitless certificate rotation 可能比較棘手 —— 但這些工作是值得的。

用 step 檢查證書過期時間:

step certificate inspect cert.pem --format json | jq .validity.end
step certificate inspect https://smallstep.com --format json | jq .validity.end

將這種命令行封裝到監控採集腳本,就可以實現某種程度的監控和自動化。

10.6 Revocation(撤銷)

如果一個私鑰泄露了,或者一個證書已經不再用了,就需要撤銷它。即希望:

  1. 明確地將其標記爲非法的,
  2. 所有 RP 都不再信任這個證書了,即使它還未過期。

但實際上,撤銷證書過程也是一團糟。

10.6.1 主動撤銷的困難

  • 與過期類似,執行撤回的職責在 RP。
  • 與過期不同的是,撤銷狀態無法編碼在證書中。RP 只能依靠某些帶外過程(out-of-band process) 來判斷證書的撤銷狀態。

除非顯式配置,否則大部分 Web PKI TLS RP 並不關注撤銷狀態。換句話說,默認情況下, 大部分 TLS 實現都樂於接受已經撤銷的證書。

10.6.2 Internal PKI:被動撤銷機制

Internal PKI 的趨勢是接受這個現實,然後試圖通過被動撤銷(passive revocation)機制來彌補, 具體來說就是簽發生命週期很短的證書,這樣就使撤銷過程變得不再那麼重要了。 想撤銷一個證書時,直接不給它續期就行了,過一段時間就會自動過期。

可以看到,這個機制有效的前提就是使用生命週期很短的證書。具體有多短?

  1. 取決於你的威脅模型(安全專家說了算)。
  2. 24 小時是很常見的,但也有短到 5 分鐘的。
  3. 如果生命週期太短,顯然也會給可擴展性和可用性帶來挑戰:每次續期都需要與 online CA 交互, 因此 CA 有性能壓力。
  4. 如果縮短了證書的生命週期,記得確保你的時鐘是同步的,否則就有罪受了。

對於 web 和其他的被動撤銷不適合的場景,如果認真思考之後發現真的 需要撤銷功能,那有兩個選擇:

  1. CRL(,證書撤銷列表,RFC 5280)
  2. OCSP(Online Certificate Signing Protocol,在線證書籤名協議,RFC 2560)

10.6.3 主動檢查機制:CRL(Certificate Revocation Lists)

CRL 定義在 RFC 5280 中,這是一個相當龐雜的 RFC,還定義了很多其他東西。 簡單來是,CRL 是一個有符號整數序列,用來識別已撤銷的證書。

這個維護在一個 CRL distribution point 服務中,每個證書中都包含指向這個服務的 URL。 工作流程:每個 RP 下載這個列表並緩存到本地,在對證書進行驗證時,從本地緩存查詢撤銷狀態。 但這裏也有一些明顯的問題:

  1. CRL 可能很大,
  2. distribution point 也可能失效。
  3. RP 的 CRL 緩存同步經常是天級的,因此如果一個證書撤銷了,可能要幾天之後才能同步到這個狀態。
  4. 此外,RP fail open 也很常見 —— CRL distribution point 掛了之後,就接受這個證書。 這顯然是一個安全問題:只要對 CRL distribution point 發起 DDoS 攻擊,就能讓 RP 接受一個已經撤銷的證書。

因此,即使已經在用 CRL,也應該考慮使用短時證書來保持 CRL size 比較小。 CRL 只需要包含已撤銷但還未過期的證書的 serial numbers,因此 證書生命週期越短,CRL 越短。

10.6.4 主動檢查機制:OCSP(Online Certificate Signing Protocol)

主動檢查機制除了 CRL 之外,另一個選擇是 OCSP,它允許 RP 實時查詢一個 OCSP responder: 指定證書的 serial number 來獲取這個證書的撤銷狀態。

與 CRL distribution point 類似,OCSP responder URL 也包含在證書中。 這樣看,OCSP 似乎更加友好,但實際上它也有自己的問題。對於 Web PKI,它引入了驗證的隱私問題:

  1. 每次查詢 OCSP responder,使得它能看到我正在訪問哪個網站。
  2. 此外,它還增加了每個 TLS 連接的開銷:需要一個額外請求來檢查證實的撤銷狀態。
  3. 與 CRL 一樣,很多 RPs (including browsers) 會在 OCSP responder 失效時直接認爲證書有效(未撤銷)。

10.6.5 主動檢查機制:OCSP stapling(合訂,綁定)

OCSP stapling 是 OCSP 的一個變種,目的是解決以上提到的那些問題。

相比於讓 RP 每次都去查詢 OCSP responder,OCSP stapling 中讓證書的 subscriber 來做這件事情。 OCSP response 是一個經過簽名的、時間較短的證詞(signed attestation),證明這個證書未被撤銷。

attestation 包含在 subscriber 和 RP 的 TLS handshake (“stapled to” the certificate) 中。 這給 RP 提供了相對比較及時的撤銷狀態,而不用每次都去查詢 OCSP responder。 subscriber 可以在 signed OCSP response 過期之前多次使用它。這減少了 OCSP 的負擔,也解決了 OCSP 的隱私問題。

但是,所有這些東西其實最終都像是一個 魯布·戈德堡裝置(Rube Goldberg Device) ,

魯布·戈德堡機械(Rube Goldberg machine)是一種被設計得過度複雜的機械組合,以 迂迴曲折的方法去完成一些其實是非常簡單的工作,例如倒一杯茶,或打一隻蛋等等。 設計者必須計算精確,令機械的每個部件都能夠準確發揮功用,因爲任何一個環節出錯 ,都極有可能令原定的任務不能達成。

解釋來自 知乎

如果讓 subscribers 去 CA 獲取一些生命週期很短的證詞(signed attestation)來證明對應的證書並沒有過期, 爲什麼不直接幹掉中間環節,直接使用生命週期很短的證書呢?

11 使用證書

雖然理解 PKI 需要以上長篇大論,但在實際中用證書其實是非常簡單的。

下面以 TLS 爲例,其他方式也是類似的:

  1. 配置 PKI relying party 使用哪個根證書;

    對於 Web PKI,通常已經默認配置了正確的根證書,這一步可以跳過。

  2. 配置 PKI subscriber 使用哪個證書和私鑰(或如何生成自己的密鑰對、如何提交 CSR);

    某個 entity (code, device, server, etc) 既是 RP 又是 subscriber 是很常見的。 這樣的 entities 需要同時配置根證書、證書和私鑰。

下面是個完整例子,展示 certificate issuance, root certificate distribution, and TLS client (RP) and server (subscriber) configuration:

希望這展示了使用 internal PKI 和 TLS 是如何簡單直接。

  • 有了這樣的基礎,就無需使用自簽名的證書或做一些危險的事情,例如禁用 certificate path validation(curl -k)。
  • 幾乎每個 TLS client/server 都支持這些參數;但是,它們又幾乎都不關注祕鑰和證書 的聲明週期:都假設證書會出現在磁盤上的恰當位置,有人或服務會幫它們完成 rotate 等工作。這項生命週期相關的工作纔是難點。

12 結束語

公鑰加密系統使計算機能在網絡上看到對方(”see” across networks)。

  • 如果我有公鑰,就能“看到”你有對應的私鑰,但我自己是無法使用這個私鑰的。
  • 如果還沒有對方的公鑰,就需要證書來幫忙。證書將公鑰和私鑰擁有者的名字(name)相關聯, 它們就像是計算機和代碼的駕照。
  • 證書權威(CA)用它們的私鑰對證書進行簽名,對這些綁定關係作出擔保,它們就像是車管局(DMV),
  • 如果你出示一張車管局頒發的駕照,臉長得也和駕照上的照片一樣,那別人就可以認爲你就是駕照上這個人(名字)。 同理,如果你是唯一知道某個祕鑰的 entity,你給我的證書也是從我信任的某個 CA 來的,那我就認爲證書中的 name 就是你。

現實中,

  1. 大部分證書都是 X.509 v3 證書,用 ASN.1 格式定義,通常序列化爲 PEM-encoded DER。
  2. 相應的私鑰通常表示爲 PKCS#8 objects,也序列化爲 PEM-encoded DER。
  3. 如果你用 Java 或微軟的產品,可能會遇到 PKCS#7 and PKCS#12 封裝格式。

加密領域有沉重的歷史包袱,使當前的這些東西學起來、用起來非常讓人沮喪,這比一項技術因爲太難而不想學更加令人沮喪。

PKI 是使用公鑰基礎設施時涉及到的所有東西的統稱:names, key types, certificates, CAs, cron jobs, libraries 等。

  • Web PKI 是瀏覽器默認使用的 PKI。Web PKI CA 是受信但不可靠的(trusted but not trustworthy)。
  • Internal PKI 是用戶自己構建和維護的 PKI。需要它是因爲 Web PKI 並不是針對 internal 使用場景設計的, Internal PKI 更易於自動化和擴展,並且能讓用戶控制很多細節,例如 naming and certificate lifetime。
  • 建議公網上使用 Web PKI,內網使用自己的 internal PKI (例如,use TLS 來替代 VPN)。
  • Smallstep Certificate Manager 使構建 internal PKI 非常簡單。

要獲得一個證書,需要命令和生成證書。建議 name 用 SAN:

  • DNS SANs for code and machines
  • EMAIL SANs for people
  • 如果這些都不能用,就用 URI SAN

祕鑰類型(key type)是很大一個主題,但幾乎不重要:你可以隨便修改祕鑰類型, 而且實際上加密本身(crypto)並不是 PKI 中最弱的一環。

要從 CA 獲取一個證書,需要提交一個 CSR 並證明申請者的身份(identity)。 使用生命週期較短的證書和 passive revocation。 自動化證書續期過程。不要禁用 certificate path validation。

最後還是那句話:證書和 PKI 將名字關聯到公鑰(bind names to public keys)。 其他都是細節。

13 延伸閱讀(譯註)

更多相關內容或實踐,推薦:

  1. Illustrated X.509 Certificate,2020

    超詳細圖解 X.509 證書。

  2. Cilium TLS inspection,2021

    圖解 X.509 證書、信任鏈,及 Cilium/hubble L7 實戰。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章