SwiftNIO SSL

repository·main·Indexed 19 days ago

https://github.com/apple/swift-nio-ssl

A Swift package providing a TLS implementation based on BoringSSL, enabling SwiftNIO users to secure protocol clients and servers. It features core abstractions like NIOSSLClientHandler and NIOSSLServerHandler, configurable via TLSConfiguration and NIOSSLContext. The library supports quantum-secure key exchange using hybrid curves and provides tools for managing certificate chains, private keys, and Pre-Shared Key (PSK) configurations.

Tokens
3.8K
Snippets
10
Records
19
Agent score
65%

What's inside swift-nio-ssl

  1. SwiftNIO SSL core abstractions: Handlers and Contexts

    main

    SwiftNIO SSL provides two primary ChannelHandlers to secure data streams within a SwiftNIO Channel:

    1. NIOSSLClientHandler: Used by clients to initiate and manage TLS connections.
    2. NIOSSLServerHandler: Used by servers to handle incoming TLS connections.

    These handlers are initialized using a NIOSSLContext. The context is configured via a TLSConfiguration object, which defines the TLS parameters such as certificate chains and private keys.

  2. Understand Trust Roots and Certificate Validation Behavior

    main

    SwiftNIO SSL uses two properties in TLSConfiguration to manage certificate validation: trustRoots and additionalTrustRoots. The behavior of these properties depends on the platform and the specific configuration chosen, which determines whether the system uses the SecTrust backend or the BoringSSL backend.

    Certificate Validation Backends

    • SecTrust (Apple platforms only): The system's native certificate validator. It is stricter, follows Apple's specific certificate validation policies, and behaves similarly to Safari. It may reject certificate chains that BoringSSL would accept.
    • BoringSSL: The embedded certificate validator. It is more permissive regarding certificate formatting and extensions, and it provides consistent behavior across all platforms (Apple and non-Apple).

    Behavioral Matrix

    ConfigurationApple PlatformsOther Platforms
    trustRoots = .default, no additional trust rootsSecTrust with default settingsBoringSSL with system PEM files
    trustRoots = nil, no additional trust rootsSecTrust with default settingsBoringSSL with system PEM files
    trustRoots = .file(_)BoringSSL with specified fileBoringSSL with specified file
    trustRoots = .certificates(_)BoringSSL with specified certificatesBoringSSL with specified certificates
    trustRoots = .default, additional roots providedSecTrust with additional roots via SecTrustSetAnchorCertificatesBoringSSL with system PEM files and additional roots
    trustRoots = nil, additional roots providedSecTrust with additional roots via SecTrustSetAnchorCertificatesBoringSSL with system PEM files and additional roots
    trustRoots = .file(_), additional roots providedBoringSSL with specified file and additional rootsBoringSSL with specified file and additional roots
    trustRoots = .certificates(_), additional roots providedBoringSSL with specified certificates and additional rootsBoringSSL with specified certificates and additional roots
  3. Secure a client connection with NIOSSLClientHandler

    main

    Client TLS setup is simpler than server setup as a certificate chain and private key are typically not required. You can use TLSConfiguration.makeClientConfiguration() to create a default configuration, wrap it in a NIOSSLContext, and then use NIOSSLClientHandler in your client bootstrap.

    Important: The NIOSSLClientHandler must be initialized inside the channelInitializer of your ClientBootstrap.

    let configuration = TLSConfiguration.makeClientConfiguration()
    let sslContext = try NIOSSLContext(configuration: configuration)
    
    let client = ClientBootstrap(group: group)
        .channelInitializer { channel in
            // important: The handler must be initialized _inside_ the `channelInitializer`
            let handler = try NIOSSLClientHandler(context: sslContext)
    
            [...] 
            channel.pipeline.syncOperations.addHandler(handler)
            [...] 
        }
  4. Create a TLS configuration

    main

    Use TLSConfiguration to define the security parameters for your TLS connection. Depending on your role (client or server), use the following methods:

    • For Clients:
      • Use TLSConfiguration.clientDefault for a standard configuration.
      • Use makeClientConfiguration() for more granular control.
    • For Servers:
      • Use makeServerConfiguration(certificateChain:privateKey:) to provide the necessary identity (certificate and private key) for the server.
    • For Pre-Shared Key (PSK):
      • Use makePreSharedKeyConfiguration() when using PSK instead of certificate-based authentication.
    // Example: Creating a server configuration
    let config = try TLSConfiguration.makeServerConfiguration(
        certificateChain: [.certificate(myCert)],
        privateKey: .privateKey(myKey)
    )
  5. Secure a server connection with NIOSSLServerHandler

    main

    To secure a server connection, you must provide an X.509 certificate chain (PEM or DER) and the associated private key. These are used to create a TLSConfiguration, which is then used to initialize a NIOSSLContext.

    Important: The NIOSSLServerHandler must be initialized inside the childChannelInitializer of your ServerBootstrap to ensure it is correctly associated with the specific channel.

    let configuration = TLSConfiguration.makeServerConfiguration(
        certificateChain: try NIOSSLCertificate.fromPEMFile("cert.pem").map { .certificate($0) },
        privateKey: try .privateKey(.init(file: "key.pem", format: .pem))
    )
    let sslContext = try NIOSSLContext(configuration: configuration)
    
    let server = ServerBootstrap(group: group)
        .childChannelInitializer { channel in
            // important: The handler must be initialized _inside_ the `childChannelInitializer`
            let handler = NIOSSLServerHandler(context: sslContext)
    
            [...] 
            channel.pipeline.syncOperations.addHandler(handler)
            [...] 
        }
  6. Run the NIOSSLHTTP1Client sample application

    main

    The NIOSSLHTTP1Client sample application is a tool for testing HTTPS client functionality. You can invoke it via the Swift CLI to fetch content from a server using SSL/TLS. It supports targeting specific hosts and ports via URL strings.

    # Get content from a default server (::1, port 4433) using SSL/TLS
    swift run NIOSSLHTTP1Client
    
    # Get content from a specific domain (example.com, port 443) using SSL/TLS
    swift run NIOSSLHTTP1Client "https://example.com"
    
    # Get content from a specific domain and port (example.com, port 4433) using SSL/TLS
    swift run NIOSSLHTTP1Client "https://example.com:4433"
  7. Run tests using Docker Compose

    main

    The docker-compose.yaml file is not intended to be run directly. Instead, you must combine it with OS-specific and Swift-version-specific compose files to ensure the correct environment.

    To run integration tests, use the following pattern: docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.<os>.<swift>.yaml run test

    docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.1804.50.yaml run test
  8. Debug Certificate Validation Issues on Apple Platforms

    main

    If certificate validation fails on macOS or iOS, you can inspect the system logs to find detailed error messages. These logs can help identify if the failure is due to certificate formatting, missing extensions, or chain validation problems.

    Use the following command to view the last minute of security-related logs:

    # macOS/iOS system logs often contain detailed certificate validation errors
    log show --predicate 'subsystem == "com.apple.security"' --last 1m
  9. Customize TLS curves for post-quantum or classical-only exchange

    main

    You can override the default curve list in TLSConfiguration.curves to change the key exchange behavior.

    To support only post-quantum key exchange, set the curves to [.x25519_MLKEM768].

    To disable post-quantum key exchange and use only classical curves, set the curves to [.x25519, .secp256r1, .secp384r1].

    // Support only post-quantum key exchange
    tlsConfiguration.curves = [.x25519_MLKEM768]
    
    // Disable post-quantum and use only classical curves
    tlsConfiguration.curves = [.x25519, .secp256r1, .secp384r1]