diff --git a/irc/config.go b/irc/config.go index ba3dcc76..35313f02 100644 --- a/irc/config.go +++ b/irc/config.go @@ -55,12 +55,15 @@ type TLSListenConfig struct { // This is the YAML-deserializable type of the value of the `Server.Listeners` map type listenerConfigBlock struct { - TLS TLSListenConfig - Proxy bool - Tor bool - STSOnly bool `yaml:"sts-only"` - WebSocket bool - HideSTS bool `yaml:"hide-sts"` + // normal TLS configuration, with a single certificate: + TLS TLSListenConfig + // SNI configuration, with multiple certificates: + TLSCertificates []TLSListenConfig `yaml:"tls-certificates"` + Proxy bool + Tor bool + STSOnly bool `yaml:"sts-only"` + WebSocket bool + HideSTS bool `yaml:"hide-sts"` } type HistoryCutoff uint @@ -537,11 +540,10 @@ type Config struct { passwordBytes []byte Name string nameCasefolded string - // Listeners is the new style for configuring listeners: - Listeners map[string]listenerConfigBlock - UnixBindMode os.FileMode `yaml:"unix-bind-mode"` - TorListeners TorListenersConfig `yaml:"tor-listeners"` - WebSockets struct { + Listeners map[string]listenerConfigBlock + UnixBindMode os.FileMode `yaml:"unix-bind-mode"` + TorListeners TorListenersConfig `yaml:"tor-listeners"` + WebSockets struct { AllowedOrigins []string `yaml:"allowed-origins"` allowedOriginRegexps []*regexp.Regexp } @@ -846,13 +848,30 @@ func (conf *Config) Operators(oc map[string]*OperClass) (map[string]*Oper, error return operators, nil } -func loadTlsConfig(config TLSListenConfig, webSocket bool) (tlsConfig *tls.Config, err error) { - cert, err := loadCertWithLeaf(config.Cert, config.Key) - if err != nil { - return nil, &CertKeyError{Err: err} +func loadTlsConfig(config listenerConfigBlock) (tlsConfig *tls.Config, err error) { + var certificates []tls.Certificate + if len(config.TLSCertificates) != 0 { + // SNI configuration with multiple certificates + for _, certPairConf := range config.TLSCertificates { + cert, err := loadCertWithLeaf(certPairConf.Cert, certPairConf.Key) + if err != nil { + return nil, err + } + certificates = append(certificates, cert) + } + } else if config.TLS.Cert != "" { + // normal configuration with one certificate + cert, err := loadCertWithLeaf(config.TLS.Cert, config.TLS.Key) + if err != nil { + return nil, err + } + certificates = append(certificates, cert) + } else { + // plaintext! + return nil, nil } clientAuth := tls.RequestClientCert - if webSocket { + if config.WebSocket { // if Chrome receives a server request for a client certificate // on a websocket connection, it will immediately disconnect: // https://bugs.chromium.org/p/chromium/issues/detail?id=329884 @@ -860,7 +879,7 @@ func loadTlsConfig(config TLSListenConfig, webSocket bool) (tlsConfig *tls.Confi clientAuth = tls.NoClientCert } result := tls.Config{ - Certificates: []tls.Certificate{cert}, + Certificates: certificates, ClientAuth: clientAuth, } return &result, nil @@ -895,12 +914,9 @@ func (conf *Config) prepareListeners() (err error) { if lconf.STSOnly && !conf.Server.STS.Enabled { return fmt.Errorf("%s is configured as a STS-only listener, but STS is disabled", addr) } - if block.TLS.Cert != "" { - tlsConfig, err := loadTlsConfig(block.TLS, block.WebSocket) - if err != nil { - return err - } - lconf.TLSConfig = tlsConfig + lconf.TLSConfig, err = loadTlsConfig(block) + if err != nil { + return &CertKeyError{Err: err} } lconf.RequireProxy = block.TLS.Proxy || block.Proxy lconf.WebSocket = block.WebSocket