Skip to content

FTPS Connection using BouncyCastle

Robin Rodricks edited this page Oct 17, 2026 · 12 revisions

Overview

FluentFTP normally uses .NET SslStream/SChannel for secure encrypted connections. This combination unfortunately does not support TLS session resumption.

Therefore we integrated the powerful BouncyCastle networking library, a .NET library ported from Java, which contains a more flexible implementation of TLS, as an alternative secure and encrypted transport medium. It does NOT currently support TLS 1.3.

We developed a FluentFTP adapter wrapper for BouncyCastle called FluentFTP.BouncyCastle, which works on Windows, Unix and Mac OSX.

diagram

Packaging

It is published as an independent Nuget FluentFTP.BouncyCastle, and integrates with the FluentFTP client object.

diagram

Features

Benefits

  • Allows for TLS session resumption
  • Allows an FTPS data connection to explicitly resume the TLS 1.2 session established by the control connection
  • Useful for 3D printers and other hardware FTP servers that reject data connections unless the TLS session is reused
  • Useful to fix this error 522 SSL connection failed: session reuse required.

Known limitations

  • Only supports TLS 1.2
  • Does not support client certificates.
  • If client.Config.ClientCertificates is filled, a warning is logged and the handshake proceeds without one.
  • FTP connection handshake is synchronous, even when used through AsyncFtpClient (we do not pass a cancellation token to the handshake)
  • Cancellation tokens are not supported (Bouncy Castle's stream does not observe cancellation tokens on reads and writes)

Settings

All these settings are properties of the FtpClient object.

  • Config.CustomStream - Set this option to typeof(BouncyCastleFtpStream) to activate the BouncyCastle package in your FTP client.

  • Config.CustomStreamConfig - Set this to an instance of BouncyCastleFtpConfig to configure the GnuTLS connection settings.

BouncyCastle Settings

All these settings are properties of the BouncyCastleFtpConfig object.

  • RequireSessionResumption - Indicating whether a data connection must fail when the server does not resume the control connection's TLS session.
  • AllowLegacyResumption - Indicating whether TLS 1.2 sessions without RFC 7627 Extended Master Secret may be resumed. Keep disabled unless a known legacy server requires it.
  • Diagnostic - Optional callback that receives the non-sensitive connection diagnostics the stream also writes to FluentFTP's log at the Verbose level.

How do I connect with BouncyCastle stream?

using FluentFTP;
using FluentFTP.BouncyCastle;

var client = new AsyncFtpClient(host, username, password, 990);
client.Config.EncryptionMode = FtpEncryptionMode.Implicit;
client.Config.CustomStream = typeof(BouncyCastleFtpStream);
client.Config.CustomStreamConfig = new BouncyCastleFtpConfig {
    RequireSessionResumption = true,
};

Technical details by Claude

Warning: This section was generated by Claude and may be difficult to read.

Certificate validation

The adapter checks certificate chain trust, server-authentication usage, and the requested server's identity before calling FluentFTP's validation callback. DNS Subject Alternative Names (SANs) take precedence over the Common Name (CN). A DNS wildcard matches one whole label only. CN fallback uses an exact match when no DNS SAN is present; an IP address always requires a matching IP SAN. Internationalized hostnames are converted to ASCII for matching and SNI. SNI is sent for DNS hosts and omitted for IP addresses.

The adapter preserves Bouncy Castle's default cipher suites, whose AES support stops at AES-128, and adds the two forward-secret AES-256-GCM suites needed to reach servers restricted to AES-256 — ECDHE-RSA-AES256-GCM-SHA384 (the only suite vsftpd's default configuration accepts) and its ECDSA-certificate counterpart ECDHE-ECDSA-AES256-GCM-SHA384. It adds no AES-256 CBC or static-RSA suites. The original defaults include AES-128 static-RSA suites, which remain available for compatibility and lack forward secrecy.

This is a compatibility measure, not a hardening one. Whether the stock SslStream path would reach the same servers depends on the platform TLS stack and its configured cipher policy, which an administrator can change, so it is not a fixed comparison.

client.Config.ValidateCertificateRevocation controls online revocation checks (off by default, matching FluentFTP). When enabled, unavailable revocation information is reported as a validation error, just like a revoked certificate.

Validation failures are passed through FluentFTP's custom-stream callback and reject the connection by default. An explicit validation callback or ValidateAnyCertificate can override them. For self-signed devices, prefer a callback that checks a known certificate fingerprint; accepting any certificate disables server identity protection. FluentFTP's existing custom-stream API carries validation details in PolicyErrorMessage; its PolicyErrors enum does not distinguish chain failures from hostname failures.

A rejected certificate sends a bad_certificate TLS alert to the server. The certificate passed to the validation callback stays valid until the stream is disposed, as with SslStream; copy it if you need it for longer.

