Skip to content

Commit

Permalink
Fix certificate pinning though proxy
Browse files Browse the repository at this point in the history
When using proxy DialTLS is not available, instead
“VerifyPeerCertificate” should be used.
  • Loading branch information
buger committed May 22, 2018
1 parent e353a70 commit aa7e81f
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 150 deletions.
33 changes: 33 additions & 0 deletions cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,39 @@ func getUpstreamCertificate(host string, spec *APISpec) (cert *tls.Certificate)
return certs[0]
}

func verifyPeerCertificatePinnedCheck(spec *APISpec, tlsConfig *tls.Config) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if (spec == nil || len(spec.PinnedPublicKeys) == 0) && len(config.Global().Security.PinnedPublicKeys) == 0 {
return nil
}

tlsConfig.InsecureSkipVerify = true

whitelist := getPinnedPublicKeys("*", spec)
if len(whitelist) == 0 {
return nil
}

return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
for _, rawCert := range rawCerts {
cert, _ := x509.ParseCertificate(rawCert)
pub, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
if err != nil {
continue
}

fingerprint := certs.HexSHA256(pub)

for _, w := range whitelist {
if w == fingerprint {
return nil
}
}
}

return errors.New("Certificate public key pinning error. Public keys do not match.")
}
}

func dialTLSPinnedCheck(spec *APISpec, tc *tls.Config) func(network, addr string) (net.Conn, error) {
if (spec == nil || len(spec.PinnedPublicKeys) == 0) && len(config.Global().Security.PinnedPublicKeys) == 0 {
return nil
Expand Down
212 changes: 212 additions & 0 deletions cert_go1.10_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// +build go1.10

package main

import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"net/http"
"net/http/httptest"
"testing"

"github.com/TykTechnologies/tyk/certs"
"github.com/TykTechnologies/tyk/config"
"github.com/TykTechnologies/tyk/test"
)

func TestPublicKeyPinning(t *testing.T) {
_, _, _, serverCert := genServerCertificate()
x509Cert, _ := x509.ParseCertificate(serverCert.Certificate[0])
pubDer, _ := x509.MarshalPKIXPublicKey(x509Cert.PublicKey)
pubPem := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubDer})
pubID, _ := CertificateManager.Add(pubPem, "")
defer CertificateManager.Delete(pubID)

if pubID != certs.HexSHA256(pubDer) {
t.Error("Certmanager returned wrong pub key fingerprint:", certs.HexSHA256(pubDer), pubID)
}

upstream := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
}))
upstream.TLS = &tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{serverCert},
}

upstream.StartTLS()
defer upstream.Close()

t.Run("Pub key match", func(t *testing.T) {
globalConf := config.Global()
// For host using pinning, it should ignore standard verification in all cases, e.g setting variable below does nothing
globalConf.ProxySSLInsecureSkipVerify = false
config.SetGlobal(globalConf)
defer resetTestConfig()

ts := newTykTestServer()
defer ts.Close()

buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.PinnedPublicKeys = map[string]string{"127.0.0.1": pubID}
spec.Proxy.TargetURL = upstream.URL
})

ts.Run(t, test.TestCase{Code: 200})
})

t.Run("Pub key not match", func(t *testing.T) {
ts := newTykTestServer()
defer ts.Close()

buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.PinnedPublicKeys = map[string]string{"127.0.0.1": "wrong"}
spec.Proxy.TargetURL = upstream.URL
})

ts.Run(t, test.TestCase{Code: 500})
})

t.Run("Global setting", func(t *testing.T) {
globalConf := config.Global()
globalConf.Security.PinnedPublicKeys = map[string]string{"127.0.0.1": "wrong"}
config.SetGlobal(globalConf)
defer resetTestConfig()

ts := newTykTestServer()
defer ts.Close()

buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.Proxy.TargetURL = upstream.URL
})

ts.Run(t, test.TestCase{Code: 500})
})

