Db2 における TLS/SSL通信時 の hostname validation 機能

## hostname validation とは
Db2 11.5.6 では、TLS/SSL通信時、クライアント側でhostname validation(ホスト名検証)を行う新機能が追加されました。

Db2クライアントで「ホスト名の検証(hostname validation)」を有効化しておくと、
クライアントが接続先として設定している宛先ホスト名が、サーバー証明書に記載されているホスト名と一致するかどうか確認してから接続するようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468682/1a775d6f-4fe8-a76c-7da6-41fa6e29a099.png)


ホスト名の検証は、IETF RFC 6125で規定されている規格に基づいて行われます。

 * 宛先ホスト名が、サーバー証明書に記載されているホスト名のいずれかと一致する場合に接続成功します。
 * 宛先ホスト名が、サーバー証明書に記載されるホスト名に一致しない場合、下記のエラーメッセージが出力され、DB接続は失敗します。

---

java.sql.SQLNonTransientException: [jcc][t4][20162][14259][4.29.24] 接続に失敗しました: サーバーとの SSL 接続を確立できませんでした。
ホスト名検証のため接続が構成されましたが、クライアントによって構成された値に一致するホスト名または IP アドレスが、サーバーの TLS 証明書に含まれていません。 
ERRORCODE=-20576, SQLSTATE=08001 DSRA0010E: SQL 状態 = 08001、エラー・コード = -20,576

---

:::note warn
※Db2 V12.1から、この機能はデフォルトで有効となりました。TLS/SSL接続を行う環境で、サーバー証明書と一致しないホスト名を指定していると接続エラーとなります。
:::

## hostname validation で照合されるホスト名

サーバー証明書に含まれるコモンネーム(CN)、Subject Alternate Name(SAN) などの名前が照合されます。

#### コモンネーム(CN)

例えば xyz.db2.example.com をコモン ネームとする証明書を作成するには、gsk8capicmd_64 -cert -create コマンド (自己署名証明書の場合) または gsk8capicmd_64 -certreq コマンド (CSR の場合) の -dn オプションの一部として CN=xyz.db2.example.com が存在する必要があります。

 * 単一のFQDN(ドメイン名+ホスト名)<br> もしくは ワイルドカード(\*)を使用したFQDN (例：「*.domain-a.com」)
 * 証明書にSAN（Subject Alternate Name）エントリが存在する場合は、コモンネームは無視される
 * 指定方法
 	* 自己署名証明書の場合：gsk8capicmd_64 -cert -create コマンド -dn オプション(CN=<hostname>..)
 	* CSRの場合：gsk8capicmd_64 -certreq コマンド の -dn オプション(同上)<br>
      (以下、自己署名証明書の確認例)
      
      ```
      Key Size : 2048
      Version : X509 V3
      Serial : xxx
      Issuer : CN=db2.myssl-test.com,OU=myOrganizationUnit,O=myOrganization,L=myLocation,ST=ON,C=CA
      Subject : CN=db2.myssl-test.com,OU=myOrganizationUnit,O=myOrganization,L=myLocation,ST=ON,C=CA
      Not Before : August 22, 2021 6:32:10 AM GMT+00:00
      
      Not After : August 23, 2022 6:32:10 AM GMT+00:00
      ```

#### Subject Alternate Name (SAN)

 * 1つの証明書に複数のドメイン名をまとめたい時に利用されることが多い
 	* host.domain-a.com と host.domain-b.com など、複数指定可能
 * 指定方法
 	* 自己署名証明書の場合：gsk8capicmd_64 -cert -create コマンド -san_dnsname オプション
 	* CSRの場合：gsk8capicmd_64 -certreq コマンド の -san_dnsname オプション
 * 確認方法
 	* 自己署名証明書の場合：gsk8capicmd_64 -cert -details コマンド
 	　<br>(以下、例)
 	
 	```
 	Extensions
    subjectAlternativeName
        dNSName: db2.myssl-test-alt.com
 	```
 	
#### その他