Failures and diagnostics

Handshake and certificate failures are raised as System.Security.Authentication.AuthenticationException, matching FluentFTP's default SslStream behaviour, so FluentFTP's own error handling and existing catch blocks keep working. The Bouncy Castle TlsException is attached as the inner exception and carries the TLS alert description.

Connection diagnostics (session offers, resumption results and certificate acceptance) are written to FluentFTP's log at the Verbose level with a BouncyCastle: prefix. BouncyCastleFtpConfig.Diagnostic optionally receives the same messages.

Legacy session resumption

Some legacy servers require session resumption without RFC 7627 Extended Master Secret. Bouncy Castle blocks that by default. Enable compatibility only for a known server that requires it:

client.Config.CustomStreamConfig = new BouncyCastleFtpConfig {
    RequireSessionResumption = true,
    AllowLegacyResumption = true,
};

Allowing legacy resumption reduces TLS protections and should not be enabled as a general fallback.

Tests

# Only needed if the .NET 7 runtime is absent.
$env:DOTNET_ROLL_FORWARD = 'Major'
dotnet test FluentFTP.Tests --filter FullyQualifiedName~Unit.BouncyCastle

The tests generate their own certificates, use custom chain trust without changing the operating system's trusted roots, and run loopback TLS, FTPS and CRL servers. No printer or external server is required.

Test results

The original adapter was developed against a Bambu Lab X1 Carbon printer. We tested real printer listing, upload, and download tests with both sync and async clients on Windows/.NET 8. ach client uploaded 32,769 bytes, downloaded them twice with an exact byte match, resumed five TLS data connections, and deleted its test file. Both negotiated ECDHE-RSA-AES256-GCM-SHA384.

The tests used ValidateAnyCertificate = true and AllowLegacyResumption = true, matching the existing printer connection settings; they do not establish acceptance under the default certificate policy.

Security notes by Claude

Warning: This section was generated by Claude and may be difficult to read. These notes cover the FluentFTP.BouncyCastle stream only. They do not apply to, FluentFTP's default SslStream.

Fixed security issues

  • High. The TLS target host was not used during certificate validation, so a trusted certificate for a different hostname satisfied the default policy. ServerCertificateValidation now checks DNS or IP identity independently of chain trust.
  • Medium. The chain was built without a server-authentication application policy, so trust alone did not enforce the certificate's intended usage. The server-authentication extended key usage is now required.
  • Medium. Revocation checking was always disabled, even when ValidateCertificateRevocation was enabled. The setting is now honoured, and missing revocation information fails closed when checking is enabled.
  • Low. An exception thrown while closing the TLS protocol during a failed initialization could replace the original failure. The original exception is now preserved.
  • Low. Handshake and certificate failures surfaced as Bouncy Castle TlsFatalAlert exceptions and sent an internal_error alert to the server. They are now raised as AuthenticationException, matching SslStream, and a rejected certificate sends bad_certificate.

Validation policy

  • Chain trust uses the operating system's trust store, with the certificates sent by the server as extra intermediates.
  • DNS SANs take precedence over the CN; a wildcard matches one label; an IP address host requires a matching IP SAN; IDN hosts are matched in ASCII.
  • Malformed SAN or subject data fails closed.
  • Online revocation checks follow client.Config.ValidateCertificateRevocation.
  • The validation result is passed to FluentFTP's callback, which can override it explicitly.

Verifying

$env:DOTNET_ROLL_FORWARD = 'Major'
dotnet test FluentFTP.Tests --filter FullyQualifiedName~Unit.BouncyCastle

The tests generate disposable certificates, use custom chain trust without altering the operating system's roots, and run loopback TLS, FTPS and CRL servers. They exercise TLS streams and FluentFTP login; they are not end-to-end file transfer tests.

Remaining limits

Cipher review date: 2026-09-13. The adapter preserves the original Bouncy Castle defaults, which stop at AES-128, and adds two forward-secret AES-256-GCM suites: ECDHE-RSA-AES256-GCM-SHA384 and ECDHE-ECDSA-AES256-GCM-SHA384. Both are a compatibility measure for servers restricted to AES-256, not a hardening change. Loopback tests negotiate each suite against a server offering only that suite, using an RSA or an EC server certificate respectively, and verify repeated data connection resumption plus an exact 32769-byte round trip on every handshake. Negative tests confirm the addition did not widen further: AES-256 CBC (ECDHE-RSA and ECDHE-ECDSA), DHE-RSA-AES256-GCM and the AES-256 static-RSA suites are all still refused. The original AES-128 static-RSA compatibility remains; this is not a policy that guarantees forward secrecy for every accepted connection.

Clone this wiki locally

⌂ 换网址