diff --git a/client/client.go b/client/client.go index dfc19a25c..40801a4a0 100644 --- a/client/client.go +++ b/client/client.go @@ -29,6 +29,7 @@ import ( "github.com/hashicorp/nomad/command/agent/consul" "github.com/hashicorp/nomad/nomad" "github.com/hashicorp/nomad/nomad/structs" + "github.com/hashicorp/nomad/tlsutil" vaultapi "github.com/hashicorp/vault/api" "github.com/mitchellh/hashstructure" ) @@ -164,12 +165,22 @@ var ( // NewClient is used to create a new client from the given configuration func NewClient(cfg *config.Config, consulSyncer *consul.Syncer, logger *log.Logger) (*Client, error) { + //Create the tls wrapper + var tlsWrap tlsutil.Wrapper + if cfg.RpcTLS { + tw, err := cfg.TLSConfig().OutgoingTLSWrapper() + if err != nil { + return nil, err + } + tlsWrap = tw + } + // Create the client c := &Client{ config: cfg, consulSyncer: consulSyncer, start: time.Now(), - connPool: nomad.NewPool(cfg.LogOutput, clientRPCCache, clientMaxStreams, nil), + connPool: nomad.NewPool(cfg.LogOutput, clientRPCCache, clientMaxStreams, tlsWrap), logger: logger, hostStatsCollector: stats.NewHostStatsCollector(), allocs: make(map[string]*AllocRunner), diff --git a/client/config/config.go b/client/config/config.go index 65cb822ab..420b653c6 100644 --- a/client/config/config.go +++ b/client/config/config.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs/config" + "github.com/hashicorp/nomad/tlsutil" ) var ( @@ -132,6 +133,32 @@ type Config struct { // PublishAllocationMetrics determines whether nomad is going to publish // allocation metrics to remote Telemetry sinks PublishAllocationMetrics bool + + // Don't verify callers identity + HttpTLS bool `mapstructure:"http_tls"` + + // Verify inbound and outbound + RpcTLS bool `mapstructure:"rpc_tls"` + + // VerifyServerHostname is used to enable hostname verification of servers. This + // ensures that the certificate presented is valid for server... + // This prevents a compromised client from being restarted as a server, and then + // intercepting request traffic as well as being added as a raft peer. This should be + // enabled by default with VerifyOutgoing, but for legacy reasons we cannot break + // existing clients. + VerifyServerHostname bool `mapstructure:"verify_server_hostname"` + + // CAFile is a path to a certificate authority file. This is used with VerifyIncoming + // or VerifyOutgoing to verify the TLS connection. + CAFile string `mapstructure:"ca_file"` + + // CertFile is used to provide a TLS certificate that is used for serving TLS connections. + // Must be provided to serve TLS connections. + CertFile string `mapstructure:"cert_file"` + + // KeyFile is used to provide a TLS key that is used for serving TLS connections. + // Must be provided to serve TLS connections. + KeyFile string `mapstructure:"key_file"` } func (c *Config) Copy() *Config { @@ -226,3 +253,16 @@ func (c *Config) ReadStringListToMapDefault(key, defaultValue string) map[string } return list } + +func (c *Config) TLSConfig() *tlsutil.Config { + tlsConf := &tlsutil.Config{ + VerifyIncoming: true, + VerifyOutgoing: true, + VerifyServerHostname: c.VerifyServerHostname, + CAFile: c.CAFile, + CertFile: c.CertFile, + KeyFile: c.KeyFile, + ServerName: c.Node.Name, + } + return tlsConf +} diff --git a/command/agent/agent.go b/command/agent/agent.go index 71e298d27..b737ac4fd 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -244,6 +244,13 @@ func (a *Agent) serverConfig() (*nomad.Config, error) { conf.ConsulConfig = a.config.Consul conf.VaultConfig = a.config.Vault + // Set the TLS related configs + conf.RpcTLS = a.config.RpcTLS + conf.VerifyServerHostname = a.config.VerifyServerHostname + conf.CAFile = a.config.CAFile + conf.CertFile = a.config.CertFile + conf.KeyFile = a.config.KeyFile + return conf, nil } @@ -357,6 +364,15 @@ func (a *Agent) clientConfig() (*clientconfig.Config, error) { conf.StatsCollectionInterval = a.config.Telemetry.collectionInterval conf.PublishNodeMetrics = a.config.Telemetry.PublishNodeMetrics conf.PublishAllocationMetrics = a.config.Telemetry.PublishAllocationMetrics + + // Set the TLS related configs + conf.HttpTLS = a.config.HttpTLS + conf.RpcTLS = a.config.RpcTLS + conf.VerifyServerHostname = a.config.VerifyServerHostname + conf.CAFile = a.config.CAFile + conf.CertFile = a.config.CertFile + conf.KeyFile = a.config.KeyFile + return conf, nil } diff --git a/command/agent/config.go b/command/agent/config.go index 62637e6e9..383d1232f 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -112,6 +112,32 @@ type Config struct { // List of config files that have been loaded (in order) Files []string `mapstructure:"-"` + // Don't verify callers identity + HttpTLS bool `mapstructure:"http_tls"` + + // Verify inbound and outbound + RpcTLS bool `mapstructure:"rpc_tls"` + + // VerifyServerHostname is used to enable hostname verification of servers. This + // ensures that the certificate presented is valid for server... + // This prevents a compromised client from being restarted as a server, and then + // intercepting request traffic as well as being added as a raft peer. This should be + // enabled by default with VerifyOutgoing, but for legacy reasons we cannot break + // existing clients. + VerifyServerHostname bool `mapstructure:"verify_server_hostname"` + + // CAFile is a path to a certificate authority file. This is used with VerifyIncoming + // or VerifyOutgoing to verify the TLS connection. + CAFile string `mapstructure:"ca_file"` + + // CertFile is used to provide a TLS certificate that is used for serving TLS connections. + // Must be provided to serve TLS connections. + CertFile string `mapstructure:"cert_file"` + + // KeyFile is used to provide a TLS key that is used for serving TLS connections. + // Must be provided to serve TLS connections. + KeyFile string `mapstructure:"key_file"` + // HTTPAPIResponseHeaders allows users to configure the Nomad http agent to // set arbritrary headers on API responses HTTPAPIResponseHeaders map[string]string `mapstructure:"http_api_response_headers"` @@ -557,6 +583,24 @@ func (c *Config) Merge(b *Config) *Config { if b.DisableAnonymousSignature { result.DisableAnonymousSignature = true } + if b.HttpTLS { + result.HttpTLS = true + } + if b.RpcTLS { + result.RpcTLS = true + } + if b.VerifyServerHostname { + result.VerifyServerHostname = true + } + if b.CAFile != "" { + result.CAFile = b.CAFile + } + if b.CertFile != "" { + result.CertFile = b.CertFile + } + if b.KeyFile != "" { + result.KeyFile = b.KeyFile + } // Apply the telemetry config if result.Telemetry == nil && b.Telemetry != nil { diff --git a/command/agent/config_parse.go b/command/agent/config_parse.go index 78c13df61..e88e263c7 100644 --- a/command/agent/config_parse.go +++ b/command/agent/config_parse.go @@ -94,6 +94,12 @@ func parseConfig(result *Config, list *ast.ObjectList) error { "atlas", "consul", "vault", + "http_tls", + "rpc_tls", + "verify_server_hostname", + "ca_file", + "cert_file", + "key_file", "http_api_response_headers", } if err := checkHCLKeys(list, valid); err != nil { diff --git a/command/agent/http.go b/command/agent/http.go index ec2c4fdb4..44050c849 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -2,6 +2,7 @@ package agent import ( "bytes" + "crypto/tls" "encoding/json" "fmt" "io" @@ -14,6 +15,7 @@ import ( "github.com/NYTimes/gziphandler" "github.com/hashicorp/nomad/nomad/structs" + "github.com/hashicorp/nomad/tlsutil" "github.com/ugorji/go/codec" ) @@ -52,6 +54,23 @@ func NewHTTPServer(agent *Agent, config *Config, logOutput io.Writer) (*HTTPServ return nil, fmt.Errorf("failed to start HTTP listener: %v", err) } + if config.HttpTLS { + tlsConf := &tlsutil.Config{ + VerifyIncoming: true, + VerifyOutgoing: true, + VerifyServerHostname: config.VerifyServerHostname, + CAFile: config.CAFile, + CertFile: config.CertFile, + KeyFile: config.KeyFile, + ServerName: config.NodeName, + } + tlsConfig, err := tlsConf.IncomingTLSConfig() + if err != nil { + return nil, err + } + ln = tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, tlsConfig) + } + // Create the mux mux := http.NewServeMux() @@ -91,6 +110,23 @@ func newScadaHttp(agent *Agent, list net.Listener) *HTTPServer { return srv } +// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted +// connections. It's used by NewHttpServer so +// dead TCP connections eventually go away. +type tcpKeepAliveListener struct { + *net.TCPListener +} + +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { + tc, err := ln.AcceptTCP() + if err != nil { + return + } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(30 * time.Second) + return tc, nil +} + // Shutdown is used to shutdown the HTTP server func (s *HTTPServer) Shutdown() { if s != nil { diff --git a/nomad/config.go b/nomad/config.go index 7d58f81a3..82be3e72a 100644 --- a/nomad/config.go +++ b/nomad/config.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs/config" "github.com/hashicorp/nomad/scheduler" + "github.com/hashicorp/nomad/tlsutil" "github.com/hashicorp/raft" "github.com/hashicorp/serf/serf" ) @@ -191,6 +192,28 @@ type Config struct { // This period is meant to be long enough for a leader election to take // place, and a small jitter is applied to avoid a thundering herd. RPCHoldTimeout time.Duration + + RpcTLS bool + + // VerifyServerHostname is used to enable hostname verification of servers. This + // ensures that the certificate presented is valid for server... + // This prevents a compromised client from being restarted as a server, and then + // intercepting request traffic as well as being added as a raft peer. This should be + // enabled by default with VerifyOutgoing, but for legacy reasons we cannot break + // existing clients. + VerifyServerHostname bool + + // CAFile is a path to a certificate authority file. This is used with VerifyIncoming + // or VerifyOutgoing to verify the TLS connection. + CAFile string + + // CertFile is used to provide a TLS certificate that is used for serving TLS connections. + // Must be provided to serve TLS connections. + CertFile string + + // KeyFile is used to provide a TLS key that is used for serving TLS connections. + // Must be provided to serve TLS connections. + KeyFile string } // CheckVersion is used to check if the ProtocolVersion is valid @@ -263,3 +286,16 @@ func DefaultConfig() *Config { c.RaftConfig.ShutdownOnRemove = false return c } + +func (c *Config) tlsConfig() *tlsutil.Config { + tlsConf := &tlsutil.Config{ + VerifyIncoming: true, + VerifyOutgoing: true, + VerifyServerHostname: c.VerifyServerHostname, + CAFile: c.CAFile, + CertFile: c.CertFile, + KeyFile: c.KeyFile, + ServerName: c.NodeName, + } + return tlsConf +} diff --git a/nomad/pool.go b/nomad/pool.go index c00a601b6..71269efbf 100644 --- a/nomad/pool.go +++ b/nomad/pool.go @@ -10,8 +10,8 @@ import ( "sync/atomic" "time" - "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/net-rpc-msgpackrpc" + "github.com/hashicorp/nomad/tlsutil" "github.com/hashicorp/yamux" ) @@ -129,7 +129,7 @@ type ConnPool struct { limiter map[string]chan struct{} // TLS wrapper - tlsWrap tlsutil.DCWrapper + tlsWrap tlsutil.Wrapper // Used to indicate the pool is shutdown shutdown bool @@ -141,7 +141,7 @@ type ConnPool struct { // Set maxTime to 0 to disable reaping. maxStreams is used to control // the number of idle streams allowed. // If TLS settings are provided outgoing connections use TLS. -func NewPool(logOutput io.Writer, maxTime time.Duration, maxStreams int, tlsWrap tlsutil.DCWrapper) *ConnPool { +func NewPool(logOutput io.Writer, maxTime time.Duration, maxStreams int, tlsWrap tlsutil.Wrapper) *ConnPool { pool := &ConnPool{ logOutput: logOutput, maxTime: maxTime, @@ -261,7 +261,7 @@ func (p *ConnPool) getNewConn(region string, addr net.Addr, version int) (*Conn, } // Wrap the connection in a TLS client - tlsConn, err := p.tlsWrap(region, conn) + tlsConn, err := p.tlsWrap(conn) if err != nil { conn.Close() return nil, err diff --git a/nomad/raft_rpc.go b/nomad/raft_rpc.go index dfbc2235f..4ed9ee4d3 100644 --- a/nomad/raft_rpc.go +++ b/nomad/raft_rpc.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/hashicorp/consul/tlsutil" + "github.com/hashicorp/nomad/tlsutil" ) // RaftLayer implements the raft.StreamLayer interface, diff --git a/nomad/server.go b/nomad/server.go index 35e1fd94e..59694976e 100644 --- a/nomad/server.go +++ b/nomad/server.go @@ -18,11 +18,11 @@ import ( consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/lib" - "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/go-multierror" "github.com/hashicorp/nomad/command/agent/consul" "github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/structs" + "github.com/hashicorp/nomad/tlsutil" "github.com/hashicorp/raft" "github.com/hashicorp/raft-boltdb" "github.com/hashicorp/serf/serf" @@ -187,11 +187,29 @@ func NewServer(config *Config, consulSyncer *consul.Syncer, logger *log.Logger) return nil, err } + // Configure TLS + var tlsWrap tlsutil.Wrapper + var incomingTLS *tls.Config + if config.RpcTLS { + tlsConf := config.tlsConfig() + tw, err := tlsConf.OutgoingTLSWrapper() + if err != nil { + return nil, err + } + tlsWrap = tw + + itls, err := tlsConf.IncomingTLSConfig() + if err != nil { + return nil, err + } + incomingTLS = itls + } + // Create the server s := &Server{ config: config, consulSyncer: consulSyncer, - connPool: NewPool(config.LogOutput, serverRPCCache, serverMaxStreams, nil), + connPool: NewPool(config.LogOutput, serverRPCCache, serverMaxStreams, tlsWrap), logger: logger, rpcServer: rpc.NewServer(), peers: make(map[string][]*serverParts), @@ -201,6 +219,7 @@ func NewServer(config *Config, consulSyncer *consul.Syncer, logger *log.Logger) evalBroker: evalBroker, blockedEvals: blockedEvals, planQueue: planQueue, + rpcTLS: incomingTLS, shutdownCh: make(chan struct{}), } @@ -216,7 +235,7 @@ func NewServer(config *Config, consulSyncer *consul.Syncer, logger *log.Logger) // Initialize the RPC layer // TODO: TLS... - if err := s.setupRPC(nil); err != nil { + if err := s.setupRPC(tlsWrap); err != nil { s.Shutdown() s.logger.Printf("[ERR] nomad: failed to start RPC layer: %s", err) return nil, fmt.Errorf("Failed to start RPC layer: %v", err) @@ -576,7 +595,7 @@ func (s *Server) setupVaultClient() error { } // setupRPC is used to setup the RPC listener -func (s *Server) setupRPC(tlsWrap tlsutil.DCWrapper) error { +func (s *Server) setupRPC(tlsWrap tlsutil.Wrapper) error { // Create endpoints s.endpoints.Status = &Status{s} s.endpoints.Node = &Node{srv: s} @@ -622,11 +641,7 @@ func (s *Server) setupRPC(tlsWrap tlsutil.DCWrapper) error { return fmt.Errorf("RPC advertise address is not advertisable: %v", addr) } - // Provide a DC specific wrapper. Raft replication is only - // ever done in the same datacenter, so we can provide it as a constant. - // wrapper := tlsutil.SpecificDC(s.config.Datacenter, tlsWrap) - // TODO: TLS... - s.raftLayer = NewRaftLayer(s.rpcAdvertise, nil) + s.raftLayer = NewRaftLayer(s.rpcAdvertise, tlsWrap) return nil } diff --git a/test/ca/certindex b/test/ca/certindex new file mode 100644 index 000000000..e3abd88b0 --- /dev/null +++ b/test/ca/certindex @@ -0,0 +1,5 @@ +V 150407190456Z 0A unknown /CN=testco.internal/ST=California/C=US/emailAddress=test@testco.com/O=TestCo/OU=Beta +V 150407194146Z 0B unknown /CN=testco.internal/ST=California/C=US/emailAddress=test@testco.com/O=TestCo/OU=Beta +V 150526223338Z 0C unknown /CN=*.testco.internal/ST=California/C=US/emailAddress=test@testco.com/O=TestCo/OU=Beta +V 160526220537Z 0D unknown /CN=test.internal/ST=CA/C=US/emailAddress=test@internal.com/O=HashiCorp Test Cert/OU=Dev +V 170604185910Z 0E unknown /CN=testco.internal/ST=California/C=US/emailAddress=test@testco.com/O=Hashicorp Test Cert/OU=Beta diff --git a/test/ca/myca.conf b/test/ca/myca.conf new file mode 100644 index 000000000..922660dec --- /dev/null +++ b/test/ca/myca.conf @@ -0,0 +1,34 @@ +[ ca ] +default_ca = myca + +[ crl_ext ] +# issuerAltName=issuer:copy #this would copy the issuer name to altname +authorityKeyIdentifier=keyid:always + +[ myca ] +new_certs_dir = /tmp +unique_subject = no +certificate = root.cer +database = certindex +private_key = privkey.pem +serial = serialfile +default_days = 365 +default_md = sha1 +policy = myca_policy +x509_extensions = myca_extensions + +[ myca_policy ] +commonName = supplied +stateOrProvinceName = supplied +countryName = supplied +emailAddress = optional +organizationName = supplied +organizationalUnitName = optional + +[ myca_extensions ] +basicConstraints = CA:false +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always +keyUsage = digitalSignature,keyEncipherment +extendedKeyUsage = serverAuth,clientAuth +crlDistributionPoints = URI:http://path.to.crl/myca.crl diff --git a/test/ca/privkey.pem b/test/ca/privkey.pem new file mode 100644 index 000000000..3f22711a1 --- /dev/null +++ b/test/ca/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxrs6JK4NpiOItxrpNR/1ppUUmH7p2BgLCBZ6eHdclle9J56i +68adt8J85zaqphCfz6VDP58DsFx+N50PZyjQaDsUd0HejRqfHRMtg2O+UQkv4Z66 ++Vo+gc6uGuANi2xMtSYDVTAqqzF48OOPQDgYkzcGxcFZzTRFFZt2vPnyHj8cHcaF +o/NMNVh7C3yTXevRGNm9u2mrbxCEeiHzFC2WUnvgU2jQuC7Fhnl33Zd3B6d3mQH6 +O23ncmwxTcPUJe6xZaIRrDuzwUcyhLj5Z3faag/fpFIIcHSiHRfoqHLGsGg+3swI +d/zVJSSDHr7pJUu7Cre+vZa63FqDaooqvnisrQIDAQABAoIBABreo6zj76p/8XM4 +a0GokZE1ZPR9bGawUYWFbIevM9CMCmI5+7M/RoHbBQJKDOapJsJviNkoSdpllxcz +4CpFhXAiVNEPEeUoLU1EE4pJSSkxwcySppsiTYNFi5rMomgwe2qeuiKhgZNl/AEt +82dubjwxW3QPgXHSWGjkfTht3wOhrczA8xyEjc9Bsad2ooA9IQk+VXYlPZXyXjs1 +WwLYHmcSfveauLliLXeVU2Ux5PPwyreKMhyAfSHVQCycxK008u8WPy8nkAlpxKMC +UwCN+JKl69WCCA3CxXgM83zz4pXvB4EyMr8aTiqmOID8RIIrPcjCmVJki6KbJ9WG +S2CQVG0CgYEA5kVACrnjLtov426ZNifF2zUXu9x//7D6GkbJxzZLwXP/BJFcEOdQ +Fnjcs3s7wYh/wdTnEcQVWSJSAqnRt98c9yAXVnG5z1M0DYpAsY8xrdhEitxOf2oB +2cbvi4+cvUuUxk1hgva18UCT23aLP+iY2+t/ydBXAZ9kq1zz5CcpEBMCgYEA3O/R +g1Y9O36XxBmSYnkoCF5yGrPunnKKNBJc/WA7pTkQFYHr64Y/h5EKubzHD/VEd1Li +nDuGYxVMewf+5pHUhqSdpZtTxv25hjOsqLf5o5wm18JThGifs2zEVCTJOPti5n2M +RHakxuq1I625/QHidLBTQYuEBS/vywhapfaSaD8CgYEAhd1OPK4R30PiQRIjqXL3 +t9ampISsOKXWz33FgbUT1zOq1in23rDKQzYh/4ktlPXYZ4NwjUhzrKyiBoBYtc7T +1OpoBs34Wgmhohl0QIThOZIXTq6CR9oFl2fqDDUBxp3wsFN905e+77A+BIBmtVFv +w7GlSVp/qibSbDiOZF1LptcCgYB8sJBi+jnmqOSIVRJLpysTxhHJxkDmhahACRkY +Gsau0cylBsUaEJMsNIyEFOmXtQml+k5QdDu9EdkvGm0evbDfKGqce1RF2w5okiNg +uSwXzVoSrOartMxk2/7VqkkycpX3lWWjgf4vEWmXsEVmaDjhOF5UgKPKtao0wQs/ +3S/1ywKBgAIGgOuvL/GBcGqLikHLC+cputMvBAuE/tJnFHPxFoobskocVsMKbDTy +NYF7uPlzSGGClZsjE6DQyyGf5E9/U+EdwDKZwHYGCkzVjplUBo0BT3EN0vcc9jB/ +ML9Ta4ETPyf66BhSVcD+eeNipPFAul0Q7uZhErH1zr1evTy8XXyI +-----END RSA PRIVATE KEY----- diff --git a/test/ca/root.cer b/test/ca/root.cer new file mode 100644 index 000000000..ae9fb00b1 --- /dev/null +++ b/test/ca/root.cer @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEtzCCA5+gAwIBAgIJAIewRMI8OnvTMA0GCSqGSIb3DQEBBQUAMIGYMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHDAa +BgNVBAoTE0hhc2hpQ29ycCBUZXN0IENlcnQxDDAKBgNVBAsTA0RldjEWMBQGA1UE +AxMNdGVzdC5pbnRlcm5hbDEgMB4GCSqGSIb3DQEJARYRdGVzdEBpbnRlcm5hbC5j +b20wHhcNMTQwNDA3MTkwMTA4WhcNMjQwNDA0MTkwMTA4WjCBmDELMAkGA1UEBhMC +VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRwwGgYDVQQK +ExNIYXNoaUNvcnAgVGVzdCBDZXJ0MQwwCgYDVQQLEwNEZXYxFjAUBgNVBAMTDXRl +c3QuaW50ZXJuYWwxIDAeBgkqhkiG9w0BCQEWEXRlc3RAaW50ZXJuYWwuY29tMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxrs6JK4NpiOItxrpNR/1ppUU +mH7p2BgLCBZ6eHdclle9J56i68adt8J85zaqphCfz6VDP58DsFx+N50PZyjQaDsU +d0HejRqfHRMtg2O+UQkv4Z66+Vo+gc6uGuANi2xMtSYDVTAqqzF48OOPQDgYkzcG +xcFZzTRFFZt2vPnyHj8cHcaFo/NMNVh7C3yTXevRGNm9u2mrbxCEeiHzFC2WUnvg +U2jQuC7Fhnl33Zd3B6d3mQH6O23ncmwxTcPUJe6xZaIRrDuzwUcyhLj5Z3faag/f +pFIIcHSiHRfoqHLGsGg+3swId/zVJSSDHr7pJUu7Cre+vZa63FqDaooqvnisrQID +AQABo4IBADCB/TAdBgNVHQ4EFgQUo/nrOfqvbee2VklVKIFlyQEbuJUwgc0GA1Ud +IwSBxTCBwoAUo/nrOfqvbee2VklVKIFlyQEbuJWhgZ6kgZswgZgxCzAJBgNVBAYT +AlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEcMBoGA1UE +ChMTSGFzaGlDb3JwIFRlc3QgQ2VydDEMMAoGA1UECxMDRGV2MRYwFAYDVQQDEw10 +ZXN0LmludGVybmFsMSAwHgYJKoZIhvcNAQkBFhF0ZXN0QGludGVybmFsLmNvbYIJ +AIewRMI8OnvTMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADa9fV9h +gjapBlkNmu64WX0Ufub5dsJrdHS8672P30S7ILB7Mk0W8sL65IezRsZnG898yHf9 +2uzmz5OvNTM9K380g7xFlyobSVq+6yqmmSAlA/ptAcIIZT727P5jig/DB7fzJM3g +jctDlEGOmEe50GQXc25VKpcpjAsNQi5ER5gowQ0v3IXNZs+yU+LvxLHc0rUJ/XSp +lFCAMOqd5uRoMOejnT51G6krvLNzPaQ3N9jQfNVY4Q0zfs0M+6dRWvqfqB9Vyq8/ +POLMld+HyAZEBk9zK3ZVIXx6XS4dkDnSNR91njLq7eouf6M7+7s/oMQZZRtAfQ6r +wlW975rYa1ZqEdA= +-----END CERTIFICATE----- diff --git a/test/ca/serialfile b/test/ca/serialfile new file mode 100644 index 000000000..0ced2f35e --- /dev/null +++ b/test/ca/serialfile @@ -0,0 +1 @@ +0F diff --git a/test/hostname/Alice.crt b/test/hostname/Alice.crt new file mode 100644 index 000000000..b56d79179 --- /dev/null +++ b/test/hostname/Alice.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEOzCCAiWgAwIBAgIRAOpEyvnjEG/Z15f0PrOT7iowCwYJKoZIhvcNAQELMBMx +ETAPBgNVBAMTCENlcnRBdXRoMB4XDTE1MDUxMTIyNTMxN1oXDTE3MDUxMTIyNTMx +OFowEDEOMAwGA1UEAxMFQWxpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCyxmMV9V0Cdp2mAXxL6h64cWLQlKsumsQfhZNImea8jLYT7+yyLpeHIF4G +7JiushloTnERyTi1wbq9BlU3BVYdX6tqvPXFFwFUXyOkDaSGS3vMCZUYd9PZg0TI +pyQK0/6+jSU7x7jDGVUMhJyvmXB9CgKxG0S8WiR6uGB9oWrTeDnXAzN1T4wNE4M+ +a3P1ToT2k2IDklZ1t5gg6u9EiOAzK7QfpKXrO2MsGyGHhm+tQqNP6LuZv0u2nGW3 +up+i3beQOvLQV0aeiy7zfR3KkIUCvDnmiPnkm35o6wmqFOXTNIU6VoT/l4WtU85F +Ikdtk1gkDLO1iyKiMRbj/hlRqKGxAgMBAAGjgZAwgY0wDgYDVR0PAQH/BAQDAgC4 +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUgt2Os881 +V/je/BOaLavjeorhbi4wHwYDVR0jBBgwFoAUB2s4Gdz7ornOiti84HF+W+nwAj8w +HAYDVR0RBBUwE4IRc2VydmVyLmRjMS5jb25zdWwwCwYJKoZIhvcNAQELA4ICAQCX +Thsbgo1Z5maIyvBJOKX5vQifaSF8kRtX9fZvipvzHCjYxvOHfaTvgtWyxHXCc3tK +DyBswsc2MeHiZ5g0KG113lwLrhcSwEsg5yo0eB7tOTQp1rmCiF6DQYs1XyOqD7P8 +S6clMgJWgpM8Ltw5mYALqDpShv1ND3AOJqENj/0tvdP7Y7cilG+s76HFXRcwKTRw +4rVP+Wr+t4WdXeS8cGxboQqGc40L3HNd5cxsbIM1kucfdrPBljWmyM9aiO1Nipm2 +8dyyir8AFnvoGQ6DPi58jVCCbqosL/GXtVk+IgJ+8eE5T8jvhxBovzxArSSVYIaj +ZxYi85ixfLr1DC5mg5CWWB8ZzmjaUwfyQAcL/F3Q11CkqHw1VDoDzvTBWbguBu6X +xXexlgOQx4/lr8X1pjbbAZktNTOYDt4dTuhrKPU35zW35wTnSBoPrQ3cpGlRcszE +IksZSHi41IQd0zUOGCNZYpPFq8mTwu5ECGHfNvWDH7zEuSkO54tS5Dukxqd8VIQl +h9GB2Uyel8tFm4s/Dx9+glKyvsXDJQz3JmFaB2wPyAPZ1KL4GFI5R0LjUVSFJapP +TO3Ia24naOu3qYXWQK6jGwaCbTT6tdhgNy8EI0aDmv2AgqOXycutMJXF5UqkDmwY +ZqpVdf/TrmBy42pk/C0vpqiy6E4N7WllxhiY2AekkA== +-----END CERTIFICATE----- diff --git a/test/hostname/Alice.key b/test/hostname/Alice.key new file mode 100644 index 000000000..fc37e7258 --- /dev/null +++ b/test/hostname/Alice.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAssZjFfVdAnadpgF8S+oeuHFi0JSrLprEH4WTSJnmvIy2E+/s +si6XhyBeBuyYrrIZaE5xEck4tcG6vQZVNwVWHV+rarz1xRcBVF8jpA2khkt7zAmV +GHfT2YNEyKckCtP+vo0lO8e4wxlVDIScr5lwfQoCsRtEvFokerhgfaFq03g51wMz +dU+MDRODPmtz9U6E9pNiA5JWdbeYIOrvRIjgMyu0H6Sl6ztjLBshh4ZvrUKjT+i7 +mb9Ltpxlt7qfot23kDry0FdGnosu830dypCFArw55oj55Jt+aOsJqhTl0zSFOlaE +/5eFrVPORSJHbZNYJAyztYsiojEW4/4ZUaihsQIDAQABAoIBAF3C9szZdwKHu38J +YGtgSuRpc235yx4SRbJSmECHlyBknEowl2+MSCSysR3okNtuxSyTl3HAm2GYTZw9 +6guFXPji6EB/AldwDV5213Z/QT698Bu/GtdOYWm/EyA5qQmUzhKabGDCCwEoFBcQ +piziyMCLs4W3y4ENtfw3H0REmIZ3s0XQRzuDdFCEMbr0Ij6EhP3hSD6es4PWTeHY +LSwoXm0WAxyZudJLhWZaBRxvl+TDY7nVV1jRPQ+ojMJjXfyPo+c2hbbS6luj++qH +6qO7fEpr8EXhO8/0/bPUi0ozE1LVy1kXtEwfszesU9r5XeBq7yTCIa7TTJ35Niwf +T7Ar9NECgYEA0L77+B/3dtedhsMdyiHpcxV6A77OIHsezh8uJzE+nQEgqvJ88N1W +BbF7YByYmaP1/dBrPI7ON52AnDOyo2lM7fVOwr7Ch12tpoa5HFb2WndKt6KokXi/ +Tk8+zoCCZICCv1mtfIaepRTmxAeyqaFthchAv1nc7ojS1BWeXMLa9usCgYEA2z6N +YD8wV44d/qIMaSDVJlusyp8pi9l0ddB591KOYHhJ5RRF1qEd0pshj9sW6pcGGJqf +XFHAkEr/ZIACJK65Y+AFcbgzhyqX8Vy9LLYzWtpFP4SpjH19pYDTHaXvWsIjBlNG +poxtGYCQ8Uedm8IhtrbUorElQVjPlmRGU14B2tMCgYAPQCTAd/VoZVBI7DBc+CVK +FyOW6nW8wcH6ZSTGED720YJFevnNzx3dxJ2y4+PyNZxfMr7i6bv/LC6dOtmuPp80 +M1vRtoYXxaxOIkGb5G6TJWv8BpIyLpQrcHayN4lPNmRW/oJCOsOUY/aIE9fltLl/ +sKWqVTJi6vQcMogjVskQiQKBgHcH7f+sLtXKTdSaLDzDW5X4vcZARXEs/YKdTiqN +wsjzZcMej5AoZyWZnc4Zd8ajeebPw+d+Zxqv7RqmOQOrbPGhhbMo+6jN4jJjVD27 +KgSQbno+z0J8O0QovfXhyiKvNg7QFZKEuRLYb1jftd0DuAQYHTe7D2v8CLAw/tFy +P3WLAoGABcEQEDUWqxfFFCed4mYSoOHvD44YMIzeMMOHXRnGGWug0WkULxzUV1L4 +fTFPCqo6xsn/F3i7xRFpIWXlOzjZKHvw16ZpeZBNcdPjyk7XifhafJYLuknRe1fZ +lzLjhmvizTpd9GQIUS+39aGwGE9JI3H0NAdNA4pvEdKlPhJnG5U= +-----END RSA PRIVATE KEY----- diff --git a/test/hostname/CertAuth.crt b/test/hostname/CertAuth.crt new file mode 100644 index 000000000..6c9d01ed0 --- /dev/null +++ b/test/hostname/CertAuth.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFADCCAuqgAwIBAgIBATALBgkqhkiG9w0BAQswEzERMA8GA1UEAxMIQ2VydEF1 +dGgwHhcNMTUwNTExMjI0NjQzWhcNMjUwNTExMjI0NjU0WjATMREwDwYDVQQDEwhD +ZXJ0QXV0aDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALcMByyynHsA ++K4PJwo5+XHygaEZAhPGvHiKQK2Cbc9NDm0ZTzx0rA/dRTZlvouhDyzcJHm+6R1F +j6zQv7iaSC3qQtJiPnPsfZ+/0XhFZ3fQWMnfDiGbZpF1kJF01ofB6vnsuocFC0zG +aGC+SZiLAzs+QMP3Bebw1elCBIeoN+8NWnRYmLsYIaYGJGBSbNo/lCpLTuinofUn +L3ehWEGv1INwpHnSVeN0Ml2GFe23d7PUlj/wNIHgUdpUR+KEJxIP3klwtsI3QpSH +c4VjWdf4aIcka6K3IFuw+K0PUh3xAAPnMpAQOtCZk0AhF5rlvUbevC6jADxpKxLp +OONmvCTer4LtyNURAoBH52vbK0r/DNcTpPEFV0IP66nXUFgkk0mRKsu8HTb4IOkC +X3K4mp18EiWUUtrHZAnNct0iIniDBqKK0yhSNhztG6VakVt/1WdQY9Ey3mNtxN1O +thqWFKdpKUzPKYC3P6PfVpiE7+VbWTLLXba+8BPe8BxWPsVkjJqGSGnCte4COusz +M8/7bbTgifwJfsepwFtZG53tvwjWlO46Exl30VoDNTaIGvs1fO0GqJlh2A7FN5F2 +S1rS5VYHtPK8QdmUSvyq+7JDBc1HNT5I2zsIQbNcLwDTZ5EsbU6QR7NHDJKxjv/w +bs3eTXJSSNcFD74wRU10pXjgE5wOFu9TAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIA +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQHazgZ3Puiuc6K2LzgcX5b6fAC +PzAfBgNVHSMEGDAWgBQHazgZ3Puiuc6K2LzgcX5b6fACPzALBgkqhkiG9w0BAQsD +ggIBAEmeNrSUhpHg1I8dtfqu9hCU/6IZThjtcFA+QcPkkMa+Z1k0SOtsgW8MdlcA +gCf5g5yQZ0DdpWM9nDB6xDIhQdccm91idHgf8wmpEHUj0an4uyn2ESCt8eqrAWf7 +AClYORCASTYfguJCxcfvwtI1uqaOeCxSOdmFay79UVitVsWeonbCRGsVgBDifJxw +G2oCQqoYAmXPM4J6syk5GHhB1O9MMq+g1+hOx9s+XHyTui9FL4V+IUO1ygVqEQB5 +PSiRBvcIsajSGVao+vK0gf2XfcXzqr3y3NhBky9rFMp1g+ykb2yWekV4WiROJlCj +TsWwWZDRyjiGahDbho/XW8JciouHZhJdjhmO31rqW3HdFviCTdXMiGk3GQIzz/Jg +P+enOaHXoY9lcxzDvY9z1BysWBgNvNrMnVge/fLP9o+a0a0PRIIVl8T0Ef3zeg1O +CLCSy/1Vae5Tx63ZTFvGFdOSusYkG9rlAUHXZE364JRCKzM9Bz0bM+t+LaO0MaEb +YoxcXEPU+gB2IvmARpInN3oHexR6ekuYHVTRGdWrdmuHFzc7eFwygRqTFdoCCU+G +QZEkd+lOEyv0zvQqYg+Jp0AEGz2B2zB53uBVECtn0EqrSdPtRzUBSByXVs6QhSXn +eVmy+z3U3MecP63X6oSPXekqSyZFuegXpNNuHkjNoL4ep2ix +-----END CERTIFICATE----- diff --git a/test/key/ourdomain.cer b/test/key/ourdomain.cer new file mode 100644 index 000000000..447cd3710 --- /dev/null +++ b/test/key/ourdomain.cer @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBDjANBgkqhkiG9w0BAQUFADCBmDELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRwwGgYDVQQKExNI +YXNoaUNvcnAgVGVzdCBDZXJ0MQwwCgYDVQQLEwNEZXYxFjAUBgNVBAMTDXRlc3Qu +aW50ZXJuYWwxIDAeBgkqhkiG9w0BCQEWEXRlc3RAaW50ZXJuYWwuY29tMB4XDTE2 +MDYwNDE4NTkxMFoXDTE3MDYwNDE4NTkxMFowgYkxGDAWBgNVBAMTD3Rlc3Rjby5p +bnRlcm5hbDETMBEGA1UECBMKQ2FsaWZvcm5pYTELMAkGA1UEBhMCVVMxHjAcBgkq +hkiG9w0BCQEWD3Rlc3RAdGVzdGNvLmNvbTEcMBoGA1UEChMTSGFzaGljb3JwIFRl +c3QgQ2VydDENMAsGA1UECxMEQmV0YTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzNCiwaIhJ26tqXzmwB+oJzstp41X8ygpsywCOzEuHgD+Dck2CLOhlJHmqO3b +sHfv87g3HtdJaF1tO1PQqdHxpJS3vyogUuP8iy6rHgkGtExWgsRxILtbpfzyG1Rt +TgO4pg7uOlCe1p5oDNnHj1YTgmhtRpB0x054DdSvjKVHY5UCAwEAAaOBqDCBpTAJ +BgNVHRMEAjAAMB0GA1UdDgQWBBTHULSXbpeHHFM+lq/HYq8m38a00TAfBgNVHSME +GDAWgBSj+es5+q9t57ZWSVUogWXJARu4lTALBgNVHQ8EBAMCBaAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMCwGA1UdHwQlMCMwIaAfoB2GG2h0dHA6Ly9w +YXRoLnRvLmNybC9teWNhLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAjJKtWTVyFk7u +WGQlXHRBgt0/qWjnHzVOXcVJxaL1uuddhY1mOsmHxuvbM5SqfvnyRf1Dbvmm4y+E +8hNn9lESmQKRiq1XX2H0/K8AXfNIglqLCB+Fx5GgLJ8jboDsuJsq7SxYHVj4NIOY +9gf83cwxpxAV9JuR9izBIs7afP75MvjjuveLgIpyNzbdCt9/71WQY3Wt0epYRWLD +DGeP4zEBR6KMu3vILWX3kRlpvX1OcfqvcRpzpNRDmpzzMwOHQBtBp3/7lf43yGLg +ZxWuUzWCN5RXZZHhsSNqlplXjcyCEyAPTxiE0hbAKhAJeWjadnUJ88SC41bGjDor +FU3JVe6aqw== +-----END CERTIFICATE----- diff --git a/test/key/ourdomain.csr b/test/key/ourdomain.csr new file mode 100644 index 000000000..149827cb3 --- /dev/null +++ b/test/key/ourdomain.csr @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB4DCCAUkCAQAwgZ8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh +MRQwEgYDVQQHEwtMb3MgQW5nZWxlczEcMBoGA1UEChMTSGFzaGljb3JwIFRlc3Qg +Q2VydDENMAsGA1UECxMEQmV0YTEYMBYGA1UEAxMPdGVzdGNvLmludGVybmFsMR4w +HAYJKoZIhvcNAQkBFg90ZXN0QHRlc3Rjby5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBAMzQosGiISdural85sAfqCc7LaeNV/MoKbMsAjsxLh4A/g3JNgiz +oZSR5qjt27B37/O4Nx7XSWhdbTtT0KnR8aSUt78qIFLj/Isuqx4JBrRMVoLEcSC7 +W6X88htUbU4DuKYO7jpQntaeaAzZx49WE4JobUaQdMdOeA3Ur4ylR2OVAgMBAAGg +ADANBgkqhkiG9w0BAQUFAAOBgQBvz0CFO6td/cc6MzyWXNgfiYqdvDvq9JoEvROS +CG6ZsOAJc15ePw9px4wYK2fQZQVZkSWl0vpIZNnIWAdQboAKAQmfnE6CPHc+5ePJ +LxFIL2vG/4UYnKdbR2dxSpNxyfF59tXwVDS6qeietaZz596F2D3cBwBflxJcK2nD +JkTOOA== +-----END CERTIFICATE REQUEST----- diff --git a/test/key/ourdomain.key b/test/key/ourdomain.key new file mode 100644 index 000000000..9e033369e --- /dev/null +++ b/test/key/ourdomain.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDM0KLBoiEnbq2pfObAH6gnOy2njVfzKCmzLAI7MS4eAP4NyTYI +s6GUkeao7duwd+/zuDce10loXW07U9Cp0fGklLe/KiBS4/yLLqseCQa0TFaCxHEg +u1ul/PIbVG1OA7imDu46UJ7WnmgM2cePVhOCaG1GkHTHTngN1K+MpUdjlQIDAQAB +AoGAJY4oGdtRuvpHa6mUYiwr2C24vF1lgBegNdNfAV2OOEA0VXDMsKHHggeSxO0A +eIY1gHoJ2WTed/2RluxSeaEebpwNp1e2V+JpJRVAF+PNBGng1WMIlK81vC6zZspm +AKe9O3XQZmIGKw1YyTOmYERAImjAarP0sKRAjkdfRTHEEcECQQD1W6ZtCJQvdDC/ +GKKEOUzIB8l/hUAQSHc0pEhctLWU1VnFGfhFFRnUr2FFrbnF5gvvz8GdQtEXXuzX +9NSlnrblAkEA1bLQNwRz77ZQoMHme/E13ITDoBl+YUWZVN7+n0DkUgu6JdRsmYh6 +nAIcoAS0CjxyNoPssDshdjyLcMxuT27+8QJAYCYEN+IOv/HWUGyE8y/JKPH0Qmaj +Tj43iIeAW+Ps7GAXB6g/pEK6lqALjFSh4i1eLCHMC6Ztba0jPxWQZz6lCQJBAJfD +oQmhmPChyssQSa62FGlixEecM1vDgOx8XdodzMjT2URqgyHagjDvcwLVtEMcwIQk +1uAlblM5FlJqbODu1BECQQC71WumyTo9/9Z9QR67WRd0yE1JHs3HUUvzw4JFNkRs +n0Tu7GZHDdzLaSodtC47tPJfYkdJwxBr20+RSCzF3VZF +-----END RSA PRIVATE KEY----- diff --git a/test/key/ssl-cert-snakeoil.key b/test/key/ssl-cert-snakeoil.key new file mode 100644 index 000000000..22cc4acb1 --- /dev/null +++ b/test/key/ssl-cert-snakeoil.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYVw5skn/3Ka72 +32ZaCrKtRVoQzan3tghq41KpQe3yZxIZbKy7sbwfdXnXVSwTAbq/3BYi9rya2t/v +W95yZh6JgfrLBvWl9Jo1EttZIxDhzCXGP+MPWm2KdNtHr84JznJbdxRpR0Jb4ykK +2d9dXbLJvCw8eEDFgOVGrj60USMir46sZFRvGWlMi+yHSOE+WQXaU40Dr0ZJqNvd +RNO9BtqpLaecZQaYTvlkyVdhjUE3+gQ0zEAQqpLcWi+zB5/IyR2+KwxDT3vAJumd +G7rIaGatPE8k0Ahb+zMKFFGYCoQ3sjbAbrQmrVtH4SU6ggl+CxpVdxshrK1W05Ms +WAiPw81/AgMBAAECggEAKjDIKlpjxGMHsTOeNV8yu2H0D6TcSefhOl885q9p5UU+ +nWC5Sx19b7EsYtdEcix7LCGS25y86YJX+8kx16OcvvpvW5ru2z+Zt1IHHxocl7yF +fWVGNd9Pz5m8jf12NClj2fyeKW3xPhROE8Srr/yu+nLNObnF//6EOEWRCv9r176C ++dzYvYVNPP48Ug7NpjQB94CBprtJyqvuoXvBPtpARXazVniYEhnzG1Gaj1TiCII5 ++emaMjKcWIEJ5stbBb3lUtqgm8bRNb/qcxoFfqTzHP+hbum9hbRz0KEIlAkm7uAv +S0TlyLuaj+gPQ+LwNX8EhGKUdlK/VM5bj2kq/tg3AQKBgQD/+A8ruHNa5nKGKNzP +dp+hXiL2sSzefMjDa2+sRJ0yftIMqYRfCJwzYumjfyycfCsu1LHainlQjSO6Kkgc +c0xVxnahWyPCQiqZuo9lLx4EVXCdRqWRg+pbyQhTSz90hfWEKD7XWsI8uRkOEnW8 +36FiyovGDFxl0esaKrFNSFdmgQKBgQDYXcSIRJk41f7vL6FVmchpUnVYoD75k9YT +FqEplNMw6gXcqbC2aNH5wj7EJlRboyVpjXV4N0d2Cz6AwREJpr/rYpq68AixXmVs +kTKwevoHm/tln7CN+CyIEy6KXdLp4KoWLFfSG6tHWRwIGFxWEGrrIZS6Eznu4GPe +V2yOnMkz/wKBgC6nXtSALP5PbGZJgl2J6HR3/PVru5rdsZX0ugjzBJfUh6JpL0hH +AHlZOO5k2pO3CgPiHnyPqqbk4rMmy7frx+kGYE7ulqjseGlGmKY/nT/69qij3L+W +BJwwGwVbfLhXRjWNRE7qKub4cbmf4bfIJtkjw7AYRqsERM6jI2fLnKqBAoGAUBzY +CkSsHxlNXa7bI+DfDfBUNs6OwsZ0e3jjj4vlbrUYGo5SOhgxtzKvHt26Wnvb/Gs+ +VZbSROkA6ZeTAWnWogdOl20NKu9yynIwvJusPGkK+qPYMZj0lCXWE7GNyL9A+xjM +I6XPE4nxESZD+jH2BL3YXdWEm+hF0iu4rE1tSm0CgYEAxssvvX7qcfTmxsp1YSHJ +H5j9ifkakci5W2VbCbdMtdOlgIlCFr2JYguaL98jx7WIJ4iH54ue/fbOdlkPCOsz +YGU4TceSRHeEJ7F6c67NOXm8j2TquAW2uYH87w07g2PIUwl/pp439qoDiThA6jEX +2ztyXgNUi7poqehPUoQuvC0= +-----END PRIVATE KEY----- diff --git a/test/key/ssl-cert-snakeoil.pem b/test/key/ssl-cert-snakeoil.pem new file mode 100644 index 000000000..b8ad2c8a6 --- /dev/null +++ b/test/key/ssl-cert-snakeoil.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICsjCCAZqgAwIBAgIJAMi7aUCplU3VMA0GCSqGSIb3DQEBBQUAMBExDzANBgNV +BAMTBnVidW50dTAeFw0xMjEyMDIwNDQ3MzBaFw0yMjExMzAwNDQ3MzBaMBExDzAN +BgNVBAMTBnVidW50dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhX +DmySf/cprvbfZloKsq1FWhDNqfe2CGrjUqlB7fJnEhlsrLuxvB91eddVLBMBur/c +FiL2vJra3+9b3nJmHomB+ssG9aX0mjUS21kjEOHMJcY/4w9abYp020evzgnOclt3 +FGlHQlvjKQrZ311dssm8LDx4QMWA5UauPrRRIyKvjqxkVG8ZaUyL7IdI4T5ZBdpT +jQOvRkmo291E070G2qktp5xlBphO+WTJV2GNQTf6BDTMQBCqktxaL7MHn8jJHb4r +DENPe8Am6Z0bushoZq08TyTQCFv7MwoUUZgKhDeyNsButCatW0fhJTqCCX4LGlV3 +GyGsrVbTkyxYCI/DzX8CAwEAAaMNMAswCQYDVR0TBAIwADANBgkqhkiG9w0BAQUF +AAOCAQEAQaS5yAih5NBV2edX1wkIQfAUElqmzoXvxsozDYy+P+S5tJeFXDSqzTAy +qkd/6qjkBdaARfKUJZeT/jRjqxoNtE9SR4PMOnD4zrqD26ujgZRVtPImbmVxCnMI +1B9LwvhpDHZuPGN5bPp3o+iDYea8zkS3Y31Ic889KSwKBDb1LlNogOdved+2DGd1 +yCxEErImbl4B0+QPrRk2bWbDfKhDfJ2FV+9kWIoEuCQBpr2tj1E5zvTadOVm5P2M +u7kjGl4w0GIAONiMC9l2TwMmPuG1jpM/WjQkG0sTKOCl7xQKgXBNJ78Wm2bfGtgb +shr/PNbS/EyISlUa07+zJtiRnr/EiQ== +-----END CERTIFICATE----- diff --git a/test/notes.txt b/test/notes.txt new file mode 100644 index 000000000..ae4f70982 --- /dev/null +++ b/test/notes.txt @@ -0,0 +1 @@ +Instructions from https://langui.sh/2009/01/18/openssl-self-signed-ca/ diff --git a/tlsutil/config.go b/tlsutil/config.go new file mode 100644 index 000000000..8d1ebc1bf --- /dev/null +++ b/tlsutil/config.go @@ -0,0 +1,238 @@ +package tlsutil + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "time" +) + +// Wrapper is a variant of DCWrapper, where the DC is provided as +// a constant value. This is usually done by currying DCWrapper. +type Wrapper func(conn net.Conn) (net.Conn, error) + +// Config used to create tls.Config +type Config struct { + // VerifyIncoming is used to verify the authenticity of incoming connections. + // This means that TCP requests are forbidden, only allowing for TLS. TLS connections + // must match a provided certificate authority. This can be used to force client auth. + VerifyIncoming bool + + // VerifyOutgoing is used to verify the authenticity of outgoing connections. + // This means that TLS requests are used, and TCP requests are not made. TLS connections + // must match a provided certificate authority. This is used to verify authenticity of + // server nodes. + VerifyOutgoing bool + + // VerifyServerHostname is used to enable hostname verification of servers. This + // ensures that the certificate presented is valid for server... + // This prevents a compromised client from being restarted as a server, and then + // intercepting request traffic as well as being added as a raft peer. This should be + // enabled by default with VerifyOutgoing, but for legacy reasons we cannot break + // existing clients. + VerifyServerHostname bool + + // CAFile is a path to a certificate authority file. This is used with VerifyIncoming + // or VerifyOutgoing to verify the TLS connection. + CAFile string + + // CertFile is used to provide a TLS certificate that is used for serving TLS connections. + // Must be provided to serve TLS connections. + CertFile string + + // KeyFile is used to provide a TLS key that is used for serving TLS connections. + // Must be provided to serve TLS connections. + KeyFile string + + // ServerName is used with the TLS certificate to ensure the name we + // provide matches the certificate + ServerName string +} + +// AppendCA opens and parses the CA file and adds the certificates to +// the provided CertPool. +func (c *Config) AppendCA(pool *x509.CertPool) error { + if c.CAFile == "" { + return nil + } + + // Read the file + data, err := ioutil.ReadFile(c.CAFile) + if err != nil { + return fmt.Errorf("Failed to read CA file: %v", err) + } + + if !pool.AppendCertsFromPEM(data) { + return fmt.Errorf("Failed to parse any CA certificates") + } + + return nil +} + +// KeyPair is used to open and parse a certificate and key file +func (c *Config) KeyPair() (*tls.Certificate, error) { + if c.CertFile == "" || c.KeyFile == "" { + return nil, nil + } + cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile) + if err != nil { + return nil, fmt.Errorf("Failed to load cert/key pair: %v", err) + } + return &cert, err +} + +// OutgoingTLSConfig generates a TLS configuration for outgoing +// requests. It will return a nil config if this configuration should +// not use TLS for outgoing connections. +func (c *Config) OutgoingTLSConfig() (*tls.Config, error) { + // If VerifyServerHostname is true, that implies VerifyOutgoing + if c.VerifyServerHostname { + c.VerifyOutgoing = true + } + if !c.VerifyOutgoing { + return nil, nil + } + // Create the tlsConfig + tlsConfig := &tls.Config{ + RootCAs: x509.NewCertPool(), + InsecureSkipVerify: true, + } + if c.ServerName != "" { + tlsConfig.ServerName = c.ServerName + tlsConfig.InsecureSkipVerify = false + } + + // Ensure we have a CA if VerifyOutgoing is set + if c.VerifyOutgoing && c.CAFile == "" { + return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!") + } + + // Parse the CA cert if any + err := c.AppendCA(tlsConfig.RootCAs) + if err != nil { + return nil, err + } + + // Add cert/key + cert, err := c.KeyPair() + if err != nil { + return nil, err + } else if cert != nil { + tlsConfig.Certificates = []tls.Certificate{*cert} + } + + return tlsConfig, nil +} + +// OutgoingTLSWrapper returns a a DCWrapper based on the OutgoingTLS +// configuration. If hostname verification is on, the wrapper +// will properly generate the dynamic server name for verification. +func (c *Config) OutgoingTLSWrapper() (Wrapper, error) { + // Get the TLS config + tlsConfig, err := c.OutgoingTLSConfig() + if err != nil { + return nil, err + } + + // Check if TLS is not enabled + if tlsConfig == nil { + return nil, nil + } + + wrapper := func(conn net.Conn) (net.Conn, error) { + return WrapTLSClient(conn, tlsConfig) + } + return wrapper, nil +} + +// Wrap a net.Conn into a client tls connection, performing any +// additional verification as needed. +// +// As of go 1.3, crypto/tls only supports either doing no certificate +// verification, or doing full verification including of the peer's +// DNS name. For consul, we want to validate that the certificate is +// signed by a known CA, but because consul doesn't use DNS names for +// node names, we don't verify the certificate DNS names. Since go 1.3 +// no longer supports this mode of operation, we have to do it +// manually. +func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { + var err error + var tlsConn *tls.Conn + + tlsConn = tls.Client(conn, tlsConfig) + + // If crypto/tls is doing verification, there's no need to do + // our own. + if tlsConfig.InsecureSkipVerify == false { + return tlsConn, nil + } + + if err = tlsConn.Handshake(); err != nil { + tlsConn.Close() + return nil, err + } + + // The following is lightly-modified from the doFullHandshake + // method in crypto/tls's handshake_client.go. + opts := x509.VerifyOptions{ + Roots: tlsConfig.RootCAs, + CurrentTime: time.Now(), + DNSName: "", + Intermediates: x509.NewCertPool(), + } + + certs := tlsConn.ConnectionState().PeerCertificates + for i, cert := range certs { + if i == 0 { + continue + } + opts.Intermediates.AddCert(cert) + } + + _, err = certs[0].Verify(opts) + if err != nil { + tlsConn.Close() + return nil, err + } + + return tlsConn, err +} + +// IncomingTLSConfig generates a TLS configuration for incoming requests +func (c *Config) IncomingTLSConfig() (*tls.Config, error) { + // Create the tlsConfig + tlsConfig := &tls.Config{ + ServerName: c.ServerName, + ClientCAs: x509.NewCertPool(), + ClientAuth: tls.NoClientCert, + } + + // Parse the CA cert if any + err := c.AppendCA(tlsConfig.ClientCAs) + if err != nil { + return nil, err + } + + // Add cert/key + cert, err := c.KeyPair() + if err != nil { + return nil, err + } else if cert != nil { + tlsConfig.Certificates = []tls.Certificate{*cert} + } + + // Check if we require verification + if c.VerifyIncoming { + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + if c.CAFile == "" { + return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!") + } + if cert == nil { + return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!") + } + } + + return tlsConfig, nil +} diff --git a/tlsutil/config_test.go b/tlsutil/config_test.go new file mode 100644 index 000000000..712ded2b7 --- /dev/null +++ b/tlsutil/config_test.go @@ -0,0 +1,424 @@ +package tlsutil + +import ( + "crypto/tls" + "crypto/x509" + "io" + "io/ioutil" + "net" + "testing" + + "github.com/hashicorp/yamux" +) + +func TestConfig_AppendCA_None(t *testing.T) { + conf := &Config{} + pool := x509.NewCertPool() + err := conf.AppendCA(pool) + if err != nil { + t.Fatalf("err: %v", err) + } + if len(pool.Subjects()) != 0 { + t.Fatalf("bad: %v", pool.Subjects()) + } +} + +func TestConfig_CACertificate_Valid(t *testing.T) { + conf := &Config{ + CAFile: "../test/ca/root.cer", + } + pool := x509.NewCertPool() + err := conf.AppendCA(pool) + if err != nil { + t.Fatalf("err: %v", err) + } + if len(pool.Subjects()) == 0 { + t.Fatalf("expected cert") + } +} + +func TestConfig_KeyPair_None(t *testing.T) { + conf := &Config{} + cert, err := conf.KeyPair() + if err != nil { + t.Fatalf("err: %v", err) + } + if cert != nil { + t.Fatalf("bad: %v", cert) + } +} + +func TestConfig_KeyPair_Valid(t *testing.T) { + conf := &Config{ + CertFile: "../test/key/ourdomain.cer", + KeyFile: "../test/key/ourdomain.key", + } + cert, err := conf.KeyPair() + if err != nil { + t.Fatalf("err: %v", err) + } + if cert == nil { + t.Fatalf("expected cert") + } +} + +func TestConfig_OutgoingTLS_MissingCA(t *testing.T) { + conf := &Config{ + VerifyOutgoing: true, + } + tls, err := conf.OutgoingTLSConfig() + if err == nil { + t.Fatalf("expected err") + } + if tls != nil { + t.Fatalf("bad: %v", tls) + } +} + +func TestConfig_OutgoingTLS_OnlyCA(t *testing.T) { + conf := &Config{ + CAFile: "../test/ca/root.cer", + } + tls, err := conf.OutgoingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tls != nil { + t.Fatalf("expected no config") + } +} + +func TestConfig_OutgoingTLS_VerifyOutgoing(t *testing.T) { + conf := &Config{ + VerifyOutgoing: true, + CAFile: "../test/ca/root.cer", + } + tls, err := conf.OutgoingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tls == nil { + t.Fatalf("expected config") + } + if len(tls.RootCAs.Subjects()) != 1 { + t.Fatalf("expect root cert") + } + if tls.ServerName != "" { + t.Fatalf("expect no server name verification") + } + if !tls.InsecureSkipVerify { + t.Fatalf("should skip built-in verification") + } +} + +func TestConfig_OutgoingTLS_ServerName(t *testing.T) { + conf := &Config{ + VerifyOutgoing: true, + CAFile: "../test/ca/root.cer", + ServerName: "consul.example.com", + } + tls, err := conf.OutgoingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tls == nil { + t.Fatalf("expected config") + } + if len(tls.RootCAs.Subjects()) != 1 { + t.Fatalf("expect root cert") + } + if tls.ServerName != "consul.example.com" { + t.Fatalf("expect server name") + } + if tls.InsecureSkipVerify { + t.Fatalf("should not skip built-in verification") + } +} + +func TestConfig_OutgoingTLS_VerifyHostname(t *testing.T) { + conf := &Config{ + VerifyServerHostname: true, + CAFile: "../test/ca/root.cer", + ServerName: "foo", + } + tls, err := conf.OutgoingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tls == nil { + t.Fatalf("expected config") + } + if len(tls.RootCAs.Subjects()) != 1 { + t.Fatalf("expect root cert") + } + if tls.ServerName != "foo" { + t.Fatalf("expect server name") + } + if tls.InsecureSkipVerify { + t.Fatalf("should not skip built-in verification") + } +} + +func TestConfig_OutgoingTLS_WithKeyPair(t *testing.T) { + conf := &Config{ + VerifyOutgoing: true, + CAFile: "../test/ca/root.cer", + CertFile: "../test/key/ourdomain.cer", + KeyFile: "../test/key/ourdomain.key", + } + tls, err := conf.OutgoingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tls == nil { + t.Fatalf("expected config") + } + if len(tls.RootCAs.Subjects()) != 1 { + t.Fatalf("expect root cert") + } + if !tls.InsecureSkipVerify { + t.Fatalf("should skip verification") + } + if len(tls.Certificates) != 1 { + t.Fatalf("expected client cert") + } +} + +func startTLSServer(config *Config) (net.Conn, chan error) { + errc := make(chan error, 1) + + tlsConfigServer, err := config.IncomingTLSConfig() + if err != nil { + errc <- err + return nil, errc + } + + client, server := net.Pipe() + + // Use yamux to buffer the reads, otherwise it's easy to deadlock + muxConf := yamux.DefaultConfig() + serverSession, _ := yamux.Server(server, muxConf) + clientSession, _ := yamux.Client(client, muxConf) + clientConn, _ := clientSession.Open() + serverConn, _ := serverSession.Accept() + + go func() { + tlsServer := tls.Server(serverConn, tlsConfigServer) + if err := tlsServer.Handshake(); err != nil { + errc <- err + } + close(errc) + // Because net.Pipe() is unbuffered, if both sides + // Close() simultaneously, we will deadlock as they + // both send an alert and then block. So we make the + // server read any data from the client until error or + // EOF, which will allow the client to Close(), and + // *then* we Close() the server. + io.Copy(ioutil.Discard, tlsServer) + tlsServer.Close() + }() + return clientConn, errc +} + +func TestConfig_outgoingWrapper_OK(t *testing.T) { + config := &Config{ + CAFile: "../test/hostname/CertAuth.crt", + CertFile: "../test/hostname/Alice.crt", + KeyFile: "../test/hostname/Alice.key", + VerifyServerHostname: true, + VerifyOutgoing: true, + ServerName: "server.dc1.consul", + } + + client, errc := startTLSServer(config) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + wrap, err := config.OutgoingTLSWrapper() + if err != nil { + t.Fatalf("OutgoingTLSWrapper err: %v", err) + } + + tlsClient, err := wrap(client) + if err != nil { + t.Fatalf("wrapTLS err: %v", err) + } + defer tlsClient.Close() + if err := tlsClient.(*tls.Conn).Handshake(); err != nil { + t.Fatalf("write err: %v", err) + } + + err = <-errc + if err != nil { + t.Fatalf("server: %v", err) + } +} + +func TestConfig_outgoingWrapper_BadCert(t *testing.T) { + t.SkipNow() + config := &Config{ + CAFile: "../test/ca/root.cer", + CertFile: "../test/key/ourdomain.cer", + KeyFile: "../test/key/ourdomain.key", + ServerName: "foo", + VerifyServerHostname: true, + VerifyOutgoing: true, + } + + client, errc := startTLSServer(config) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + wrap, err := config.OutgoingTLSWrapper() + if err != nil { + t.Fatalf("OutgoingTLSWrapper err: %v", err) + } + + tlsClient, err := wrap(client) + if err != nil { + t.Fatalf("wrapTLS err: %v", err) + } + defer tlsClient.Close() + err = tlsClient.(*tls.Conn).Handshake() + + if _, ok := err.(x509.HostnameError); !ok { + t.Fatalf("should get hostname err: %v", err) + } + + <-errc +} + +func TestConfig_wrapTLS_OK(t *testing.T) { + config := &Config{ + CAFile: "../test/ca/root.cer", + CertFile: "../test/key/ourdomain.cer", + KeyFile: "../test/key/ourdomain.key", + VerifyOutgoing: true, + } + + client, errc := startTLSServer(config) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + clientConfig, err := config.OutgoingTLSConfig() + if err != nil { + t.Fatalf("OutgoingTLSConfig err: %v", err) + } + + tlsClient, err := WrapTLSClient(client, clientConfig) + if err != nil { + t.Fatalf("wrapTLS err: %v", err) + } else { + tlsClient.Close() + } + err = <-errc + if err != nil { + t.Fatalf("server: %v", err) + } +} + +func TestConfig_wrapTLS_BadCert(t *testing.T) { + serverConfig := &Config{ + CertFile: "../test/key/ssl-cert-snakeoil.pem", + KeyFile: "../test/key/ssl-cert-snakeoil.key", + } + + client, errc := startTLSServer(serverConfig) + if client == nil { + t.Fatalf("startTLSServer err: %v", <-errc) + } + + clientConfig := &Config{ + CAFile: "../test/ca/root.cer", + VerifyOutgoing: true, + } + + clientTLSConfig, err := clientConfig.OutgoingTLSConfig() + if err != nil { + t.Fatalf("OutgoingTLSConfig err: %v", err) + } + + tlsClient, err := WrapTLSClient(client, clientTLSConfig) + if err == nil { + t.Fatalf("wrapTLS no err") + } + if tlsClient != nil { + t.Fatalf("returned a client") + } + + err = <-errc + if err != nil { + t.Fatalf("server: %v", err) + } +} + +func TestConfig_IncomingTLS(t *testing.T) { + conf := &Config{ + VerifyIncoming: true, + CAFile: "../test/ca/root.cer", + CertFile: "../test/key/ourdomain.cer", + KeyFile: "../test/key/ourdomain.key", + } + tlsC, err := conf.IncomingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tlsC == nil { + t.Fatalf("expected config") + } + if len(tlsC.ClientCAs.Subjects()) != 1 { + t.Fatalf("expect client cert") + } + if tlsC.ClientAuth != tls.RequireAndVerifyClientCert { + t.Fatalf("should not skip verification") + } + if len(tlsC.Certificates) != 1 { + t.Fatalf("expected client cert") + } +} + +func TestConfig_IncomingTLS_MissingCA(t *testing.T) { + conf := &Config{ + VerifyIncoming: true, + CertFile: "../test/key/ourdomain.cer", + KeyFile: "../test/key/ourdomain.key", + } + _, err := conf.IncomingTLSConfig() + if err == nil { + t.Fatalf("expected err") + } +} + +func TestConfig_IncomingTLS_MissingKey(t *testing.T) { + conf := &Config{ + VerifyIncoming: true, + CAFile: "../test/ca/root.cer", + } + _, err := conf.IncomingTLSConfig() + if err == nil { + t.Fatalf("expected err") + } +} + +func TestConfig_IncomingTLS_NoVerify(t *testing.T) { + conf := &Config{} + tlsC, err := conf.IncomingTLSConfig() + if err != nil { + t.Fatalf("err: %v", err) + } + if tlsC == nil { + t.Fatalf("expected config") + } + if len(tlsC.ClientCAs.Subjects()) != 0 { + t.Fatalf("do not expect client cert") + } + if tlsC.ClientAuth != tls.NoClientCert { + t.Fatalf("should skip verification") + } + if len(tlsC.Certificates) != 0 { + t.Fatalf("unexpected client cert") + } +}