package ca import ( "crypto/tls" "crypto/x509" "github.com/smallstep/certificates/api" ) // TLSOption defines the type of a function that modifies a tls.Config. type TLSOption func(ctx *TLSOptionCtx) error // TLSOptionCtx is the context modified on TLSOption methods. type TLSOptionCtx struct { Client *Client Config *tls.Config Sign *api.SignResponse OnRenewFunc []TLSOption mutableConfig *mutableTLSConfig hasRootCA bool hasClientCA bool } // newTLSOptionCtx creates the TLSOption context. func newTLSOptionCtx(c *Client, config *tls.Config, sign *api.SignResponse) *TLSOptionCtx { return &TLSOptionCtx{ Client: c, Config: config, Sign: sign, mutableConfig: newMutableTLSConfig(), } } func (ctx *TLSOptionCtx) apply(options []TLSOption) error { for _, fn := range options { if err := fn(ctx); err != nil { return err } } // Initialize mutable config with the fully configured tls.Config ctx.mutableConfig.Init(ctx.Config) // Build RootCAs and ClientCAs with given root certificate if necessary if root, err := RootCertificate(ctx.Sign); err == nil { if !ctx.hasRootCA { if ctx.Config.RootCAs == nil { ctx.Config.RootCAs = x509.NewCertPool() } ctx.Config.RootCAs.AddCert(root) ctx.mutableConfig.AddImmutableRootCACert(root) } if !ctx.hasClientCA && ctx.Config.ClientAuth != tls.NoClientCert { if ctx.Config.ClientCAs == nil { ctx.Config.ClientCAs = x509.NewCertPool() } ctx.Config.ClientCAs.AddCert(root) ctx.mutableConfig.AddImmutableClientCACert(root) } } // Update tls.Config with mutable data if ctx.Config.RootCAs == nil && len(ctx.mutableConfig.mutRootCerts) > 0 { ctx.Config.RootCAs = x509.NewCertPool() } if ctx.Config.ClientCAs == nil && len(ctx.mutableConfig.mutClientCerts) > 0 { ctx.Config.ClientCAs = x509.NewCertPool() } // Add mutable certificates for _, cert := range ctx.mutableConfig.mutRootCerts { ctx.Config.RootCAs.AddCert(cert) } for _, cert := range ctx.mutableConfig.mutClientCerts { ctx.Config.ClientCAs.AddCert(cert) } ctx.mutableConfig.Reload() return nil } func (ctx *TLSOptionCtx) applyRenew() error { for _, fn := range ctx.OnRenewFunc { if err := fn(ctx); err != nil { return err } } // Reload mutable config with the changes ctx.mutableConfig.Reload() return nil } // RequireAndVerifyClientCert is a tls.Config option used on servers to enforce // a valid TLS client certificate. This is the default option for mTLS servers. func RequireAndVerifyClientCert() TLSOption { return func(ctx *TLSOptionCtx) error { ctx.Config.ClientAuth = tls.RequireAndVerifyClientCert return nil } } // VerifyClientCertIfGiven is a tls.Config option used on on servers to validate // a TLS client certificate if it is provided. It does not requires a certificate. func VerifyClientCertIfGiven() TLSOption { return func(ctx *TLSOptionCtx) error { ctx.Config.ClientAuth = tls.VerifyClientCertIfGiven return nil } } // AddRootCA adds to the tls.Config RootCAs the given certificate. RootCAs // defines the set of root certificate authorities that clients use when // verifying server certificates. func AddRootCA(cert *x509.Certificate) TLSOption { return func(ctx *TLSOptionCtx) error { if ctx.Config.RootCAs == nil { ctx.Config.RootCAs = x509.NewCertPool() } ctx.hasRootCA = true ctx.Config.RootCAs.AddCert(cert) ctx.mutableConfig.AddImmutableRootCACert(cert) return nil } } // AddClientCA adds to the tls.Config ClientCAs the given certificate. ClientCAs // defines the set of root certificate authorities that servers use if required // to verify a client certificate by the policy in ClientAuth. func AddClientCA(cert *x509.Certificate) TLSOption { return func(ctx *TLSOptionCtx) error { if ctx.Config.ClientCAs == nil { ctx.Config.ClientCAs = x509.NewCertPool() } ctx.hasClientCA = true ctx.Config.ClientCAs.AddCert(cert) ctx.mutableConfig.AddImmutableClientCACert(cert) return nil } } // AddRootsToRootCAs does a roots request and adds to the tls.Config RootCAs all // the certificates in the response. RootCAs defines the set of root certificate // authorities that clients use when verifying server certificates. // // BootstrapServer and BootstrapClient methods include this option by default. func AddRootsToRootCAs() TLSOption { // var once sync.Once fn := func(ctx *TLSOptionCtx) error { certs, err := ctx.Client.Roots() if err != nil { return err } ctx.hasRootCA = true ctx.mutableConfig.AddRootCAs(certs.Certificates) return nil } return func(ctx *TLSOptionCtx) error { ctx.OnRenewFunc = append(ctx.OnRenewFunc, fn) return fn(ctx) } } // AddRootsToClientCAs does a roots request and adds to the tls.Config ClientCAs // all the certificates in the response. ClientCAs defines the set of root // certificate authorities that servers use if required to verify a client // certificate by the policy in ClientAuth. // // BootstrapServer method includes this option by default. func AddRootsToClientCAs() TLSOption { // var once sync.Once fn := func(ctx *TLSOptionCtx) error { certs, err := ctx.Client.Roots() if err != nil { return err } ctx.hasClientCA = true ctx.mutableConfig.AddClientCAs(certs.Certificates) return nil } return func(ctx *TLSOptionCtx) error { ctx.OnRenewFunc = append(ctx.OnRenewFunc, fn) return fn(ctx) } } // AddFederationToRootCAs does a federation request and adds to the tls.Config // RootCAs all the certificates in the response. RootCAs defines the set of root // certificate authorities that clients use when verifying server certificates. func AddFederationToRootCAs() TLSOption { fn := func(ctx *TLSOptionCtx) error { certs, err := ctx.Client.Federation() if err != nil { return err } ctx.mutableConfig.AddRootCAs(certs.Certificates) return nil } return func(ctx *TLSOptionCtx) error { ctx.OnRenewFunc = append(ctx.OnRenewFunc, fn) return fn(ctx) } } // AddFederationToClientCAs does a federation request and adds to the tls.Config // ClientCAs all the certificates in the response. ClientCAs defines the set of // root certificate authorities that servers use if required to verify a client // certificate by the policy in ClientAuth. func AddFederationToClientCAs() TLSOption { fn := func(ctx *TLSOptionCtx) error { certs, err := ctx.Client.Federation() if err != nil { return err } ctx.mutableConfig.AddClientCAs(certs.Certificates) return nil } return func(ctx *TLSOptionCtx) error { ctx.OnRenewFunc = append(ctx.OnRenewFunc, fn) return fn(ctx) } } // AddRootsToCAs does a roots request and adds the resulting certs to the // tls.Config RootCAs and ClientCAs. Combines the functionality of // AddRootsToRootCAs and AddRootsToClientCAs. func AddRootsToCAs() TLSOption { fn := func(ctx *TLSOptionCtx) error { certs, err := ctx.Client.Roots() if err != nil { return err } ctx.hasRootCA = true ctx.hasClientCA = true ctx.mutableConfig.AddRootCAs(certs.Certificates) ctx.mutableConfig.AddClientCAs(certs.Certificates) return nil } return func(ctx *TLSOptionCtx) error { ctx.OnRenewFunc = append(ctx.OnRenewFunc, fn) return fn(ctx) } } // AddFederationToCAs does a federation request and adds the resulting certs to the // tls.Config RootCAs and ClientCAs. Combines the functionality of // AddFederationToRootCAs and AddFederationToClientCAs. func AddFederationToCAs() TLSOption { fn := func(ctx *TLSOptionCtx) error { certs, err := ctx.Client.Federation() if err != nil { return err } if ctx.mutableConfig == nil { if ctx.Config.RootCAs == nil { ctx.Config.RootCAs = x509.NewCertPool() } if ctx.Config.ClientCAs == nil { ctx.Config.ClientCAs = x509.NewCertPool() } for _, cert := range certs.Certificates { ctx.Config.RootCAs.AddCert(cert.Certificate) ctx.Config.ClientCAs.AddCert(cert.Certificate) } } else { ctx.mutableConfig.AddRootCAs(certs.Certificates) ctx.mutableConfig.AddClientCAs(certs.Certificates) } return nil } return func(ctx *TLSOptionCtx) error { ctx.OnRenewFunc = append(ctx.OnRenewFunc, fn) return fn(ctx) } }