Skip to content

Commit

Permalink
Merge pull request #2212 from cyli/simplify-external-ca-cross-signing
Browse files Browse the repository at this point in the history
[ca] Include a signing profile for cross-signing CA certs externally
  • Loading branch information
aaronlehmann authored Jun 2, 2017
2 parents 389f88b + 8d11a94 commit 54d696b
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 74 deletions.
4 changes: 4 additions & 0 deletions ca/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
107 changes: 33 additions & 74 deletions ca/testutils/externalutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package testutils

import (
"crypto/tls"
"encoding/asn1"
"encoding/json"
"fmt"
"net"
Expand All @@ -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
Expand All @@ -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{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -123,45 +132,29 @@ 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
}

// DisableCASigning prevents the server from being able to sign CA certificates
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) {
Expand Down Expand Up @@ -214,52 +207,18 @@ 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)
errResponse := api.NewErrorResponse("sign request subject org does not match client certificate org", cfsslErr.ErrorCode)
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)
Expand Down

0 comments on commit 54d696b

Please sign in to comment.