Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ func Route(r Router) {
r.MethodFunc("GET", "/roots.pem", RootsPEM)
r.MethodFunc("GET", "/intermediates", Intermediates)
r.MethodFunc("GET", "/intermediates.pem", IntermediatesPEM)
r.MethodFunc("GET", "/intermediate.crt", IntermediateCert)
r.MethodFunc("GET", "/federation", Federation)

// SSH CA
Expand Down Expand Up @@ -511,6 +512,25 @@ func IntermediatesPEM(w http.ResponseWriter, r *http.Request) {
}
}

// IntermediateCert returns the CA's issuing intermediate certificate as a
// single DER-encoded X.509 certificate for use as an Authority Information
// Access (AIA) caIssuers URI. RFC 5280 Section 4.2.2.1 permits HTTP
// caIssuers URIs to point to a single DER certificate as specified by RFC
// 2585 Section 3; RFC 5280 Section 4.2.2.1 and RFC 2585 Section 4.1 use
// Content-Type application/pkix-cert for that representation.
func IntermediateCert(w http.ResponseWriter, r *http.Request) {
intermediates := mustAuthority(r.Context()).GetIntermediateCertificates()
if len(intermediates) == 0 {
render.Error(w, r, errs.NotImplemented("error getting intermediate: method not implemented"))
return
}

w.Header().Set("Content-Type", "application/pkix-cert")
if _, err := w.Write(intermediates[0].Raw); err != nil {
log.Error(w, r, err)
}
}

// Federation returns all the public certificates in the federation.
func Federation(w http.ResponseWriter, r *http.Request) {
federated, err := mustAuthority(r.Context()).GetFederation()
Expand Down
37 changes: 37 additions & 0 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1718,6 +1718,43 @@ func TestIntermediates(t *testing.T) {
}
}

func TestIntermediateCert(t *testing.T) {
ca, err := minica.New()
require.NoError(t, err)

getRequest := func(t *testing.T, crt []*x509.Certificate) *http.Request {
mockMustAuthority(t, &mockAuthority{
ret1: crt,
})
return httptest.NewRequest("GET", "/intermediate.crt", http.NoBody)
}

type args struct {
crts []*x509.Certificate
}
tests := []struct {
name string
args args
wantStatusCode int
wantContentType string
wantBody []byte
}{
{"ok", args{[]*x509.Certificate{ca.Intermediate}}, http.StatusOK, "application/pkix-cert", ca.Intermediate.Raw},
{"ok multiple returns first", args{[]*x509.Certificate{ca.Intermediate, ca.Root}}, http.StatusOK, "application/pkix-cert", ca.Intermediate.Raw},
{"fail", args{}, http.StatusNotImplemented, "application/json", mustJSON(t, errs.NotImplemented("not implemented"))},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
r := getRequest(t, tt.args.crts)
IntermediateCert(w, r)
assert.Equal(t, tt.wantStatusCode, w.Result().StatusCode)
assert.Equal(t, tt.wantContentType, w.Result().Header.Get("Content-Type"))
assert.Equal(t, tt.wantBody, w.Body.Bytes())
})
}
}

func TestIntermediatesPEM(t *testing.T) {
ca, err := minica.New()
require.NoError(t, err)
Expand Down
21 changes: 17 additions & 4 deletions ca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,14 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
insecureMux.Get("/crl", api.CRL)
insecureMux.Get("/1.0/crl", api.CRL)

// Mount the AIA issuer endpoint to the insecure mux. For TLS subscriber
// certificates, CA/Browser Forum Baseline Requirements Section 7.1.2.7.7
// describe id-ad-caIssuers as an HTTP URL of the issuing CA certificate;
// this lets clients fetch the issuer before they can validate the CA's
// TLS certificate.
insecureMux.Get("/intermediate.crt", api.IntermediateCert)
insecureMux.Get("/1.0/intermediate.crt", api.IntermediateCert)

// Add ACME api endpoints in /acme and /1.0/acme
dns := cfg.DNSNames[0]
u, err := url.Parse("https://" + cfg.Address)
Expand Down Expand Up @@ -385,10 +393,9 @@ func (ca *CA) Init(cfg *config.Config) (*CA, error) {
return ca, nil
}

// shouldServeInsecureServer returns whether or not the insecure
// server should also be started. This is (currently) only the case
// if the insecure address has been configured AND when a SCEP
// provisioner is configured or when a CRL is configured.
// shouldServeInsecureServer returns whether the insecure server should also be
// started. This requires an insecure address and at least one endpoint intended
// for HTTP: SCEP, CRL, or the AIA issuer certificate.
func (ca *CA) shouldServeInsecureServer() bool {
switch {
case ca.config.InsecureAddress == "":
Expand All @@ -397,11 +404,17 @@ func (ca *CA) shouldServeInsecureServer() bool {
return true
case ca.config.CRL.IsEnabled():
return true
case ca.shouldServeAIAIssuerEndpoint():
return true
default:
return false
}
}

func (ca *CA) shouldServeAIAIssuerEndpoint() bool {
return len(ca.auth.GetIntermediateCertificates()) > 0
}

// buildContext builds the server base context.
func buildContext(a *authority.Authority, scepAuthority *scep.Authority, acmeDB acme.DB, acmeLinker acme.Linker) context.Context {
ctx := authority.NewContext(context.Background(), a)
Expand Down
46 changes: 46 additions & 0 deletions ca/ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/smallstep/assert"
"github.com/smallstep/certificates/api"
"github.com/smallstep/certificates/authority"
authorityconfig "github.com/smallstep/certificates/authority/config"
"github.com/smallstep/certificates/authority/provisioner"
"github.com/smallstep/certificates/errs"
"go.step.sm/crypto/jose"
Expand Down Expand Up @@ -76,6 +77,51 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}

func TestCA_shouldServeInsecureServer(t *testing.T) {
config, err := authority.LoadConfiguration("testdata/ca.json")
assert.FatalError(t, err)
config.InsecureAddress = ""

ca, err := New(config)
assert.FatalError(t, err)
assert.False(t, ca.shouldServeSCEPEndpoints())
assert.False(t, ca.config.CRL.IsEnabled())
assert.True(t, ca.shouldServeAIAIssuerEndpoint())
assert.False(t, ca.shouldServeInsecureServer())

ca.config.InsecureAddress = "127.0.0.1:8080"
assert.True(t, ca.shouldServeInsecureServer())

authWithoutIntermediate := newTestAuthorityWithoutIntermediate(t)
caWithoutPublicHTTP := &CA{
auth: authWithoutIntermediate,
config: &authorityconfig.Config{
InsecureAddress: "127.0.0.1:8080",
},
}
assert.False(t, caWithoutPublicHTTP.shouldServeAIAIssuerEndpoint())
assert.False(t, caWithoutPublicHTTP.shouldServeInsecureServer())
}

func newTestAuthorityWithoutIntermediate(t *testing.T) *authority.Authority {
t.Helper()

root, err := pemutil.ReadCertificate("testdata/secrets/root_ca.crt")
assert.FatalError(t, err)
signer, err := keyutil.GenerateDefaultSigner()
assert.FatalError(t, err)

auth, err := authority.NewEmbedded(
authority.WithX509RootCerts(root),
authority.WithX509SignerFunc(func() ([]*x509.Certificate, crypto.Signer, error) {
return nil, signer, nil
}),
)
assert.FatalError(t, err)

return auth
}

func TestCASign(t *testing.T) {
pub, priv, err := keyutil.GenerateDefaultKeyPair()
assert.FatalError(t, err)
Expand Down