diff --git a/cmd/multirootca/api.go b/cmd/multirootca/api.go index 8145c056a..8295c4f4b 100644 --- a/cmd/multirootca/api.go +++ b/cmd/multirootca/api.go @@ -8,7 +8,9 @@ import ( "net/http/httputil" "github.com/cloudflare/cfssl/api" + "github.com/cloudflare/cfssl/api/signhandler" "github.com/cloudflare/cfssl/auth" + "github.com/cloudflare/cfssl/bundler" "github.com/cloudflare/cfssl/helpers" "github.com/cloudflare/cfssl/log" "github.com/cloudflare/cfssl/signer" @@ -20,7 +22,8 @@ import ( // A SignatureResponse contains only a certificate, as there is no other // useful data for the CA to return at this time. type SignatureResponse struct { - Certificate string `json:"certificate"` + Certificate string `json:"certificate"` + Bundle *bundler.Bundle `json:"bundle,omitempty"` } type filter func(string, *signer.SignRequest) bool @@ -185,7 +188,23 @@ func dispatchRequest(w http.ResponseWriter, req *http.Request) { log.Infof("signature: requester=%s, label=%s, profile=%s, serialno=%s", req.RemoteAddr, sigRequest.Label, sigRequest.Profile, x509Cert.SerialNumber) - res := api.NewSuccessResponse(&SignatureResponse{Certificate: string(cert)}) + sigResp := &SignatureResponse{Certificate: string(cert)} + + if sigRequest.Bundle { + if mrBundler == nil { + fail(w, req, http.StatusInternalServerError, 1, signhandler.NoBundlerMessage, "") + return + } + + bundle, err := mrBundler.BundleFromPEMorDER(cert, nil, bundler.Optimal, "") + if err != nil { + fail(w, req, http.StatusInternalServerError, 1, "error creating bundle", fmt.Sprintf("%v", err)) + return + } + + sigResp.Bundle = bundle + } + res := api.NewSuccessResponse(sigResp) jenc := json.NewEncoder(w) err = jenc.Encode(res) if err != nil { diff --git a/cmd/multirootca/bundler.go b/cmd/multirootca/bundler.go new file mode 100644 index 000000000..ff0945159 --- /dev/null +++ b/cmd/multirootca/bundler.go @@ -0,0 +1,38 @@ +package main + +import ( + "crypto/x509" + + "github.com/cloudflare/cfssl/bundler" + "github.com/cloudflare/cfssl/log" +) + +type MultirootBundler struct { + *bundler.Bundler +} + +// NewMultirootBundler will set up a bundler with the systems root store +// as caBundle. Intermediate certificates can be added later via AddIntermediate() +func NewMultirootBundler() (*MultirootBundler, error) { + b, err := bundler.NewBundlerFromPEM(nil, nil) + if err != nil { + log.Errorf("failed creating empty bundler") + return nil, err + } + return &MultirootBundler{Bundler: b}, nil +} + +func (m *MultirootBundler) AddRoot(cert *x509.Certificate) { + // Initialize a RootPool then the first root cert is added + // If none is added, systems root store will be used + if m.Bundler.RootPool == nil { + m.Bundler.RootPool = x509.NewCertPool() + } + m.Bundler.RootPool.AddCert(cert) + m.Bundler.KnownIssuers[string(cert.Signature)] = true +} + +func (m *MultirootBundler) AddIntermediate(cert *x509.Certificate) { + m.Bundler.IntermediatePool.AddCert(cert) + m.Bundler.KnownIssuers[string(cert.Signature)] = true +} diff --git a/cmd/multirootca/ca.go b/cmd/multirootca/ca.go index 3689032f5..203e85f1f 100644 --- a/cmd/multirootca/ca.go +++ b/cmd/multirootca/ca.go @@ -43,6 +43,7 @@ func parseSigner(root *config.Root) (signer.Signer, error) { var ( defaultLabel string + mrBundler *MultirootBundler signers = map[string]signer.Signer{} whitelists = map[string]whitelist.NetACL{} ) @@ -65,6 +66,11 @@ func main() { log.Fatalf("%v", err) } + mrBundler, err = NewMultirootBundler() + if err != nil { + log.Fatalf("error creating MultirootBundler: %v", err) + } + for label, root := range roots { s, err := parseSigner(root) if err != nil { @@ -75,6 +81,13 @@ func main() { whitelists[label] = root.ACL } log.Info("loaded signer ", label) + + if root.RootCA != nil { + mrBundler.AddRoot(root.RootCA) + log.Info("loaded root CA ", label) + } + mrBundler.AddIntermediate(root.Certificate) + log.Info("loaded intermediate ", label) } defaultLabel = *flagDefaultLabel diff --git a/multiroot/config/config.go b/multiroot/config/config.go index db9f7d381..5e5d8cc9c 100644 --- a/multiroot/config/config.go +++ b/multiroot/config/config.go @@ -102,6 +102,7 @@ func (c *RawMap) SectionInConfig(section string) bool { type Root struct { PrivateKey crypto.Signer Certificate *x509.Certificate + RootCA *x509.Certificate Config *config.Signing ACL whitelist.NetACL DB *sqlx.DB @@ -126,6 +127,21 @@ func LoadRoot(cfg map[string]string) (*Root, error) { return nil, ErrMissingConfigPath } + // ca is optional + // will be added to the root pool of the bundler if set + caPath, ok := cfg["ca"] + if ok { + in, err := ioutil.ReadFile(caPath) + if err != nil { + return nil, err + } + + root.RootCA, err = helpers.ParseCertificatePEM(in) + if err != nil { + return nil, err + } + } + root.PrivateKey, err = parsePrivateKeySpec(spec, cfg) if err != nil { return nil, err diff --git a/signer/signer.go b/signer/signer.go index ea650bd6d..3d4652b62 100644 --- a/signer/signer.go +++ b/signer/signer.go @@ -74,6 +74,10 @@ type SignRequest struct { // Arbitrary metadata to be stored in certdb. Metadata map[string]interface{} `json:"metadata"` + + // Bundle is a boolean specifying whether to include an "optimal" + // certificate bundle along with the certificate + Bundle bool `json:"bundle,omitempty"` } // appendIf appends to a if s is not an empty string.