mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-14 06:46:27 +03:00
pki: rough draft for generating CSR through API
This commit is contained in:
parent
b7e472d548
commit
5b09e7df3d
2 changed files with 111 additions and 3 deletions
|
@ -18,6 +18,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
@ -29,6 +30,12 @@ func init() {
|
|||
caddy.RegisterModule(adminAPI{})
|
||||
}
|
||||
|
||||
var (
|
||||
caInfoPathPattern = regexp.MustCompile(`^ca/[^/]+$`)
|
||||
getCertPathPattern = regexp.MustCompile(`^ca/[^/]+/certificates$`)
|
||||
produceCSRPathPattern = regexp.MustCompile(`^ca/[^/]+/csr$`)
|
||||
)
|
||||
|
||||
// adminAPI is a module that serves PKI endpoints to retrieve
|
||||
// information about the CAs being managed by Caddy.
|
||||
type adminAPI struct {
|
||||
|
@ -71,12 +78,13 @@ func (a *adminAPI) Routes() []caddy.AdminRoute {
|
|||
// handleAPIEndpoints routes API requests within adminPKIEndpointBase.
|
||||
func (a *adminAPI) handleAPIEndpoints(w http.ResponseWriter, r *http.Request) error {
|
||||
uri := strings.TrimPrefix(r.URL.Path, "/pki/")
|
||||
parts := strings.Split(uri, "/")
|
||||
switch {
|
||||
case len(parts) == 2 && parts[0] == "ca" && parts[1] != "":
|
||||
case caInfoPathPattern.MatchString(uri):
|
||||
return a.handleCAInfo(w, r)
|
||||
case len(parts) == 3 && parts[0] == "ca" && parts[1] != "" && parts[2] == "certificates":
|
||||
case getCertPathPattern.MatchString(uri):
|
||||
return a.handleCACerts(w, r)
|
||||
case produceCSRPathPattern.MatchString(uri):
|
||||
return a.handleCSRGeneration(w, r)
|
||||
}
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
|
@ -168,6 +176,66 @@ func (a *adminAPI) handleCACerts(w http.ResponseWriter, r *http.Request) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type csrRequest struct {
|
||||
// SANs is a list of subject alternative names for the certificate.
|
||||
SANs []string `json:"sans"`
|
||||
}
|
||||
|
||||
func (a *adminAPI) handleCSRGeneration(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodPost {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusMethodNotAllowed,
|
||||
Err: fmt.Errorf("method not allowed: %v", r.Method),
|
||||
}
|
||||
}
|
||||
|
||||
ca, err := a.getCAFromAPIRequestPath(r)
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Decode the CSR request from the request body
|
||||
var csrReq csrRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&csrReq); err != nil {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("failed to decode CSR request: %v", err),
|
||||
}
|
||||
}
|
||||
// Generate the CSR
|
||||
csr, err := ca.generateCSR(csrReq.SANs)
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
Err: fmt.Errorf("failed to generate CSR: %v", err),
|
||||
}
|
||||
}
|
||||
if r.Header.Get("Accept") != "application/pkcs10" {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusNotAcceptable,
|
||||
Err: fmt.Errorf("only accept application/pkcs10"),
|
||||
}
|
||||
}
|
||||
bs, err := pemEncode("CERTIFICATE REQUEST", csr.Raw)
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
Err: fmt.Errorf("failed to encode CSR to PEM: %v", err),
|
||||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/pkcs10")
|
||||
if _, err := w.Write(bs); err != nil {
|
||||
return caddy.APIError{
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
Err: fmt.Errorf("failed to write CSR response: %v", err),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *adminAPI) getCAFromAPIRequestPath(r *http.Request) (*CA, error) {
|
||||
// Grab the CA ID from the request path, it should be the 4th segment (/pki/ca/<ca>)
|
||||
id := strings.Split(r.URL.Path, "/")[3]
|
||||
|
|
|
@ -29,6 +29,8 @@ import (
|
|||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/db"
|
||||
"github.com/smallstep/truststore"
|
||||
"go.step.sm/crypto/keyutil"
|
||||
"go.step.sm/crypto/x509util"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
|
@ -394,6 +396,10 @@ func (ca CA) storageKeyIntermediateKey() string {
|
|||
return path.Join(ca.storageKeyCAPrefix(), "intermediate.key")
|
||||
}
|
||||
|
||||
func (ca CA) storageKeyCSRKey() string {
|
||||
return path.Join(ca.storageKeyCAPrefix(), "csr.key")
|
||||
}
|
||||
|
||||
func (ca CA) newReplacer() *caddy.Replacer {
|
||||
repl := caddy.NewReplacer()
|
||||
repl.Set("pki.ca.name", ca.Name)
|
||||
|
@ -421,6 +427,40 @@ func (ca CA) installRoot() error {
|
|||
)
|
||||
}
|
||||
|
||||
func (ca CA) generateCSR(sans []string) (csr *x509.CertificateRequest, err error) {
|
||||
var signer crypto.Signer
|
||||
csrKeyPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyCSRKey())
|
||||
if err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, fmt.Errorf("loading csr key: %v", err)
|
||||
}
|
||||
|
||||
signer, err = keyutil.GenerateDefaultSigner()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csrKeyPEM, err = certmagic.PEMEncodePrivateKey(signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encoding csr key: %v", err)
|
||||
}
|
||||
if err := ca.storage.Store(ca.ctx, ca.storageKeyCSRKey(), csrKeyPEM); err != nil {
|
||||
return nil, fmt.Errorf("saving csr key: %v", err)
|
||||
}
|
||||
}
|
||||
if signer == nil {
|
||||
signer, err = certmagic.PEMDecodePrivateKey(csrKeyPEM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding root key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
csr, err = x509util.CreateCertificateRequest("", sans, signer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return csr, nil
|
||||
}
|
||||
|
||||
// AuthorityConfig is used to help a CA configure
|
||||
// the underlying signing authority.
|
||||
type AuthorityConfig struct {
|
||||
|
|
Loading…
Reference in a new issue