このトラブルシューティングについて
OpenSSLで作成した自己署名証明書を使用し、vsftpdでFTPSをテストしていてハマったので。結果からいうと暗号化スイートをopenssl、vsftpdの設定ファイルで設定する必要がありました。ついでにhttpdの設定もなぜか触ることに。
事の発端
まず初めに疑ったのは、Implicit モードがデフォルトで使用している990 番ポートをlftpのプログラムかftpコマンドが使用しようとしているのではないか?ということ。Expicitモードで21番ポートをファイアウォールで開けてても、そこではなく990番が空いていないためコントロールコネクションが確立していないのかもしれないと思いました。
We’ll need to open port 21 for FTPS login and commands. Port 990 is default for TLS, but WordPress appears to use port 21 exclusively ......
「TLSで990がデフォルトになっていてもFTPでのログインとコマンドは21番ポートを使用する必要がありますが、(例えば)WordPressは21番をブロックしてしまうようで......」
参考サイト
つまり、疑問点はImplicit(990ポートが使用される)とExplicit モードのどちらのモードをftp-userユーザーの接続に於いてどのように選択しているか、ということです。
環境
Amazon Linux 2023
vsftpd: version 3.0.5
httpd 2.4.58
openssl 3.0.8
確認すること
【確認その1】sudo firewall-cmd --permanent --list-allでサーバーの以下のポートへのインバウンドアクセスが許可されていることを確認する。
下記の実行結果は、ufw
(Not firewalld
in RHEL)での設定結果。
To Action From
-- ------ ----
21/tcp ALLOW Anywhere
65000:65535/tcp ALLOW Anywhere
とくに65000〜65535はパッシブモードで接続するときのポート番号の値の範囲。
以下の出力は必須かどうかわからない(手元の環境では出力されなかった)。
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
上記をチェックした上で、lftpコマンドをデバッグオプション (-dフラグ)を付けて実行すると、パスワードを入力した直後に下記のエラーメッセージが。
An unexpected TLS packet was received
このようなエラーメッセージの場合、以下の投稿のトラブルシューティングにしたがい、エラーが解消できるか試してみてください。
【確認その2】(任意)990番ポートを開放してnetstatでLISTENできているか確認する
もしこれでFTPSがいけたら、Implicitモードが使用されていることがわかる。
Explicitモードはコントロールコネクションとデータコネクションに分かれているから接続できないのでは?という疑問
追記 (2025/5/13):
本節タイトルに誤謬があります。コネクションが2つに分かれているのはFTP全般でいえることです。Explicitモードにおいてネゴシエーションが平文と暗号化の2つに分かれているということが言いたかったです。
lftpは厳密にはFTPクライアントではないらしいです。
Despite its name lftp is not an ftp "client". It is a tool that allows you to run ftp, ftps or other commands
「lftpという名前ですがこれはFTPクライアントではありません。ftpやftps、その他のコマンドを実行できるようにするツールです。」
It is important to note that lftp is a session tool. It is not staying logged in as you might think but rather issues new commands to the remote using the credentials you supplied when you started lftp. This caused me grief early on because I thought it logged in successfully when I started lftp and it was only after viewing debug/verbose output that I realized it was attempting and failing to login when I ran individual commands within it.
「重要なのはlftpはセッションツールであることに注意が必要だということです。誰もが思っているようにログインしたままにはならないのです。
lftpを起動するときに入力する認証情報で、リモートマシンにたいして新規にコマンドを発行しているだけです。
私も最初これには頭を抱えました。lftpを起動した時点でログインが成功していると思っていたのですが、デバッグ/バーボスの出力を表示したあとで、コマンドを個別に実行する時にログインしようとして失敗したことに気づいただけでした。」
FTP with the SSL modules turned on ( LinuxQuestions.org )
この人が書いているログインというのは、FTPによるリモートマシンへのログインのことだと思われます。つまりコントロールコネクションがlftpによって行われるわけではなく、あくまでローカルマシンがftpコマンドを実行している。
私がlftpを使っていたのは、FTPで転送する宛先のグローバルIPアドレスを指定するためでした。その用途ではうまくいきました。暗号化されていないFTPはファイルのアップロードもlftpでできていました。
が、FTPSのExplicitモードでlftpを使用しようとすると、セッションがデータコネクションには自動では切り替わらないのかもしれません。おそらくコントロールコネクションとデータコネクションが別のセッションであり、lftp側でサーバーの設定にしたがってセッションを発行している。つまり、FTPのAUTHコマンドでコネクションを初期化したあと、別のセッションでlsとかcdでリモートのディレクトリやファイルを参照する時に、個別のコマンドレベルで失敗しているのでは。
トラブルシューティング
ケース 1:
lftp等でvsftpdサーバーにFTP接続しようとした場合に“An unexpected tls packet is recieved” というメッセージが表示される。
- Explicitモードのvsftpdあるいはサーバーの設定を見直す
- opensslを見直す
- ftpコマンドの設定を見直す
ftpコマンドについては、このサイト の質問の回答を参考にすると、ImplicitモードしかサポートしていないFTPクライアントはほとんどなく、ftpコマンドの設定如何(いかん)で状況が変わるとはあまり思えないため、今回は見直さないことにします。
Step1. opensslで稼働確認
Explicit modeで確認する場合、以下のopensslコマンドを-starttlsフラグを付けて実行。(Implicit modeの場合は openssl s_client -connect localhost:990を実行)この方法は後述の参考記事に記載されています。
openssl s_client -connect localhost:21 -starttls ftp
実行結果 (概略)
「Verify return code: 18 (self-signed certificate)」や「220 (vsFTPd 3.0.5)」、
Post-Handshake New Session Ticket arrived: SSL-Session: Protocol : TLSv1.3 Cipher : TLS_AES_256_GCM_SHA384 Session-ID: 00F53A0C46727.....
のように表示される。SSL-Session
のブロックが2か所出力され、それぞれ別のSession-ID
が表示されている。
上記の詳細の行につづき、以下のようにサーバー証明書の詳細などが出力される。気になったのは、その下のSSL handshake...
以降の部分。(一部を抜粋)
CONNECTED(00000003) # 接続されている
...
......
......
---
Server certificate #サーバー証明書
-----BEGIN CERTIFICATE-----
MIIDMjCCA......
............
cAJYhI
-----END CERTIFICATE-----
...
......
---
SSL handshake has read 1622 bytes and written 743 bytes
Verification error: self-signed certificate # lftpの設定ファイルで、 ssl:verify-certificateの値をfalse(証明書を検証しない)にすればOK (自己署名証明書の場合)
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 # TLSのバージョン、Cipher (暗号化スイート)
Server public key is 2048 bit # 公開鍵のサイズ
Secure Renegotiation IS NOT supported # 失敗 (Cipher (暗号化スイート) がサポートされていない
上の抜粋の一番下の方にSecure Renegotiation IS NOT supported
と表示されており、これは暗号化スイートのTLS_AES_256_GCM_SHA384
がサポートされていない可能性がある (下記の投稿を参照)ため、そのあたりを確認する。
Step2. 暗号化スイートがサポートされているか確認する
以下の記事を参考にすると、openssl s_client
で確認した結果、Secure Renegotiation IS NOT supported
となっている場合、指定した暗号化スイートはサポートされていない可能性がある。
openssl s_client
を実行した後、Cipherの行にはTLS_AES_128_GCM_SHA256
と出力されているが、その2行下くらいでSecure Renegotiation IS NOT supported
と表示されている。
実行結果 (概略)
再度確認のため、以下のopenssl s_client
コマンドを実行。
# 別の暗号化スイートを指定して確認。
openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES256-GCM-SHA384
CONNECTED(00000003)
...
......
---
SSL handshake has read 5489 bytes and written 318 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256 # TLSのバージョン (v1.3)、暗号化スイート
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
# サーバーが特定のTLSバージョンで、指定した暗号化スイートをサポートしているか確認する
sudo openssl s_client -connect example.com:443 -cipher TLS_AES_256_GCM_SHA384 -quiet -no_tls1_3
Call to SSL_CONF_cmd(-cipher, TLS_AES_256_GCM_SHA384) failed # サポートしていない
また、Copilot (MicrosoftのChatgpt) にきいてみると、vsftpdは TLS v1.2を標準でサポートしているらしい。
しかしTLS_AES_128_GCM_SHA256
はTLS v1.3に固有のものらしいので、vsftpdがこの暗号化スイートをサポートしていないのかもしれない。(おそらく AES_256_GCM_SHA384
も同じくTLS v1.3に固有のものと考えられるため。)
Step3. TLS v1.2でサポートされている暗号化スイートに変えてみる
TLSのバージョンごとに、サポートされている暗号化スイートの一覧をopenssl ciphers
コマンドで調べることができるようです。
4行目までは、TLSv1.3のみでサポートされているものが出てきている。上記の説明で出てきたTLS_AES_256_GCM_SHA384
も、一番上の行に出ています。これらはvsftpdでサポートされていない可能性があるので、今回はそれ以外の暗号化スイートを使うように修正します。
# サポートされている暗号化スイートの一覧を取得(一部を抜粋)
openssl ciphers -v
TLS_AES_256_GCM_SHA384 TLSv1.3 Kx=any Au=any Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any Au=any Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESGCM(128) Mac=AEAD
TLS_AES_128_CCM_SHA256 TLSv1.3 Kx=any Au=any Enc=AESCCM(128) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
AES256-GCM-SHA384 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(256) Mac=AEAD
AES256-CCM TLSv1.2 Kx=RSA Au=RSA Enc=AESCCM(256) Mac=AEAD
AES128-GCM-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AESGCM(128) Mac=AEAD
AES128-CCM TLSv1.2 Kx=RSA Au=RSA Enc=AESCCM(128) Mac=AEAD
AES256-SHA256 TLSv1.2 Kx=RSA Au=RSA Enc=AES(256) Mac=SHA256
DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH Au=RSA Enc=AESGCM(256) Mac=AEAD
DHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=DH Au=RSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
DHE-RSA-AES256-CCM TLSv1.2 Kx=DH Au=RSA Enc=AESCCM(256) Mac=AEAD
DHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=DH Au=RSA Enc=AESGCM(128) Mac=AEAD
DHE-RSA-AES128-CCM TLSv1.2 Kx=DH Au=RSA Enc=AESCCM(128) Mac=AEAD
......
..........
vsftpd.confの設定箇所
vsftpdの設定ファイルに、暗号化スイート (Cipher) の設定を行います。
openssl genpkey -algorithm ec
で秘密鍵を作ったので、ECDSA
( Elliptic Curve Digital Signature Algorithm)を指定していますが、-algorithm rsa
でカギを作成した場合は、RSAに対応した暗号化スイートを指定する必要があることに注意。
また、:
(コロン)でつなぐことで複数の暗号化スイートを指定することも可能ですが、下記の例では簡単化のために一つor2つだけ指定しています。
# ssl_ciphersを指定。
例1) ECDSAでカギを生成した場合
# AES256 & SHA384
ssl_ciphers=ECDHE-ECDSA-AES256-GCM-SHA384
例2) ECDSAでカギを生成した場合(AES128 & SHA256)
# AES128 & SHA256
ssl_ciphers=ECDHE-ECDSA-AES128-GCM-SHA256
例3) RSAの鍵交換でカギを生成した場合
# AES256 & SHA384
ssl_ciphers=DHE-RSA-AES256-GCM-SHA384
今回実際に設定したvsftpd.confは以下です。
# ECDHの鍵交換でカギを生成した場合 (OpenSSL方式でして)
ssl_ciphers=kEECDH+AESGCM+AES128:kEECDH+AESGCM:kEECDH+AES128:kEECDH+AES:!aNULL:!eNULL:!LOW:!EXP
# 秘密鍵と証明書のパスを指定。
rsa_cert_file=/path/to/your/certificate.pem
rsa_private_key_file=/path/to/your/ecdsa_private.key
# 使用プロトコル
ssl_sslv2=NO
ssl_sslv3=NO
ssl_tlsv1=NO
ssl_tlsv11=NO
ssl_tlsv12=YES
下の方で秘密鍵と証明書のパスを指定していますが、Copilotによるとrsa_
というディレクティブ名でもECDSA
をRSAのかわりに指定してもOKなようです。openssl genpkey -algorithm ec
のようにアルゴリズム(左記のコマンドの場合はECDSA
)を指定して秘密鍵を作成していてもRSA
以外の場合でも、rsa_private_key_file
にそのパスを設定すれば大丈夫です。
openssl.cnfの設定箇所
opensslの設定ファイルにTLS v1.2を使用するように指定する。
MaxProtocol = TLSv1.2
その他
- vsftpd が GnuTLSでコンパイルされている場合、OpenSSL方式の暗号化スイート規約を優先しない可能性がある。そのためもしGnuTLSを使用している場合、OpenSSLベースのvsftpdパッケージへの切り替えを検討する。
# GnuTLSの情報が表示されるか確認
vsftpd -version
vsftpd: version 3.0.5 # とくになし
# libsslが表示されれば、GnuTLSを使用していない
ldd $(which vsftpd) | grep ssl
libssl.so.3 => /lib64/libssl.so.3 (0x00007f61c44f4000)
# vsftpdのパッケージを確認
rpm -qa | grep vsftpd
vsftpd-3.0.5-1.amzn2023.0.2.x86_64 # GnuTLSではない
Step4. httpdのSSL設定をおこなう
ssl.confの内容を自動で生成してくれるサイトを使用して、SSLCipherSuiteなどを設定する。以下のサイトは自分が使用するhttpdとopensslのバージョンを指定すると、設定値を自動生成してくれる。
# httpdのバージョン確認
yum list installed | grep httpd
httpd.x86_64 2.4.58-1.amzn2023 @amazonlinux
# opensslのバージョン確認
yum list installed | grep openssl
openssl.x86_64 1:3.0.8-1.amzn2023.0.10 @System
/* 許可する暗号化プロトコルを設定 */
#SSLProtocol all -SSLv3
SSLProtocol -all +TLSv1.2
/* 暗号化スイートを指定する */
#SSLCipherSuite PROFILE=SYSTEM
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305
/* ランクの高い暗号化スイートを優先するようサーバーに強制 */
SSLHonorCipherOrder on
確認
openssl s_client
コマンドでTLS接続の状態を確認します。
# TLSv1.2で接続の詳細を確認
sudo openssl s_client -connect localhost:443 -tls1_2 < /dev/null
CONNECTED(00000003)
...
......
..........
#
---
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384 # TLSv1.2がサポートしている暗号化スイートが使用されている
Server public key is 2048 bit
Secure Renegotiation IS supported # 安全な再ネゴシエーションがサポートされている
ケース 2:
lftpで ls
や cd
コマンドを打っても “Not connected” というメッセージが表示されるだけで、 pwd
コマンドを打つと何も結果が返ってこない。
lftpにログインすると、正常ならリモート側のカレントディレクトリに対してUNIX系のシェルと同じように ls
や cd
が実行できますが、ディレクトリのリストが返ってこないし、カレントディレクトリも移動しない状況の場合、以下を確認してください。
Step1. クライアント側のシステムログ
sudo journalctl -a --lines=all # すべての行を出力 (あるいは journalctl -af で最新のログだけを出力)
Step2. lftpをデバッグモードで起動
デバッグモードで ls
を実行し、表示されるメッセージを確認します。
lftp -u hoge -e "debug 12" ftp://example.com
~> ls
Step3. Step2 で以下のメッセージが出る場合
GNUTLS: Received record packet of unknown type 53
や gnutls_record_recv: An unexpected TLS packet was received.
というメッセージが出ることがあります。
lftp -u hoge -e "debug 12" ftp://example.com
Password:
~> ls
......
..........
---- Connecting to xx.xx.xxx.xxx (xx.xx.xxx.xxx) port 21
<--- 220 (vsFTPd 3.0.5)
......
..........
<--- 200 Always in UTF8 mode.
---> USER ftp-user
<--- 331 Please specify the password.
---> PASS XXXX
GNUTLS: Received record packet of unknown type 53
**** gnutls_record_recv: An unexpected TLS packet was received. # エラーが発生
---- Closing control socket
ls: Fatal error: gnutls_record_recv: An unexpected TLS packet was received. # エラーが発生
上記のようなエラーメッセージの場合、以下の投稿のトラブルシューティングにしたがい、エラーが解消できるか試してみてください。
Step 1. のシステムログに異常がない場合で、Step 3. で書いているエラーではなく "Not connected”が直らない場合、下記の投稿で対処法を書いています。手順に従い、宛先のグローバルIPアドレスを見直し、TLSプロトコルバージョンの優先順位の設定を行ってください。