14.4.5. 公開サーバーの検証(クライアントとしてのカメラ)¶
前のページで述べた、クライアントが「すでにルートを持っている」という話は、ブラウザ、スマートフォン、PCには当てはまりますが、カメラには 当てはまりません。MicroPythonの ssl には組み込みのトラストストアが付属しておらず、フラッシュしたばかりのカメラはどのCAも一切信頼していません。さらにデフォルト(ssl.CERT_NONE)では何も検証されず、中間者攻撃に対して無防備な状態です。したがって、カメラが クライアント として公開TLSサーバー(HTTPS API、MQTTブローカーなど)に接続し、そのサーバーを本当に検証したい場合は、トラストアンカーを自分で用意する必要があります。
仕組みは 自己署名証明書 の自己署名クライアントの例と同じです。唯一の違いは、読み込むファイルがピア自身の証明書ではなく実際のCA証明書であるという点です:
サーバーのチェーンを固定するCA証明書を入手します。 「固定する(Anchors)」とは、サーバーのチェーンの最上部(またはその近く)にあり、信頼の起点として選ぶ証明書のことを指します。TLSサーバーはリーフ証明書と通常はその中間証明書を送信しますが、ルート証明書は決して送信しません。このトラストアンカーは自分で、しかも サーバーとは独立に 入手しなければなりません。サーバーが渡してくるものを単純に信頼してしまっては、検証の意味がまったくなくなってしまいます。
まず、サーバーの証明書を実際に発行したCAがどれかを確認します。例えば
openmv.ioに対しては次のようにします:openssl s_client -connect openmv.io:443 -showcerts < /dev/nullCertificate chainブロックには、各証明書がそのサブジェクト(s:)と発行者(i:)とともに一覧表示されます。新しいバージョンのOpenSSLではa:(鍵の種類)やv:(有効期間)の行も表示されますが、ここでは無視してかまいません:Certificate chain 0 s:CN=openmv.io i:C=US, O=Let's Encrypt, CN=E8 1 s:C=US, O=Let's Encrypt, CN=E8 i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
エントリ0はリーフ(
openmv.io)で、中間証明書E8によって発行されています。エントリ1はその中間証明書で、ルートISRG Root X1によって発行されています。最上位エントリの発行者(i:)がルートの名前を示しており、ここではISRG Root X1です。(中間証明書が他で見かけるR10/R11ではなくE8になっているのは、openmv.ioがECDSA証明書を使用しているためです。Let's EncryptはECDSAのリーフをE系列の中間証明書で署名し、RSAのリーフをR系列の中間証明書で署名します。どちらもISRG Root X1にチェーンしています。)OpenSSLは
depth=の行も表示し、ルートについてVerification: OKと報告する場合があります。これは あなたのPC がすでにISRG Root X1を信頼しているからにすぎません。サーバーはそれを送信しておらず(サーバーがルートを送ることは決してありません)、トラストストアを持たないカメラもそれを持っていません。だからこそ、自分で用意する必要があるのです。その ルートを、CA自身が公開しているルート証明書からダウンロードします。Let's Encryptは自社のすべてのルートを Let's Encrypt certificates ページ で公開しており、ISRG Root X1の直接ファイルは isrgrootx1.pem です(DERにあらかじめエンコードされた isrgrootx1.der も提供されています)。他のCAも同様の「ルート証明書」/「リポジトリ」ページで公開しており、標準的な公開セットは Mozilla CAプログラム(CCADB) です。正しいファイルを取得できたかどうかは、そのフィンガープリントをCAが公開している値と比較して確認してください(
.derをダウンロードした場合は-inform DERを追加します):openssl x509 -in isrgrootx1.pem -noout -subject -fingerprint -sha256ルートを管理するのが面倒な場合は、代わりに
-showcertsの出力から 中間証明書 を直接コピーし(2つ目の-----BEGIN CERTIFICATE-----ブロック)、それを信頼することもできます。ただしその場合、CAが中間証明書をローテーションするたびに更新が必要になることを受け入れる必要があります。これはルートよりもはるかに頻繁に起こります(下記のトレードオフを参照)。DERに変換します。 これまでと同じ手順です:
openssl x509 -in isrgrootx1.pem -outform DER -out ca.derca.derをカメラ(ファイルシステムまたはROMFS)に コピー し、トラストアンカーとして読み込みます:import socket import ssl import ntptime ntptime.settime() # validity check needs the clock addr = socket.getaddrinfo("api.example.com", 443)[0][-1] sock = socket.socket() sock.connect(addr) ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.verify_mode = ssl.CERT_REQUIRED ctx.load_verify_locations(cafile="ca.der") ssock = ctx.wrap_socket(sock, server_hostname="api.example.com")
ここでは
server_hostnameが必須です。これはSNIを駆動し、サーバー証明書のsubjectAltNameと照合される名前になります。
Tip
よくあるケースのショートカット。 Let's Encryptは最も広く使われている公開CAであり、現時点ではRSA証明書とECDSA証明書のどちらもISRG Root X1にチェーンしています(上記の openmv.io の例が示すとおりです)。カメラが通信するサーバーがLet's Encryptを使用している場合は、検査をまったく省略できます。isrgrootx1.der をカメラに置いて load_verify_locations で読み込むだけです。
ただし、これで すべての サイトに対してTLSが機能するわけ ではありません。証明書が別のCA(DigiCert、Google Trust Services、Amazon、Sectigoなど)から発行されているサーバーは、依然として検証に失敗します。また、カメラは ssl.SSLContext ごとに1つのDER証明書しか信頼しないため、ブラウザのようにすべてのルートをバンドルすることはできません。迷った場合は、上記のようにサーバーの実際のCAを特定し、そのルートを信頼してください。
どの証明書を信頼するかはトレードオフです:
ルート(推奨)。長期間有効で、しばしば数十年に及ぶため、
ca.derはめったに変わりません。mbedTLSが リーフ → 中間 → 信頼するルート というパスを構築できるよう、サーバーが中間証明書を送信する必要がありますが、正しく構成された公開サーバーであればほぼ確実にそうしています。中間証明書。 これも機能し、サーバーが中間証明書を省略した場合でも動作し続けます。ただし、中間証明書はルートよりもはるかに頻繁にローテーションされるため、
ca.derをより頻繁に更新する必要があります。リーフ証明書そのもの(証明書ピンニング)。最も厳格ですが、リーフは更新のたびに変わります(Let's Encryptでは約90日ごと)。したがって、これはサーバーも自分で管理しており、すべてのカメラに新しいピンを一斉にプッシュできる場合にのみ意味があります。これはまさに自己署名クライアントの例で行っていることです。
注釈
ssl.SSLContext.load_verify_locations() は1つのDERエンコードされたCA証明書を受け取るため、カメラは一度に正確に1つのアンカーのみを信頼します。異なるCAに属するサーバーに到達するには、アンカーごとに別々の ssl.SSLContext を使用してください。また、その証明書自体もいずれは期限切れになるか、CAによってローテーションされるため、デバイス上の他のすべての証明書と同様に扱ってください。