package diagnose import ( "bytes" "crypto/tls" "crypto/x509" "encoding/pem" "fmt" "io/ioutil" "github.com/hashicorp/vault/internalshared/listenerutil" "github.com/hashicorp/vault/sdk/helper/tlsutil" "github.com/hashicorp/vault/vault" ) const minVersionError = "'tls_min_version' value %q not supported, please specify one of [tls10,tls11,tls12,tls13]" const maxVersionError = "'tls_max_version' value %q not supported, please specify one of [tls10,tls11,tls12,tls13]" func ListenerChecks(listeners []listenerutil.Listener) error { for _, listener := range listeners { l := listener.Config // Perform the TLS version check for listeners. if l.TLSMinVersion == "" { l.TLSMinVersion = "tls12" } if l.TLSMaxVersion == "" { l.TLSMaxVersion = "tls13" } _, ok := tlsutil.TLSLookup[l.TLSMinVersion] if !ok { return fmt.Errorf(minVersionError, l.TLSMinVersion) } _, ok = tlsutil.TLSLookup[l.TLSMaxVersion] if !ok { return fmt.Errorf(maxVersionError, l.TLSMaxVersion) } // Perform checks on the TLS Cryptographic Information. if err := TLSFileChecks(l.TLSCertFile, l.TLSKeyFile); err != nil { return err } } return nil } // TLSFileChecks contains manual error checks against the TLS configuration func TLSFileChecks(certFilePath, keyFilePath string) error { data, err := ioutil.ReadFile(certFilePath) if err != nil { return fmt.Errorf("failed to read tls_client_ca_file: %w", err) } certBlocks := []*pem.Block{} leafCerts := []*x509.Certificate{} rootPool := x509.NewCertPool() interPool := x509.NewCertPool() rst := []byte(data) for len(rst) != 0 { block, rest := pem.Decode(rst) if block == nil { return fmt.Errorf("could not decode cert") } certBlocks = append(certBlocks, block) rst = rest } if len(certBlocks) == 0 { return fmt.Errorf("no certificates found in cert file") } for _, certBlock := range certBlocks { cert, err := x509.ParseCertificate(certBlock.Bytes) if err != nil { return fmt.Errorf("A pem block does not parse to a certificate: %w", err) } // Detect if the certificate is a root, leaf, or intermediate if cert.IsCA && bytes.Equal(cert.RawIssuer, cert.RawSubject) { // It's a root rootPool.AddCert(cert) } else if cert.IsCA { // It's not a root but it's a CA, so it's an inter interPool.AddCert(cert) } else { // It's gotta be a leaf leafCerts = append(leafCerts, cert) } } // Make sure there's only one leaf. If there are multiple, it's a bad pem file. if len(leafCerts) != 1 { return fmt.Errorf("Number of leaf certificates detected is not one. Instead, it is: %d", len(leafCerts)) } rootSubjs := rootPool.Subjects() if len(rootSubjs) == 0 { // this is a self signed server certificate, or the root is just not provided. In any // case, we need to bypass the root verification step by adding the leaf itself to the // root pool. rootPool.AddCert(leafCerts[0]) } // Verify checks that certificate isn't expired, is of correct usage type, and has an appropriate // chain. _, err = leafCerts[0].Verify(x509.VerifyOptions{ Roots: rootPool, Intermediates: interPool, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, }) if err != nil { return fmt.Errorf("failed to verify certificate: %w", err) } // After verify passes, we need to check the values on the certificate itself. // This is a separate check beyond the certificate expiry and chain checks. _, err = tls.LoadX509KeyPair(certFilePath, keyFilePath) if err != nil { return err } return nil } // ServerListenerActiveProbe attempts to use TLS information to set up a TLS server with each listener // and generate a successful request through to the server. // TODO func ServerListenerActiveProbe(core *vault.Core) error { return fmt.Errorf("Method not implemented") }