Skip to content

Commit

Permalink
transport: deny incoming peer certs with wrong IP SAN
Browse files Browse the repository at this point in the history
  • Loading branch information
Anthony Romano committed Apr 7, 2017
1 parent d51d381 commit 39e32e9
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pkg/transport/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func wrapTLS(addr, scheme string, tlscfg *tls.Config, l net.Listener) (net.Liste
l.Close()
return nil, fmt.Errorf("cannot listen on TLS for %s: KeyFile and CertFile are not presented", scheme+"://"+addr)
}
return tls.NewListener(l, tlscfg), nil
return newTLSListener(l, tlscfg), nil
}

type TLSInfo struct {
Expand Down
124 changes: 124 additions & 0 deletions pkg/transport/tls_listener.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package transport

import (
"crypto/tls"
"net"
"sync"
)

// tlsListener overrides a TLS listener so it will reject client
// certificates with insufficient SAN credentials.
type tlsListener struct {
net.Listener
connc chan net.Conn
donec chan struct{}
err error
}

func newTLSListener(l net.Listener, tlscfg *tls.Config) net.Listener {
tlsl := &tlsListener{
Listener: tls.NewListener(l, tlscfg),
connc: make(chan net.Conn),
donec: make(chan struct{}),
}
go tlsl.acceptLoop()
return tlsl
}

func (l *tlsListener) Accept() (net.Conn, error) {
select {
case conn := <-l.connc:
return conn, nil
case <-l.donec:
return nil, l.err
}
}

// acceptLoop launches each TLS handshake in a separate goroutine
// to prevent a hanging TLS connection from blocking other connections.
func (l *tlsListener) acceptLoop() {
var wg sync.WaitGroup
var pendingMu sync.Mutex

pending := make(map[net.Conn]struct{})
stopc := make(chan struct{})
defer func() {
close(stopc)
pendingMu.Lock()
for c := range pending {
c.Close()
}
pendingMu.Unlock()
wg.Wait()
close(l.donec)
}()

for {
conn, err := l.Listener.Accept()
if err != nil {
l.err = err
return
}

pendingMu.Lock()
pending[conn] = struct{}{}
pendingMu.Unlock()

wg.Add(1)
go func() {
defer func() {
if conn != nil {
conn.Close()
}
wg.Done()
}()

tlsConn := conn.(*tls.Conn)
herr := tlsConn.Handshake()
pendingMu.Lock()
delete(pending, conn)
pendingMu.Unlock()
if herr != nil {
return
}

st := tlsConn.ConnectionState()
if len(st.PeerCertificates) > 0 {
cert := st.PeerCertificates[0]
if len(cert.IPAddresses) > 0 || len(cert.DNSNames) > 0 {
addr := tlsConn.RemoteAddr().String()
h, _, herr := net.SplitHostPort(addr)
if herr != nil || cert.VerifyHostname(h) != nil {
return
}
}
}

select {
case l.connc <- tlsConn:
conn = nil
case <-stopc:
}
}()
}
}

func (l *tlsListener) Close() error {
err := l.Listener.Close()
<-l.donec
return err
}

0 comments on commit 39e32e9

Please sign in to comment.