VPN環境でWSL上のWebアクセス系コマンドがx509エラーになる

Docker環境の構築で、ハマった問題の調査結果メモ。

docker image pullなどの、Webアクセス(https)系コマンドがVPN＋WSL2環境で失敗する問題。aptとかgit系のコマンドも同様にエラーになる。  

VPNソフトがGlobalProtectの場合のみ発生するかもしれない。

__~~残念ながら、結局、解決には至っていない。~~__  
~~ただ、前半の対処だけで解決する環境もあるかもしれないので、記事としては残す。~~

**⇒解決した。**
【2024/12/30追記】
本記事の対処法であっていた。VPN専用の証明書が更新されており、最新の証明書をインストールすることで解消した。

【2025/04/11追記】
回避手順を具体的に記載した記事を作成しました。

https://qiita.com/rabut/items/1ef5a3c6ddd1dc37e389


## 現象 
以下の環境において

- Windows環境にWSL2でLinuxを乗っけて動かしている
- Windows環境はVPN(GlobalProtect)を使用している

例えば、docker image pull が以下のように証明書の検証エラーになる。

```
$ docker image pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
c1ec31eb5944: Retrying in 1 second
error pulling image configuration: download failed after attempts=6: tls: failed to verify certificate: x509: certificate signed by unknown authority
```

なお、当該環境はwsl-vpnkitを導入済みで、レイヤ3までは疎通(pingは通る)している。

## 原因
GlobalProtectというVPN製品のみの仕様かもしれないが、VPN経由のSSL通信におけるサーバ認証に、以下のような動作があるため。

- SSL通信の接続先のサーバ証明書を、まず、VPNのサーバが自身で評価する
- 結果OKなら、当該サーバの証明書のチェインを、本来の中間証明書・ルート証明書へのものから、VPN専用の証明書へのものに置き換えてクライアントに渡す
- クライアントは、VPN専用の証明書(VPNセットアップ時に信頼されたルート証明書として登録)を用いて、接続先のサーバ証明書を評価する
- 結果、OKなら通信を開始する

ここで、WSL上のLinux環境には、このVPN専用の証明書がインストールされていないので、評価に失敗する。


以下はopensslコマンドで、証明書のチェックを行った例。  
(「VPNSERVER.com」となっているところに、実際のVPNのサーバの名前が表示される)

```
$ openssl s_client -connect www.google.com:443
CONNECTED(00000003)
depth=1 CN = VPNSERVER.com
verify error:num=19:self-signed certificate in certificate chain
verify return:1
depth=1 CN = VPNSERVER.com
verify return:1
depth=0 CN = www.google.com
verify return:1
---
Certificate chain
 0 s:CN = www.google.com
   i:CN = VPNSERVER.com
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: RSA-SHA256
   v:NotBefore: Oct 21 08:38:45 2024 GMT; NotAfter: Jan 13 08:38:44 2025 GMT
 1 s:CN = VPNSERVER.com
   i:CN = VPNSERVER.com
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Dec 22 06:08:43 2021 GMT; NotAfter: Dec 17 06:08:43 2041 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
・
・
---
SSL handshake has read 1802 bytes and written 396 bytes
Verification error: self-signed certificate in certificate chain
---
・
・

```



## 回避方法
VPN専用の証明書をLinux側にインストールする。

```
sudo cp certification_for_VPN.crt /usr/share/ca-certificates/
sudo update-ca-certificates
```
※「certification_for_VPN.crt」がVPN専用の証明書(事前配布)


__~~これで解決、と思ったのだが、ダメだった…。~~__
**⇒このやり方であっていた。無事解決した。**
VPN専用の証明書が更新され、かつ、手動インストールから自動インストール(VPNソフトのインストーラに入れた？)に変更されていた。しかし、この記事の記載時点では、手動時代の証明書を使ったためエラーになっていた。自端末の証明書ストアから取り出した新証明書をLinux側にインストールすることで無事回避。証明書変わったなんて聞いてねぇぞ。



## さらに発生した問題
前述の対応を行っても以下のエラーになる。
**⇒先に記載の通り、正しい証明書を登録すればこの問題は発生しない。**

```
$ docker image pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
c1ec31eb5944: Retrying in 1 second
error pulling image configuration: download failed after attempts=6: tls: failed to verify certificate: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "VPNSERVER.com")
```

```
$ openssl s_client -connect www.google.com:443
CONNECTED(00000003)
depth=1 CN = VPNSERVER.com
verify return:1
depth=0 CN = www.google.com
verify error:num=7:certificate signature failure
verify return:1
depth=0 CN = www.google.com
verify return:1
---
Certificate chain
 0 s:CN = www.google.com
   i:CN = VPNSERVER.com
   a:PKEY: id-ecPublicKey, 256 (bit); sigalg: RSA-SHA256
   v:NotBefore: Oct 21 08:38:45 2024 GMT; NotAfter: Jan 13 08:38:44 2025 GMT
 1 s:CN = VPNSERVER.com
   i:CN = VPNSERVER.com
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Dec 22 06:08:43 2021 GMT; NotAfter: Dec 17 06:08:43 2041 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
・
・

---
SSL handshake has read 1801 bytes and written 396 bytes
Verification error: certificate signature failure
---
・
・
```

エラーは以下で、署名の検証に失敗している。
Verification error: certificate signature failure

実際、前述の `openssl s_client -connect` の結果から、VPNサーバから返っているwww.googole.comのサーバ証明書を引っ張り出して(/tmp/test.crt)検証してみると、エラーになる。

```
$ openssl verify -show_chain -CAfile certification_for_VPN.crt  /tmp/test.crt
CN = www.google.com
error 7 at 0 depth lookup: certificate signature failure
error /tmp/test.crt: verification failed
40079451FE7E0000:error:0200008A:rsa routines:RSA_padding_check_PKCS1_type_1:invalid padding:../crypto/rsa/rsa_pk1.c:79:
40079451FE7E0000:error:02000072:rsa routines:rsa_ossl_public_decrypt:padding check failed:../crypto/rsa/rsa_ossl.c:697:
40079451FE7E0000:error:1C880004:Provider routines:rsa_verify:RSA lib:../providers/implementations/signature/rsa_sig.c:774:
40079451FE7E0000:error:06880006:asn1 encoding routines:ASN1_item_verify_ctx:EVP lib:../crypto/asn1/a_verify.c:217:
```

ここから先は追いきれなかったが、以下あたりを読む感じ、VPNサーバが動的に発行するサーバ証明書 or VPN専用の証明書に、何らかの不備があるように見える。

https://zenn.dev/yyu/articles/3deccbb66cba4a

> なぜかSHA1を利用すると次のようなエラーでopensslによる署名検証が失敗してしまうので、SHA256で代替した

こうなるとVPNサーバ・環境の問題なので、こちらからは手が出せない。  
残念ながら、ここでギブアップ。畜生。



## その他

私の環境では該当しなかったが、類似の問題が以下で直るパターンはありそう。  
とりあえずメモ。


- ネットワークのMTU調整

https://blog.jicoman.info/2020/12/hangup-ssh-connection-using-wsl2/

- WSL2のミラーモードを使用

https://qiita.com/shigeokamoto/items/bad3ce237f0b19aea147