Browse Source

Fetch alternative cert chains

master
Isaac 3 years ago
parent
commit
1a6ed6c1c1
  1. 24
      acme.go
  2. 52
      acme_test.go
  3. 49
      certificate.go
  4. 32
      certificate_test.go

24
acme.go

@ -268,3 +268,27 @@ func (c Client) Fetch(account Account, requestURL string, result interface{}, ex
return err
}
// Fetches all http Link header from a http response
func fetchLinks(resp *http.Response, wantedLink string) []string {
if resp == nil {
return nil
}
linkHeader := resp.Header["Link"]
if len(linkHeader) == 0 {
return nil
}
var links []string
for _, l := range linkHeader {
matches := regLink.FindAllStringSubmatch(l, -1)
for _, m := range matches {
if len(m) != 3 {
continue
}
if m[2] == wantedLink {
links = append(links, m[1])
}
}
}
return links
}

52
acme_test.go

@ -13,7 +13,7 @@ func TestNewClient(t *testing.T) {
}
}
func TestParseLinks(t *testing.T) {
func TestFetchLink(t *testing.T) {
linkTests := []struct {
Name string
LinkHeaders []string
@ -45,6 +45,56 @@ func TestParseLinks(t *testing.T) {
}
}
func stringSliceEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
func TestFetchLinks(t *testing.T) {
linkTests := []struct {
Name string
LinkHeaders []string
WantedLink string
ExpectedURLs []string
}{
{
Name: "no links",
WantedLink: "fail",
ExpectedURLs: nil,
},
{Name: "joined links",
LinkHeaders: []string{`<https://url/path>; rel="next", <http://url/path?query>; rel="up"`},
WantedLink: "up",
ExpectedURLs: []string{"http://url/path?query"},
},
{
Name: "separate links",
LinkHeaders: []string{`<https://url/path>; rel="next"`, `<http://url/path?query>; rel="up"`},
WantedLink: "up",
ExpectedURLs: []string{"http://url/path?query"},
},
{
Name: "multiple links",
LinkHeaders: []string{`<https://url/path>; rel="up"`, `<http://url/path?query>; rel="up"`},
WantedLink: "up",
ExpectedURLs: []string{"https://url/path", "http://url/path?query"},
},
}
for _, currentTest := range linkTests {
linkURLs := fetchLinks(&http.Response{Header: http.Header{"Link": currentTest.LinkHeaders}}, currentTest.WantedLink)
if !stringSliceEqual(linkURLs, currentTest.ExpectedURLs) {
t.Fatalf("%s: links not equal, expected: %s, got: %s", currentTest.Name, currentTest.ExpectedURLs, linkURLs)
}
}
}
func TestClient_Directory(t *testing.T) {
if !reflect.DeepEqual(testClient.dir, testClient.Directory()) {
t.Fatalf("directory mismatch, expected: %+v, got: %+v", testClient.dir, testClient.Directory())

49
certificate.go

@ -9,13 +9,7 @@ import (
"net/http"
)
// FetchCertificates downloads a certificate chain from a url given in an order certificate.
func (c Client) FetchCertificates(account Account, certificateURL string) ([]*x509.Certificate, error) {
resp, body, err := c.postRaw(0, certificateURL, account.URL, account.PrivateKey, "", []int{http.StatusOK})
if err != nil {
return nil, err
}
func (c Client) decodeCertificateChain(body []byte, resp *http.Response, account Account) ([]*x509.Certificate, error) {
var certs []*x509.Certificate
for {
var p *pem.Block
@ -44,6 +38,47 @@ func (c Client) FetchCertificates(account Account, certificateURL string) ([]*x5
return certs, nil
}
// FetchCertificates downloads a certificate chain from a url given in an order certificate.
func (c Client) FetchCertificates(account Account, certificateURL string) ([]*x509.Certificate, error) {
resp, body, err := c.postRaw(0, certificateURL, account.URL, account.PrivateKey, "", []int{http.StatusOK})
if err != nil {
return nil, err
}
return c.decodeCertificateChain(body, resp, account)
}
// FetchAllCertificates downloads a certificate chain from a url given in an order certificate, as well as any alternate certificates if provided.
// Returns a mapping of certificate urls to the certificate chain.
func (c Client) FetchAllCertificates(account Account, certificateURL string) (map[string][]*x509.Certificate, error) {
resp, body, err := c.postRaw(0, certificateURL, account.URL, account.PrivateKey, "", []int{http.StatusOK})
if err != nil {
return nil, err
}
certChain, err := c.decodeCertificateChain(body, resp, account)
if err != nil {
return nil, err
}
certs := map[string][]*x509.Certificate{
certificateURL: certChain,
}
alternates := fetchLinks(resp, "alternate")
for _, v := range alternates {
altCertChain, err := c.decodeCertificateChain(body, resp, account)
if err != nil {
return certs, fmt.Errorf("acme: error fetching alt cert chain at %q - %v", v, err)
}
certs[v] = altCertChain
}
return certs, nil
}
// RevokeCertificate revokes a given certificate given the certificate key or account key, and a reason.
func (c Client) RevokeCertificate(account Account, cert *x509.Certificate, key crypto.Signer, reason int) error {
revokeReq := struct {

32
certificate_test.go

@ -1,6 +1,10 @@
package acme
import "testing"
import (
"os"
"strconv"
"testing"
)
func TestClient_FetchCertificates(t *testing.T) {
account, order, _ := makeOrderFinalised(t, nil)
@ -21,6 +25,32 @@ func TestClient_FetchCertificates(t *testing.T) {
}
}
func TestClient_FetchAllCertificates(t *testing.T) {
if testClientMeta.Software == clientBoulder {
t.Skip("boulder doesnt support alt cert chains: https://github.com/letsencrypt/boulder/issues/4567")
return
}
account, order, _ := makeOrderFinalised(t, nil)
if order.Certificate == "" {
t.Fatalf("no certificate: %+v", order)
}
certs, err := testClient.FetchAllCertificates(account, order.Certificate)
if err != nil {
t.Fatalf("expeceted no error, got: %v", err)
}
roots, ok := os.LookupEnv("PEBBLE_ALTERNATE_ROOTS")
if !ok {
return
}
numRoots, err := strconv.Atoi(roots)
if err != nil {
panic(err)
}
if numRoots > 0 && len(certs) <= numRoots {
t.Fatalf("expected > %d cert chains, got: %d", numRoots, len(certs))
}
}
func TestClient_RevokeCertificate(t *testing.T) {
// test revoking cert with cert key
account, order, privKey := makeOrderFinalised(t, nil)

Loading…
Cancel
Save