Note: This bug is displayed in read-only format because the product is no longer active in Red Hat Bugzilla.
RHEL Engineering is moving the tracking of its product development work on RHEL 6 through RHEL 9 to Red Hat Jira (issues.redhat.com). If you're a Red Hat customer, please continue to file support cases via the Red Hat customer portal. If you're not, please head to the "RHEL project" in Red Hat Jira and file new tickets here. Individual Bugzilla bugs in the statuses "NEW", "ASSIGNED", and "POST" are being migrated throughout September 2023. Bugs of Red Hat partners with an assigned Engineering Partner Manager (EPM) are migrated in late September as per pre-agreed dates. Bugs against components "kernel", "kernel-rt", and "kpatch" are only migrated if still in "NEW" or "ASSIGNED". If you cannot log in to RH Jira, please consult article #7032570. That failing, please send an e-mail to the RH Jira admins at rh-issues@redhat.com to troubleshoot your issue as a user management inquiry. The email creates a ServiceNow ticket with Red Hat. Individual Bugzilla bugs that are migrated will be moved to status "CLOSED", resolution "MIGRATED", and set with "MigratedToJIRA" in "Keywords". The link to the successor Jira issue will be found under "Links", have a little "two-footprint" icon next to it, and direct you to the "RHEL project" in Red Hat Jira (issue links are of type "https://issues.redhat.com/browse/RHEL-XXXX", where "X" is a digit). This same link will be available in a blue banner at the top of the page informing you that that bug has been migrated.

Bug 1552927

Summary: curl is unable to reuse FTP control connection TLS session for data connection
Product: Red Hat Enterprise Linux 7 Reporter: Chris Cheney <ccheney>
Component: nssAssignee: nss-nspr-maint <nss-nspr-maint>
Status: CLOSED WONTFIX QA Contact: BaseOS QE Security Team <qe-baseos-security>
Severity: high Docs Contact:
Priority: high    
Version: 7.4CC: ccheney, kdudka, kwalker, mgrepl, nmavrogi, sbroz
Target Milestone: rcKeywords: Reopened
Target Release: ---   
Hardware: All   
OS: All   
Whiteboard:
Fixed In Version: Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2018-10-02 16:14:48 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On:    
Bug Blocks: 1551061    

Description Chris Cheney 2018-03-07 22:58:01 UTC
Description of problem:

curl compiled with NSS (RHEL 6.9/7.4) can't connect to ftp ssl site. It works fine with both RHEL 5.11 and Fedora 27 which use OpenSSL instead.


Version-Release number of selected component (if applicable):

curl-7.29.0-42.el7_4.1.x86_64

How reproducible:

Every time


Steps to Reproduce:

$ curl -v --ftp-ssl -k ftp://hostname:port/ --user username:password


Actual results:

