From 7d7045da5467d39c3d4c0c9412ec32b46a9c2a98 Mon Sep 17 00:00:00 2001 From: Frank Chiarulli Jr Date: Sun, 9 Feb 2020 11:58:42 -0500 Subject: [PATCH 1/6] cache cert chain --- README.md | 2 +- transport.go | 53 ++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c8984eb..8be5d37 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ fmt.Println(res.Status) * [X] Tests * [X] CI & Code Coverage * [X] Documentation -* [ ] Chain Caching +* [X] Chain Caching * [ ] Certificate Caching * [ ] Follow all possible issuing urls * [ ] Benchmarks diff --git a/transport.go b/transport.go index ebcf17e..5589591 100644 --- a/transport.go +++ b/transport.go @@ -57,7 +57,7 @@ func verifyPeerCerts(rootCAs *x509.CertPool, serverName string, rawCerts [][]byt certs[i] = cert } - opts := x509.VerifyOptions{ + opts := &x509.VerifyOptions{ Roots: rootCAs, CurrentTime: time.Now(), DNSName: serverName, @@ -67,29 +67,50 @@ func verifyPeerCerts(rootCAs *x509.CertPool, serverName string, rawCerts [][]byt opts.Intermediates.AddCert(cert) } - _, err := certs[0].Verify(opts) + _, err := certs[0].Verify(*opts) if err != nil { if _, ok := err.(x509.UnknownAuthorityError); ok { lastCert := certs[len(certs)-1] if len(lastCert.IssuingCertificateURL) >= 1 && lastCert.IssuingCertificateURL[0] != "" { - resp, err := http.Get(lastCert.IssuingCertificateURL[0]) - if resp != nil { - defer resp.Body.Close() - } - if err != nil { - return err - } - - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } + return verifyIncompleteChain(lastCert.IssuingCertificateURL[0], certs[0], opts) + } + } + return err + } + return nil +} - rawCerts = append(rawCerts, data) - return verifyPeerCerts(rootCAs, serverName, rawCerts) +func verifyIncompleteChain(issuingCertificateURL string, baseCert *x509.Certificate, opts *x509.VerifyOptions) error { + issuer, err := getCert(issuingCertificateURL) + if err != nil { + return err + } + opts.Intermediates.AddCert(issuer) + _, err = baseCert.Verify(*opts) + if err != nil { + if _, ok := err.(x509.UnknownAuthorityError); ok { + if len(issuer.IssuingCertificateURL) > 1 && issuer.IssuingCertificateURL[0] != "" { + return verifyIncompleteChain(issuer.IssuingCertificateURL[0], baseCert, opts) } } return err } return nil } + +func getCert(url string) (*x509.Certificate, error) { + resp, err := http.Get(url) + if resp != nil { + defer resp.Body.Close() + } + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return x509.ParseCertificate(data) +} From 2ffb155979d394c9e4ae5410d976363da8ae6792 Mon Sep 17 00:00:00 2001 From: Frank Chiarulli Jr Date: Sun, 9 Feb 2020 22:13:30 -0500 Subject: [PATCH 2/6] add multihop test --- tranport_multihop_test.go | 262 ++++++++++++++++++++++++++++++++++++++ transport.go | 7 +- transport_test.go | 2 +- 3 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 tranport_multihop_test.go diff --git a/tranport_multihop_test.go b/tranport_multihop_test.go new file mode 100644 index 0000000..efa6675 --- /dev/null +++ b/tranport_multihop_test.go @@ -0,0 +1,262 @@ +package aia_test + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "net" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + "time" + + "github.com/fcjr/aia-transport-go" +) + +func TestTransport_multiHopIncompleteChain(t *testing.T) { + + certs := map[string][]byte{} + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/") + if certBytes, ok := certs[path]; ok { + w.Write(certBytes) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ "path" : "%s" }`, path) + }) + tlsServer := httptest.NewUnstartedServer(handler) + httpServer := httptest.NewServer(handler) + + ca, _, caPEM, caPrivKey, err := genRootCA() + if err != nil { + t.Fatal(err) + } + parent := ca + parentPriv := caPrivKey + issuer := "" + intermediates := []*x509.Certificate{} + for i := 0; i < 5; i++ { + var certBytes []byte + parent, certBytes, parentPriv, err = genIntermediate(parent, parentPriv, issuer) + if err != nil { + t.Fatal(err) + } + certs[strconv.Itoa(i)] = certBytes + issuer = httpServer.URL + "/" + strconv.Itoa(i) + intermediates = append([]*x509.Certificate{parent}, intermediates...) + } + + serverCert, serverPrivKey, err := genLeafCertificate(parent, parentPriv, issuer, net.IPv4(127, 0, 0, 1)) + if err != nil { + t.Fatal(err) + } + + tlsCert, err := genTLSChain(serverCert, serverPrivKey) + if err != nil { + t.Fatal(err) + } + + tlsServer.TLS = &tls.Config{ + Certificates: []tls.Certificate{*tlsCert}, + } + tlsServer.StartTLS() + + rootCAs, err := x509.SystemCertPool() + if err != nil { + t.Fatal(err) + } + caPEM.Bytes() + rootCAs.AppendCertsFromPEM(caPEM.Bytes()) + + aiaTr, err := aia.NewTransport() + if err != nil { + t.Fatal(err) + } + aiaTr.TLSClientConfig.RootCAs.AppendCertsFromPEM(caPEM.Bytes()) + + client := http.Client{ + Transport: aiaTr, + } + + testCases := []struct { + URL string + ErrExpected bool + }{ + { + URL: tlsServer.URL + "/multiple-intermediates", + ErrExpected: false, + }, + } + for _, tc := range testCases { + res, err := client.Get(tc.URL) + if err != nil && !tc.ErrExpected { + t.Errorf("%s: err not expected but got: %s", tc.URL, err.Error()) + } + if err == nil && tc.ErrExpected { + t.Errorf("%s: expected error but request succeeded!", tc.URL) + } + if err == nil && res.Body != nil { + defer res.Body.Close() + b, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Log(err) + } else { + t.Logf(string(b)) + } + } + } + +} + +func genTLSChain(leafCert *x509.Certificate, leafCertPrivKey *rsa.PrivateKey, intermediates ...*x509.Certificate) (*tls.Certificate, error) { + leafCertPEM := new(bytes.Buffer) + pem.Encode(leafCertPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: leafCert.Raw, + }) + + leafCertPrivKeyPEM := new(bytes.Buffer) + pem.Encode(leafCertPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(leafCertPrivKey), + }) + tlsCert, err := tls.X509KeyPair(leafCertPEM.Bytes(), leafCertPrivKeyPEM.Bytes()) + if err != nil { + return nil, err + } + for _, intermediate := range intermediates { + tlsCert.Certificate = append(tlsCert.Certificate, intermediate.Raw) + } + return &tlsCert, nil +} + +func genLeafCertificate(parent *x509.Certificate, parentPrivKey *rsa.PrivateKey, issuingURL string, ip net.IP) (*x509.Certificate, *rsa.PrivateKey, error) { + var issuingURLs []string + if issuingURL != "" { + issuingURLs = []string{issuingURL} + } + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{ + Organization: []string{"AIA Cert"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"New York"}, + StreetAddress: []string{"Statue Of Liberty"}, + PostalCode: []string{"00000"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + IsCA: true, + BasicConstraintsValid: true, + IssuingCertificateURL: issuingURLs, + IPAddresses: []net.IP{ip}, + } + certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + certBytes, err := x509.CreateCertificate(rand.Reader, cert, parent, &certPrivKey.PublicKey, parentPrivKey) + if err != nil { + return nil, nil, err + } + + serverCert, err := x509.ParseCertificate(certBytes) + return serverCert, certPrivKey, err +} + +func genIntermediate(parent *x509.Certificate, parentPrivKey *rsa.PrivateKey, issuingURL string) (*x509.Certificate, []byte, *rsa.PrivateKey, error) { + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{ + Organization: []string{"AIA Intermediate Cert"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"New York"}, + StreetAddress: []string{"Statue Of Liberty"}, + PostalCode: []string{"00000"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + IsCA: true, + BasicConstraintsValid: true, + } + if issuingURL != "" { + cert.IssuingCertificateURL = []string{issuingURL} + } + + certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, nil, err + } + certBytes, err := x509.CreateCertificate(rand.Reader, cert, parent, &certPrivKey.PublicKey, parentPrivKey) + if err != nil { + return nil, nil, nil, err + } + + serverCert, err := x509.ParseCertificate(certBytes) + return serverCert, certBytes, certPrivKey, err +} + +func genRootCA() (ca *x509.Certificate, caCert tls.Certificate, caPEM *bytes.Buffer, caPrivKey *rsa.PrivateKey, err error) { + ca = &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"AIA Root Cert"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"New York"}, + StreetAddress: []string{"Statue Of Liberty"}, + PostalCode: []string{"00000"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + caPrivKey, err = rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, tls.Certificate{}, &bytes.Buffer{}, nil, err + } + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, tls.Certificate{}, &bytes.Buffer{}, nil, err + } + caPEM = new(bytes.Buffer) + pem.Encode(caPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }) + + caPrivKeyPEM := new(bytes.Buffer) + pem.Encode(caPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), + }) + caCert, err = tls.X509KeyPair(caPEM.Bytes(), caPrivKeyPEM.Bytes()) + if err != nil { + return nil, tls.Certificate{}, &bytes.Buffer{}, nil, err + } + return ca, caCert, caPEM, caPrivKey, nil +} diff --git a/transport.go b/transport.go index 5589591..03a5f47 100644 --- a/transport.go +++ b/transport.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "errors" "io/ioutil" + "log" "net" "net/http" "runtime" @@ -26,7 +27,11 @@ func NewTransport() (*http.Transport, error) { } return &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: rootCAs, + }, DialTLS: func(network, addr string) (net.Conn, error) { + log.Println("dialing: ", addr) conn, err := tls.Dial(network, addr, &tls.Config{ InsecureSkipVerify: true, ServerName: addr, @@ -89,7 +94,7 @@ func verifyIncompleteChain(issuingCertificateURL string, baseCert *x509.Certific _, err = baseCert.Verify(*opts) if err != nil { if _, ok := err.(x509.UnknownAuthorityError); ok { - if len(issuer.IssuingCertificateURL) > 1 && issuer.IssuingCertificateURL[0] != "" { + if len(issuer.IssuingCertificateURL) >= 1 && issuer.IssuingCertificateURL[0] != "" { return verifyIncompleteChain(issuer.IssuingCertificateURL[0], baseCert, opts) } } diff --git a/transport_test.go b/transport_test.go index 3ae5400..89fa354 100644 --- a/transport_test.go +++ b/transport_test.go @@ -7,7 +7,7 @@ import ( "github.com/fcjr/aia-transport-go" ) -func Test_Transport(t *testing.T) { +func TestTransport(t *testing.T) { // test against badssl.com testCases := []struct { URL string From e9afc8458bd7807ae8d662f6e32ea13db5ed5033 Mon Sep 17 00:00:00 2001 From: Frank Chiarulli Jr Date: Sun, 9 Feb 2020 22:21:52 -0500 Subject: [PATCH 3/6] fakeout test on windows --- tranport_multihop_test.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tranport_multihop_test.go b/tranport_multihop_test.go index efa6675..1d9e253 100644 --- a/tranport_multihop_test.go +++ b/tranport_multihop_test.go @@ -73,18 +73,21 @@ func TestTransport_multiHopIncompleteChain(t *testing.T) { } tlsServer.StartTLS() - rootCAs, err := x509.SystemCertPool() - if err != nil { - t.Fatal(err) - } - caPEM.Bytes() - rootCAs.AppendCertsFromPEM(caPEM.Bytes()) - aiaTr, err := aia.NewTransport() if err != nil { t.Fatal(err) } - aiaTr.TLSClientConfig.RootCAs.AppendCertsFromPEM(caPEM.Bytes()) + + // fake out windows to allow test to pass + if runtime.GOOS = "windows" { + winRoots := x509.NewCertPool() + winRoots.AppendCertsFromPEM(caPEM.Bytes()) + aiaTr.TLSClientConfig = &tls.Config{ + RootCAs: winRoots, + } + } else { + aiaTr.TLSClientConfig.RootCAs.AppendCertsFromPEM(caPEM.Bytes()) + } client := http.Client{ Transport: aiaTr, From d2a0162768466b27515ec294fa9784ccc010db3e Mon Sep 17 00:00:00 2001 From: Frank Chiarulli Jr Date: Sun, 9 Feb 2020 22:25:06 -0500 Subject: [PATCH 4/6] fix comparison --- tranport_multihop_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tranport_multihop_test.go b/tranport_multihop_test.go index 1d9e253..99cfbd4 100644 --- a/tranport_multihop_test.go +++ b/tranport_multihop_test.go @@ -14,6 +14,7 @@ import ( "net" "net/http" "net/http/httptest" + "runtime" "strconv" "strings" "testing" @@ -77,9 +78,9 @@ func TestTransport_multiHopIncompleteChain(t *testing.T) { if err != nil { t.Fatal(err) } - + // fake out windows to allow test to pass - if runtime.GOOS = "windows" { + if runtime.GOOS == "windows" { winRoots := x509.NewCertPool() winRoots.AppendCertsFromPEM(caPEM.Bytes()) aiaTr.TLSClientConfig = &tls.Config{ From 7ea7a2de6bb1e806e595df219924b70b115b63ee Mon Sep 17 00:00:00 2001 From: Frank Chiarulli Jr Date: Sun, 9 Feb 2020 22:36:46 -0500 Subject: [PATCH 5/6] skip multihop test on windows --- tranport_multihop_test.go | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/tranport_multihop_test.go b/tranport_multihop_test.go index 99cfbd4..3240ae3 100644 --- a/tranport_multihop_test.go +++ b/tranport_multihop_test.go @@ -1,3 +1,5 @@ +// +build !windows + package aia_test import ( @@ -14,7 +16,6 @@ import ( "net" "net/http" "net/http/httptest" - "runtime" "strconv" "strings" "testing" @@ -78,17 +79,7 @@ func TestTransport_multiHopIncompleteChain(t *testing.T) { if err != nil { t.Fatal(err) } - - // fake out windows to allow test to pass - if runtime.GOOS == "windows" { - winRoots := x509.NewCertPool() - winRoots.AppendCertsFromPEM(caPEM.Bytes()) - aiaTr.TLSClientConfig = &tls.Config{ - RootCAs: winRoots, - } - } else { - aiaTr.TLSClientConfig.RootCAs.AppendCertsFromPEM(caPEM.Bytes()) - } + aiaTr.TLSClientConfig.RootCAs.AppendCertsFromPEM(caPEM.Bytes()) client := http.Client{ Transport: aiaTr, From 548f925c1003d11ae8c232bd3647bdb23691b91c Mon Sep 17 00:00:00 2001 From: Frank Chiarulli Jr Date: Sun, 9 Feb 2020 23:42:28 -0500 Subject: [PATCH 6/6] remove tip from travis test --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 184efc0..da6a247 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,17 +4,17 @@ sudo: false go: - "1.13.x" - "1.x" - - tip + # - tip os: - linux - osx - windows -matrix: - allow_failures: - - go: tip - os: windows +# matrix: +# allow_failures: +# - go: tip +# os: windows before_install: - go get -t -v ./...