package handlers import ( "bytes" "crypto/x509" "encoding/json" "encoding/pem" "errors" "fmt" "io/ioutil" "net/http" "os" "os/exec" log "github.com/sirupsen/logrus" ) type CertificateSigningHandler struct { caCertificates []*x509.Certificate caChainMap map[string][]string } func NewCertificateSigningHandler(caCertificates []*x509.Certificate) *CertificateSigningHandler { return &CertificateSigningHandler{caCertificates: caCertificates, caChainMap: make(map[string][]string)} } func (h *CertificateSigningHandler) sign(csrPem string, commonName string) (certPem string, caChain []string, err error) { log.Printf("received CSR for %s:\n\n%s", commonName, csrPem) subjectDN := fmt.Sprintf("/CN=%s", commonName) var csrFile *os.File if csrFile, err = ioutil.TempFile("", "*.csr.pem"); err != nil { log.Errorf("could not open temporary file: %s", err) return } if _, err = csrFile.Write([]byte(csrPem)); err != nil { log.Errorf("could not write CSR to file: %s", err) return } if err = csrFile.Close(); err != nil { log.Errorf("could not close CSR file: %s", err) return } defer func(file *os.File) { err = os.Remove(file.Name()) if err != nil { log.Errorf("could not remove temporary file: %s", err) } }(csrFile) opensslCommand := exec.Command( "openssl", "ca", "-config", "ca.cnf", "-policy", "policy_match", "-extensions", "client_ext", "-batch", "-subj", subjectDN, "-utf8", "-rand_serial", "-in", "in.pem") var out, cmdErr bytes.Buffer opensslCommand.Stdout = &out opensslCommand.Stderr = &cmdErr err = opensslCommand.Run() if err != nil { log.Print(err) log.Print(cmdErr.String()) return } var block *pem.Block if block, _ = pem.Decode(out.Bytes()); block == nil { err = fmt.Errorf("could not decode pem") return } var certificate *x509.Certificate if certificate, err = x509.ParseCertificate(block.Bytes); err != nil { return } certPem = string(pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certificate.Raw, })) caChain, err = h.getCAChain(certificate) return } func (h *CertificateSigningHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, "Only POST requests support", http.StatusMethodNotAllowed) return } if r.Header.Get("content-type") != "application/json" { http.Error(w, "Only JSON content is accepted", http.StatusNotAcceptable) return } var err error var requestBody requestData var responseData responseData if err = json.NewDecoder(r.Body).Decode(&requestBody); err != nil { log.Print(err) http.Error(w, err.Error(), http.StatusBadRequest) return } responseData.Certificate, responseData.CAChain, err = h.sign(requestBody.Csr, requestBody.CommonName) if err != nil { http.Error(w, "Could not sign certificate", http.StatusInternalServerError) return } var jsonBytes []byte if jsonBytes, err = json.Marshal(&responseData); err != nil { log.Print(err) } if _, err = w.Write(jsonBytes); err != nil { log.Print(err) } } type requestData struct { Csr string `json:"csr"` CommonName string `json:"commonName"` } type responseData struct { Certificate string `json:"certificate"` CAChain []string `json:"ca_chain"` } func (h *CertificateSigningHandler) getCAChain(certificate *x509.Certificate) ([]string, error) { issuerString := string(certificate.RawIssuer) if value, exists := h.caChainMap[issuerString]; exists { return value, nil } result := make([]string, 0) appendCert := func(cert *x509.Certificate) { result = append( result, string(pem.EncodeToMemory(&pem.Block{Bytes: cert.Raw, Type: "CERTIFICATE"}))) log.Debugf("added %s to cachain", result[len(result)-1]) } var previous *x509.Certificate var count = 0 for { if len(h.caCertificates) == 0 { return nil, errors.New("no CA certificates loaded") } if count > len(h.caCertificates) { return nil, errors.New("could not construct certificate chain") } for _, caCert := range h.caCertificates { if previous == nil { if bytes.Equal(caCert.RawSubject, certificate.RawIssuer) { previous = caCert appendCert(caCert) } } else if bytes.Equal(previous.RawSubject, previous.RawIssuer) { h.caChainMap[issuerString] = result return result, nil } else if bytes.Equal(caCert.RawSubject, previous.RawIssuer) { previous = caCert appendCert(caCert) } else { log.Debugf("skipped certificate %s", caCert.Subject) } } count++ } }