$ curl -v --ftp-ssl -k ftp://hostname:port/ --user username:password
* About to connect() to hostname port port (#0)
*   Trying ipaddress...
* Connected to hostname (ipaddress) port port (#0)
< 220-FileZilla Server 0.9.55 beta
< 220-written by Tim Kosse (tim.kosse)
< 220 Please visit https://filezilla-project.org/
> AUTH SSL
< 234 Using authentication type TLS
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
* 	subject: E=email@address,OU=MTR,O=MTR,L=Hong Kong,ST=Hong Kong,C=86,CN=hostname
* 	start date: Mar 05 07:33:50 2018 GMT
* 	expire date: Mar 05 07:33:50 2019 GMT
* 	common name: hostname
* 	issuer: E=email@address,OU=MTR,O=MTR,L=Hong Kong,ST=Hong Kong,C=86,CN=hostname
> USER username
< 331 Password required for username
> PASS password
< 230 Logged on
> PBSZ 0
< 200 PBSZ=0
> PROT P
< 200 Protection level set to P
> PWD
< 257 "/" is current directory.
* Entry path is '/'
> EPSV
* Connect data stream passively
* ftp_perform ends with SECONDARY: 0
< 229 Entering Extended Passive Mode (|||8020|)
*   Trying ipaddress...
* Connecting to hostname (ipaddress) port port
* Connected to hostname (ipaddress) port port (#0)
> TYPE A
< 200 Type set to A
> LIST
< 150 Opening data channel for directory listing of "/"
* Maxdownload = -1
* Doing the SSL/TLS handshake on the data stream
* skipping SSL peer certificate verification
* NSS error -5938 (PR_END_OF_FILE_ERROR)
* Encountered end of file
* Failure sending ABOR command: SSL connect error
* Closing connection 0
curl: (35) Encountered end of file


Expected results:

It should work instead of failing in NSS.

Comment 6 Stepan Broz 2018-03-21 14:37:09 UTC
The root cause of the "NSS error -5938 (PR_END_OF_FILE_ERROR)" is clear. The FTP server has closed the connection abruptly.

However, TLS session resumption should be enabled for NSS in curl and it does not work.

I have set up a simple reproducer using vsftpd that fails for that reason (note: it closes the connection properly, there will not be the -5938 NSS error)

This is the /etc/vsftpd/vsftpd.conf I have used:

anonymous_enable=YES
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen=NO
listen_ipv6=YES
pam_service_name=vsftpd
userlist_enable=YES
tcp_wrappers=YES
rsa_cert_file=/etc/ssl/private/192.168.122.91.cert.pem
rsa_private_key_file=/etc/ssl/private/192.168.122.91.key.pem
ssl_enable=YES
ssl_tlsv1=NO
ssl_sslv2=NO
ssl_sslv3=NO
ssl_tlsv1_1=NO
ssl_tlsv1_2=YES
force_local_data_ssl=YES
force_local_logins_ssl=YES
require_ssl_reuse=YES
ssl_ciphers=HIGH

- You need a trusted certificate (and its private key) for the test.
- Mind the "require_ssl_reuse=YES" option. This explicitly requires a reuse from the command channel on the data channel.

When curl is used to connect to that server, it will not reuse. Verbose output follows:

 curl -v --user foobar:<left-out> --ftp-ssl ftp://192.168.122.91/
* About to connect() to 192.168.122.91 port 21 (#0)
*   Trying 192.168.122.91...
* Connected to 192.168.122.91 (192.168.122.91) port 21 (#0)
< 220 (vsFTPd 3.0.2)
> AUTH SSL
< 234 Proceed with negotiation.
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* NSS: client certificate not found (nickname not specified)
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
* 	subject: CN=192.168.122.91,O=Example Inc,ST=Czechia,C=CZ
* 	start date: bře 12 12:29:35 2018 GMT
* 	expire date: bře 22 12:29:35 2019 GMT
* 	common name: 192.168.122.91
* 	issuer: CN=Example Inc Intermediate CA,OU=Example Inc Certificate Authority,O=Example Inc,ST=Czechia,C=CZ
> USER foobar
< 331 Please specify the password.
> PASS <left-out>
< 230 Login successful.
> PBSZ 0
< 200 PBSZ set to 0.
> PROT P
< 200 PROT now Private.
> PWD
< 257 "/home/foobar"
* Entry path is '/home/foobar'
> EPSV
* Connect data stream passively
* ftp_perform ends with SECONDARY: 0
< 229 Entering Extended Passive Mode (|||26674|).
*   Trying 192.168.122.91...
* Connecting to 192.168.122.91 (192.168.122.91) port 26674
* Connected to 192.168.122.91 (192.168.122.91) port 21 (#0)
> TYPE A
< 200 Switching to ASCII mode.
> LIST
< 150 Here comes the directory listing.
* Maxdownload = -1
* Doing the SSL/TLS handshake on the data stream
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* NSS: client certificate not found (nickname not specified)
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
* 	subject: CN=192.168.122.91,O=Example Inc,ST=Czechia,C=CZ
* 	start date: bře 12 12:29:35 2018 GMT
* 	expire date: bře 22 12:29:35 2019 GMT
* 	common name: 192.168.122.91
* 	issuer: CN=Example Inc Intermediate CA,OU=Example Inc Certificate Authority,O=Example Inc,ST=Czechia,C=CZ
* Remembering we are in dir ""
< 522 SSL connection failed; session reuse required: see require_ssl_reuse option in vsftpd.conf man page
* server did not report OK, got 522
* Connection #0 to host 192.168.122.91 left intact

After a brief discussion with Kamil, I believe the problem lies in the interface between curl and NSS, and in NSS itself:

The session cache lookup is performed in ssl_LookupSID(), nss/lib/ssl/sslnonce.c:249

249 sslSessionID *
250 ssl_LookupSID(const PRIPv6Addr *addr, PRUint16 port, const char *peerID,
251               const char *urlSvrName)
252 {
...
268         if (sid->expirationTime < now) {
269             /*
270             ** This session-id timed out.
...
279         } else if (!memcmp(&sid->addr, addr, sizeof(PRIPv6Addr)) && /* server IP addr matches */
280                    (sid->port == port) &&                           /* server port matches */
281                    /* proxy (peerID) matches */
282                    (((peerID == NULL) && (sid->peerID == NULL)) ||
283                     ((peerID != NULL) && (sid->peerID != NULL) &&
284                      PORT_Strcmp(sid->peerID, peerID) == 0)) &&
285                    /* is cacheable */
286                    (sid->u.ssl3.keys.resumable) &&
287                    /* server hostname matches. */
288                    (sid->urlSvrName != NULL) &&
289                    (0 == PORT_Strcmp(urlSvrName, sid->urlSvrName))) {
290             /* Hit */
...

To have a cache hit, you need to match (among other things) also the (destination?) port number. However, this limitation may not matter for HTTPS, but it does for FTPS where the data channel will be opened on different port. The TLS spec (RFC 5246) does not impose such limitation, its a choice of NSS.

Having said that, it means that TLS session resumption is currently not possible for FTP using the NSS library, it seems.

On the other hand, curl does not pass the port number to the NSS library properly:

gdb --args curl --user foobar:<left-out> --ftp-ssl ftp://192.168.122.91/
...
(gdb) break ssl_LookupSID
(gdb) run
Breakpoint 1, ssl_LookupSID (addr=addr@entry=0x702e48, port=59884, peerID=0x6edf40 "192.168.122.91", urlSvrName=0x6df050 "192.168.122.91") at sslnonce.c:252
252	{
(gdb) info os sockets
...
192.168.122.91 35490      192.168.122.91 60649      ESTABLISHED root       INET       STREAM     
192.168.122.91 57906      192.168.122.91 21         ESTABLISHED root       INET       STREAM     
...

Those are related connection, nothing on port=59884 (where does it come from?)

Comment 7 Kamil Dudka 2018-03-21 17:27:09 UTC
(In reply to Stepan Broz from comment #6)
> To have a cache hit, you need to match (among other things) also the
> (destination?) port number. However, this limitation may not matter for
> HTTPS, but it does for FTPS where the data channel will be opened on
> different port. The TLS spec (RFC 5246) does not impose such limitation, its
> a choice of NSS.
> 
> Having said that, it means that TLS session resumption is currently not
> possible for FTP using the NSS library, it seems.

Yes, that seems to be the underlying problem.  Thank you for finding it out!

> On the other hand, curl does not pass the port number to the NSS library
> properly:

As far as I can tell, libcurl does not pass any port number(s) to NSS.

> gdb --args curl --user foobar:<left-out> --ftp-ssl ftp://192.168.122.91/
> ...
> (gdb) break ssl_LookupSID
> (gdb) run
> Breakpoint 1, ssl_LookupSID (addr=addr@entry=0x702e48, port=59884,
> peerID=0x6edf40 "192.168.122.91", urlSvrName=0x6df050 "192.168.122.91") at
> sslnonce.c:252
> 252	{
> (gdb) info os sockets
> ...
> 192.168.122.91 35490      192.168.122.91 60649      ESTABLISHED root      
> INET       STREAM     
> 192.168.122.91 57906      192.168.122.91 21         ESTABLISHED root      
> INET       STREAM     
> ...
> 
> Those are related connection, nothing on port=59884 (where does it come
> from?)

NSS obtains the port number from OS in ssl_GetPeerInfo() via getpeername().

Comment 8 Kamil Dudka 2018-03-29 16:05:19 UTC
As far as I can tell, this bug cannot be fixed in libcurl without implementing the needed functionality (reusing TLS session across connections with different port numbers) in nss first.  Therefore I am switching the component to nss.

Comment 12 Nikos Mavrogiannopoulos 2018-10-02 16:15:19 UTC
Please do not re-open; contact me if you have any questions.