From 8d11a9425648a3fd43907c7b2c10ca5a984f31a4 Mon Sep 17 00:00:00 2001 From: Ying Li Date: Thu, 1 Jun 2017 15:31:42 -0700 Subject: [PATCH] Include a signing profile for cross-signing CA certs when making a request to external CAs. This way it will be easier for them to implement extra policies. Signed-off-by: Ying Li --- ca/external.go | 4 ++ ca/testutils/externalutils.go | 107 +++++++++++----------------------- 2 files changed, 37 insertions(+), 74 deletions(-) diff --git a/ca/external.go b/ca/external.go index 6f23ff1fbf..c4b26a6c77 100644 --- a/ca/external.go +++ b/ca/external.go @@ -23,6 +23,9 @@ import ( "golang.org/x/net/context/ctxhttp" ) +// ExternalCrossSignProfile is the profile that we will be sending cross-signing CSR sign requests with +const ExternalCrossSignProfile = "CA" + // ErrNoExternalCAURLs is an error used it indicate that an ExternalCA is // configured with no URLs to which it can proxy certificate signing requests. var ErrNoExternalCAURLs = errors.New("no external CA URLs") @@ -157,6 +160,7 @@ func (eca *ExternalCA) CrossSignRootCA(ctx context.Context, rca RootCA) ([]byte, CN: rootCert.Subject.CommonName, Names: cfCSRObj.Names, }, + Profile: ExternalCrossSignProfile, } // cfssl actually ignores non subject alt name extensions in the CSR, so we have to add the CA extension in the signing // request as well diff --git a/ca/testutils/externalutils.go b/ca/testutils/externalutils.go index a7d1589761..8fc22374c1 100644 --- a/ca/testutils/externalutils.go +++ b/ca/testutils/externalutils.go @@ -2,7 +2,6 @@ package testutils import ( "crypto/tls" - "encoding/asn1" "encoding/json" "fmt" "net" @@ -13,18 +12,25 @@ import ( "sync" "sync/atomic" - "encoding/hex" - "github.com/cloudflare/cfssl/api" - "github.com/cloudflare/cfssl/csr" + "github.com/cloudflare/cfssl/config" cfsslerrors "github.com/cloudflare/cfssl/errors" - "github.com/cloudflare/cfssl/helpers" "github.com/cloudflare/cfssl/signer" - "github.com/cloudflare/cfssl/signer/local" "github.com/docker/swarmkit/ca" "github.com/pkg/errors" ) +var crossSignPolicy = config.SigningProfile{ + Usage: []string{"cert sign", "crl sign"}, + // we don't want the intermediate to last for very long + Expiry: ca.DefaultNodeCertExpiration, + Backdate: ca.CertBackdate, + CAConstraint: config.CAConstraint{IsCA: true}, + ExtensionWhitelist: map[string]bool{ + ca.BasicConstraintsOID.String(): true, + }, +} + // NewExternalSigningServer creates and runs a new ExternalSigningServer which // uses the given rootCA to sign node certificates. A server key and cert are // generated and saved into the given basedir and then a TLS listener is @@ -40,6 +46,8 @@ func NewExternalSigningServer(rootCA ca.RootCA, basedir string) (*ExternalSignin if err != nil { return nil, err } + // create our own copy of the local signer so we don't mutate the rootCA's signer as we enable and disable CA signing + copiedSigner := *s // Create TLS credentials for the external CA server which we will run. serverPaths := ca.CertPaths{ @@ -77,9 +85,10 @@ func NewExternalSigningServer(rootCA ca.RootCA, basedir string) (*ExternalSignin mux := http.NewServeMux() handler := &signHandler{ - numIssued: &ess.NumIssued, - leafSigner: s, - flaky: &ess.flaky, + numIssued: &ess.NumIssued, + localSigner: &copiedSigner, + origPolicy: copiedSigner.Policy(), + flaky: &ess.flaky, } mux.Handle(signURL.Path, handler) ess.handler = handler @@ -123,29 +132,13 @@ func (ess *ExternalSigningServer) EnableCASigning() error { ess.handler.mu.Lock() defer ess.handler.mu.Unlock() - rootCert, err := helpers.ParseCertificatePEM(ess.handler.leafSigner.Cert) - if err != nil { - return errors.Wrap(err, "could not parse old CA certificate") - } - rootSigner, err := helpers.ParsePrivateKeyPEM(ess.handler.leafSigner.Key) - if err != nil { - return errors.Wrap(err, "could not parse old CA key") - } - - // without the whitelist, we can't accept signing requests with CA extensions - policy := ca.SigningPolicy(ca.DefaultNodeCertExpiration) - if policy.Default.ExtensionWhitelist == nil { - policy.Default.ExtensionWhitelist = make(map[string]bool) + copied := *ess.handler.origPolicy + if copied.Profiles == nil { + copied.Profiles = make(map[string]*config.SigningProfile) } - policy.Default.ExtensionWhitelist[ca.BasicConstraintsOID.String()] = true - policy.Default.Usage = append(policy.Default.Usage, "cert sign") + copied.Profiles[ca.ExternalCrossSignProfile] = &crossSignPolicy - caSigner, err := local.NewSigner(rootSigner, rootCert, signer.DefaultSigAlgo(rootSigner), policy) - if err != nil { - return errors.Wrap(err, "could not create CA signer") - } - - ess.handler.caSigner = caSigner + ess.handler.localSigner.SetPolicy(&copied) return nil } @@ -153,15 +146,15 @@ func (ess *ExternalSigningServer) EnableCASigning() error { func (ess *ExternalSigningServer) DisableCASigning() { ess.handler.mu.Lock() defer ess.handler.mu.Unlock() - ess.handler.caSigner = nil + ess.handler.localSigner.SetPolicy(ess.handler.origPolicy) } type signHandler struct { - mu sync.Mutex - numIssued *uint64 - flaky *uint32 - leafSigner *ca.LocalSigner - caSigner signer.Signer + mu sync.Mutex + numIssued *uint64 + flaky *uint32 + localSigner *ca.LocalSigner + origPolicy *config.Signing } func (h *signHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -214,41 +207,7 @@ func (h *signHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - var ( - isCA bool - certPEM []byte - err error - ) - // is this a CA CSR? If so, do we support CA signing? - // based on cfssl/signer/signer.go's ParseCertificateRequest to tell from the extensions if it's a CA - for _, ext := range signReq.Extensions { - // Check the CSR for the X.509 BasicConstraints (RFC 5280, 4.2.1.9) - // extension and append to template if necessary - if asn1.ObjectIdentifier(ext.ID).Equal(ca.BasicConstraintsOID) { - rawVal, err := hex.DecodeString(ext.Value) - if err != nil { - continue - } - var constraints csr.BasicConstraints - rest, err := asn1.Unmarshal(rawVal, &constraints) - if err != nil || len(rest) != 0 { - // technically failure conditions, but these will actually be caught when signing the request - continue - } - - if isCA = constraints.IsCA; isCA { - break - } - } - } - - h.mu.Lock() - if isCA && h.caSigner != nil { - // Sign the requested CA certificate - certPEM, err = h.caSigner.Sign(signReq) - h.mu.Unlock() - } else { - h.mu.Unlock() + if signReq.Profile != ca.ExternalCrossSignProfile { // The client's Org should match the Org in the sign request subject. if len(reqSub.Name().Organization) == 0 || reqSub.Name().Organization[0] != clientOrg { cfsslErr := cfsslerrors.New(cfsslerrors.CSRError, cfsslerrors.BadRequest) @@ -256,10 +215,10 @@ func (h *signHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(errResponse) return } - - // Sign the requested leaf certificate. - certPEM, err = h.leafSigner.Sign(signReq) } + + // Sign the requested certificate. + certPEM, err := h.localSigner.Sign(signReq) if err != nil { cfsslErr := cfsslerrors.New(cfsslerrors.APIClientError, cfsslerrors.ServerRequestFailed) errResponse := api.NewErrorResponse(fmt.Sprintf("unable to sign requested certificate: %s", err), cfsslErr.ErrorCode)