diff --git a/builtin/logical/pki/backend.go b/builtin/logical/pki/backend.go index cf56c74a4a..2dc6895165 100644 --- a/builtin/logical/pki/backend.go +++ b/builtin/logical/pki/backend.go @@ -43,6 +43,7 @@ func Backend() *framework.Backend { pathSetCA(&b), pathConfigCA(&b), pathConfigCRL(&b), + pathSign(&b), pathIssue(&b), pathRotateCRL(&b), pathFetchCA(&b), diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index eb71a8673f..295fc8ba2a 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -9,6 +9,7 @@ import ( "crypto/sha1" "crypto/x509" "crypto/x509/pkix" + "encoding/pem" "fmt" "math/big" "net" @@ -215,7 +216,6 @@ func generateCSR(b *backend, func signCert(b *backend, role *roleEntry, signingBundle *certutil.ParsedCertBundle, - csr *x509.CertificateRequest, isCA bool, req *logical.Request, data *framework.FieldData) (*certutil.ParsedCertBundle, error) { @@ -225,6 +225,22 @@ func signCert(b *backend, return nil, certutil.UserError{Err: "The common_name field is required"} } + csrString := req.Data["csr"].(string) + if csrString == "" { + return nil, certutil.UserError{Err: fmt.Sprintf( + "\"csr\" is empty")} + } + + pemBytes := []byte(csrString) + pemBlock, pemBytes := pem.Decode(pemBytes) + if pemBlock == nil { + return nil, certutil.UserError{Err: "csr contains no data"} + } + csr, err := x509.ParseCertificateRequest(pemBlock.Bytes) + if err != nil { + return nil, certutil.UserError{Err: "certificate request could not be parsed"} + } + creationBundle, err := generateCreationBundle(b, role, signingBundle, isCA, req, data) if err != nil { return nil, err diff --git a/builtin/logical/pki/path_config_ca.go b/builtin/logical/pki/path_config_ca.go index 4cb5fd7365..a94797c2a7 100644 --- a/builtin/logical/pki/path_config_ca.go +++ b/builtin/logical/pki/path_config_ca.go @@ -1,8 +1,6 @@ package pki import ( - "crypto/x509" - "encoding/pem" "fmt" "reflect" "strings" @@ -413,22 +411,6 @@ func (b *backend) pathCASignWrite( pkiAddress = pkiAddress[:len(pkiAddress)-1] } - csrString := req.Data["csr"].(string) - if csrString == "" { - return logical.ErrorResponse(fmt.Sprintf( - "\"csr\" is empty")), nil - } - - pemBytes := []byte(csrString) - pemBlock, pemBytes := pem.Decode(pemBytes) - if pemBlock == nil { - return nil, certutil.UserError{Err: "csr contains no data"} - } - csr, err := x509.ParseCertificateRequest(pemBlock.Bytes) - if err != nil { - return nil, certutil.UserError{Err: "certificate request could not be parsed"} - } - req.Data["pki_address"] = pkiAddress role := &roleEntry{ @@ -464,7 +446,7 @@ func (b *backend) pathCASignWrite( "Error fetching CA certificate: %s", caErr)} } - parsedBundle, err := signCert(b, role, signingBundle, csr, true, req, data) + parsedBundle, err := signCert(b, role, signingBundle, true, req, data) if err != nil { switch err.(type) { case certutil.UserError: diff --git a/builtin/logical/pki/path_issue.go b/builtin/logical/pki/path_issue_sign.go similarity index 70% rename from builtin/logical/pki/path_issue.go rename to builtin/logical/pki/path_issue_sign.go index 8ab3af7eb7..e7883fb703 100644 --- a/builtin/logical/pki/path_issue.go +++ b/builtin/logical/pki/path_issue_sign.go @@ -44,10 +44,6 @@ the role default, backend default, or system default TTL is used, in that order. Cannot be later than the role max TTL.`, }, - "csr": &framework.FieldSchema{ - Type: framework.TypeString, - Description: `PEM-format CSR to be signed.`, - }, } func pathIssue(b *backend) *framework.Path { @@ -65,7 +61,7 @@ func pathIssue(b *backend) *framework.Path { } func pathSign(b *backend) *framework.Path { - return &framework.Path{ + ret := &framework.Path{ Pattern: "sign/" + framework.GenericNameRegex("role"), Fields: issueAndSignSchema, @@ -76,6 +72,13 @@ func pathSign(b *backend) *framework.Path { HelpSynopsis: pathIssueCertHelpSyn, HelpDescription: pathIssueCertHelpDesc, } + + ret.Fields["csr"] = &framework.FieldSchema{ + Type: framework.TypeString, + Description: `PEM-format CSR to be signed.`, + } + + return ret } func (b *backend) pathIssueCert( @@ -136,6 +139,64 @@ func (b *backend) pathIssueCert( return resp, nil } +func (b *backend) pathSignCSR( + req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + roleName := data.Get("role").(string) + + // Get the role + role, err := b.getRole(req.Storage, roleName) + if err != nil { + return nil, err + } + if role == nil { + return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", roleName)), nil + } + + var caErr error + signingBundle, caErr := fetchCAInfo(req) + switch caErr.(type) { + case certutil.UserError: + return nil, certutil.UserError{Err: fmt.Sprintf( + "Could not fetch the CA certificate (was one set?): %s", caErr)} + case certutil.InternalError: + return nil, certutil.InternalError{Err: fmt.Sprintf( + "Error fetching CA certificate: %s", caErr)} + } + + parsedBundle, err := signCert(b, role, signingBundle, false, req, data) + if err != nil { + switch err.(type) { + case certutil.UserError: + return logical.ErrorResponse(err.Error()), nil + case certutil.InternalError: + return nil, err + } + } + + cb, err := parsedBundle.ToCertBundle() + if err != nil { + return nil, fmt.Errorf("Error converting raw cert bundle to cert bundle: %s", err) + } + + resp := b.Secret(SecretCertsType).Response( + structs.New(cb).Map(), + map[string]interface{}{ + "serial_number": cb.SerialNumber, + }) + + resp.Secret.TTL = parsedBundle.Certificate.NotAfter.Sub(time.Now()) + + err = req.Storage.Put(&logical.StorageEntry{ + Key: "certs/" + cb.SerialNumber, + Value: parsedBundle.CertificateBytes, + }) + if err != nil { + return nil, fmt.Errorf("Unable to store certificate locally") + } + + return resp, nil +} + const pathIssueCertHelpSyn = ` Request certificates using a certain role with the provided common name. `