From 37149ed3ea76777406d3cadc73d412533b0c2b4b Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 4 Jan 2019 16:51:37 -0800 Subject: [PATCH] Add method to get all the certs. --- api/api.go | 31 ++++++++++++++++++++++++++++ api/api_test.go | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/api/api.go b/api/api.go index e4f34d51..e427bee3 100644 --- a/api/api.go +++ b/api/api.go @@ -25,6 +25,7 @@ type Authority interface { Renew(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) GetProvisioners(cursor string, limit int) ([]*authority.Provisioner, string, error) GetEncryptedKey(kid string) (string, error) + GetFederation(cert *x509.Certificate) ([]*x509.Certificate, error) } // Certificate wraps a *x509.Certificate and adds the json.Marshaler interface. @@ -186,6 +187,11 @@ type SignResponse struct { TLS *tls.ConnectionState `json:"-"` } +// FederationResponse is the response object of the federation request. +type FederationResponse struct { + Certificates []Certificate `json:"crts"` +} + // caHandler is the type used to implement the different CA HTTP endpoints. type caHandler struct { Authority Authority @@ -205,6 +211,7 @@ func (h *caHandler) Route(r Router) { r.MethodFunc("POST", "/renew", h.Renew) r.MethodFunc("GET", "/provisioners", h.Provisioners) r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey) + r.MethodFunc("GET", "/federation", h.Federation) // For compatibility with old code: r.MethodFunc("POST", "/re-sign", h.Renew) } @@ -320,6 +327,30 @@ func (h *caHandler) ProvisionerKey(w http.ResponseWriter, r *http.Request) { JSON(w, &ProvisionerKeyResponse{key}) } +// Federation returns all the public certificates in the federation. +func (h *caHandler) Federation(w http.ResponseWriter, r *http.Request) { + if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 { + WriteError(w, BadRequest(errors.New("missing peer certificate"))) + return + } + + federated, err := h.Authority.GetFederation(r.TLS.PeerCertificates[0]) + if err != nil { + WriteError(w, Forbidden(err)) + return + } + + certs := make([]Certificate, len(federated)) + for i := range federated { + certs[i] = Certificate{federated[i]} + } + + w.WriteHeader(http.StatusCreated) + JSON(w, &FederationResponse{ + Certificates: certs, + }) +} + func parseCursor(r *http.Request) (cursor string, limit int, err error) { q := r.URL.Query() cursor = q.Get("cursor") diff --git a/api/api_test.go b/api/api_test.go index e6f123b8..82e12c8c 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -392,6 +392,7 @@ type mockAuthority struct { renew func(cert *x509.Certificate) (*x509.Certificate, *x509.Certificate, error) getProvisioners func(nextCursor string, limit int) ([]*authority.Provisioner, string, error) getEncryptedKey func(kid string) (string, error) + getFederation func(cert *x509.Certificate) ([]*x509.Certificate, error) } func (m *mockAuthority) Authorize(ott string) ([]interface{}, error) { @@ -443,6 +444,13 @@ func (m *mockAuthority) GetEncryptedKey(kid string) (string, error) { return m.ret1.(string), m.err } +func (m *mockAuthority) GetFederation(cert *x509.Certificate) ([]*x509.Certificate, error) { + if m.getFederation != nil { + return m.getFederation(cert) + } + return m.ret1.([]*x509.Certificate), m.err +} + func Test_caHandler_Route(t *testing.T) { type fields struct { Authority Authority @@ -812,3 +820,50 @@ func Test_caHandler_ProvisionerKey(t *testing.T) { }) } } + +func Test_caHandler_Federation(t *testing.T) { + cs := &tls.ConnectionState{ + PeerCertificates: []*x509.Certificate{parseCertificate(certPEM)}, + } + tests := []struct { + name string + tls *tls.ConnectionState + cert *x509.Certificate + root *x509.Certificate + err error + statusCode int + }{ + {"ok", cs, parseCertificate(certPEM), parseCertificate(rootPEM), nil, http.StatusCreated}, + {"no tls", nil, nil, nil, nil, http.StatusBadRequest}, + {"no peer certificates", &tls.ConnectionState{}, nil, nil, nil, http.StatusBadRequest}, + {"renew error", cs, nil, nil, fmt.Errorf("an error"), http.StatusForbidden}, + } + + expected := []byte(`{"crts":["` + strings.Replace(rootPEM, "\n", `\n`, -1) + `\n"]}`) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := New(&mockAuthority{ret1: []*x509.Certificate{tt.root}, err: tt.err}).(*caHandler) + req := httptest.NewRequest("GET", "http://example.com/federation", nil) + req.TLS = tt.tls + w := httptest.NewRecorder() + h.Federation(w, req) + res := w.Result() + + if res.StatusCode != tt.statusCode { + t.Errorf("caHandler.Root StatusCode = %d, wants %d", res.StatusCode, tt.statusCode) + } + + body, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + t.Errorf("caHandler.Root unexpected error = %v", err) + } + if tt.statusCode < http.StatusBadRequest { + if !bytes.Equal(bytes.TrimSpace(body), expected) { + t.Errorf("caHandler.Root Body = %s, wants %s", body, expected) + } + } + }) + } +}