上記の他、IPアドレスや、ドメイン名を含まないホスト名だけでもhostname validation機能の利用は可能ですが、推奨されないため割愛。
推奨されない理由は[Db2製品マニュアル](https://www.ibm.com/docs/en/db2/11.5?topic=instances-representing-servers-in-certificate)を参照ください。


## Db2クライアントのhostname validation
対象となるのは下記の環境。

 * CLI/ODBC
 * Embedded SQL
 * JDBC

上記の挙動は、JDBC/自己署名証明書の場合の組み合わせで検証したものです。
以下、自分用の備忘録として構成手順も残しておきます。

## 構成上の注意点

構成手順は通常のJDBC over SSL構成時とほぼ同じです。違いとなるのは下記2点です。

 * クライアントでJDBCプロパティ「sslClientHostnameValidation="BASIC"」を設定すること
 * クライアントの接続先アドレスと証明書のコモンネーム(もしくはSANのdNSName)を一致させる


:::note warn
sslClientHostnameValidation プロパティの設定値「BASIC」はすべて大文字である必要があります。
大文字以外で設定しても、JDBC の hostname validation は有効になりません。
また一方でエラーにもならないため、hostname validation を有効にしたつもりが無効のままで、セキュリティレベルが低い状態のままであることに気づかず稼働し続けてしまう可能性があることに注意。
:::

## 構成手順

以下は、こちらで上記の検証を行った際の構成手順です。
JDBC over SSL 構成の流れ/イメージ図や参考情報リンクなどは、[こちら](https://qiita.com/mi-kana/items/dd6586030c4260bc9f0b)にもメモしています。


### Step1. 鍵ストア/自己署名証明書の作成

 * 実行サーバ：Db2サーバ
 * 実行ユーザ：Db2インスタンスオーナ

####1-1. 鍵ストア作成

鍵ファイルの出力先ディレクトリを作成

```
$ mkdir /database/mytmp/ssl
```

鍵データベースを作成

```
$ ~/sqllib/gskit/bin/gsk8capicmd_64 -keydb -create -db "/database/mytmp/ssl/mydbserver.kdb" -pw "PASSWORD" -stash
```

作成されたファイルを確認

```
$ ls -ltr /database/mytmp/ssl
total 16
-rw------- 1 db2inst1 db2iadm1  88 Aug 17 07:19 mydbserver.rdb
-rw------- 1 db2inst1 db2iadm1  88 Aug 17 07:19 mydbserver.kdb
-rw------- 1 db2inst1 db2iadm1  88 Aug 17 07:19 mydbserver.crl
-rw------- 1 db2inst1 db2iadm1 193 Aug 17 07:19 mydbserver.sth

```

#### 1-2. 自己署名証明書の作成 / 鍵ストアへの追加
下記コマンドを実行し、サーバーの自己署名証明書を鍵データベースに追加します。

```
$ ~/sqllib/gskit/bin/gsk8capicmd_64 -cert -create -db "/database/mytmp/ssl/mydbserver.kdb" -pw "PASSWORD" -label "myselfsigned" -dn "CN=db2.myssl-test.com,O=myOrganization,OU=myOrganizationUnit,L=myLocation,ST=ON,C=CA"
```

上記コマンドで指定する証明書関連の情報のうち、下記項目は後の構成時に必要となります

 * ラベル(label)：
	 * コマンドで指定した値：myselfsigned 
	 * Db2のTLS/SSLサーバー証明書のラベルを指定するDBM構成パラメータ ssl_srv_label に設定
 * コモンネーム(CN)： 
	 * コマンドで指定した値：db2.myssl-test.com
	 * Liberty の server.xml で、接続先ホスト名として指定する。/etc/hosts にも記述する
	 * hostname validationを行う環境では、SSL接続確立時に宛先サーバ名とここで指定するサーバ名が一致するかが検証される

鍵データベースが更新されていることを確認

```
$ ls -ltr /database/mytmp/ssl
total 20
-rw------- 1 db2inst1 db2iadm1   88 Aug 17 07:19 mydbserver.rdb
-rw------- 1 db2inst1 db2iadm1   88 Aug 17 07:19 mydbserver.crl
-rw------- 1 db2inst1 db2iadm1  193 Aug 17 07:19 mydbserver.sth
-rw------- 1 db2inst1 db2iadm1 5088 Aug 17 07:20 mydbserver.kdb
　　←最終更新時刻が変わっている
```

#### 1-3. 証明書の抽出

下記コマンドを実行して作成した自己署名証明書をファイルに抽出します。
  (→  ここで抽出した証明書ファイルは、後述の  Step2. でクライアントに配置するためのトラストファイルにimportする)

```
$ ~/sqllib/gskit/bin/gsk8capicmd_64 -cert -extract -db "/database/mytmp/ssl/mydbserver.kdb" -pw "PASSWORD" -label "myselfsigned" -target "/database/mytmp/ssl/mydbserver.arm" -format ascii -fips
```

サーバ証明書(mydbserver.arm)が作成されていることを確認

```
$ ls -ltr /database/mytmp/ssl
total 24
-rw------- 1 db2inst1 db2iadm1   88 Aug 17 07:19 mydbserver.rdb
-rw------- 1 db2inst1 db2iadm1   88 Aug 17 07:19 mydbserver.crl
-rw------- 1 db2inst1 db2iadm1  193 Aug 17 07:19 mydbserver.sth
-rw------- 1 db2inst1 db2iadm1 5088 Aug 17 07:20 mydbserver.kdb
-rw-r--r-- 1 db2inst1 db2iadm1 1375 Aug 17 07:20 mydbserver.arm
　　←作成されたファイル
```

### Step2. トラストストアの作成/転送

 * 実行サーバ：Db2サーバ
 * 実行ユーザ：Db2インスタンスオーナ

トラストストアを作成し、ひとつ前の手順で作成(extract) したサーバ証明書「mydbserver.arm」をトラストストアに追加します。

トラストストアとは、クライアントが信頼するすべての証明書を保存する場所です。
Db2サーバが利用する鍵ストアファイル(拡張子 .kdb) は ikeyman で作成します。
Db2クライアントが非Javaであれば、クライアント側にも ikeyman で作成した鍵ストアファイル( .kdb) を配置しますが、今回のクライアントはLiberty(Javaランタイム)であるため .kdb の鍵ストアファイルの利用は出来ません。
代わりに、JDKが提供する keytool コマンドを利用してトラストストアファイル(拡張子 .jks) を作成します。

__補足：__

 * ikeyman(GSKit) も keytool コマンドも、キーと証明書を管理するためのユーティリティです。
 * ikeyman は、IBM 固有の形式であるCMS形式(Certificate Management System)(拡張子 .kdb) と、 Public-Key Cryptography Standards #12 (PKCS12) オープン・スタンダード形式(拡張子 .jks) を作成できます。
 * Db2 は、CMS 鍵ストア (拡張子 .kdb) 、PKCS#12 鍵ストア (拡張子 .p12)  の両方をサポートします。

#### 2-1. トラストストアの作成 / 証明書のインポート
以下のコマンドを実行すると、トラストストア「myTrustStore.jks」が存在しない場合は新規作成し、サーバー証明書「mydbserver.arm」が「myTrustStore.jks」に追加されます。

```
$ cd /database/mytmp/ssl
/opt/ibm/db2/V11.5/java/jdk64/bin/keytool -import -trustcacerts -alias myalias -file mydbserver.arm -keystore myTrustStore.jks
Enter keystore password:    【 ← パスワードを指定 】
Re-enter new password:      【 ← パスワードを指定(再入力) 】
Owner: CN=db2.myssl-test.com, OU=myOrganizationUnit, O=myOrganization, L=myLocation, ST=ON, C=CA
Issuer: CN=db2.myssl-test.com, OU=myOrganizationUnit, O=myOrganization, L=myLocation, ST=ON, C=CA
Serial number: 2548b9b4b8a2ea3c
Valid from: 8/16/21 7:20 AM until: 8/17/22 7:20 AM
Certificate fingerprints:
         MD5:  7E:83:F6:C3:37:B7:98:74:3A:8E:2D:23:61:8D:22:E2
         SHA1: B8:C4:1E:D2:32:FA:8B:39:7C:94:8E:93:6E:D7:58:E1:15:0E:B2:05
         SHA256: 92:BC:24:C3:97:B9:BD:1F:7D:56:D4:28:69:A7:58:E6:CB:7F:A9:C9:5C:BD:FF:98:91:7E:95:33:D1:CB:D2:8E
         Signature algorithm name: SHA256withRSA
         Version: 3

Extensions:

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: e6 3f 66 67 a8 0c 2a 69  01 3e 0b 90 fb 9e 48 58  ..fg...i......HX
0010: 56 74 14 5e                                        Vt..
]
]

#2: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: e6 3f 66 67 a8 0c 2a 69  01 3e 0b 90 fb 9e 48 58  ..fg...i......HX
0010: 56 74 14 5e                                        Vt..
]
]

Trust this certificate? [no]:  yes　　　【 ← yesを入力、Enter 】
Certificate was added to keystore

```

#### 2-2. TrustStoreに証明書が存在することを確認

2-1、で指定したトラストストアのパスワードを入力する必要があります。
キーストアの形式(ここではjks)や、このトラストストアに格納される証明書の情報などを確認することができます。
例えば、コモンネームはdb2.myssl-test.comで、期限はデフォルトだと1年間、などといった情報が確認できます。

```
$ /opt/ibm/db2/V11.5/java/jdk64/bin/keytool -list -v -keystore myTrustStore.jks
Enter keystore password:　←2-1.(トラストストア作成時)で決めたパスワード

Keystore type: jks
Keystore provider: IBMJCE

Your keystore contains 1 entry

Alias name: myalias
Creation date: Aug 17, 2021
Entry type: trustedCertEntry

Owner: CN=db2.myssl-test.com, OU=myOrganizationUnit, O=myOrganization, L=myLocation, ST=ON, C=CA
Issuer: CN=db2.myssl-test.com, OU=myOrganizationUnit, O=myOrganization, L=myLocation, ST=ON, C=CA
Serial number: 2548b9b4b8a2ea3c
Valid from: 8/16/21 7:20 AM until: 8/17/22 7:20 AM
Certificate fingerprints:
         MD5:  7E:83:F6:C3:37:B7:98:74:3A:8E:2D:23:61:8D:22:E2
         SHA1: B8:C4:1E:D2:32:FA:8B:39:7C:94:8E:93:6E:D7:58:E1:15:0E:B2:05
         SHA256: 92:BC:24:C3:97:B9:BD:1F:7D:56:D4:28:69:A7:58:E6:CB:7F:A9:C9:5C:BD:FF:98:91:7E:95:33:D1:CB:D2:8E
         Signature algorithm name: SHA256withRSA
         Version: 3

Extensions:

#1: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
0000: e6 3f 66 67 a8 0c 2a 69  01 3e 0b 90 fb 9e 48 58  ..fg...i......HX
0010: 56 74 14 5e                                        Vt..
]
]

#2: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: e6 3f 66 67 a8 0c 2a 69  01 3e 0b 90 fb 9e 48 58  ..fg...i......HX
0010: 56 74 14 5e                                        Vt..
]
]



*******************************************
*******************************************

```

#### 2-3. 作成したトラストストアファイルをクライアント側に転送
scpコマンド、WinSCPなどのユーティリティを用いてクライアント(Liberty)稼働サーバにトラストストアを転送しておきます。

後続のStep4.で、転送したトラストファイルのフルパスをLiberty server.xml に指定します。


### Step3. Db2サーバの構成

 * 実行サーバ：Db2サーバ
 * 実行ユーザ：Db2インスタンスオーナ
    * ※ /etc/services 編集作業は root ユーザで実行ナ

#### 3-1. 鍵ストアファイル、スタッシュファイルの配置

鍵ストアファイルを、Step1で鍵ストア作成等の作業を行ったワーク用ディレクトリ(/database/mytmp/ssl)から、実際に鍵ストアファイル/スタッシュファイルを配置するためのディレクトリにコピーします。

 * コピー先：$HOME/sqllib/security/keystore

```
$ cp -p /database/mytmp/ssl/mydbserver.kdb $HOME/sqllib/security/keystore/
$ cp -p /database/mytmp/ssl/mydbserver.sth $HOME/sqllib/security/keystore/
$ ls -ltr
total 12
-rw------- 1 db2inst1 db2iadm1  193 Aug 17 07:19 mydbserver.sth
-rw------- 1 db2inst1 db2iadm1 5088 Aug 17 07:20 mydbserver.kdb
```

#### 3-2. Db2 パラメータ設定
レジストリー変数とデータベースマネージャー構成パラメータを設定します。

##### レジストリー変数
Db2が受け付ける通信プロトコルを設定します。
(V11.5デフォルトではTCPIPのみ設定されています。SSLは明示的に追加設定する)

```
$ db2set db2comm=ssl,tcpip
```

##### データベースマネージャー構成パラメータ ① ssl_svr_keydb
鍵ストアファイルの絶対パス名を指定します。

```
$ db2 update dbm cfg using SSL_SVR_KEYDB $HOME/sqllib/security/keystore/mydbserver.kdb
DB20000I  The UPDATE DATABASE MANAGER CONFIGURATION command completed
successfully.
```

##### データベースマネージャー構成パラメータ ② ssl_svr_stash
スタッシュファイルの絶対パス名を指定します。
(スタッシュファイルは、鍵ストアファイル作成時に -stash オプションを付与することで同時作成されます)

```
$ db2 update dbm cfg using SSL_SVR_STASH $HOME/sqllib/security/keystore/mydbserver.sth
DB20000I  The UPDATE DATABASE MANAGER CONFIGURATION command completed
successfully.
```


##### データベースマネージャー構成パラメータ ③ ssl_svr_label
Step1.で自己署名証明書を作成した際のコマンドオプションに指定したラベル(-label オプションに続いて入力した値）を指定します。

```
$ db2 update dbm cfg using SSL_SVR_LABEL myselfsigned
DB20000I  The UPDATE DATABASE MANAGER CONFIGURATION command completed
successfully.
```


##### データベースマネージャー構成パラメータ ④ ssl_versions
Db2 11.5.6 でサポートされるTLSバージョンはTLSv1.2までです。
TLSv1.2もデフォルトでは有効にならず、製品デフォルトの状態では TLSv1.0/v1.1が有効になるとマニュアルに記載されているので、ここで明示的に変更しておきます。

```
$ db2 update dbm cfg using SSL_VERSIONS TLSv12
DB20000I  The UPDATE DATABASE MANAGER CONFIGURATION command completed
successfully.
SQL1362W  One or more of the parameters submitted for immediate modification
were not changed dynamically. Client changes will not be effective until the
next time the application is started or the TERMINATE command has been issued.
Server changes will not be effective until the next DB2START command.
```

＊上のメッセージの通り、TLSv1.2通信を開始するためににはクライアント側からは接続を一度切断し、新規接続を作り直す必要があります。

##### データベースマネージャー構成パラメータ ⑤ ssl_svcename
Db2がTLS/SSL接続をListenするポートを設定します。
TLS/SSL用Listenポートは、ポート番号を直接記述してもいいですし、 /etc/services に記載されるサービス名を指定しても良いです。
(ポート番号を変えざるを得なくなった時にDb2構成に影響を与えない目的では、サービス名を指定するほうが望ましい)

```
$ db2 update dbm cfg using ssl_svcename db2inst1_ssl
DB20000I  The UPDATE DATABASE MANAGER CONFIGURATION command completed
successfully.
```

Tips：
DBM構成パラメータ SSL_SVCENAME に指定する サービス名が長すぎると文字数制限で update dbm cfg コマンドが失敗します。サービス名は14文字以内である必要があります。

```
$ db2 update dbm cfg using ssl_svcename db2c_db2inst1_ssl
SQL5132N  The configuration parameter "ssl_svcename" is null or too long.  The
maximum length is "14".
```


##### 補足： /etc/services ファイルの編集

デフォルトでTLS/SSL用通信ポートが/etc/servicesファイルに定義されていない場合、/etc/services を編集してTLS/SSL通信用ポートを設定します。
/etc/services ファイルの編集は root ユーザで行います。

/etc/services サンプル(抜粋)：

```
db2c_db2inst1   50000/tcp
db2inst1_ssl    50001/tcp　　← TLS/SSL通信用ポートを追記
```


##### Db2 インスタンス再起動

上記設定変更を反映するため、インスタンスの再起動を行います。

```
$ db2stop
$ db2start
```



### Step4. Db2クライアント(Liberty)の構成

 * 実行サーバ：Db2クライアント(Libertyサーバ)
 * 実行ユーザ：Liberty実行ユーザ
      * ※ /etc/hosts 編集作業は root ユーザで実行

#### 4-1. トラストストア・ファイルの配置

Step2. で作成して Liberty稼働サーバに転送しておいたトラストストア「myTrustStore.jks」のロケーションを確認してください。
今回は、下記ロケーションに配置。
[Liberty_root]/usr/shared/resources/db2

* usr/shared/resources までは Liberty に存在するディレクトリ
* Db2との通信用に使われるJDBCドライバーファイル、トラストファイルの置き場所として db2 サブディレクトリを手動作成



#### 4-2. JDBC データソース関連プロパティの構成

WebSphere Liberty の構成情報は、server.xml ファイルに記述します。

最も単純な構成では下記のロケーションに server.xml ファイルを作成/編集します。
[Liberty_root]/usr/servers/liberty1

TLS/SSL通信を行うために設定する必要のなるJDBCドライバープロパティは下記の通りです。

* serverName="DBサーバのホスト名"
* portNumber="DBサーバのTLS/SSL通信用ポート番号"
* databaseName="接続先DB名"
* user="DB接続ユーザID"
* password="DB接続ユーザのパスワード"
* sslConnection="true"
* sslTrustStoreLocation="トラストストアのファイルパス"
* sslTrustStorePassword="トラストストアのパスワード"
* __sslClientHostnameValidation="BASIC"__

:::note warn
sslClientHostnameValidationプロパティの値「BASIC」はすべて大文字です。
それ以外の記述では認識されず、hostname validation が有効化されません。
:::

<br>
server.xml記述例(抜粋)：

```xml
<!-- Enable features -->
<featureManager>
 <feature>webProfile-8.0</feature>
    ---中略---
<library id="DB2JCC4Lib">
  <fileset dir="${shared.resource.dir}/db2" includes="db2jcc4.jar"/>
</library>
<dataSource id="Db2-DataSource" jndiName="jdbc/sample">
 <jdbcDriver libraryRef="DB2JCC4"/>
 <properties.db2.jcc databaseName="SAMPLE" driverType="4" serverName="db2.myssl-test.com" portNumber="50001" user="user01" password="password" sslConnection="true" sslTrustStoreLocation="${shared.resource.dir}/db2/myTrustStore.jks" sslClientHostnameValidation="BASIC" sslTrustStorePassword="password"/>
</dataSource>
```


#### 4-3. /etc/hosts ファイルの編集
上の server.xml に記述したホスト名が名前解決できる状態であるか確認します。
今回は /etc/hosts ファイルに下記のエントリを記載します。
/etc/hosts ファイル編集は root ユーザで実行します。

```
192.168.0.2       db2.myssl-test.com
```

以上で、必要な構成は完了です。


## 参考資料
[Db2 マニュアル [IBM Data Server Driver for JDBC and SQLJ enhancements for Db2 V11.5 Mod Pack 6]](
https://www.ibm.com/docs/en/db2/11.5?topic=enhancements-jdbc-sqlj-driver)
[Db2 マニュアル [Hostname validation for Db2 11.5.6 clients]](
https://www.ibm.com/docs/en/db2/11.5?topic=transit-hostname-validation)
[Db2 マニュアル [Common IBM Data Server Driver for JDBC and SQLJ properties for all supported database products]](
https://www.ibm.com/docs/en/db2/11.5?topic=pdsdjs-common-data-server-driver-jdbc-sqlj-properties-all-database-products)
[脆弱性事例に学ぶセキュアコーディング「SSL/TLS証明書検証」編 (KOF2014)](
https://www.slideshare.net/jpcert_securecoding/certificate-verification-kof2014)