FirefoxでオレオレEVSSL証明書

# はじめに
## 背景と目的
元ネタは、[@ozuma5119氏](https://twitter.com/ozuma5119)のスライド[個人でEV SSL証明書が欲しい話](https://speakerdeck.com/ozuma/ge-ren-deev-sslzheng-ming-shu-gayu-siihua)でした。[スライド36枚目](https://speakerdeck.com/ozuma/ge-ren-deev-sslzheng-ming-shu-gayu-siihua?slide=36)から、**オレオレEVSSL証明書**[^1]を作ってブラウザ(Firefox)に認識させようという試みが説明されているのですが、結局成功していませんでした。
そこでこの話に触発されて、

* オレオレEVSSL証明書をブラウザ(Firefox)に認識させることはできるのか
* 件のスライドの説明よりもっと楽な方法はないか
* 件のスライドでうまく行かなかった理由は何か

を調べた話になります。
## 前提
SSL/TLSの本当に基礎については、拙記事[SSL/TLSの見落とされがちな「認証」という基本機能](/angel_p_57/items/dcb12a6a864f9aeba427)をご覧ください。その中でEVについても軽く触れています。
また、今回の話の全体像をつかむためには、冒頭で紹介した[スライド](https://speakerdeck.com/ozuma/ge-ren-deev-sslzheng-ming-shu-gayu-siihua)を一読されることをお勧めします。

## 注意
本記事の趣旨はあくまで、**ブラウザがEVだと認識するメカニズムに迫る**ことにあります。そのため、一般には通用しない**オレオレ(EV)証明書の使用を推奨する意図もありません**。
また、本記事で行う操作にはセキュリティ上好ましいとは言えないものも含まれます。もし実際に自分で試す場合は十分に注意し、自己責任でお願いします。

## 環境
検証は以下の環境で行いました。

* OS: CentOS 7.5.1804
* ブラウザ: Firefox(ESR)60.1.0-4

ブラウザに関しては、最新バージョン[^2]でも同じ挙動になると推測はしていますが、定かではありません。
また、Chrome等別のブラウザでの挙動がどうなるかは確認していません。

# オレオレEVSSL証明書を使う
## 概要
[冒頭で紹介したスライド](https://speakerdeck.com/ozuma/ge-ren-deev-sslzheng-ming-shu-gayu-siihua)によれば、次のようになっていました。

* EV証明書を発行できる認証局は限られている
* 認証局毎に、EV証明書であることを示すポリシーとしてEV OID[^3]がある
* ブラウザに、認証局とEV OIDの組がハードコーディングされている ( つまり設定等で追加することはできない )
* その登録されている認証局から直接・間接に発行され、対応するEV OIDを含む証明書がEVと認識される

なので、オレオレEVSSL証明書を使うには、次の手順でいけるはずだと。

* プライベートな認証局をたてる ( お好みで中間認証局も ) 
* その認証局用のEV OIDを適当に決める
* その認証局 ( の証明書データ ) とEV OIDの組をブラウザのソースに追加しビルドする
* その認証局 ( あるいは配下の中間認証局 ) から、決定したEV OIDを含むサーバ証明書を発行する
* そのサーバ証明書を使ってWebサーバを動かす
* ビルドしたブラウザにその認証局の証明書をインポートする[^4]
* ビルドしたブラウザからそのWebサーバにアクセスする

しかし、それだけではうまく行かなかった…ということで、色々検証を行いました。

## 結論
次の3点について、結論です。

* オレオレEVSSL証明書をブラウザ(Firefox)に認識させることはできるのか  
  → もちろんできる。こんな風に。  
  ※**画面にあるドメインは架空であり実在のサイトではありません!!**![img1.png](https://qiita-image-store.s3.amazonaws.com/0/74396/6964725d-f4db-f67a-a6b9-9029d8732975.png)
* 件のスライドの説明よりもっと楽な方法はないか  
  最も大変、かつ汎用性を失う「ブラウザのビルド」これを避けられないか検証したものの、無理であることが判明した。つまり**ブラウザのビルドは必要**[^5]、楽はできない。  
  ※2018/8/31追記: ブラウザのビルドを行わなくとも、**実行時パッチにより対応する方法**がありました。[Firefoxに実行時パッチ(オレオレEVSSL番外編)](/angel_p_57/items/973960bc4b301c91611b)をご参照ください。
* 件のスライドでうまく行かなかった理由は何か  
  証明書発行時にOCSP情報(後述)がなかったため。

## 検証
実際にブラウザの挙動を追ったところ、通常のSSL/TLSの成立とは別に、EVかどうかを次の4段階で判定していることが分かりました。

1. そのサイトのサーバ証明書が、何らかのEV OIDを持っているか
2. そのサーバ証明書を直接・間接に発行した認証局証明書が、当該EV OIDのポリシーを満たしているか
3. そのサーバ証明書を直接・間接に発行した認証局証明書が**TrustAnchor**か
4. そのサーバ証明書のOCSP情報が確認できたか

1は簡単です。ソースにハードコーディングされているのは確かですが、その中から**どの認証局のでもいいので何かしらのEV OID**を、サーバ証明書発行時に盛り込めば済むのです。

2について「ポリシーを満たす」というと分かりにくいですが、要は同じEV OIDを認証局証明書にも持たせていればクリアすることができます。ただし、3のTrustAnchorである条件を満たしていればそれでも十分です。ハードコーディングされている認証局はこの後者の方に該当します。

この1,2だけであれば、ブラウザをビルドし直す必要はありません。認証局だけで何とかできる話です。なので、この時点で「面倒なことしなくても済むのでは??」と大きな希望を持っていたのですが…。

問題は3でした。TrustAnchorというのは、ソース内での定数名をそのまま持ってきているのですが、要はハードコーディングされた認証局でないと満たせない条件になります。なので、**やはりブラウザのビルドは必要**ということが判明しました。

そして最後に4、冒頭で紹介したスライドではここまで考慮してなかったがために、成功に至らなかったものと推測できます。

## OCSPとは
OCSPとは、オンラインで証明書が失効してないかどうかを確かめる仕組みです。[^6]
目的はCRL[^7]とは似ていますが、モノとしては別物です。

証明書の"Authority Information Access"情報として、OCSPの問い合わせ先(OCSPレスポンダ)を載せることができるようになっています。
例えば、このQiitaのサイトの証明書を見ると次のようになっています。ブラウザがこのURLに問い合わせをかけることで、証明書が失効していないかを確認できるということです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/74396/8bdeb511-c420-4eb0-3aee-c0fc89713945.png)

ということで、EVとして認識させる最後のOCSPの条件は次のようになります。

* OCSPサーバをたて、証明書の状態の問い合わせに応答させる
* OCSPサーバのURLを盛り込んでサーバ証明書を発行する

ちなみに、Firefoxでは設定でOCSPへの問い合わせを無効化することができます。
そうすると、EVの判定そのものに影響が出るようで、**正規に発行されているEV証明書すらも判別できなくなります**。

実際に試した時の画像です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/74396/cd246675-05fb-93c1-747e-b6f08c1227d0.png)

正規のEVSSL証明書を使っている三井住友銀行のサイトですが、EVと認識できなくなることが分かります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/74396/cfb708db-1eb3-f36f-e853-d60ca2fd2148.png)

## 操作
### 概要
では、実際に検証を行った時の操作をまとめます。

* プライベートな認証局をたてる ( 今回中間認証局は立てませんでした )
* その認証局用のEV OIDを適当に決める
* その認証局 ( の証明書データ ) とEV OIDの組をブラウザのソースに追加しビルドする
* その認証局から、決定したEV OID、OCSPサーバ情報を含むサーバ証明書を発行する
* OCSPサーバをたてる
* そのサーバ証明書を使ってWebサーバを動かす
* ビルドしたブラウザにその認証局の証明書をインポートする
* ビルドしたブラウザからそのWebサーバにアクセスする

### 認証局をたてる
拙記事[static-DHによるSSL/TLS](/angel_p_57/items/8ca86cc0ec0ca3a27105)の時のように、opensslで各種ファイルを作りました。
内訳は、認証局証明書`ca.crt`, 秘密鍵`ca.key`, シリアル管理ファイル`ca.srl`です。

以下、opensslの設定ファイルとスクリプトです。

```conf:設定ファイルtest_ca.cnf
[ ca_test ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical, cRLSign, keyCertSign, digitalSignature
```

```sh:スクリプトmkca.sh
#!/bin/bash

CONF=/etc/pki/tls/openssl.cnf
set -e
openssl req -config $CONF -new -newkey rsa:2048 -nodes -keyout ca.key -out ca.csr -subj "/CN=Six Gates Test CA/O=Soukai Synd./ST=Neo-Saitama/C=JP"
openssl x509 -signkey ca.key -days 100 -req -in ca.csr -out ca.crt -sha256 -extfile test_ca.cnf -extensions ca_test
openssl x509 -serial -noout -in ca.crt | sed -e 's/.*=//' > ca.srl
```

### ビルドする
今回、EV OIDは、もともとテスト用にソースに記述されている`1.3.6.1.4.1.13769.666.666.666.1.500.9.1`を使うことにしました。

先ほどたてた認証局の情報をデータ化し、以下のようにパッチを作っています。
このデータ化の部分は、冒頭で紹介した[スライドの58枚目](https://speakerdeck.com/ozuma/ge-ren-deev-sslzheng-ming-shu-gayu-siihua?slide=58)をご参照ください。
CentOSの場合、ツール`pp`は`/usr/lib64/nss/unsupported-tools/pp`にあります。[^8]

```patch:firefox-oreore-ev.patch
--- firefox-60.1.0/security/certverifier/ExtendedValidation.cpp.org     2018-06-22 04:07:26.000000000 +0900
+++ firefox-60.1.0/security/certverifier/ExtendedValidation.cpp 2018-08-24 21:10:14.997010094 +0900
@@ -1038,6 +1038,17 @@ static const struct EVInfo kEVInfos[] =
     "ViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQSBSMg==",
     "VrYpzTS8ePY=",
   },
+  {
+    // Oreore-test CA
+    "1.3.6.1.4.1.13769.666.666.666.1.500.9.1",
+    "Oreore EV OID",
+    { 0xAB, 0xC2, 0x3A, 0x8E, 0x6E, 0x87, 0x3E, 0xCE, 0xD0, 0xCB, 0x0A,
+      0xDB, 0x86, 0xDA, 0x04, 0x15, 0x02, 0x5B, 0x23, 0xC1, 0x54, 0xDD,
+      0xED, 0xBC, 0x41, 0xC6, 0x18, 0xF4, 0x1D, 0x41, 0xCB, 0x4C },
+    "MFYxGjAYBgNVBAMMEVNpeCBHYXRlcyBUZXN0IENBMRUwEwYDVQQKDAxTb3VrYWkg"
+    "U3luZC4xFDASBgNVBAgMC05lby1TYWl0YW1hMQswCQYDVQQGEwJKUA==",
+    "AIKXJVrdaqvM",
+  },
 };

 static SECOidTag sEVInfoOIDTags[ArrayLength(kEVInfos)];
```

なお、パッチの適用からビルドまでを解説するのは私の手には余りますので、割愛します。

### 証明書の発行
今回、localhost(127.0.0.1)に適当なドメイン名を割り当て、証明書を発行しました。OCSPサーバのドメイン名も適当に決めています。

これによって、サーバ証明書`svr.crt`、秘密鍵`svr.key`ができます。
また、OCSPサーバをたてる時に必要な証明書のインデクスデータ`index.db`もここで作っています。

なお、OCSPに関しては、はてなブログ「プログラム日記」の記事、[「OpenSSL」 authorityInfoAccess に OCSP サーバ ( OCSP レスポンダ ) の情報を持つ証明書を作成する](http://a4dosanddos.hatenablog.com/entry/2015/05/27/010545)を参考にさせて頂きました。
※次の章のOCSPサーバのたて方も含め

```conf:設定ファイルtest_crt.cnf
[ test_cert ]
basicConstraints                = CA:FALSE
nsCertType                      = server
keyUsage                        = digitalSignature
extendedKeyUsage                = serverAuth
nsComment                       = "There is no mercy."
certificatePolicies             = 1.3.6.1.4.1.13769.666.666.666.1.500.9.1
authorityInfoAccess             = OCSP;URI:http://ocsp.angel.p57
subjectKeyIdentifier            = hash
authorityKeyIdentifier          = keyid,issuer
```

```sh:スクリプトmkcrt.sh
#!/bin/bash

CONF=/etc/pki/tls/openssl.cnf
set -e

openssl req -config $CONF -new -newkey rsa:2048 -nodes -keyout svr.key -out svr.csr -subj "/CN=angel.p57/O=Omura Industries MC./ST=Neo-Saitama/C=JP"
openssl x509 -req -in svr.csr -out svr.crt -CAkey ca.key -CA ca.crt -days 30 -sha256 -CAserial ca.srl -extfile test_crt.cnf -extensions test_cert

declare -A crtinfo

while read key val; do
  crtinfo[$key]="$val"
done < <( openssl x509 -enddate -serial -subject -noout -in svr.crt  | sed -e 's/= */ /' )

printf 'V\t%s\t\t%s\tunknown\t%s\n' $(TZ= date +%y%m%d%H%M%SZ -d "${crtinfo[notAfter]}") ${crtinfo[serial]} "${crtinfo[subject]}" >> index.db
```
### OCSPサーバをたてる
次のスクリプトでOCSPサーバを起動します。~~面倒なので~~OCSPでの署名に使う証明書・秘密鍵は認証局証明書・秘密鍵をそのまま流用しています。
なお、ポート80番なのでroot権限が必要でした。root権限を使いたくない場合は、証明書に登録する情報も含め別のポートを使うようにしてください。

```sh:OCSPサーバ起動スクリプトdoocsp.sh
#!/bin/bash

openssl ocsp -index index.db -port 80 -rsigner ca.crt -CA ca.crt -rkey ca.key
```
### Webサーバの起動
Apacheやnginxでも良いんですが、簡単な検証のためここでもopensslで、簡易サーバ機能を使いました。コンテンツ用のファイルは別途作っておきます。
ここも443番ポートを指定しているのでroot権限が必要です。

```sh:Webサーバ起動スクリプトdosvr.sh
#!/bin/bash

openssl s_server -accept 443 -key svr.key -cert svr.crt -WWW
```

### インポート
詳細は割愛しますが、ビルドしたブラウザを起動し、作成した`ca.crt`をインポートします。
このように、証明書マネージャー上に登録されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/74396/67a28d10-f466-c3ae-3a09-6915ec74d203.png)

後は、起動したサーバへアクセスすれば、[結論](#結論)にあるように、オレオレEVなサイトの完成です!!
…そのビルドしたブラウザでしかEVじゃないんですけどね。

# 一旦まとめ
ということで、オレオレEVSSL証明書についていろいろ書いてきました。
もちろん、オレオレな上にブラウザのビルドが必要なので、実用性のある話ではありませんが、EVのチェックのために様々な判定がされていることを感じて頂ければ幸いです。

この後は、ソースと処理との対応を追っていきたいと思います。
かなり込み入った話になりますので、予めご了承ください。

# ブラウザの操作を追う
## 概要
ここから、実際の処理をソースに照らし合わせて追っていきます。
なお、参照するソースは`security`ディレクトリに全部入っているものですので、ファイル名についてはそこからの相対パスで表します。

## はじまり
まずは、サーバ証明書を検証するところからです。これは`AuthCertificate`という関数が担当します。

```cpp:manager/ssl/SSLServerCertVerification.cpp
1380 SECStatus
1381 AuthCertificate(CertVerifier& certVerifier,
...
1389 {
...
1413   Result rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse,
...
1421                                                &evOidPolicy,
...
1427   uint32_t evStatus = (rv != Success) ? 0                   // 0 = Failure
1428                     : (evOidPolicy == SEC_OID_UNKNOWN) ? 1  // 1 = DV
1429                     : 2;                                    // 2 = EV
```

そして、`CertVerifier::VerifySSLServerCert`で検証するわけですが、この時`evOidPolicy`にEV OIDを保存します。これがあれば、EVと認識するということです。

なお、メインの処理はこの後`CertVerifier::VerifyCert`関数に任されます。

```cpp:certverifier/CertVerifier.cpp
    872 Result
    873 CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert,
...
    889 {
...
    904   Result rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time,
```

## EV OIDの検出
この`CertVerifier::VerifyCert`ですが、607行目で`GetFirstEVPolicy`関数を呼び出して、まず**認証局関係なしに**EV OIDの検出だけ行います。
`GetFirstEVPolicy`関数の実体は`certverifier/ExtendedValidation.cpp`にありますが、これはハードコードされたEV OID情報の**どれかと一致するかどうかだけ**を見るだけです。

しかし、EV OIDを検出すると、直後608行目からのループに突入します。この中の636行目`BuildCertChainForOneKeyUsage`関数のチェックをパスできないとEV OIDを保存してくれません (658行目)。
なお、その処理の内容は`pkix/lib/pkixbuild.cpp`の`BuildCertChain`関数→`BuildForward`関数とほぼ丸投げされます。

```cpp:certverifier/CertVerifier.cpp
    450 Result
    451 CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
...
    464 {
...
    606       SECOidTag evPolicyOidTag;
    607       bool foundEVPolicy = GetFirstEVPolicy(*cert, evPolicy, evPolicyOidTag);
    608       for (size_t i = 0;
    609            i < sha1ModeConfigurationsCount && rv != Success && foundEVPolicy;
    610            i++) {
...
    636         rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
...
    641                                           evPolicy, stapledOCSPResponse,
...
    654         if (rv == Success) {
...
    657           if (evOidPolicy) {
    658             *evOidPolicy = evPolicyOidTag;
    659           }
```

## ポリシーチェック
さて、`BuildForward`関数ですが、発行者(認証局)のチェックをするために、349行目で、` NSSCertDBTrustDomain::FindIssuer`関数(`certverifier/NSSCertDBTrustDomain.cpp`)を呼び出します。
これだけだと`NSSCertDBTrustDomain::FindIssuer`が色々やるように見えますが、本命はその上で生成している`pathBuildingStep`オブジェクトだったりします。こいつがチェックの結果を内部的に持っていて、354行目の`pathBuildingStep::CheckResult`関数で結果を取り出す感じです。

```cpp:pkix/lib/pkixbuild.cpp
280 static Result
281 BuildForward(TrustDomain& trustDomain,
...
289 {
...
341   // Find a trusted issuer.
342
343   PathBuildingStep pathBuilder(trustDomain, subject, time,
...
348   // TODO(bug 965136): Add SKI/AKI matching optimizations
349   rv = trustDomain.FindIssuer(subject.GetIssuer(), pathBuilder, time);
...
354   rv = pathBuilder.CheckResult();
```

なお、` NSSCertDBTrustDomain::FindIssuer`は`FindIssuerInner`にほぼ処理を丸投げしていて、更に次のように130行目で実行されている`checker.Check`がチェックの本体になります。この`checker`は呼び出し元で作られた`PathBuildingStep`オブジェクトです。

```cpp:certverifier/NSSCertDBTrustDomain.cpp
  98 static Result
  99 FindIssuerInner(const UniqueCERTCertList& candidates, bool useRoots,
...
 102 {
...
 130       rv = checker.Check(certDER, nullptr, keepGoing);
```

さて、肝心の`PathBuildingStep::Check`ですが、発行者の認証局に対して前述の`BuildForward`でのチェックを行っています。このため、再帰的に発行元を辿るような処理になっています。

```cpp:pkix/lib/pkixbuild.cpp
141 Result
142 PathBuildingStep::Check(Input potentialIssuerDER,
...
145 {
...
198   rv = BuildForward(trustDomain, potentialIssuer, time, KeyUsage::keyCertSign,
```

今度、発行者の認証局に対する`BuildForward`では、`CheckIssuerIndependentProperties`関数のチェックが変わってきます。

```cpp:pkix/lib/pkixbuild.cpp
280 static Result
281 BuildForward(TrustDomain& trustDomain,
...
289 {
...
296   rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
```

`CheckIssuerIndependentProperties`関数から呼び出される`CheckCertificatePolicies`、これには上位から引き継がれたEV OID情報が渡ってきていてポリシーチェックが行われます。
後述するTrustAnchorであれば無条件でパスするのですが、そうでない場合でも、認証局証明書がサーバ証明書と同様にEV OIDを持っていればここはパスすることができます。

```cpp:pkix/lib/pkixcheck.cpp
 944 Result
 945 CheckIssuerIndependentProperties(TrustDomain& trustDomain,
...
 952                                  /*out*/ TrustLevel& trustLevel)
 953 {
...
1035   rv = CheckCertificatePolicies(endEntityOrCA, cert.GetCertificatePolicies(),
1036                                 cert.GetInhibitAnyPolicy(), trustLevel,
1037                                 requiredPolicy);
```

## TrustAnchor判定
さて、`CheckIssuerIndependentProperties`関数の、ポリシーチェックより前の処理に着目します。
ポリシーチェックに先んじてTrustAnchorかどうかのチェックが行われるのですが、それが961行目の`NSSCertDBTrustDOmain::GetCertTrust`です。
ここでハードコーディングされた認証局の場合には`trustLevel`変数に`TrustLevel::TrustAnchor`がセットされます。

```cpp:pkix/lib/pkixcheck.cpp
 944 Result
 945 CheckIssuerIndependentProperties(TrustDomain& trustDomain,
...
 952                                  /*out*/ TrustLevel& trustLevel)
 953 {
...
 961   rv = trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy, cert.GetDER(),
 962                                 trustLevel);
```

TrustAnchorかどうかの判定は、`CertIsAuthoritativeForEVPolicy`関数(`certverifier/ExtendedValidation.cpp`)といういかにもな関数の結果によって行われます。これがハードコーディングされた情報を見るものです。

```cpp:certverifier/NSSCertDBTrustDomain.cpp
 182 Result
 183 NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
...
 186                                    /*out*/ TrustLevel& trustLevel)
 187 {
...
 267       if (CertIsAuthoritativeForEVPolicy(candidateCert, policy)) {
 268         trustLevel = TrustLevel::TrustAnchor;
 269         return Success;
 270       }
```

問題は、話を戻して再帰的に呼ばれる`BuildForward`関数です。
311行目に"End of the recursion"というコメントがあるとおり、TrustAnchorであれば323行目で正常に再帰を終えることができるのですが、そうでない部分は延々再帰を繰り返すことになります。( そのうちループ検出によってエラー扱いになります )
なので、TrustAnchor条件が必要、ひいてはハードコーディングが必要ということになるのです。

```cpp:pkix/lib/pkixbuild.cpp
280 static Result
281 BuildForward(TrustDomain& trustDomain,
...
289 {
...
310   if (trustLevel == TrustLevel::TrustAnchor) {
311     // End of the recursion.
...
323     return trustDomain.IsChainValid(chain, time, requiredPolicy);
324   }
```

## OCSP情報確認
さて。ここまで認証局とEV OIDをハードコーディングすればチェックを通すことができますが、更にOCSP情報確認が入ります。
`PathBuildingStep::Check`関数に話を戻します。198行目の`BuildForward`は、認証局に対するチェックでした。
このチェックが通った後、サーバ証明書自体のチェックとして、240行目に`NSSCertDBTrustDomain::CheckRevocation`が呼ばれます。これが最後の砦です。

```cpp:pkix/lib/pkixbuild.cpp
141 Result
142 PathBuildingStep::Check(Input potentialIssuerDER,
...
145 {
...
198   rv = BuildForward(trustDomain, potentialIssuer, time, KeyUsage::keyCertSign,
...
240     rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time,
241                                      validityDuration, stapledOCSPResponse,
242                                      subject.GetAuthorityInfoAccess());
```

引数に`subject.GetAuthorityInfoAccess()`とありますが、これがサーバ証明書に盛り込んだ"Authority Information Access"情報です。つまり、"Authority Information Access"がなかったらアウトです。また、あったとしてもOCSP情報がなければ同様にアウトです。552行目でエラーステータスが返されることになります。

```cpp:certverifier/NSSCertDBTrustDomain.cpp
 363 Result
 364 NSSCertDBTrustDomain::CheckRevocation(EndEntityOrCA endEntityOrCA,
...
 368                          /*optional*/ const Input* aiaExtension)
 369 {
...
 527   if (mOCSPFetching == LocalOnlyOCSPForEV) {
...
 531     return Result::ERROR_OCSP_UNKNOWN_CERT;
 532   }
...
 540   const char* url = nullptr; // owned by the arena
 541
 542   if (aiaExtension) {
 543     rv = GetOCSPAuthorityInfoAccessLocation(arena, *aiaExtension, url);
...
 547   }
 548
 549   if (!url) {
 550     if (mOCSPFetching == FetchOCSPForEV ||
 551         cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) {
 552       return Result::ERROR_OCSP_UNKNOWN_CERT;
 553     }
```

それより前ですが、527行目の分岐、これが設定でOCSPを無効にしているケースです。本当は正規のEV証明書かも知れないにも関わらず、早々にエラー扱いで判定を抜けてしまいます。

ということで、以降はOCSP情報を参照して…と処理が続くのですが、その詳細は~~読み切れてないので~~割愛します。
ただ、ここまででも実際の処理の流れが感じて頂けるのではないかと思います。

# 脚注
[^1]: **オレオレEV証明書**: 「オレオレ～」という言葉は、セキュリティ研究家である高木浩光氏が紹介・言及し広まった言葉です ( 例えば[高木浩光@自宅の日記 2007年11月17日](http://takagi-hiromitsu.jp/diary/20071117.html) )。ここでは、正規の認証局の認定を受けずに自作した、公には通用しないEV証明書のことを指します。
[^2]: **ブラウザバージョン**: 2018/8/25時点でのFirefoxの最新版は61.0.2でした。
[^3]: **OID**: OIDとはObject Identifierの略であり、証明書内でデータの種類や内容を表すために割り振られたIDです。複数の数値をドットでつなげて表します。
[^4]: **証明書のインポート**: これは紹介したスライドでは書いてませんが、ブラウザへのハードコーディングされたEV用認証局情報とは別の話として必要です。
[^5]: **ブラウザのビルドは必要**: 逆に言えば、設定をいじる位では正式でないEV証明書を認識させることはできなくて、ブラウザ本体を差し替える必要があるわけで、「事故」を起こしにくいつくりになっているとは言えます。
[^6]: **OCSPとは**: 詳細は各自にお任せします…。例えば[Wikipediaの記事](https://ja.wikipedia.org/wiki/Online_Certificate_Status_Protocol)とか。
[^7]: **CRLとは**: 証明書失効リストのこと。例えば[Wikipediaの記事](https://ja.wikipedia.org/wiki/%E8%A8%BC%E6%98%8E%E6%9B%B8%E5%A4%B1%E5%8A%B9%E3%83%AA%E3%82%B9%E3%83%88)とか。
[^8]: **ツールpp**: CentOSの場合、`nss-tools`というパッケージに含まれます。