t.Run("Though proxy", func(t *testing.T) {
_, _, _, proxyCert := genServerCertificate()
proxy := initProxy("https", &tls.Config{
Certificates: []tls.Certificate{proxyCert},
})

globalConf := config.Global()
globalConf.ProxySSLInsecureSkipVerify = true
config.SetGlobal(globalConf)
defer resetTestConfig()

defer proxy.Stop()

ts := newTykTestServer()
defer ts.Close()

buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.Proxy.TargetURL = upstream.URL
spec.Proxy.Transport.ProxyURL = proxy.URL
spec.PinnedPublicKeys = map[string]string{"*": "wrong"}
})

ts.Run(t, test.TestCase{Code: 500})
})
}

func TestProxyTransport(t *testing.T) {
upstream := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test"))
}))
defer upstream.Close()

globalConf := config.Global()
globalConf.ProxySSLInsecureSkipVerify = true
// force creating new transport on each reque
globalConf.MaxConnTime = -1
config.SetGlobal(globalConf)
defer resetTestConfig()

ts := newTykTestServer()
defer ts.Close()

//matching ciphers
t.Run("Global: Cipher match", func(t *testing.T) {
globalConf.ProxySSLCipherSuites = []string{"TLS_RSA_WITH_AES_128_CBC_SHA"}
config.SetGlobal(globalConf)
buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.Proxy.TargetURL = upstream.URL
})
ts.Run(t, test.TestCase{Path: "/", Code: 200})
})

t.Run("Global: Cipher not match", func(t *testing.T) {
globalConf.ProxySSLCipherSuites = []string{"TLS_RSA_WITH_RC4_128_SHA"}
config.SetGlobal(globalConf)
buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.Proxy.TargetURL = upstream.URL
})
ts.Run(t, test.TestCase{Path: "/", Code: 500})
})

t.Run("API: Cipher override", func(t *testing.T) {
globalConf.ProxySSLCipherSuites = []string{"TLS_RSA_WITH_RC4_128_SHA"}
config.SetGlobal(globalConf)
buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.Proxy.TargetURL = upstream.URL
spec.Proxy.Transport.SSLCipherSuites = []string{"TLS_RSA_WITH_AES_128_CBC_SHA"}
})

ts.Run(t, test.TestCase{Path: "/", Code: 200})
})

t.Run("API: MinTLS not match", func(t *testing.T) {
globalConf.ProxySSLMinVersion = 772
config.SetGlobal(globalConf)
buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.Proxy.TargetURL = upstream.URL
spec.Proxy.Transport.SSLCipherSuites = []string{"TLS_RSA_WITH_AES_128_CBC_SHA"}
})

ts.Run(t, test.TestCase{Path: "/", Code: 500})
})

t.Run("API: Invalid proxy", func(t *testing.T) {
globalConf.ProxySSLMinVersion = 771
config.SetGlobal(globalConf)
buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.Proxy.TargetURL = upstream.URL
spec.Proxy.Transport.SSLCipherSuites = []string{"TLS_RSA_WITH_AES_128_CBC_SHA"}
// Invalid proxy
spec.Proxy.Transport.ProxyURL = upstream.URL
})

ts.Run(t, test.TestCase{Path: "/", Code: 500})
})

t.Run("API: Valid proxy", func(t *testing.T) {
_, _, _, proxyCert := genServerCertificate()
proxy := initProxy("https", &tls.Config{
Certificates: []tls.Certificate{proxyCert},
})
defer proxy.Stop()

buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.ListenPath = "/"
spec.Proxy.TargetURL = upstream.URL
spec.Proxy.Transport.SSLCipherSuites = []string{"TLS_RSA_WITH_AES_128_CBC_SHA"}
// Invalid proxy
spec.Proxy.Transport.ProxyURL = proxy.URL
})

client := getTLSClient(nil, nil)
client.Transport = &http.Transport{
TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper),
}
ts.Run(t, test.TestCase{Path: "/", Code: 200, Client: client})
})
}
Loading

0 comments on commit aa7e81f

Please sign in to comment.