軽量プロトコルUDPのセキュリティ:DTLS
1.1 概要
このシリーズでは、製品開発に携わるソフトウェアエンジニアが、製品組み込みのためのセキュリティ機能に関するプログラミングを進めるにあたり必要となる知識について、整理して具体的にまとめます。一口にセキュリティといっても最近は非常に幅広い分野を含みます。ここでは、主にネットワークのセキュリティプロトコルや暗号アルゴリズムなど、製品に直接組み込んで利用するような機能を中心に取りあげて行きます。
シリーズ第二回の今回は、DTLS(Datagram Transport Layer Security)を取り上げます。DTLSはUDP(User Datagram Protocol)のような軽量だけど不安定な通信の上でセキュリティを実現するためのトランスポート層のプロトコルです。UDPはTCPのような接続の概念がなく、単純にパケットを送受することができる軽量で簡単に使えるプロトコルとして広く使われてきました。このプロトコルは小規模なシステムに使われる場合が多く、従来はセキュリティについてはあまり強く求められないケースが多かったのですが、最近は本格的な応用ではセキュリティが求められることが多くなってきています。そのような場合に活躍するのがDTLSです。
今回は、これまでUDPプロトコルを使ったネットーワーク機器を開発してきたエンジニアが、DTLSでセキュリティを実現していくのに必要な知識を解説していきます。
1.2 不安定なトランスポート層の上でセキュリティを実現
UDPは軽量のプロトコルである一方で、パケットロスや転送順序の保証がなかったりといった暗号処理には少々やっかいな問題をかかえています。そこで、DTLSの処理の下層部分ではパケットロスや順序保証といったTCPに似たような処理を行わせています。ただし、TCPとは違って安全な接続を確立するための準備(ハンドシェークプロトコル)の間だけそうした不安定要素を取り除くようにしています。接続が確立したあとはUDPと同様の軽量さを維持するため、上位のアプリケーションに対して順序性を保証したりロストパケットを再送要求して補ったりはしません。
DTLSではそのような管理の上にセキュリティプロトコルを実現します。一方、DTLSのハンドシェーク自身はTLSによく似たプロトコルになっています。TLSと同様に通信ノードの役割はクライアントとサーバに分かれていて、接続の概念もTLSと同様です。DTLSの通信はクライアントからの接続要求からはじまり、クライアントとサーバの間で一連のハンドシェークが行われ安全な接続を確立し、実際のアプリケーションデータの転送を行います。そのために、通信するノードはUDPではなかったサーバ、クライアントの区別が導入されています。接続要求をするほうがクライアント、要求を待つほうがサーバです。
1.3 DTLSの標準化
DTLSの標準化はTLSと同様IETFのRFCとして規定されています。当初TLSとは比較的独立に行われていたのですが、最近はTLSと歩調をあわせるようになってきています。DTLS1.0の次の検討ではTLS1.2と歩調を合わせるために、プロトコルのバージョン名も一つスキップしてDTLS1.2としています。また、最新のバージョンDTLS1.3は2022年4月に発行されましたが、TLS1.3で実現した多くの強化、改善が取り込まれています。wolfSSLでも最新版ではDTLS1.3がサポートされており(2022/9現在ベータ版)、今後、利用されるバージョンは順次DTLS1.3に移行していくものと見られます。この記事では、1.2と1.3の違いについても紹介していきます。
DTLSプロトコルを見てみる
2.1 DTLS1.2
それでは早速DTLSのプロトコルを見てみましょう。下の図はDTLS1.2のサーバとクライアントが簡単な一往復のメッセージ通信をしているところをパケットキャプチャーツールWiresharkで見たものです。UDPでは一回一回のメッセージの送受信が独立しているデータグラムなのでサーバ、クライアントという概念はありません。DTLSの場合はTLSと同様に一連の安全な通信を行うための「接続」の概念があります。DTLS接続を要求するほうがクライアント、要求を待つほうがサーバです。
通信の最初はクライアントからサーバへの接続要求メッセージ(Client Hello)です。これに対して、サーバとクライアントの間で安全な接続を確立するための一連のメッセージの交換(ハンドシェーク)が行われます。ハンドシェークでは、両者が利用する一連の暗号アルゴリズムの方式(暗号スイート)や使用する鍵そのものを合意(鍵交換または鍵合意)したりします。また、相手が正当な相手であることを確認(サーバ認証、クライアント認証)なども行います。これらの内容はTLSの場合とほとんど同じです。
少し違うのはClient Helloの後、Hello Verify Requestというメッセージで相手を再確認している点です。これはDTLSが接続の概念がないUDPの上のプロトコルであるためにDTLSとしてDDos攻撃を防ぐ必要があるためです。
下の図はClient Helloメッセージの詳細を見たものですが、DTLSメッセージがUDPの上に乗っているのが確認できます。また、TLSに馴染みのある方ならDTLSのClient Helloの中身はTLSのものと非常に似ている点に気づかれるかと思います。
2.2 DTLS1.3
それでは次にDTLS1.3を見てみましょう。下の図はDTLS1.2の場合と同じようにサーバとクライアントが簡単な一往復のメッセージ通信をしているところをWiresharkで見たものです。
本稿執筆時点ではWiresharkがまだDTLS1.3に対応していないのでプロトコルとしては”DTLSv1.2″ と表示されてしまいますが、実際にはDTLS1.3のプロトコルがやりとりされています。DTLS1.2とは異なり、最初の”Client Hello”と”Server Hello” のあとは暗号化されているようで単なるUDPパケットとして表示されますが、この部分もハンドシェークが続きます。ハンドシェークが完了すると、1往復のアプリケーションメッセージとAlertのやりとりがありますが、この部分も暗号化されているのでハンドシェークメッセージと区別がつきません。
DTLS1.3ではDTLS1.2に比べて古い弱くなった暗号アルゴリズムやスキームを整理したり、ハンドシェークそのものも整理されたり安全上だけでなくスループット上もたくさんの改善が取り込まれています( 詳細はこちら)。
DTLSプログラミング
3.1 UDPプログラム
では次にDTLSを利用するにはどのようなプログラムが必要となるか、ベースとなるUDPの簡単なプログラムをDTLS化する過程をC言語のプログラムで見ていきます( https://github.com/wolfSSL/wolfssl-examples/blob/master/dtls/client-udp.c )。
プログラムはBSD Socketを使ったUDPサーバへのメッセージ送信、サーバからのメッセージ受信を繰り返すだけの単純なものです。送信メッセージは標準入力から入力し、入力がなくなれば(EOF)プログラムを終了します。
はじめにUDP用のソケットを確保し、相手先サーバのIPアドレスなどの情報を設定します。IPアドレスはコマンドの第一アーギュメントで指定します。ポート番号は11111を使用します。
sendtoでサーバに対してメッセージを送信し、recvfromでメッセージを受信します。
ヘッダーや変数宣言を除いたプログラムは概ね下のようになります。
3.2 UDPとDTLSの違い
UDPとDTLSの主な違いは以下の点です。
- UDPには「接続」処理は存在しておらず、クライアントは通信したいメッセージをいきなり相手に対して送信することができます。DTLSはセキュリティのために一連のメッセージ送受信の前に「接続」処理が必要です。
- 接続時に意図した正しい相手であることの確認(サーバ認証、クライアント認証)を行います。TLSと同様にサーバ認証は必須、クライアント認証はオプションです。
- 当然ですが、送受信するメッセージは暗号化によって秘匿され、また一貫性も保証します。
3.3 DTLSのための主な変更点
それらのために、次のような追加変更が必要となります。
- DTLS利用準備
一連のDTLS処理を管理するためにコンテキストの管理ブロックを使います。一つの管理ブロックを確保して、サーバ認証、クライアント認証に必要な証明書や鍵をロードしておきます。また、一つのDTLS接続を管理するための管理ブロックも用意します。この管理ブロックには確保したソケットを登録しておきます。 - DTLS接続
通信の相手方にDTLS接続を要求します。UDPの場合、メッセージはデータグラムとして一つ一つが個別のメッセージ送受信として扱われますが、DTLSでは安全な接続を確保し、その上で通信します。 - DTLSメッセージ送信、受信
アプリケーションの一塊のメッセージを送受信しますが、DTLS暗号化、復号のためのAPIを使います。 - DTLS切断
通信の相手方にDTLS切断を要求します。接続と異なり、どちらからでも要求することができます。 - DTLSリソース解放
DTLS接続やコンテクストの管理ブロックを解放します。
では、具体的なプログラムを項目ごとに見ていきます。
- DTLS利用準備はプログラムの先頭付近で行います。wolfSSL_CTX_newでコンテクスト管理ブロックを確保します。この時、使用するDTLSプロトコルのバージョンを指定します。ここではDTLS1.2を指定します。次に、クライアント側ではwolfSSL_CTX_load_verify_locationsでサーバ認証のためのCA証明書をロードします。
- DTLS接続処理は次のようになります。
UDPソケットの確保したあと、wolfSSL_newでDTLS接続のための管理ブロックを確保します。次に、wolfSSL_dtls_set_peerで通信相手を登録し、wolfSSL_set_fdでソケットディスクリプタを登録した上で、wolfSSL_connectでDTLS接続処理を行います。 - DTLSメッセージ送信、受信では、sendto をwolfSSL_writeに、recvfromをwolfSSL_readに置き換えてDTLSの暗号化処理をさせます。
- DTLS切断はwolfSSL_shutdownで行います。DTLSリソース解放 は wolfSSL_freeとwolfSSL_CTX_freeです。wolfSSLライブラリ処理全体を終了する場合はwolfSSL_Cleanupを呼びます。
3.4 DTLS1.3を試してみる
このサンプルはDTLS1.2でしたが、DTLS1.3を試してみます。DTLS1.2と1.3のアプリケーションプログラム上の違いは、wolfSSL_CTX_newのアーギュメントでプロトコルバージョンをDTLS1.3と指定する点だけです。
- クライアント側:wolfDTLSv1_3_client_method()
- サーバ側: wolfDTLSv1_3_server_method()
また、場合によっては相手側はv1.3だけでなくサポートされている範囲で最新のバージョンで接続を許すという場合は、次のように指定します。
- クライアント側:wolfDTLS_client_method()
- サーバ側: wolfDTLS_server_method()
下のようにUSE_DTLS12マクロが定義されている場合はDTLS1.2、されていない場合はDTLS1.3が指定するようにすることができます。
ライブラリ、サンプルプログラムの入手、ビルド
4.1 wolfSSLの入手、ビルド
wolfSSLは商用版と無償オープンソース版のデュアルライセンスのソフトウェアです。商用の製品に組み込む場合は商用ライセンスを必要としますが、社内での技術評価などのためには機能的にはまったく同じ無償オープンソース版を利用することができます。オープンソース版を入手して実際に実行させてみることにします。
この記事では、UbuntuなどのLinux、MacOSやWindowsのWSLなどのGCC, make環境で実際にサンプルプログラムを動かしながらプロトコルについて理解しています。まずはライブラリー、サンプルプログラム一式をダウンロード、ビルドします。
1) ソースコード一式のダウンロード
正式版はwolfSSLサイトから、最新版はGithubからダウンロードします。DTLSプロトコル機能はwolfSSLライブラリ製品のオプション機能として含まれているのでwolfSSLを入手します。
正式版:
https://www.wolfssl.jp/download/ からwolfssl-x.x.0.zip を選択してダウンロード、解凍します。
最新版:
https://github.com/wolfssl/wolfssl からcloneします。cloneの後、wolfsslデイレクトリでsh autogen.shを実行しconfigureなど必要ファイルを生成します。
$ git clone --depth 1 https://github.com/wolfssl/wolfssl
autogen.shではautoconf, automake, libtoolなどを必要としますが、インストール刺されていない場合は次のコマンドでインストールします。
$ sudo apt install autoconf automake libtool
2) ビルド
次に、ライブラリとサンプルプログラム一式をビルドします。configureコマンドでMakefileを生成し、makeコマンドにてビルドします。configureコマンドではDTLS機能を有効化指定(”–enable-dtls”と”–enable-dtls13”)してビルドします。make checkのように指定するとコンパイル完了後、ローカルテストを実行するのですべてのテストが正常終了することを確認します。
$ cd wolfssl $ ./autogen.sh (githubからcloneした場合のみ必要です) $ ./configure --enable-dtls –enable-dtls13 ... * DTLS: yes * DTLS1.3: yes $ make check ============================================================================ Testsuite summary for wolfssl 5.5.0 ============================================================================ # TOTAL: 7 # PASS: 7 # SKIP: 0 # XFAIL: 0 # FAIL: 0 # XPASS: 0 # ERROR: 0 ============================================================================
4.2 サンプルプログラムの入手、ビルド
下記のGithubレポジトリの内容をダウンロードします。DTLS関連のディレクトリに移動し、makeコマンドで一連のサンプルプログラムをコンパイルします。
$ git clone –depth 1 https://github.com/wolfSSL/wolfssl-examples $ cd wolfssl-examples/dtls $ make
まとめ
今回は、組み込み向け軽量プロトコルUDPのセキュリティを実現するDTLSについて紹介しました。wolfSSLのオープンソース版は、検証用に無料で使えるので、計画段階、研究段階でぜひ実際の環境で試してみてください。機能要求を満たすことが確認できたら、そのまま商用版ライセンスへの移行が可能ですので、無駄なく開発を進めることができます。今後発生する脆弱性対応などは、wolfSSLが提供するサポートパッケージに含まれますので、安心してビジネスユースでお使いいただけます。
本稿よりさらに詳細については下記の記事もあわせて参照してください。
- DTLS1.3で何が変わったか
IETF RFC 9147によるDLTS1.3についてさらに詳しい解説です。 - wolfSSLでDTLS1.3を試してみる
DTLS1.3の動作については本稿よりさらに掘り下げて解説します - UDPプログラムをDTLS化してみる
DTLSプログラミングについて、本稿よりさらに詳細のケースについても解説します。
wolfSSLは、wolfSSL Inc. が独自に開発し、権利関係の一切を有するTLSライブラリです。wolfSSLに関するご質問、お問い合わせは info@wolfssl.jp宛お送りください。