Add /roots.pem handler (#866)

* Add /roots.pem handler

* Review changes

* Remove no peer cert test case
This commit is contained in:
Andrew Reed 2022-03-28 09:18:18 -05:00 committed by Mariano Cano
parent 750e9ee2f8
commit 52d7f084d2
2 changed files with 65 additions and 0 deletions

View file

@ -21,6 +21,7 @@ import (
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/api/log"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/authority/provisioner"
@ -259,6 +260,7 @@ func (h *caHandler) Route(r Router) {
r.MethodFunc("GET", "/provisioners", h.Provisioners) r.MethodFunc("GET", "/provisioners", h.Provisioners)
r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey) r.MethodFunc("GET", "/provisioners/{kid}/encrypted-key", h.ProvisionerKey)
r.MethodFunc("GET", "/roots", h.Roots) r.MethodFunc("GET", "/roots", h.Roots)
r.MethodFunc("GET", "/roots.pem", h.RootsPEM)
r.MethodFunc("GET", "/federation", h.Federation) r.MethodFunc("GET", "/federation", h.Federation)
// SSH CA // SSH CA
r.MethodFunc("POST", "/ssh/sign", h.SSHSign) r.MethodFunc("POST", "/ssh/sign", h.SSHSign)
@ -364,6 +366,29 @@ func (h *caHandler) Roots(w http.ResponseWriter, r *http.Request) {
}, http.StatusCreated) }, http.StatusCreated)
} }
// RootsPEM returns all the root certificates for the CA in PEM format.
func (h *caHandler) RootsPEM(w http.ResponseWriter, r *http.Request) {
roots, err := h.Authority.GetRoots()
if err != nil {
WriteError(w, errs.InternalServerErr(err))
return
}
w.Header().Set("Content-Type", "application/x-pem-file")
for _, root := range roots {
block := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: root.Raw,
})
if _, err := w.Write(block); err != nil {
log.Error(w, err)
return
}
}
}
// Federation returns all the public certificates in the federation. // Federation returns all the public certificates in the federation.
func (h *caHandler) Federation(w http.ResponseWriter, r *http.Request) { func (h *caHandler) Federation(w http.ResponseWriter, r *http.Request) {
federated, err := h.Authority.GetFederation() federated, err := h.Authority.GetFederation()

View file

@ -1344,6 +1344,46 @@ func Test_caHandler_Roots(t *testing.T) {
} }
} }
func Test_caHandler_RootsPEM(t *testing.T) {
parsedRoot := parseCertificate(rootPEM)
tests := []struct {
name string
roots []*x509.Certificate
err error
statusCode int
expect string
}{
{"one root", []*x509.Certificate{parsedRoot}, nil, http.StatusOK, rootPEM},
{"two roots", []*x509.Certificate{parsedRoot, parsedRoot}, nil, http.StatusOK, rootPEM + "\n" + rootPEM},
{"fail", nil, errors.New("an error"), http.StatusInternalServerError, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := New(&mockAuthority{ret1: tt.roots, err: tt.err}).(*caHandler)
req := httptest.NewRequest("GET", "https://example.com/roots", nil)
w := httptest.NewRecorder()
h.RootsPEM(w, req)
res := w.Result()
if res.StatusCode != tt.statusCode {
t.Errorf("caHandler.RootsPEM StatusCode = %d, wants %d", res.StatusCode, tt.statusCode)
}
body, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Errorf("caHandler.RootsPEM unexpected error = %v", err)
}
if tt.statusCode < http.StatusBadRequest {
if !bytes.Equal(bytes.TrimSpace(body), []byte(tt.expect)) {
t.Errorf("caHandler.RootsPEM Body = %s, wants %s", body, tt.expect)
}
}
})
}
}
func Test_caHandler_Federation(t *testing.T) { func Test_caHandler_Federation(t *testing.T) {
cs := &tls.ConnectionState{ cs := &tls.ConnectionState{
PeerCertificates: []*x509.Certificate{parseCertificate(certPEM)}, PeerCertificates: []*x509.Certificate{parseCertificate(certPEM)},