Gumdrop

Gumdrop Security

This document covers security features available across all Gumdrop servers, including identity and access management, rate limiting, and transport security.

HTTP services add default security headers (X-Frame-Options, X-Content-Type-Options) to responses unless the application sets them. See HTTP Configuration for the add-security-headers property.

Contents

Transport Layer Security

All Gumdrop servers support TLS for encrypted connections. For TCP-based protocols (HTTP/1.x, HTTP/2, SMTP, IMAP, POP3, FTP), Gumdrop uses the JVM's built-in SSLEngine and standard Java keystores (PKCS#12, JKS). QUIC/HTTP/3 uses a separate TLS stack (BoringSSL via quiche) with different certificate requirements, described below.

TLS Certificates for Local Development

The HTTPS listener requires a PKCS#12 keystore and the HTTP/3 (QUIC) listener requires PEM certificate and key files (see QUIC / HTTP/3 Certificate Requirements for details on why these differ). These are not checked in to the repository; you must generate them before running the server.

The easiest way for local development is mkcert, which creates certificates trusted by your browser:

# One-time: install the local CA into your system trust store
mkcert -install

# Generate certificate and key for localhost into etc/
mkcert -cert-file etc/localhost+2.pem -key-file etc/localhost+2-key.pem \
    localhost 127.0.0.1 ::1

This produces etc/localhost+2.pem and etc/localhost+2-key.pem. Then generate a PKCS#12 keystore from the same certificate for the HTTPS listener:

openssl pkcs12 -export -in etc/localhost+2.pem -inkey etc/localhost+2-key.pem \
    -out etc/localhost+2.p12 -name localhost -passout pass:changeit

The configuration files in etc/ reference these by filename (e.g. localhost+2.p12); they are not checked in to the repository.

TCP/TLS Configuration

Implicit TLS

Connection is encrypted from the start (e.g., SMTPS port 465, IMAPS port 993):

<service id="imaps" class="org.bluezoo.gumdrop.imap.DefaultIMAPService">
    <listener class="org.bluezoo.gumdrop.imap.IMAPListener">
        <property name="port">993</property>
        <property name="secure">true</property>
        <property name="keystore-file">keystore.p12</property>
        <property name="keystore-pass">secret</property>
    </listener>
</service>

STARTTLS

Connection starts unencrypted and upgrades to TLS on client request:

<service id="smtp" class="org.bluezoo.gumdrop.smtp.SimpleRelayService">
    <listener class="org.bluezoo.gumdrop.smtp.SMTPListener">
        <property name="port">25</property>
        <property name="keystore-file">keystore.p12</property>
        <property name="keystore-pass">secret</property>
        <!-- Advertises STARTTLS capability -->
    </listener>
</service>

Client Certificate Authentication (mTLS)

Servers can require or request client certificates for mutual TLS (mTLS):

<service id="smtp" class="org.bluezoo.gumdrop.smtp.SimpleRelayService">
    <listener class="org.bluezoo.gumdrop.smtp.SMTPListener">
        <property name="need-client-auth">true</property>
        <property name="truststore-file">truststore.p12</property>
        <property name="truststore-pass">secret</property>
    </listener>
</service>

SNI (Server Name Indication)

SNI allows a server to present different certificates based on the hostname the client requests during the TLS handshake. This enables hosting multiple secure domains on a single IP address and port.

<service id="https" class="org.bluezoo.gumdrop.servlet.ServletService">
    <listener class="org.bluezoo.gumdrop.http.HTTPListener">
        <property name="port">443</property>
        <property name="secure">true</property>
        <property name="keystore-file">multi-domain.p12</property>
        <property name="keystore-pass">secret</property>
        <property name="sni-hostnames">
            <map>
                <entry key="example.com" value="example-cert"/>
                <entry key="*.example.com" value="example-wildcard"/>
                <entry key="other.com" value="other-cert"/>
            </map>
        </property>
        <property name="sni-default-alias">default-cert</property>
    </listener>
