cyberark/secretless-broker

View on GitHub
internal/plugin/connectors/tcp/ssl/ssl.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package ssl

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "net"
)

type options map[string]string

// DbSSLMode holds information about the DB's SSL options.
type DbSSLMode struct {
    tls.Config
    UseTLS       bool
    VerifyCaOnly bool
    Options      options
}

// NewDbSSLMode configures and creates a DbSSLMode
func NewDbSSLMode(o options, requireCanVerifyCA bool) (DbSSLMode, error) {
    // NOTE for the "require" case:
    //
    // From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
    //
    // Note: For backwards compatibility with earlier versions of
    // PostgreSQL, if a root CA file exists, the behavior of
    // sslmode=require will be the same as that of verify-ca, meaning the
    // server certificate is validated against the CA. Relying on this
    // behavior is discouraged, and applications that need certificate
    // validation should always use verify-ca or verify-full.
    sslMode := DbSSLMode{Options: o, UseTLS: true}

    switch mode := o["sslmode"]; mode {
    case "disable":
        sslMode.UseTLS = false

    // "require" is the default.
    case "", "require":
        // Skip stdlib's verification: it requires full verification since Go 1.3.
        sslMode.InsecureSkipVerify = true

        // From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
        //
        // Note: For backwards compatibility with earlier versions of
        // PostgreSQL, if a root CA file exists, the behavior of
        // sslmode=require will be the same as that of verify-ca, meaning the
        // server certificate is validated against the CA. Relying on this
        // behavior is discouraged, and applications that need certificate
        // validation should always use verify-ca or verify-full.

        // MySQL on the other hand notes in its docs that it ignores
        // SSL certs if supplied in REQUIRED sslmode.
        if requireCanVerifyCA && len(o["sslrootcert"]) > 0 {
            sslMode.VerifyCaOnly = true
        }

    case "verify-ca":
        // Skip stdlib's verification: it requires full verification since Go 1.3.
        sslMode.InsecureSkipVerify = true
        sslMode.VerifyCaOnly = true

    case "verify-full":
        // Use stdlib's verification
        sslMode.InsecureSkipVerify = false
        sslMode.VerifyCaOnly = false

        // 'sslhost', when not empty, takes precedence over 'host'
        if len(o["sslhost"]) > 0 {
            sslMode.ServerName = o["sslhost"]
        } else {
            sslMode.ServerName = o["host"]
        }

    default:
        return DbSSLMode{}, fmt.Errorf(`unsupported sslmode %q; only "require" (default), "verify-ca", "verify-full" and "disable" supported`, mode)
    }

    return sslMode, nil
}

// HandleSSLUpgrade upgrades a net.Conn using DbSSLMode
func HandleSSLUpgrade(connection net.Conn, tlsConf DbSSLMode) (net.Conn, error) {
    err := sslClientCertificates(&tlsConf.Config, tlsConf.Options)
    if err != nil {
        return nil, err
    }

    // Add the root CA certificate specified in the "sslrootcert" setting to the root CA
    // pool on the tls configuration.
    sslRootCert := []byte(tlsConf.Options["sslrootcert"])
    if len(sslRootCert) > 0 {
        tlsConf.RootCAs = x509.NewCertPool()

        if !tlsConf.RootCAs.AppendCertsFromPEM(sslRootCert) {
            return nil, fmt.Errorf("couldn't parse pem in sslrootcert")
        }
    }

    // Accept renegotiation requests initiated by the backend.
    //
    // Renegotiation was deprecated then removed from PostgreSQL 9.5, but
    // the default configuration of older versions has it enabled. Redshift
    // also initiates renegotiations and cannot be reconfigured.
    tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient

    client := tls.Client(connection, &tlsConf.Config)
    if tlsConf.VerifyCaOnly {
        err := sslVerifyCertificateAuthority(client, &tlsConf.Config)
        if err != nil {
            return nil, err
        }
    }
    err = client.Handshake()
    if err != nil {
        return nil, err
    }

    return client, nil
}

// sslClientCertificates adds the certificate specified in the "sslcert" and
// "sslkey" settings
func sslClientCertificates(tlsConf *tls.Config, o options) error {
    // The client certificate is only loaded if the setting is not blank.
    sslcert := o["sslcert"]
    if len(sslcert) == 0 {
        return nil
    }

    sslkey := o["sslkey"]

    certPEMBlock := []byte(sslcert)
    keyPEMBlock := []byte(sslkey)

    cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
    if err != nil {
        return err
    }

    tlsConf.Certificates = []tls.Certificate{cert}
    return nil
}

// sslVerifyCertificateAuthority carries out a TLS handshake to the server and
// verifies the presented certificate against the CA, i.e. the one specified in
// sslrootcert or the system CA if sslrootcert was not specified.
func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) error {
    err := client.Handshake()
    if err != nil {
        return err
    }
    certs := client.ConnectionState().PeerCertificates
    opts := x509.VerifyOptions{
        DNSName:       client.ConnectionState().ServerName,
        Intermediates: x509.NewCertPool(),
        Roots:         tlsConf.RootCAs,
    }
    for i, cert := range certs {
        if i == 0 {
            continue
        }
        opts.Intermediates.AddCert(cert)
    }
    _, err = certs[0].Verify(opts)
    return err
}