2021-04-12 15:39:40 +00:00
package diagnose
import (
"bytes"
2021-06-15 16:53:29 +00:00
"context"
2021-04-12 15:39:40 +00:00
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
2021-06-18 23:35:38 +00:00
"strings"
2021-06-15 16:53:29 +00:00
"time"
2021-04-12 15:39:40 +00:00
2021-07-16 00:17:31 +00:00
"github.com/hashicorp/go-secure-stdlib/tlsutil"
2021-06-18 23:35:38 +00:00
"github.com/hashicorp/vault/internalshared/configutil"
2021-04-12 15:39:40 +00:00
)
2021-07-16 00:17:31 +00:00
const (
minVersionError = "'tls_min_version' value %q not supported, please specify one of [tls10,tls11,tls12,tls13]"
maxVersionError = "'tls_max_version' value %q not supported, please specify one of [tls10,tls11,tls12,tls13]"
)
2021-04-12 15:39:40 +00:00
2021-06-15 16:53:29 +00:00
// ListenerChecks diagnoses warnings and the first encountered error for the listener
// configuration stanzas.
2021-06-24 17:43:49 +00:00
func ListenerChecks ( ctx context . Context , listeners [ ] * configutil . Listener ) ( [ ] string , [ ] error ) {
2021-07-11 22:44:19 +00:00
testName := "Check Listener TLS"
2021-06-24 17:43:49 +00:00
ctx , span := StartSpan ( ctx , testName )
defer span . End ( )
2021-06-15 16:53:29 +00:00
// These aggregated warnings and errors are returned purely for testing purposes.
// The errors and warnings will report in this function itself.
var listenerWarnings [ ] string
var listenerErrors [ ] error
2021-06-24 17:43:49 +00:00
for _ , l := range listeners {
2021-06-15 16:53:29 +00:00
listenerID := l . Address
2021-04-12 15:39:40 +00:00
2021-06-24 17:43:49 +00:00
if l . TLSDisable {
2021-07-11 22:44:19 +00:00
Warn ( ctx , fmt . Sprintf ( "Listener at address %s: TLS is disabled in a listener config stanza." , listenerID ) )
2021-06-24 17:43:49 +00:00
continue
}
if l . TLSDisableClientCerts {
2021-07-11 22:44:19 +00:00
Warn ( ctx , fmt . Sprintf ( "Listener at address %s: TLS for a listener is turned on without requiring client certificates." , listenerID ) )
2021-06-24 17:43:49 +00:00
}
status , warning := TLSMutualExclusionCertCheck ( l )
if status == 1 {
Warn ( ctx , warning )
}
2021-04-12 15:39:40 +00:00
// 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 {
2021-07-11 22:44:19 +00:00
err := fmt . Errorf ( "Listener at address %s: %s." , listenerID , fmt . Sprintf ( minVersionError , l . TLSMinVersion ) )
2021-06-15 16:53:29 +00:00
listenerErrors = append ( listenerErrors , err )
2021-06-24 17:43:49 +00:00
Fail ( ctx , err . Error ( ) )
2021-04-12 15:39:40 +00:00
}
_ , ok = tlsutil . TLSLookup [ l . TLSMaxVersion ]
if ! ok {
2021-07-11 22:44:19 +00:00
err := fmt . Errorf ( "Listener at address %s: %s." , listenerID , fmt . Sprintf ( maxVersionError , l . TLSMaxVersion ) )
2021-06-15 16:53:29 +00:00
listenerErrors = append ( listenerErrors , err )
2021-06-24 17:43:49 +00:00
Fail ( ctx , err . Error ( ) )
2021-04-12 15:39:40 +00:00
}
// Perform checks on the TLS Cryptographic Information.
2021-06-15 16:53:29 +00:00
warnings , err := TLSFileChecks ( l . TLSCertFile , l . TLSKeyFile )
2021-06-18 23:35:38 +00:00
listenerWarnings , listenerErrors = outputError ( ctx , warnings , listenerWarnings , err , listenerErrors , listenerID )
// Perform checks on the Client CA Cert
warnings , err = TLSClientCAFileCheck ( l )
listenerWarnings , listenerErrors = outputError ( ctx , warnings , listenerWarnings , err , listenerErrors , listenerID )
2021-06-15 16:53:29 +00:00
// TODO: Use listenerutil.TLSConfig to warn on incorrect protocol specified
// Alternatively, use tlsutil.SetupTLSConfig.
2021-04-12 15:39:40 +00:00
}
2021-06-15 16:53:29 +00:00
return listenerWarnings , listenerErrors
}
2021-06-18 23:35:38 +00:00
func outputError ( ctx context . Context , newWarnings , listenerWarnings [ ] string , newErr error , listenerErrors [ ] error , listenerID string ) ( [ ] string , [ ] error ) {
for _ , warning := range newWarnings {
warning = listenerID + ": " + warning
listenerWarnings = append ( listenerWarnings , warning )
Warn ( ctx , warning )
}
if newErr != nil {
errMsg := listenerID + ": " + newErr . Error ( )
listenerErrors = append ( listenerErrors , fmt . Errorf ( errMsg ) )
2021-06-24 17:43:49 +00:00
Fail ( ctx , errMsg )
2021-06-18 23:35:38 +00:00
}
return listenerWarnings , listenerErrors
}
2021-06-15 16:53:29 +00:00
// TLSFileChecks returns an error and warnings after checking TLS information
func TLSFileChecks ( certpath , keypath string ) ( [ ] string , error ) {
2021-06-24 22:30:42 +00:00
warnings , err := TLSCertCheck ( certpath )
if err != nil {
return warnings , err
}
// Utilize the native TLS Loading mechanism to ensure we have missed no errors
_ , err = tls . LoadX509KeyPair ( certpath , keypath )
return warnings , err
}
// TLSCertCheck returns an error and warning after checking TLS information on the given cert
func TLSCertCheck ( certpath string ) ( [ ] string , error ) {
2021-06-15 16:53:29 +00:00
// Parse TLS Certs from the certpath
leafCerts , interCerts , rootCerts , err := ParseTLSInformation ( certpath )
if err != nil {
return nil , err
}
// Check for TLS Warnings
warnings , err := TLSFileWarningChecks ( leafCerts , interCerts , rootCerts )
if err != nil {
return warnings , err
}
// Check for TLS Errors
if err = TLSErrorChecks ( leafCerts , interCerts , rootCerts ) ; err != nil {
return warnings , err
}
return warnings , err
2021-04-12 15:39:40 +00:00
}
2021-06-15 16:53:29 +00:00
// ParseTLSInformation parses certficate information and returns it from a cert path.
func ParseTLSInformation ( certFilePath string ) ( [ ] * x509 . Certificate , [ ] * x509 . Certificate , [ ] * x509 . Certificate , error ) {
leafCerts := [ ] * x509 . Certificate { }
interCerts := [ ] * x509 . Certificate { }
rootCerts := [ ] * x509 . Certificate { }
2021-04-12 15:39:40 +00:00
data , err := ioutil . ReadFile ( certFilePath )
if err != nil {
2021-07-11 22:44:19 +00:00
return leafCerts , interCerts , rootCerts , fmt . Errorf ( "Failed to read certificate file: %w." , err )
2021-04-12 15:39:40 +00:00
}
certBlocks := [ ] * pem . Block { }
rst := [ ] byte ( data )
for len ( rst ) != 0 {
block , rest := pem . Decode ( rst )
if block == nil {
2021-07-11 22:44:19 +00:00
return leafCerts , interCerts , rootCerts , fmt . Errorf ( "Could not decode certificate in certificate file." )
2021-04-12 15:39:40 +00:00
}
certBlocks = append ( certBlocks , block )
rst = rest
}
if len ( certBlocks ) == 0 {
2021-07-11 22:44:19 +00:00
return leafCerts , interCerts , rootCerts , fmt . Errorf ( "No certificates found in certificate file." )
2021-04-12 15:39:40 +00:00
}
for _ , certBlock := range certBlocks {
cert , err := x509 . ParseCertificate ( certBlock . Bytes )
if err != nil {
2021-07-11 22:44:19 +00:00
return leafCerts , interCerts , rootCerts , fmt . Errorf ( "A PEM block does not parse to a certificate: %w." , err )
2021-04-12 15:39:40 +00:00
}
// Detect if the certificate is a root, leaf, or intermediate
if cert . IsCA && bytes . Equal ( cert . RawIssuer , cert . RawSubject ) {
// It's a root
2021-06-15 16:53:29 +00:00
rootCerts = append ( rootCerts , cert )
2021-04-12 15:39:40 +00:00
} else if cert . IsCA {
// It's not a root but it's a CA, so it's an inter
2021-06-15 16:53:29 +00:00
interCerts = append ( interCerts , cert )
2021-04-12 15:39:40 +00:00
} else {
// It's gotta be a leaf
leafCerts = append ( leafCerts , cert )
}
}
2021-06-18 23:35:38 +00:00
2021-06-15 16:53:29 +00:00
return leafCerts , interCerts , rootCerts , nil
}
// TLSErrorChecks contains manual error checks against the TLS configuration
func TLSErrorChecks ( leafCerts , interCerts , rootCerts [ ] * x509 . Certificate ) error {
2021-06-18 23:35:38 +00:00
// Make sure there's the proper number of leafCerts. If there are multiple, it's a bad pem file.
if len ( leafCerts ) == 0 {
return fmt . Errorf ( "No leaf certificates detected." )
}
2021-06-15 16:53:29 +00:00
2021-06-18 23:35:38 +00:00
// First, create root pools and interPools from the root and inter certs lists
2021-06-15 16:53:29 +00:00
rootPool := x509 . NewCertPool ( )
interPool := x509 . NewCertPool ( )
for _ , root := range rootCerts {
rootPool . AddCert ( root )
}
for _ , inter := range interCerts {
interPool . AddCert ( inter )
}
2021-04-12 15:39:40 +00:00
2021-06-18 23:35:38 +00:00
var err error
// Verify checks that certificate isn't expired, is of correct usage type, and has an appropriate
// chain. We start with Root
for _ , root := range rootCerts {
_ , err = root . Verify ( x509 . VerifyOptions {
Roots : rootPool ,
KeyUsages : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageAny } ,
} )
if err != nil {
2021-07-11 22:44:19 +00:00
return fmt . Errorf ( "Failed to verify root certificate: %w." , err )
2021-06-18 23:35:38 +00:00
}
}
// Verifying intermediate certs
for _ , inter := range interCerts {
_ , err = inter . Verify ( x509 . VerifyOptions {
Roots : rootPool ,
KeyUsages : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageAny } ,
} )
if err != nil {
2021-07-11 22:44:19 +00:00
return fmt . Errorf ( "Failed to verify intermediate certificate: %w." , err )
2021-06-18 23:35:38 +00:00
}
2021-04-12 15:39:40 +00:00
}
rootSubjs := rootPool . Subjects ( )
2021-06-18 23:35:38 +00:00
if len ( rootSubjs ) == 0 && len ( leafCerts ) > 0 {
2021-04-12 15:39:40 +00:00
// 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 ] )
}
2021-06-18 23:35:38 +00:00
// Verifying leaf cert
for _ , leaf := range leafCerts {
_ , err = leaf . Verify ( x509 . VerifyOptions {
Roots : rootPool ,
Intermediates : interPool ,
KeyUsages : [ ] x509 . ExtKeyUsage { x509 . ExtKeyUsageAny } ,
} )
if err != nil {
2021-07-11 22:44:19 +00:00
return fmt . Errorf ( "Failed to verify primary provided leaf certificate: %w." , err )
2021-06-18 23:35:38 +00:00
}
2021-04-12 15:39:40 +00:00
}
2021-06-15 16:53:29 +00:00
return nil
}
2021-04-12 15:39:40 +00:00
2021-06-15 16:53:29 +00:00
// TLSFileWarningChecks returns warnings based on the leaf certificates, intermediate certificates,
// and root certificates provided.
func TLSFileWarningChecks ( leafCerts , interCerts , rootCerts [ ] * x509 . Certificate ) ( [ ] string , error ) {
var warnings [ ] string
2021-06-18 23:35:38 +00:00
// add a warning for when there are more than one leaf certs
if len ( leafCerts ) > 1 {
2021-07-11 22:44:19 +00:00
warnings = append ( warnings , fmt . Sprintf ( "More than one leaf certificate detected. Please ensure that there is one unique leaf certificate being supplied to Vault in the Vault server configuration file." ) )
2021-06-18 23:35:38 +00:00
}
2021-06-15 16:53:29 +00:00
for _ , c := range leafCerts {
2021-06-15 19:31:28 +00:00
if willExpire , timeToExpiry := NearExpiration ( c ) ; willExpire {
2021-07-11 22:44:19 +00:00
warnings = append ( warnings , fmt . Sprintf ( "Leaf certificate %d is expired or near expiry. Time to expire is: %s." , c . SerialNumber , timeToExpiry ) )
2021-06-15 16:53:29 +00:00
}
}
for _ , c := range interCerts {
2021-06-15 19:31:28 +00:00
if willExpire , timeToExpiry := NearExpiration ( c ) ; willExpire {
2021-07-11 22:44:19 +00:00
warnings = append ( warnings , fmt . Sprintf ( "Intermediate certificate %d is expired or near expiry. Time to expire is: %s." , c . SerialNumber , timeToExpiry ) )
2021-06-15 16:53:29 +00:00
}
}
for _ , c := range rootCerts {
2021-06-15 19:31:28 +00:00
if willExpire , timeToExpiry := NearExpiration ( c ) ; willExpire {
2021-07-11 22:44:19 +00:00
warnings = append ( warnings , fmt . Sprintf ( "Root certificate %d is expired or near expiry. Time to expire is: %s." , c . SerialNumber , timeToExpiry ) )
2021-06-15 16:53:29 +00:00
}
2021-04-12 15:39:40 +00:00
}
2021-06-15 16:53:29 +00:00
return warnings , nil
}
2021-06-15 19:31:28 +00:00
// NearExpiration returns a true if a certficate will expire in a month and false otherwise
func NearExpiration ( c * x509 . Certificate ) ( bool , time . Duration ) {
oneMonthFromNow := time . Now ( ) . Add ( 30 * 24 * time . Hour )
var timeToExpiry time . Duration
if oneMonthFromNow . After ( c . NotAfter ) {
timeToExpiry := oneMonthFromNow . Sub ( c . NotAfter )
return true , timeToExpiry
2021-06-15 16:53:29 +00:00
}
2021-06-15 19:31:28 +00:00
return false , timeToExpiry
2021-04-12 15:39:40 +00:00
}
2021-06-18 23:35:38 +00:00
// TLSMutualExclusionCertCheck returns error if both TLSDisableClientCerts and TLSRequireAndVerifyClientCert are set
2021-06-24 17:43:49 +00:00
func TLSMutualExclusionCertCheck ( l * configutil . Listener ) ( int , string ) {
2021-06-18 23:35:38 +00:00
if l . TLSDisableClientCerts {
if l . TLSRequireAndVerifyClientCert {
2021-07-11 22:44:19 +00:00
return 1 , "The tls_disable_client_certs and tls_require_and_verify_client_cert fields in the listener stanza of the Vault server configuration are mutually exclusive fields. Please ensure they are not both set to true."
2021-06-18 23:35:38 +00:00
}
}
2021-06-24 17:43:49 +00:00
return 0 , ""
2021-06-18 23:35:38 +00:00
}
// TLSClientCAFileCheck Checks the validity of a client CA file
func TLSClientCAFileCheck ( l * configutil . Listener ) ( [ ] string , error ) {
if l . TLSDisableClientCerts {
return nil , nil
} else if ! l . TLSRequireAndVerifyClientCert {
return nil , nil
}
2021-06-24 22:30:42 +00:00
return TLSCAFileCheck ( l . TLSClientCAFile )
}
2021-06-18 23:35:38 +00:00
2021-06-24 22:30:42 +00:00
// TLSCAFileCheck checks the validity of a TLS CA file
func TLSCAFileCheck ( CAFilePath string ) ( [ ] string , error ) {
2021-06-18 23:35:38 +00:00
var warningsSlc [ ] string
// Parse TLS Certs from the tls config
2021-06-24 22:30:42 +00:00
leafCerts , interCerts , rootCerts , err := ParseTLSInformation ( CAFilePath )
2021-06-18 23:35:38 +00:00
if err != nil {
return nil , err
}
if len ( rootCerts ) == 0 {
2021-07-11 22:44:19 +00:00
return nil , fmt . Errorf ( "No root certificate found in CA certificate file." )
2021-06-18 23:35:38 +00:00
}
if len ( rootCerts ) > 1 {
2021-07-11 22:44:19 +00:00
warningsSlc = append ( warningsSlc , fmt . Sprintf ( "Found multiple root certificates in CA Certificate file instead of just one." ) )
2021-06-18 23:35:38 +00:00
}
// Checking for Self-Signed cert and return an explicit error about it.
// Self-Signed certs are placed in the leafCerts slice when parsed.
if len ( leafCerts ) > 0 && ! leafCerts [ 0 ] . IsCA && bytes . Equal ( leafCerts [ 0 ] . RawIssuer , leafCerts [ 0 ] . RawSubject ) {
2021-07-11 22:44:19 +00:00
warningsSlc = append ( warningsSlc , "Found a self-signed certificate in the CA certificate file." )
2021-06-18 23:35:38 +00:00
}
if len ( interCerts ) > 0 {
2021-07-11 22:44:19 +00:00
warningsSlc = append ( warningsSlc , "Found at least one intermediate certificate in the CA certificate file." )
2021-06-18 23:35:38 +00:00
}
if len ( leafCerts ) > 0 {
2021-07-11 22:44:19 +00:00
warningsSlc = append ( warningsSlc , "Found at least one leaf certificate in the CA certificate file." )
2021-06-18 23:35:38 +00:00
}
var warnings [ ] string
// Check for TLS Warnings
warnings , err = TLSFileWarningChecks ( leafCerts , interCerts , rootCerts )
2021-07-11 22:44:19 +00:00
for i , warning := range warnings {
warnings [ i ] = strings . Replace ( warning , "leaf" , "root" , - 1 )
2021-06-18 23:35:38 +00:00
}
2021-07-11 22:44:19 +00:00
warningsSlc = append ( warningsSlc , warnings ... )
2021-06-18 23:35:38 +00:00
if err != nil {
return warningsSlc , err
}
// Adding rootCerts to leafCert to perform verification in TLSErrorChecks
leafCerts = append ( leafCerts , rootCerts [ 0 ] )
// Check for TLS Errors
if err = TLSErrorChecks ( leafCerts , interCerts , rootCerts ) ; err != nil {
return warningsSlc , fmt . Errorf ( strings . Replace ( err . Error ( ) , "leaf" , "root" , - 1 ) )
}
return warningsSlc , err
}