Skip to content

Commit

Permalink
Verify the incomplete chain from the first cert (#9)
Browse files Browse the repository at this point in the history
* Verify the incomplete chain from the first cert

When using the last cert, the following error is encountered from
www.musl-libc.org, currently using a certificate generated by
Let's Encrypt:

```shell
    transport_test.go:106: https://www.musl-libc.org/: err not expected but got: Get "https://www.musl-libc.org/": asn1: structure error: tags don't match (16 vs {class:0 tag:6 length:9 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} tbsCertificate @2
--- FAIL: TestTransport (2.43s)
```

Using the first cert to verify the incomplete chain works correctly for
this case, and appears to do so for others as well.

* remove musl-libc.org test

* add semicomplete chain tests

Co-authored-by: Frank Chiarulli Jr <[email protected]>
  • Loading branch information
jhuntwork and fcjr authored Feb 16, 2021
1 parent e854b86 commit 206c2fd
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 6 deletions.
5 changes: 2 additions & 3 deletions transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,8 @@ func verifyPeerCerts(rootCAs *x509.CertPool, serverName string, rawCerts [][]byt
_, 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] != "" {
return verifyIncompleteChain(lastCert.IssuingCertificateURL[0], certs[0], opts)
if len(certs[0].IssuingCertificateURL) >= 1 && certs[0].IssuingCertificateURL[0] != "" {
return verifyIncompleteChain(certs[0].IssuingCertificateURL[0], certs[0], opts)
}
}
return err
Expand Down
189 changes: 186 additions & 3 deletions transport_multihop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,189 @@ import (
"github.com/fcjr/aia-transport-go"
)

func TestTransport_multiHopSemicompleteChainEnd(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, fmt.Sprintf("AIA Intermediate Cert %d", i), 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, intermediates[4])
if err != nil {
t.Fatal(err)
}

tlsServer.TLS = &tls.Config{
Certificates: []tls.Certificate{*tlsCert},
}
tlsServer.StartTLS()

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 TestTransport_multiHopSemicompleteChainBeginning(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, fmt.Sprintf("AIA Intermediate Cert %d", i), 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, intermediates[0], intermediates[1])
if err != nil {
t.Fatal(err)
}

tlsServer.TLS = &tls.Config{
Certificates: []tls.Certificate{*tlsCert},
}
tlsServer.StartTLS()

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 TestTransport_multiHopIncompleteChain(t *testing.T) {

certs := map[string][]byte{}
Expand Down Expand Up @@ -54,7 +237,7 @@ func TestTransport_multiHopIncompleteChain(t *testing.T) {

for i := 0; i < 5; i++ {
var certBytes []byte
parent, certBytes, parentPriv, err = genIntermediate(parent, parentPriv, issuer)
parent, certBytes, parentPriv, err = genIntermediate(parent, parentPriv, "AIA Intermediate Cert", issuer)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -188,11 +371,11 @@ func genLeafCertificate(parent *x509.Certificate, parentPrivKey *rsa.PrivateKey,
return serverCert, certPrivKey, err
}

func genIntermediate(parent *x509.Certificate, parentPrivKey *rsa.PrivateKey, issuingURL string) (*x509.Certificate, []byte, *rsa.PrivateKey, error) {
func genIntermediate(parent *x509.Certificate, parentPrivKey *rsa.PrivateKey, orgName, issuingURL string) (*x509.Certificate, []byte, *rsa.PrivateKey, error) {
cert := &x509.Certificate{
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
Organization: []string{"AIA Intermediate Cert"},
Organization: []string{orgName},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"New York"},
Expand Down

0 comments on commit 206c2fd

Please sign in to comment.