</service>

The sniHostnames map specifies hostname-to-certificate-alias mappings. Hostnames can be exact (example.com) or wildcard patterns (*.example.com). The keystore must contain certificates with the specified aliases. If no match is found, sniDefaultAlias is used.

QUIC / HTTP/3 Certificate Requirements

QUIC mandates TLS 1.3 — there is no unencrypted mode and no STARTTLS upgrade. The TLS handshake is integrated into the QUIC connection establishment, so every QUIC connection is encrypted from the first packet.

Because Gumdrop's QUIC implementation uses BoringSSL (via the quiche library) rather than the JVM's SSLEngine, its certificate configuration differs from TCP-based listeners:

TCP/TLS (HTTP/1.x, HTTP/2, SMTP, etc.) QUIC (HTTP/3)
TLS implementation JVM SSLEngine BoringSSL (via quiche)
Certificate format Java keystore (PKCS#12, JKS) PEM files
Certificate property keystore-file cert-file, key-file
Password keystore-pass (keystore password) N/A (key file is unencrypted)
TLS versions TLS 1.0 – 1.3 (configurable) TLS 1.3 only (QUIC requirement)

PEM File Format

QUIC listeners require certificates and keys in PEM format — Java keystores (PKCS#12, JKS) are not supported. The certificate file must contain the full certificate chain (server certificate followed by any intermediate CA certificates). The private key file must be unencrypted PEM (PKCS#8 or traditional format).

Generating PEM Certificates

For development, mkcert generates locally-trusted PEM certificates. See TLS Certificates for Local Development above for the complete setup including the PKCS#12 keystore required for HTTPS.

For production, most certificate authorities (Let's Encrypt, etc.) issue PEM files directly. If you already have a Java keystore, you can export to PEM:

# Extract certificate chain
keytool -exportcert -alias myalias -keystore keystore.p12 \
    -storepass secret -rfc -file cert.pem

# Extract private key (via PKCS#12 + OpenSSL)
openssl pkcs12 -in keystore.p12 -nocerts -nodes -out key.pem

Example: Co-located HTTPS and HTTP/3 Listeners

A typical deployment shares the same port (443 or 8443) between an HTTPS listener on TCP and an HTTP/3 listener on UDP. Because they use different TLS stacks, they require separate certificate configurations:

<service id="http" class="org.bluezoo.gumdrop.servlet.ServletService">
    <property name="container" ref="#mainContainer"/>

    <!-- HTTPS on TCP (Java SSLEngine, PKCS#12 keystore) -->
    <listener class="org.bluezoo.gumdrop.http.HTTPListener">
        <property name="port">8443</property>
        <property name="secure">true</property>
        <property name="keystore-file">keystore.p12</property>
        <property name="keystore-pass">secret</property>
    </listener>

    <!-- HTTP/3 on UDP (BoringSSL, PEM files) -->
    <listener class="org.bluezoo.gumdrop.http.h3.HTTP3Listener">
        <property name="port">8443</property>
        <property name="cert-file">cert.pem</property>
        <property name="key-file">key.pem</property>
    </listener>
</service>

Both listeners must present the same certificate identity (same subject/SAN) to avoid browser warnings when upgrading from HTTP/2 to HTTP/3. The browser discovers HTTP/3 support via the Alt-Svc response header on the initial HTTP/2 connection and will only attempt QUIC if the certificate matches.

Cipher Suite and Named Group Configuration

QUIC listeners support BoringSSL cipher suite and named group configuration:

<listener class="org.bluezoo.gumdrop.http.h3.HTTP3Listener">
    <property name="port">443</property>
    <property name="cert-file">cert.pem</property>
    <property name="key-file">key.pem</property>
    <property name="cipher-suites">TLS_AES_256_GCM_SHA384</property>
    <property name="named-groups">X25519</property>
</listener>

The default cipher suites are BoringSSL's defaults (AES-128-GCM and AES-256-GCM with SHA-256/384). The default named group is X25519.

Certificate Pinning

PinnedCertTrustManager wraps an X509TrustManager and validates server certificates against a set of SHA-256 fingerprints. If the presented certificate does not match any pinned fingerprint, the TLS handshake is rejected.

All client classes support certificate pinning via setTrustManager(X509TrustManager): HTTPClient, SMTPClient, IMAPClient, POP3Client, WebSocketClient, RedisClient, LDAPClient.

Identity and Access Management (IAM)

Gumdrop provides a unified identity and access management framework used across all protocol implementations. At its core is the Realm interface, which abstracts credential storage and verification.

The Realm Interface

A Realm represents a collection of authenticatable principals with associated passwords and roles. The interface supports multiple authentication mechanisms without exposing stored credentials:

public interface Realm {
    Realm forSelectorLoop(SelectorLoop loop);
    Set<SASLMechanism> getSupportedSASLMechanisms();
    boolean passwordMatch(String username, String password);
    String getDigestHA1(String username, String realmName);
    boolean isUserInRole(String username, String role);
    boolean userExists(String username);
    String getCramMD5Response(String username, String challenge);
    ScramCredentials getScramCredentials(String username);
    TokenValidationResult validateBearerToken(String token);
    TokenValidationResult validateOAuthToken(String accessToken);
    CertificateAuthenticationResult authenticateCertificate(X509Certificate cert);
    boolean authorizeAs(String authenticatedUser, String requestedUser);
}

Most methods are default and throw UnsupportedOperationException — realms need only implement the mechanisms they support. This design allows the realm to store passwords in any form (plaintext, hashed, or derived keys) without requiring the authentication layer to know the storage format.

BasicRealm Implementation

The built-in BasicRealm reads users and groups from an XML file:

<realm>
  <!-- Group definitions -->
  <group id="readers" name="mail-read imap-read pop3-read"/>
  <group id="writers" name="mail-read mail-write imap-read imap-write"/>
  <group id="admins" name="admin mail-admin ftp-admin"/>
  
  <!-- User definitions -->
  <user name="alice" password="secret123" groups="#readers #writers"/>
  <user name="bob" password="password456" groups="#readers"/>
  <user name="admin" password="adminpass" groups="#admins"/>
  
  <!-- Inline roles (legacy format) -->
  <user name="guest" password="guest" roles="guest read-only"/>
</realm>

Group References

Groups are defined with an id for referencing and a name containing space-separated role names. Users reference groups using the #id syntax in their groups attribute.

Configuration

<realm id="mailRealm" class="org.bluezoo.gumdrop.auth.BasicRealm">
    <property name="href">users.xml</property>
</realm>

<!-- Or inline -->
<realm id="simpleRealm" class="org.bluezoo.gumdrop.auth.BasicRealm">
    <user name="testuser" password="testpass" roles="user"/>
</realm>

Production Use

BasicRealm with plaintext passwords is intended for development and testing only. For production, use LDAPRealm or OAuthRealm. A warning is logged at startup when plaintext passwords are loaded.

Hashed Passwords (RFC 2307)

Passwords may use LDAP-style hashed formats for improved security. Supported schemes: {SHA}, {SSHA}, {SHA256}, {SSHA256}. Values can be exported from LDAP userPassword and used directly. Hashed users support only PLAIN and LOGIN SASL mechanisms; CRAM-MD5, SCRAM, Digest, and APOP require plaintext and will not work.

<user name="alice" password="{SSHA256}base64digest+salt" groups="readers"/>

Use slappasswd -h {SSHA256} -s password (OpenLDAP) or equivalent tools to generate hashed values.

OAuthRealm

OAuthRealm validates OAuth 2.0 tokens via RFC 7662 (token introspection) and optionally via local RFC 7519 JWT validation. Constructed with a Properties instance specifying the introspection endpoint, client credentials, and role-scope mappings. Not directly DI-instantiable; use a factory or programmatic setup.

Local JWT Validation (RFC 7519)

When oauth.jwt.enabled=true, tokens that look like JWTs (three Base64url-encoded dot-separated segments) are validated locally before falling back to the introspection endpoint. This eliminates the round-trip for self-contained JWTs.

Supported signature algorithms (RFC 7518 ยง3):

Standard claims are verified: exp (expiration), nbf (not-before) with configurable clock skew tolerance, iss (issuer), and aud (audience).

# Enable local JWT validation
oauth.jwt.enabled=true
oauth.jwt.secret=my-shared-secret

# Optional claim validation
oauth.jwt.issuer=https://auth.example.com
oauth.jwt.audience=my-service
oauth.jwt.clock.skew=30

isUserInRole() checks cached token scopes against role-scope mappings. See the oauth-servlet-auth example for a working configuration.

Custom Realm Implementations

For production deployments, implement the Realm interface to integrate with your identity provider:

SASL Authentication

SMTP, IMAP, POP3, and FTP servers support SASL (Simple Authentication and Security Layer) mechanisms for secure credential exchange.

Supported Mechanisms

Mechanism Security Requirements Recommended
PLAIN Password transmitted (base64) TLS required Over TLS only
LOGIN Password transmitted (base64) TLS required Legacy compatibility
CRAM-MD5 Challenge-response, no password sent Realm must store plaintext No
DIGEST-MD5 Digest authentication Realm must provide H(A1) No (deprecated)
SCRAM-SHA-256 Mutual auth, no password sent Realm provides derived keys Yes
OAUTHBEARER Bearer token OAuth 2.0 infrastructure For OAuth deployments
GSSAPI Kerberos V5 mutual auth Keytab + service principal For Kerberos/enterprise SSO
EXTERNAL TLS client certificate mTLS configured For certificate-based auth

SCRAM-SHA-256 (Recommended)

SCRAM-SHA-256 is the recommended mechanism because:

The realm stores StoredKey and ServerKey instead of the password. Even if the credential store is compromised, attackers cannot recover the original password.

GSSAPI/Kerberos (RFC 4752)

GSSAPI enables Kerberos V5 single sign-on authentication for IMAP, POP3, and SMTP. Each service requires a keytab file and service principal.

Server-side GSSAPI authentication is event-loop safe: the acceptSecContext() operation decrypts the service ticket using the local keytab with no KDC network I/O. The LDAP client's GSSAPI bind automatically offloads the first initSecContext() call to a worker thread since it may contact the KDC.

Gumdrop negotiates "no security layer" (RFC 4752 §3.1) since TLS already provides confidentiality and integrity.

Configuration
// Programmatic
imapListener.configureGSSAPI(
    Path.of("/etc/krb5.keytab"),
    "imap/mail.example.com@EXAMPLE.COM");

// Or via setGSSAPIServer() for shared keytab
GSSAPIServer gss = new GSSAPIServer(
    Path.of("/etc/krb5.keytab"),
    "imap/mail.example.com@EXAMPLE.COM");
imapListener.setGSSAPIServer(gss);

gumdroprc XML configuration:

<service id="imap" class="org.bluezoo.gumdrop.imap.IMAPService">
    <property name="gssapiKeytab" value="/etc/krb5.keytab"/>
    <property name="gssapiPrincipal" value="imap/mail.example.com@EXAMPLE.COM"/>
</service>

After successful GSSAPI authentication, the authenticated Kerberos principal (e.g. user@EXAMPLE.COM) is mapped to a local username via the Realm's mapKerberosPrincipal() method. The default implementation strips the realm portion.

Role-Based Access Control

Many Gumdrop servers support role-based access control (RBAC) for fine-grained authorization. Roles are assigned to users in the realm and checked by the server during operations.

Protocol-Specific Roles

ProtocolRolesDescription
FTP ftp-read Download files, list directories
ftp-write Upload files (implies ftp-read)
ftp-delete Delete files/directories (implies ftp-write)
ftp-admin Full access, SITE commands, quota management
Servlet (custom) Roles defined in web.xml security constraints
manager Access to manager application

Example: FTP Role Configuration

<!-- Define role groups -->
<realm>
  <group id="readers" name="ftp-read"/>
  <group id="uploaders" name="ftp-read ftp-write"/>
  <group id="managers" name="ftp-read ftp-write ftp-delete"/>
  <group id="admins" name="ftp-admin"/>
  
  <user name="guest" password="guest" groups="#readers"/>
  <user name="contributor" password="secret" groups="#uploaders"/>
  <user name="editor" password="secret" groups="#managers"/>
  <user name="admin" password="secret" groups="#admins"/>
</realm>

Rate Limiting

Gumdrop provides comprehensive rate limiting to protect servers from abuse, denial-of-service attacks, and brute-force authentication attempts.

Connection Rate Limiting

The ConnectionRateLimiter provides two types of protection:

Concurrent Connection Limits

Limits the number of simultaneous connections from a single IP address, preventing resource exhaustion from connection flooding.

<service id="smtp" class="org.bluezoo.gumdrop.smtp.SimpleRelayService">
    <property name="max-connections-per-ip">10</property>
    <listener class="org.bluezoo.gumdrop.smtp.SMTPListener" port="25"/>
</service>

Connection Rate Limits

Limits the number of connection attempts per IP address within a time window, preventing rapid reconnection attacks.

<service id="ftp" class="org.bluezoo.gumdrop.ftp.file.RoleBasedFTPService">
    <property name="rate-limit">100/60s</property>
    <listener class="org.bluezoo.gumdrop.ftp.FTPListener" port="21"/>
</service>

Rate Limit Format

The rate-limit property accepts the format count/duration:

Implementation

Rate limiting uses a sliding window algorithm that efficiently tracks connection timestamps. Old entries are automatically expired, and a background cleanup task removes stale tracking data to prevent memory leaks.

Authentication Rate Limiting

The AuthenticationRateLimiter protects against brute-force password attacks by tracking failed authentication attempts and temporarily locking out offending clients.

<service id="imap" class="org.bluezoo.gumdrop.imap.DefaultIMAPService">
    <property name="max-auth-failures">5</property>
    <property name="auth-lockout-time">5m</property>
    <listener class="org.bluezoo.gumdrop.imap.IMAPListener" port="143"/>
</service>

Exponential Backoff

By default, lockout duration increases exponentially for repeat offenders:

Tracking Keys

Authentication failures can be tracked by:

Network Filtering (CIDR)

SMTP servers support CIDR-based network filtering for allow/block lists:

<service id="smtp" class="org.bluezoo.gumdrop.smtp.SimpleRelayService">
    <property name="allowed-networks">
        192.168.0.0/16,10.0.0.0/8,2001:db8::/32
    </property>
    <property name="blocked-networks">
        192.168.100.0/24,2001:db8:bad::/48
    </property>
    <listener class="org.bluezoo.gumdrop.smtp.SMTPListener" port="25"/>
</service>

Blocked networks are checked first, then allowed networks. If allowed networks are configured, only connections from those networks are accepted.

Security Telemetry

Security-related events are integrated with Gumdrop's telemetry system for monitoring and alerting:

Error Categories

Telemetry Events

Example: Monitoring Authentication Failures

Configure your OpenTelemetry backend to alert on authentication failures:

// Prometheus query example
sum(rate(span_events{event="AUTH_FAILURE"}[5m])) by (server) > 10

← Back to Main Page | SMTP Server | IMAP Server | FTP Server | Telemetry

Gumdrop Security